Commit Graph
10 Commits
Author SHA1 Message Date
4bf3c73a15 feat(plugins): agent integration — skill resources, agent trigger, mcp transport (MUL-6350 PR 4) (#7242)
* feat(plugins): skill resources into the existing skill table (MUL-6350 PR 4, 1/3)

A plugin's skill resource becomes an ordinary row in the skill table. Not a
plugin-owned copy, not a bundle, not an artifact with a digest: the previous
plugin system built all of that across fourteen tables and, at the end of it,
delivered one SKILL.md that this table could already hold.

The only thing the platform has to remember is which installation contributed
which skill, so uninstall removes exactly those and nothing a person wrote.
That is one nullable column.

The upsert is scoped to rows this installation owns, so a plugin claiming a
name somebody already used fails the install instead of silently replacing
their work — and the test asserts the human's content survives. Upgrade prunes
what the new manifest no longer declares, or a renamed skill would leave its
predecessor behind with nothing to attribute it to.

Fresh install is now a transaction too. It was a single INSERT; skills have to
land in the same commit, because an installation whose skills half-arrived is
worse than one that failed — the missing half is invisible.

Skill resources flip on here rather than with the rest of PR 4: nothing
executes, so it does not wait for the daemon-side MCP server that the agent
trigger and mcp transport still need.

Co-authored-by: multica-agent <[email protected]>

* feat(plugins): agent trigger — hooks as MCP tools (MUL-6350 PR 4, 2/3)

The fourth call site, and the only one that is not the host deciding to call
something: an agent reads a tool description and chooses. That choice is why
hooks may be reached from an agent at all — the alternative, a hook that must
run before or after every turn, is a third party holding the product's main
loop open, and no such position exists anywhere in this design.

The daemon renders the tools but does not call the plugin. A tool call goes
back to Multica, which makes the signed request. Two reasons, and the second
matters more: the signing secret is derived from the deployment key and putting
it on every laptop running an agent would hand out a credential that can
impersonate the server to every plugin backend; and routing through the server
keeps the rate limit, circuit breaker, `net:` destination check and invocation
record on one code path for all four triggers instead of a daemon-side copy
that would drift.

A failing hook is a TOOL error, not a transport error. The agent reads it,
works around it, and finishes the issue — an unreachable plugin endpoint must
not fail somebody's task. The server returns 200 with a status for the same
reason: a non-2xx would look to the daemon like the broker itself is broken.

TestPluginToolNameSeparatorCannotBeForged caught a real flaw in my first
naming scheme. Sanitizing both halves and joining with an underscore is not
injective — a plugin key uses `.` and `-`, both of which have to become `_`, so
`a.b` and `a-b` collapse together and `a.b_` + `c` collides with `a.b` + `c`.
Two plugins would have been offering the agent one tool name. The prefix now
carries a short digest of the full key rather than a lossy transliteration.

Co-authored-by: multica-agent <[email protected]>

* feat(plugins): mcp transport with pinned tool approval (MUL-6350 PR 4, 3/3)

An `mcp` hook adopts tools from an MCP server the plugin author already runs.
The difference from an `http` hook is who decides the shape: an http hook
declares one endpoint in a manifest an administrator read, while an MCP server
decides its own tool list at runtime. So installing the plugin is not the grant
— an administrator discovers the tools and pins a specific set by name and
schema digest, and a tool that appears later is not adopted until approved.

Approved hooks reach the daemon as ordinary remotemcp.Connection values, so the
existing broker proxies them and validatePinnedRemoteMCPTools already refuses to
start when an approved tool went missing or its schema drifted. No new
enforcement path.

Plugin connections carry a `plugin:` contribution-id prefix. They share the
broker and the resolver with a workspace's own Remote MCP connections but keep
their credential in the Plugin's secret storage, which a different route serves,
and the contribution id is all the broker hands back at dial time.

Flips TransportMCP on in HostCapabilities, completing PR 4's capability set.

Co-authored-by: multica-agent <[email protected]>

* test(plugins): cover mcp connection startup via the dev-origin seam

The `http` transport has had MULTICA_PLUGIN_DEV_ORIGINS since the hook engine
landed: an operator names exact origins, and endpoints there skip the
public-internet requirement so a plugin author can develop against a server on
their own machine. The `mcp` transport never got it.

Two symptoms, one cause. A plugin author could not point an mcp hook at a local
MCP server, and — for the same reason — the daemon's connection startup could
not be reached by a test, because it validates the endpoint as public HTTPS and
the repo's MCP fixture serves plain HTTP on loopback. That left the layer
carrying the entire approval guarantee at 0% coverage:

  startTaskRemoteMCPBrokers      0.0% -> 59.5%
  validatePinnedRemoteMCPTools   0.0% -> 100.0%

The seam is off unless the variable is set, and a test asserts that: the same
loopback connection is refused when it is empty. It skips only the
public-internet requirement — the net: scope still applies, and the dial-time
host pin still refuses a redirect that changes host.

Tests cover what the approval is for: an approved tool the server dropped, an
approved tool whose schema drifted, and an optional connection that fails
without failing the task. Each was verified by breaking the guard it covers.

Co-authored-by: multica-agent <[email protected]>

* feat(plugins): a real example plugin, and the two bugs it found

examples/plugins/deploy-sentinel is an incident-response plugin: it correlates
an issue with recent deploys, files rollback requests under the team's own rules
(refusing deploys past a window, and reasons too short to be evidence), pages
on-call on issue events, and adopts an external metrics MCP server. Four
triggers, both transports, and a skill that tells an agent when to use any of it.

The other examples each demonstrate one mechanism. This one was written to find
out whether the mechanisms compose, and it found two things that no unit test
was ever going to:

1. mcp discovery could not succeed against ANY server. DiscoverMCPHookTools
   passed a nil protocol-version list, and Discover checks the server's answer
   against that same slice — empty means nothing is acceptable. Discover now
   falls back to SupportedProtocolVersions() when the caller passes none.

2. A hook handler had no way to read its own configuration. The manifest's
   config block drives a host-rendered form, the Action API has no config
   endpoint, and the hook body did not carry the values — so a plugin would have
   had to keep a second copy on its own server. The body now carries non-secret
   config. Secret-typed fields are excluded; they live in a separate table and
   are the plugin's credentials for its own services.

Also lets an mcp dev origin trust MULTICA_PLUGIN_DEV_CA, mirroring the http
transport. The manifest validator requires HTTPS, so without this a local MCP
server could not be developed against at all.

plugin_example_test.go installs the real manifest from examples/, not a copy, so
a broken example fails the build rather than rotting quietly.

Migrations renumbered to 368/369 after main advanced. 362 is added to the
legacy-duplicate allowlist: two PRs took that prefix within the same hour and
both merged, and the ledger keys on the full stem, so renaming one would re-run
it everywhere it has already applied. main is red on this today.

Co-authored-by: multica-agent <[email protected]>

* fix(plugins): the deploy-sentinel example servers must serve HTTPS

Both .mjs servers used node:http. A hook's transport URL has to be an https://
URL or the manifest fails validation, so the example as shipped could not
actually be installed against a running Multica — the README told you to set
MULTICA_PLUGIN_DEV_ORIGINS to https:// origins that nothing was listening on.

Found by running it: install, config, skill contribution and live MCP tool
discovery all work against the real servers once they speak TLS. They read
TLS_CERT / TLS_KEY and the README carries the openssl one-liner.

MULTICA_PLUGIN_DEV_CA changes which certificate is trusted; it never disables
verification, which is why a self-signed certificate is needed rather than
plain HTTP.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-20 02:35:29 +08:00
71bd844955 MUL-6055 refactor(agents): remove legacy agent templates (#6820)
Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-12 16:09:16 +08:00
Jiayuan Zhang a14098288b feat: redesign agent Skills and MCP capabilities (#5277) 2026-07-12 02:53:17 +08:00
81bde585ba MUL-3467: batch load squad roster skills (#4386)
* MUL-3467: batch load squad roster skills

Co-authored-by: multica-agent <[email protected]>

* test: isolate redis-backed test databases

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-06-22 13:14:22 +08:00
1c91c2a3b2 security(db): scope DELETE/UpdateIssueStatus by workspace_id (defense-in-depth) (#3027)
* fix(security): scope DELETE/UpdateIssueStatus by workspace_id

Add workspace_id to the WHERE clause of DeleteIssue, DeleteComment,
DeleteProject, DeleteSkill, DeleteChatSession, and UpdateIssueStatus
as SQL-layer defense-in-depth.

Handler loaders (loadIssueForUser / loadSkillForUser / etc.) already
enforce workspace membership today, so this is not patching a known
live vuln. But the tenant invariant is currently a handler-layer
guarantee — a future loader bypass or a new caller skipping the
loader would be silently catastrophic. Making workspace_id part of
the SQL identity collapses the trust surface to the schema itself:
forging a sibling-workspace UUID becomes ErrNoRows instead of a
cross-tenant write.

Reference: incident #1661 (util.ParseUUID silent zero UUID returning
204 on a DELETE that matched zero rows) — same class of failure,
prevented at a different layer.

Scope:
- 5 DELETE queries: issue, comment, project, skill, chat_session
- 1 simple UPDATE: UpdateIssueStatus (2 narg, no SET ordering risk)
- All callers updated (handlers, service, runtime sweeper fallback)

Multi-narg UPDATE queries (UpdateIssue, UpdateProject, UpdateSkill,
UpdateComment, UpdateChatSession*) are deferred to a follow-up to
keep this change reviewable: each needs its narg pinning shifted
and per-caller verification.

sqlc was regenerated by hand (no local sqlc toolchain); CI's
backend job is the authoritative compile check.

* test(security): add workspace_scope_guard regression test

Locks in the SQL-layer tenant guard added in this PR. For each of the 6
scoped queries (DeleteIssue, DeleteComment, DeleteProject, DeleteSkill,
DeleteChatSession, UpdateIssueStatus), creates the resource in workspace
A, invokes the query with a foreign workspace UUID, and asserts the row
is untouched (0 rows affected with no error for :exec; pgx.ErrNoRows for
:one). A future refactor that drops the workspace_id arg from any of
these queries will now fail loudly instead of silently regressing.

Includes a sanity sub-test that the in-workspace path still mutates, so
a buggy guard that returns no-op for every call would not pass.

Co-Authored-By: Claude Opus 4 <[email protected]>

---------

Co-authored-by: Tom Qiao <[email protected]>
Co-authored-by: Claude Opus 4 <[email protected]>
2026-05-22 14:39:47 +08:00
LinYushenandmultica-agent 5bacfd9742 MUL-2526 feat: add member(user_id, workspace_id) index + upgrade sqlc to v1.31.1 (#3046)
- Add migration 106: CREATE INDEX CONCURRENTLY on member(user_id, workspace_id)
- Rewrite ListWorkspaces to drive from member table with explicit fields
- Regenerate all sqlc code with v1.31.1 (intentional version upgrade)

Co-authored-by: multica-agent <[email protected]>
2026-05-22 12:26:56 +08:00
623d29f276 feat(agents): one-click create from curated templates (Phase 1) (#2520)
* docs(agents): three-phase agent quick-create plan

Captures the full design for moving agent creation from manual form +
one-by-one skill attachment to a tiered experience:

- Phase 1 (this PR): one-click curated templates, AI-free.
- Phase 2 (next): AI-recommended skills via the existing quick-create
  task mechanism — no new server-side LLM dependency.
- Phase 3 (later): AI creates the whole agent end-to-end, composing
  Phase 2 with a new `multica agent create` CLI driver.

Documents the architectural decisions that keep all three phases on
existing infrastructure (no SSE, no server-side LLM SDK, no new WS
channels), the two soft blockers Phase 1 unlocks for later phases
(createSkillWithFiles TX composability + skill same-name dedupe), and
the scope decisions we explicitly opted out of (Anthropic plugin
marketplace, ClawHub UI affordances).

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

* fix(skills): harden import against invalid UTF-8 and binary files

PG rejects two byte patterns in a TEXT column. Both crashed real skill
imports we hit while assembling the template catalog:

- Embedded NUL (0x00) -> SQLSTATE 22021. Already stripped by
  sanitizeNullBytes, kept as-is.
- Other invalid UTF-8 (e.g. 0x91 — Windows-1252 smart quote in a skill
  whose author saved prose from Word). sanitizeNullBytes now also runs
  strings.ToValidUTF8 over the content so the second class no longer
  takes the whole import down.

For non-text payloads (images, fonts, archives, compiled binaries),
sanitization isn't the right fix — agents never read those as text,
and the bytes can't survive a TEXT column at all. addFile now skips
them by extension before the per-bundle cap counters tick, logging
the skip so an unexpected drop leaves a breadcrumb.

Function name kept for compatibility with the many call sites; both
behaviours are strict supersets of the original.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

* refactor(skills): split createSkillWithFiles for tx composition + add workspace find-or-create query

Two soft blockers cleared so create-from-template (next commit) can
fold N skill creates and the agent + binding writes into one outer
transaction:

1. createSkillWithFiles used to Begin/Commit its own tx. Caller
   composition was impossible — N invocations meant N separate
   transactions and no atomicity over the whole materialise step.
   Pull the body into createSkillWithFilesInTx(ctx, qtx, input); the
   original function becomes a thin wrapper that manages its own tx
   for standalone callers. Existing call sites: zero behaviour change.

2. Add GetSkillByWorkspaceAndName sqlc query — workspace skill lookup
   by name, anchored to UNIQUE(workspace_id, name) from migration
   008. Lets the template materialiser implement find-or-create:
   reuse the workspace's existing skill row when a template
   references the same name, rather than crashing on the unique
   constraint or polluting the workspace with `<name>-2` clones.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

* feat(agents): agent template catalog + create-from-template endpoint

Server-side foundation for Phase 1 of the quick-create roadmap (see
docs/agent-quick-create-plan.md). Adds:

- server/internal/agenttmpl/ — embed-loaded catalog of curated agent
  templates. Each template ships pre-written instructions plus a list
  of skill URLs that get materialised into the workspace at create
  time. Validation runs at startup (init() panics on a malformed
  template) so a bad JSON ships as a deploy-time defect, not a
  runtime 500. Slug must equal the filename basename so the URL
  router is mirror-symmetric with the file layout.

- 11 starter templates covering Engineering / Writing / Building /
  Testing (code-reviewer, frontend-builder, planner, docs-writer,
  one-pager, html-slides, full-stack-engineer, …).

- Three new endpoints, all behind RequireWorkspaceMember:
    GET  /api/agent-templates           — picker list (no instructions)
    GET  /api/agent-templates/:slug     — detail with instructions
    POST /api/agents/from-template      — materialise + create

  Create flow:
    1. Auth + runtime authorization happen BEFORE the GitHub fan-out
       so a 403 never wastes 20s of upstream fetches.
    2. Pre-flight dedupe by cached_name reuses workspace skills
       without an HTTP fetch — second create-from-the-same-template
       drops from 20s to <100ms.
    3. Parallel fetch (30s per-URL timeout) for the remaining skills.
    4. Single transaction: every skill insert, the agent insert, and
       the agent_skill bindings. On any upstream fetch failure the TX
       rolls back and the API returns 422 with `failed_urls` so the
       UI can name the bad source(s).
    5. extra_skill_ids (user-supplied additions) are verified through
       GetSkillInWorkspace per id before attach, so a malicious client
       can't graft a skill from another workspace via UUID guessing.

- multica agent create --from-template <slug> CLI flag dispatches to
  the new endpoint with a 60s ceiling, matching `multica skill import`.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

* feat(agents): one-click create-from-template UI

Frontend half of Phase 1. CreateAgentDialog becomes a state machine
spanning four steps:

  chooser          → Start blank / From template cards
  blank-form       → existing manual form (post-chooser)
  duplicate-form   → existing form pre-filled from a duplicated agent
  template-picker  → grid of templates, click navigates to detail
  template-detail  → instructions + skill list preview + one-click Use

Picking a template never lands on the form: name auto-deduped against
existingAgentNames, runtime = first usable one, visibility = private.
Refinement happens on the agent detail page if needed. Same rationale
the doc spells out — templates exist precisely to skip configuration.

New components, all collapsible-by-default so quick-create stays fast:
  - template-picker.tsx — categorised grid, lucide icons + semantic
    accent tokens resolved through static maps so Tailwind's JIT picks
    up every variant (dynamic class strings would silently miss).
  - template-detail.tsx — instructions preview, skill list with cached
    descriptions, Use CTA. Renders the failedURLs banner when a 422
    fires — the only step that can trigger that response.
  - instructions-editor.tsx — collapsed preview-card / expanded full
    ContentEditor.
  - skill-multi-select.tsx + skill-picker-list.tsx — shared multi-
    select surface, also adopted by the existing skill-add-dialog.
  - avatar-picker.tsx — agent avatar upload, mirrors the inspector's
    visual language.

Schema-defended client (CLAUDE.md → API Response Compatibility): the
three new endpoints are wired through parseWithFallback with lenient
zod schemas. Desktop builds outlive any given server — a future
field rename / wrapping must not white-screen older installs.
listAgentTemplates accepts both the current bare array and a future
{templates: [...]} envelope. Coverage: 7 new schema-test cases in
schema.test.ts (null body, missing skills/instructions, malformed
create response, envelope migration).

Catalog + detail go through TanStack Query with staleTime: Infinity —
workspace-independent static data, no per-mount refetch.

Other:
- skill-add-dialog becomes a true multi-select (Confirm button +
  checkbox list); attached skills are filtered out of the list.
- agents-page hands the freshly-created Agent back to the dialog so a
  follow-up setAgentSkills can attach the form-selected skills.
- agent-overview-pane drops the mx-auto/max-w-2xl frame on config-
  tab content; the wider dialog visual language reads better with
  tabs filling the column.
- Every new UI string lives in both en/agents.json and
  zh-Hans/agents.json under create_dialog.* / tab_body.skills.* —
  locales/parity.test.ts blocks drift in CI.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

* fix(ci): align skill import test + drop next-only lint suppression

- TestFetchFromSkillsSh_ResolvesRootLevelSkillMd now expects assets/logo.png
  to be skipped; matches the new addFile binary-extension guard
  (6fafd86e). The .png is intentionally dropped so PG TEXT inserts don't
  hit SQLSTATE 22021.
- packages/views shares zero next/* deps, so the @next/next/no-img-element
  eslint plugin isn't loaded there. The eslint-disable directive
  referencing it produced a hard "rule not found" error in CI lint. Raw
  <img> is the right primitive in views; remove the disable comment.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* test(agents): wrap CreateAgentDialog tests in workspace/navigation providers

The dialog now calls useNavigation() and useWorkspacePaths(), both of
which throw outside their providers. The existing tests rendered the
dialog bare and tripped both new requirements:

- NavigationProvider — supply a stub adapter so push() works for the
  agent-detail redirect.
- WorkspaceSlugProvider — useWorkspacePaths() requires a slug.

The blank-vs-template chooser is now the default first step; the
existing tests target the runtime picker on the manual form, so the
helper auto-clicks "Start blank" when no template is passed
(duplicate-mode tests skip the chooser).

Manual afterEach(cleanup) + document.body wipe. Base UI's Dialog
portal renders into document.body and leaves focus-guard/inert wrapper
divs behind across tests, so the second test in the suite saw two
"All" / "My Runtime" matches and getByText failed. The wipe is local
to this file rather than the shared setup because it isn't a global
issue — only suites that open Base UI dialogs hit it.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-05-13 18:26:04 +08:00
Bohan Jiangandmultica-agent d0ac67dea2 fix(skills): drop SKILL.md content from list endpoints (#2180)
* fix(skills): drop SKILL.md content from list endpoints (#2174)

`GET /api/skills` and `GET /api/agents/{id}/skills` were SELECT *'ing the
skill row and shipping the full SKILL.md `content` blob to every caller.
SKILL.md bodies routinely run 50–200KB each, so a workspace with 30–40
skills returned multi-megabyte JSON arrays — past the CLI's 15s timeout
on high-latency links and locking out non-US users entirely.

Add `ListSkillSummariesByWorkspace` / `ListAgentSkillSummaries` sqlc
queries that omit `content`, plus a dedicated `SkillSummaryResponse`
wire shape so the contract is explicit (versus stuffing
`Content: ""` back into the existing struct). Detail endpoints
(`GET /api/skills/{id}`, agent CRUD return values) keep returning the
full body.

`AgentResponse.skills` and the matching TS `Agent.skills` now use
`SkillSummary[]` — frontend list/columns code already only read
id/name/description/config.origin, so the type narrowing matches actual
usage and prevents new code from accidentally depending on a content
field that won't be there.

Co-authored-by: multica-agent <[email protected]>

* fix(agents): narrow embedded skills to AgentSkillSummary; gofmt agent.go

GPT-Boy review of #2180: the previous commit typed AgentResponse.Skills as
[]SkillSummaryResponse, but the agent list batch query
(ListAgentSkillsByWorkspace) only joins agent_id/id/name/description, so
the wider type left workspace_id/config/created_at/updated_at as zero
values. Define a dedicated AgentSkillSummary {id,name,description} that
matches what the batch query actually returns and what the frontend
actually reads (`agent.skills.map(s => s.name|s.id)`); the standalone
GET /api/agents/{id}/skills endpoint keeps SkillSummaryResponse for
callers that need the source/origin info.

Switch GetAgent's per-agent skills load from ListAgentSkills (full Skill
rows including content) back to ListAgentSkillSummaries to avoid reading
SKILL.md bodies just to discard them.

Re-run gofmt on agent.go to fix the field-tag alignment that drifted when
Skills changed type.

Co-authored-by: multica-agent <[email protected]>

* docs(types): correct SkillSummary JSDoc — Agent.skills is AgentSkillSummary[]

GPT-Boy spotted on review: comment said SkillSummary was "embedded in
Agent.skills", but that field is now AgentSkillSummary[]. Re-point the
reader at the right type to avoid future confusion.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-05-07 01:36:29 +08:00
Naiyuan QingandClaude Opus 4.6 9ede795c5b feat(api): strict workspace isolation + agent parity fixes
Enforce workspace isolation at every layer:

- Router: move RequireWorkspaceMember middleware to group level so ALL
  workspace-scoped routes (issues, agents, skills, runtimes, inbox,
  comments) require workspace context
- SQL: add GetXxxInWorkspace queries that filter by workspace_id,
  eliminating cross-workspace data access at the query level
- Handlers: loadXForUser functions use workspace-scoped queries,
  no fallback to unscoped queries
- Migration 025: add workspace_id column to comment table with backfill
- ListComments: add workspace_id filter for defense-in-depth

Fix daemon workspace mapping:
- Server returns workspace_id in task claim response (from issue)
- Daemon uses task.WorkspaceID directly instead of unreliable
  workspaceIDForRuntime() local map lookup
- Remove workspaceIDForRuntime function

Fix agent/human parity:
- Comment update/delete: use resolveActor for isAuthor check so agents
  can edit/delete their own comments
- Event attribution: replace hardcoded "member" with resolveActor in
  agent, skill, and subscriber publish calls

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-30 16:49:13 +08:00
Jiayuan ZhangandClaude Opus 4.6 02df33803a feat: structured skills system with meta skill runtime injection
Replace agent.skills TEXT field with structured skill/skill_file/agent_skill
tables. Skills are workspace-level entities with supporting files, reusable
across agents via many-to-many bindings.

Backend: migration 008, sqlc queries, CRUD handler, agent-skill junction,
structured skill loading in task context snapshot.

Daemon: meta skill injection via runtime-native config (.claude/CLAUDE.md
for Claude, AGENTS.md for Codex) so agents discover .agent_context/ skills
through their native mechanism. Lean prompt without inlined skill content.

Frontend: Skills management page, agent Skills tab picker, SDK methods,
TypeScript types, workspace store integration.

Also removes auto-creation of init issues when creating agents.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-25 15:17:59 +08:00