@akira-tl/forgerelay 0.8.8 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +10 -18
- package/capabilities/code-intelligence/GUIDE.md +7 -3
- package/capabilities/host-integration/GUIDE.md +2 -2
- package/capabilities/subagents/GUIDE.md +9 -0
- package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +6 -0
- package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
- package/dist/activity/history/bash-output-store.js +250 -0
- package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
- package/dist/activity/{query-service.js → history/query-service.js} +13 -7
- package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
- package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
- package/dist/activity/storage/history.js +12 -0
- package/dist/activity/storage/paths.js +56 -0
- package/dist/activity/storage/segmented-log.js +149 -0
- package/dist/cli/init.js +180 -0
- package/dist/cli/setup-support.js +181 -0
- package/dist/cli.js +23 -216
- package/dist/lsp/code-intelligence.js +18 -4
- package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
- package/dist/lsp/runtime/managed-language-servers.js +172 -0
- package/dist/lsp/runtime/manager.js +37 -2
- package/dist/lsp/runtime/process-launch.js +3 -0
- package/dist/lsp/test-support/server-fixture.js +14 -11
- package/dist/mcp/filesystem/filesystem-tools.js +245 -0
- package/dist/mcp/hooks/command-runner.js +79 -0
- package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
- package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
- package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
- package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
- package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
- package/dist/mcp/panel/app.js +143 -0
- package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
- package/dist/{process-platform.js → mcp/process/process-platform.js} +21 -9
- package/dist/{process-sessions.js → mcp/process/process-sessions.js} +3 -3
- package/dist/mcp/process/runtime.js +257 -0
- package/dist/mcp/process/tools.js +346 -0
- package/dist/mcp/server/core/activity-support.js +70 -0
- package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
- package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +9 -4
- package/dist/mcp/server/core/capability-support.js +90 -0
- package/dist/mcp/server/core/schemas.js +216 -0
- package/dist/mcp/server/core/tool-support.js +210 -0
- package/dist/mcp/server/core/version.js +9 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +455 -0
- package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +524 -0
- package/dist/mcp/server/transport/http-server.js +301 -0
- package/dist/mcp/server/transport/http-support.js +43 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +242 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +555 -0
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/{config.js → runtime/config/config.js} +4 -3
- package/dist/{user-config.js → runtime/config/user-config.js} +14 -63
- package/dist/{db → runtime/state/db}/client.js +1 -1
- package/dist/{db → runtime/state/db}/migrations.js +34 -3
- package/dist/runtime/state/lock/file-lock.js +130 -0
- package/dist/runtime/testing/server-fixture.js +190 -0
- package/dist/server.js +133 -4189
- package/dist/subagents/providers/adapters/claude.js +86 -52
- package/dist/subagents/providers/adapters/codex.js +103 -39
- package/dist/subagents/providers/adapters/opencode.js +114 -84
- package/dist/subagents/providers/adapters/pi.js +59 -8
- package/dist/subagents/providers/availability.js +15 -30
- package/dist/subagents/providers/runtime/external-command.js +56 -0
- package/dist/subagents/sessions/capability.js +1 -1
- package/dist/subagents/sessions/execution.js +1 -1
- package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
- package/dist/subagents/sessions/store.js +1 -1
- package/dist/ui/.vite/manifest.json +342 -410
- package/dist/ui/activity-panel-app.html +2 -4
- package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
- package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
- package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
- package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
- package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
- package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
- package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
- package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
- package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
- package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
- package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
- package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
- package/dist/ui/assets/css-C1tjBtVN.js +1 -0
- package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
- package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
- package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
- package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
- package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
- package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
- package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
- package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
- package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
- package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
- package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
- package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
- package/dist/ui/assets/html-By4dNnM4.js +1 -0
- package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
- package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
- package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
- package/dist/ui/assets/java-DYRe2lWG.js +1 -0
- package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
- package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
- package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
- package/dist/ui/assets/json-dbltgiiA.js +1 -0
- package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
- package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
- package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
- package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
- package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
- package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
- package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
- package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
- package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
- package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
- package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
- package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
- package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
- package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
- package/dist/ui/assets/r-CcezTbLq.js +1 -0
- package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
- package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
- package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
- package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
- package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
- package/dist/ui/assets/scss-BQb-cp01.js +1 -0
- package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
- package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
- package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
- package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
- package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
- package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
- package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
- package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
- package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
- package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
- package/dist/ui/assets/tsx-CajzROyy.js +1 -0
- package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
- package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
- package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
- package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
- package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
- package/dist/ui/assets/xml-BI86-WbH.js +1 -0
- package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
- package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
- package/dist/workspaces/bootstrap.js +72 -0
- package/dist/workspaces/context.js +337 -0
- package/dist/workspaces/conversation-test-support.js +86 -0
- package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
- package/dist/workspaces/inventory.js +131 -0
- package/dist/workspaces/paths.js +55 -0
- package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
- package/dist/workspaces/relay/result-support.js +111 -0
- package/dist/workspaces/relay/tests/test-support.js +203 -0
- package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
- package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +30 -3
- package/dist/workspaces/relay/types.js +1 -0
- package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
- package/dist/workspaces/resources/resource-monitor.js +377 -0
- package/dist/workspaces/resources/skills.js +283 -0
- package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
- package/dist/workspaces/sessions.js +460 -0
- package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
- package/dist/workspaces.js +77 -993
- package/docs/chatgpt-coding-workflow.md +7 -14
- package/docs/configuration.md +26 -34
- package/docs/gotchas.md +0 -30
- package/docs/roadmap.md +6 -10
- package/docs/security.md +1 -5
- package/docs/setup.md +0 -11
- package/package.json +6 -9
- package/scripts/ci/architecture.mjs +109 -0
- package/scripts/ci/verify.mjs +5 -0
- package/scripts/debug/accept/bootstrap.mjs +326 -0
- package/scripts/debug/accept/harness.mjs +76 -0
- package/scripts/debug/accept/support.mjs +432 -0
- package/scripts/debug/accept.mjs +43 -806
- package/scripts/debug/relay-accept/support.mjs +135 -0
- package/scripts/debug/relay-accept.mjs +26 -115
- package/scripts/debug/traffic/support.mjs +167 -0
- package/scripts/debug/traffic/traffic-audit.mjs +4 -161
- package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
- package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
- package/scripts/release/publish.mjs +12 -9
- package/scripts/release/release-gate.test.mjs +3 -1
- package/scripts/release/release-version.test.mjs +13 -1
- package/scripts/release-parity.mjs +1 -1
- package/scripts/release-proof.mjs +15 -0
- package/scripts/release-proof.test.mjs +27 -1
- package/scripts/release-version.mjs +16 -1
- package/scripts/wiki/sync.mjs +21 -10
- package/dist/activity/bash-output-store.js +0 -127
- package/dist/pi-tools.js +0 -110
- package/dist/skills.js +0 -127
- package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
- package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
- package/dist/ui/assets/c-IBzBpxAO.js +0 -1
- package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
- package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
- package/dist/ui/assets/css-DEfus9J5.js +0 -1
- package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
- package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
- package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
- package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
- package/dist/ui/assets/html-D-C6YZk4.js +0 -1
- package/dist/ui/assets/java-ppHvycj5.js +0 -1
- package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
- package/dist/ui/assets/json-Cw91r8h1.js +0 -1
- package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
- package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
- package/dist/ui/assets/r-DfFga4si.js +0 -1
- package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
- package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
- package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
- package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
- package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
- package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
- package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
- package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
- package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
- package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
- package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
- package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
- package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
- package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
- package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
- package/dist/ui/workspace-app.html +0 -18
- package/dist/ui/workspace-lifecycle-app.html +0 -18
- package/docs/agent-profile-schema.md +0 -114
- package/docs/artifact-exchange.md +0 -65
- package/docs/versioning.md +0 -205
- package/skills/subagent-delegation/SKILL.md +0 -132
- /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
- /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
- /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
- /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
- /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
- /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
- /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
- /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
- /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
- /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
- /package/dist/{oauth → mcp/oauth}/router.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
- /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
- /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
- /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
- /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
- /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
- /package/dist/{db → runtime/state/db}/schema.js +0 -0
- /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
- /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
- /package/dist/{git.js → workspaces/git/git.js} +0 -0
- /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
- /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
- /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
- /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
2
|
+
import { openAiConversationScopeId } from "../../../request-meta.js";
|
|
3
|
+
import { formatPathForPrompt } from "../../../../workspaces/resources/skills.js";
|
|
4
|
+
import { compositeCapabilityContext } from "../../core/capability-support.js";
|
|
5
|
+
import { logToolCall, textBlock } from "../../core/tool-support.js";
|
|
6
|
+
import { openWorkspaceToolDefinition } from "./workspace-open-schema.js";
|
|
7
|
+
import { presentLocalWorkspaceOpen, workspaceTaskUsageInstruction, } from "./workspace-open-presentation.js";
|
|
8
|
+
export function registerOpenWorkspaceTool(options) {
|
|
9
|
+
const { server } = options;
|
|
10
|
+
registerAppTool(server, "open_workspace", openWorkspaceToolDefinition, (args, extra) => handleOpenWorkspace(options, args, extra));
|
|
11
|
+
}
|
|
12
|
+
async function handleOpenWorkspace(options, input, extra) {
|
|
13
|
+
const { config, workspaces, remoteWorkspaces, compositeWorkspaces, workspaceTasks, processSessions, capabilityRegistry, compositeTaskGuides, loadCompositeMemberContext, rememberWorkspacePanelState, hostScopeIdFor, presentation, } = options;
|
|
14
|
+
const { action = "open", memberAction, member, kind, name, memberName, path, relay, workspaceId, mode, baseRef, newWorktree, newWorkspace, context, root, status, state, staleOnly, offset, limit, } = input;
|
|
15
|
+
const { _meta, sessionId } = extra;
|
|
16
|
+
const startedAt = performance.now();
|
|
17
|
+
const conversationScopeId = openAiConversationScopeId(_meta);
|
|
18
|
+
const protectedWorkspaceIds = processSessions.activeWorkspaceIds();
|
|
19
|
+
const inspectTaskSummary = (targetWorkspaceId) => {
|
|
20
|
+
try {
|
|
21
|
+
const summary = workspaceTasks.inspectSummary(targetWorkspaceId);
|
|
22
|
+
if (!summary)
|
|
23
|
+
return undefined;
|
|
24
|
+
const { fingerprint: _fingerprint, ...inspectionSummary } = summary;
|
|
25
|
+
return inspectionSummary;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const inspectCompositeMember = async (entry) => {
|
|
32
|
+
if (remoteWorkspaces.has(entry.workspaceId)) {
|
|
33
|
+
try {
|
|
34
|
+
const inspected = await remoteWorkspaces.inspectWorkspace(entry.workspaceId);
|
|
35
|
+
return {
|
|
36
|
+
name: entry.name,
|
|
37
|
+
purpose: entry.purpose,
|
|
38
|
+
workspaceId: entry.workspaceId,
|
|
39
|
+
known: true,
|
|
40
|
+
location: inspected.location,
|
|
41
|
+
routeState: inspected.routeState,
|
|
42
|
+
state: inspected.state,
|
|
43
|
+
status: inspected.status,
|
|
44
|
+
mode: inspected.mode,
|
|
45
|
+
rootValid: inspected.rootValid,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return {
|
|
50
|
+
name: entry.name,
|
|
51
|
+
purpose: entry.purpose,
|
|
52
|
+
workspaceId: entry.workspaceId,
|
|
53
|
+
known: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const inspected = await workspaces.inspectWorkspace(entry.workspaceId);
|
|
59
|
+
return {
|
|
60
|
+
name: entry.name,
|
|
61
|
+
purpose: entry.purpose,
|
|
62
|
+
workspaceId: entry.workspaceId,
|
|
63
|
+
known: true,
|
|
64
|
+
location: inspected.location,
|
|
65
|
+
state: inspected.state,
|
|
66
|
+
status: inspected.status,
|
|
67
|
+
mode: inspected.mode,
|
|
68
|
+
rootValid: inspected.rootValid,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return {
|
|
73
|
+
name: entry.name,
|
|
74
|
+
purpose: entry.purpose,
|
|
75
|
+
workspaceId: entry.workspaceId,
|
|
76
|
+
known: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
if (action === "inspect") {
|
|
81
|
+
if (!workspaceId) {
|
|
82
|
+
throw new Error("open_workspace action=inspect requires workspaceId.");
|
|
83
|
+
}
|
|
84
|
+
if (memberAction !== undefined || member !== undefined || kind !== undefined || name !== undefined ||
|
|
85
|
+
memberName !== undefined || path !== undefined || relay !== undefined || mode !== undefined ||
|
|
86
|
+
baseRef !== undefined || newWorktree !== undefined || newWorkspace !== undefined || context !== undefined ||
|
|
87
|
+
root !== undefined || status !== undefined || state !== undefined || staleOnly !== undefined ||
|
|
88
|
+
offset !== undefined || limit !== undefined) {
|
|
89
|
+
throw new Error("open_workspace action=inspect accepts only workspaceId. It never opens, resumes, binds, or mutates the inspected Workspace.");
|
|
90
|
+
}
|
|
91
|
+
let inspection;
|
|
92
|
+
if (compositeWorkspaces.has(workspaceId)) {
|
|
93
|
+
const composite = compositeWorkspaces.get(workspaceId);
|
|
94
|
+
const members = await Promise.all(composite.members.map(inspectCompositeMember));
|
|
95
|
+
const taskSummary = inspectTaskSummary(composite.id);
|
|
96
|
+
inspection = {
|
|
97
|
+
workspaceId: composite.id,
|
|
98
|
+
kind: "composite",
|
|
99
|
+
name: composite.name,
|
|
100
|
+
status: composite.status,
|
|
101
|
+
state: composite.status,
|
|
102
|
+
createdAt: composite.createdAt,
|
|
103
|
+
lastUsedAt: composite.lastUsedAt,
|
|
104
|
+
members,
|
|
105
|
+
...(taskSummary ? { taskSummary } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else if (remoteWorkspaces.has(workspaceId)) {
|
|
109
|
+
inspection = await remoteWorkspaces.inspectWorkspace(workspaceId);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const inspected = await workspaces.inspectWorkspace(workspaceId);
|
|
113
|
+
const taskSummary = inspectTaskSummary(inspected.workspaceId);
|
|
114
|
+
inspection = {
|
|
115
|
+
...inspected,
|
|
116
|
+
...(taskSummary ? { taskSummary } : {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const instruction = "This is a bounded read-only Workspace inspection. It does not open/resume the target, deliver bootstrap context, bind this conversation, or grant file/process/Git/Capability authority. Explicitly open the Workspace before modifying or executing against it.";
|
|
120
|
+
const result = [
|
|
121
|
+
`Inspected Workspace ${inspection.workspaceId} (${inspection.kind}).`,
|
|
122
|
+
inspection.kind === "composite"
|
|
123
|
+
? `State: ${inspection.state}; members=${inspection.members.length}.`
|
|
124
|
+
: inspection.location === "relay"
|
|
125
|
+
? inspection.state
|
|
126
|
+
? `State: ${inspection.state}; route=${inspection.routeState}; mode=${inspection.mode}; location=${inspection.location}. Lifecycle and Task facts come from the Execution ForgeRelay.`
|
|
127
|
+
: `Route: ${inspection.routeState}; mode=${inspection.mode}; location=${inspection.location}.`
|
|
128
|
+
: `State: ${inspection.state}; mode=${inspection.mode}; location=${inspection.location}.`,
|
|
129
|
+
"Task summary is included only when durable Task state already exists on the owning Workspace; Task bodies are never returned.",
|
|
130
|
+
instruction,
|
|
131
|
+
].join("\n");
|
|
132
|
+
logToolCall(config, {
|
|
133
|
+
tool: "open_workspace",
|
|
134
|
+
action: "inspect",
|
|
135
|
+
success: true,
|
|
136
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
content: [textBlock(result)],
|
|
140
|
+
structuredContent: {
|
|
141
|
+
action: "inspect",
|
|
142
|
+
workspaceId: inspection.workspaceId,
|
|
143
|
+
kind: inspection.kind,
|
|
144
|
+
inspection,
|
|
145
|
+
instruction,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (action === "member") {
|
|
150
|
+
if (!workspaceId || !compositeWorkspaces.has(workspaceId)) {
|
|
151
|
+
throw new Error("open_workspace action=member requires an existing Composite Workspace workspaceId.");
|
|
152
|
+
}
|
|
153
|
+
if (!compositeWorkspaces.isActive(workspaceId)) {
|
|
154
|
+
throw new Error(`Composite Workspace ${workspaceId} is closed. Reopen it with open_workspace before changing members.`);
|
|
155
|
+
}
|
|
156
|
+
if (!memberAction || !member) {
|
|
157
|
+
throw new Error("open_workspace action=member requires memberAction and member.");
|
|
158
|
+
}
|
|
159
|
+
if (kind !== undefined || name !== undefined || memberName !== undefined || path !== undefined || relay !== undefined || mode !== undefined ||
|
|
160
|
+
baseRef !== undefined || newWorktree !== undefined || newWorkspace !== undefined || context !== undefined ||
|
|
161
|
+
root !== undefined || status !== undefined || state !== undefined || staleOnly !== undefined ||
|
|
162
|
+
offset !== undefined || limit !== undefined) {
|
|
163
|
+
throw new Error("open_workspace action=member accepts workspaceId, memberAction, and member only. Put any Workspace open definition inside member.");
|
|
164
|
+
}
|
|
165
|
+
const resolveMemberTargetWorkspaceId = async () => {
|
|
166
|
+
const byWorkspaceId = typeof member.workspaceId === "string" && member.workspaceId.length > 0;
|
|
167
|
+
const byPath = typeof member.path === "string" && member.path.length > 0;
|
|
168
|
+
if (byWorkspaceId === byPath) {
|
|
169
|
+
throw new Error("A Composite Workspace member target requires exactly one of member.workspaceId or member.path.");
|
|
170
|
+
}
|
|
171
|
+
if (byWorkspaceId) {
|
|
172
|
+
const targetWorkspaceId = member.workspaceId;
|
|
173
|
+
if (compositeWorkspaces.has(targetWorkspaceId)) {
|
|
174
|
+
throw new Error("A Composite Workspace cannot be mounted as a Composite Workspace member.");
|
|
175
|
+
}
|
|
176
|
+
if (member.relay !== undefined || member.mode !== undefined || member.baseRef !== undefined ||
|
|
177
|
+
member.newWorktree !== undefined || member.newWorkspace !== undefined) {
|
|
178
|
+
throw new Error("member.workspaceId cannot be combined with relay/mode/baseRef/newWorktree/newWorkspace.");
|
|
179
|
+
}
|
|
180
|
+
if (!remoteWorkspaces.has(targetWorkspaceId))
|
|
181
|
+
workspaces.getWorkspace(targetWorkspaceId);
|
|
182
|
+
return targetWorkspaceId;
|
|
183
|
+
}
|
|
184
|
+
if (member.relay !== undefined) {
|
|
185
|
+
const opened = await remoteWorkspaces.openWorkspace(member.relay, {
|
|
186
|
+
path: member.path,
|
|
187
|
+
...(member.mode ? { mode: member.mode } : {}),
|
|
188
|
+
...(member.baseRef ? { baseRef: member.baseRef } : {}),
|
|
189
|
+
...(member.newWorktree !== undefined ? { newWorktree: member.newWorktree } : {}),
|
|
190
|
+
...(member.newWorkspace !== undefined ? { newWorkspace: member.newWorkspace } : {}),
|
|
191
|
+
context: "none",
|
|
192
|
+
});
|
|
193
|
+
return opened.workspaceId;
|
|
194
|
+
}
|
|
195
|
+
const opened = await workspaces.openWorkspace({
|
|
196
|
+
path: member.path,
|
|
197
|
+
...(member.mode ? { mode: member.mode } : {}),
|
|
198
|
+
...(member.baseRef ? { baseRef: member.baseRef } : {}),
|
|
199
|
+
...(member.newWorktree !== undefined ? { newWorktree: member.newWorktree } : {}),
|
|
200
|
+
...(member.newWorkspace !== undefined ? { newWorkspace: member.newWorkspace } : {}),
|
|
201
|
+
context: "none",
|
|
202
|
+
}, { protectedWorkspaceIds });
|
|
203
|
+
return opened.workspace.id;
|
|
204
|
+
};
|
|
205
|
+
let composite;
|
|
206
|
+
if (memberAction === "add") {
|
|
207
|
+
if (member.newName !== undefined) {
|
|
208
|
+
throw new Error("Adding a Composite Workspace member does not accept member.newName.");
|
|
209
|
+
}
|
|
210
|
+
const purpose = member.purpose?.trim();
|
|
211
|
+
if (!purpose)
|
|
212
|
+
throw new Error("Adding a Composite Workspace member requires member.purpose.");
|
|
213
|
+
const targetWorkspaceId = await resolveMemberTargetWorkspaceId();
|
|
214
|
+
composite = compositeWorkspaces.addMember(workspaceId, {
|
|
215
|
+
name: member.name,
|
|
216
|
+
purpose,
|
|
217
|
+
workspaceId: targetWorkspaceId,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
else if (memberAction === "update") {
|
|
221
|
+
const targetFieldsPresent = member.workspaceId !== undefined || member.path !== undefined || member.relay !== undefined ||
|
|
222
|
+
member.mode !== undefined || member.baseRef !== undefined || member.newWorktree !== undefined ||
|
|
223
|
+
member.newWorkspace !== undefined;
|
|
224
|
+
if (member.newName === undefined && member.purpose === undefined && !targetFieldsPresent) {
|
|
225
|
+
throw new Error("Updating a Composite Workspace member requires newName, purpose, or a replacement Workspace target.");
|
|
226
|
+
}
|
|
227
|
+
const targetWorkspaceId = targetFieldsPresent
|
|
228
|
+
? await resolveMemberTargetWorkspaceId()
|
|
229
|
+
: undefined;
|
|
230
|
+
composite = compositeWorkspaces.updateMember(workspaceId, member.name, {
|
|
231
|
+
...(member.newName !== undefined ? { name: member.newName } : {}),
|
|
232
|
+
...(member.purpose !== undefined ? { purpose: member.purpose } : {}),
|
|
233
|
+
...(targetWorkspaceId !== undefined ? { workspaceId: targetWorkspaceId } : {}),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
if (member.newName !== undefined || member.purpose !== undefined || member.workspaceId !== undefined || member.path !== undefined ||
|
|
238
|
+
member.relay !== undefined || member.mode !== undefined || member.baseRef !== undefined ||
|
|
239
|
+
member.newWorktree !== undefined || member.newWorkspace !== undefined) {
|
|
240
|
+
throw new Error("Removing a Composite Workspace member accepts only member.name.");
|
|
241
|
+
}
|
|
242
|
+
composite = compositeWorkspaces.removeMember(workspaceId, member.name);
|
|
243
|
+
}
|
|
244
|
+
const memberActionVerb = memberAction === "add"
|
|
245
|
+
? "Added"
|
|
246
|
+
: memberAction === "update"
|
|
247
|
+
? "Updated"
|
|
248
|
+
: "Removed";
|
|
249
|
+
const memberActionPreposition = memberAction === "remove" ? "from" : "in";
|
|
250
|
+
const instruction = [
|
|
251
|
+
`${memberActionVerb} member ${member.name} ${memberActionPreposition} Composite Workspace ${composite.name} (${composite.id}).`,
|
|
252
|
+
composite.members.length > 0
|
|
253
|
+
? `Members: ${composite.members.map((entry) => `${entry.name} — ${entry.purpose}`).join("; ")}.`
|
|
254
|
+
: "This Composite Workspace currently has no members.",
|
|
255
|
+
"Use the Composite workspaceId as the top-level handle. Work operations on it require an explicit member name; ForgeRelay never infers a member from tool type or purpose.",
|
|
256
|
+
].join("\n");
|
|
257
|
+
const response = {
|
|
258
|
+
content: [textBlock(instruction)],
|
|
259
|
+
_meta: {
|
|
260
|
+
tool: "open_workspace",
|
|
261
|
+
card: {
|
|
262
|
+
workspaceId: composite.id,
|
|
263
|
+
kind: "composite",
|
|
264
|
+
name: composite.name,
|
|
265
|
+
path: composite.name,
|
|
266
|
+
members: composite.members,
|
|
267
|
+
instruction,
|
|
268
|
+
summary: { members: composite.members.length },
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
structuredContent: {
|
|
272
|
+
action: "member",
|
|
273
|
+
workspaceId: composite.id,
|
|
274
|
+
memberAction,
|
|
275
|
+
kind: "composite",
|
|
276
|
+
name: composite.name,
|
|
277
|
+
members: composite.members,
|
|
278
|
+
instruction,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
rememberWorkspacePanelState(composite.id, response);
|
|
282
|
+
return response;
|
|
283
|
+
}
|
|
284
|
+
if (action === "list") {
|
|
285
|
+
if (path !== undefined || relay !== undefined || name !== undefined || memberName !== undefined || baseRef !== undefined || newWorktree !== undefined ||
|
|
286
|
+
newWorkspace !== undefined || context !== undefined) {
|
|
287
|
+
throw new Error("open_workspace action=list does not accept path, relay, name, memberName, baseRef, newWorktree, newWorkspace, or context. Use kind/root/workspaceId/mode/status/state/staleOnly for inventory filters.");
|
|
288
|
+
}
|
|
289
|
+
const compositeInventory = () => compositeWorkspaces.list()
|
|
290
|
+
.filter((entry) => workspaceId === undefined || entry.id === workspaceId)
|
|
291
|
+
.filter((entry) => status === undefined || entry.status === status)
|
|
292
|
+
.filter((entry) => state === undefined || entry.status === state)
|
|
293
|
+
.map((entry) => ({
|
|
294
|
+
workspaceId: entry.id,
|
|
295
|
+
kind: entry.kind,
|
|
296
|
+
name: entry.name,
|
|
297
|
+
status: entry.status,
|
|
298
|
+
state: entry.status,
|
|
299
|
+
members: entry.members,
|
|
300
|
+
createdAt: entry.createdAt,
|
|
301
|
+
lastUsedAt: entry.lastUsedAt,
|
|
302
|
+
}));
|
|
303
|
+
if (kind === "composite") {
|
|
304
|
+
if (root !== undefined || mode !== undefined || staleOnly !== undefined ||
|
|
305
|
+
offset !== undefined || limit !== undefined) {
|
|
306
|
+
throw new Error("Composite Workspace inventory does not accept root/mode/staleOnly/offset/limit filters; use workspaceId/status/state when selecting Composite Workspaces.");
|
|
307
|
+
}
|
|
308
|
+
const composites = compositeInventory();
|
|
309
|
+
const instruction = "Open a Composite Workspace by workspaceId to resume or reopen it. close_workspace preserves its identity; action=delete permanently dissolves only Composite-owned state.";
|
|
310
|
+
const result = [
|
|
311
|
+
`Composite Workspace inventory: ${composites.length} matching record${composites.length === 1 ? "" : "s"}.`,
|
|
312
|
+
...composites.map((entry) => `${entry.name} [${entry.workspaceId}] state=${entry.state} members=${entry.members.length} last-used=${entry.lastUsedAt}`),
|
|
313
|
+
instruction,
|
|
314
|
+
].join("\n");
|
|
315
|
+
return {
|
|
316
|
+
content: [textBlock(result)],
|
|
317
|
+
structuredContent: {
|
|
318
|
+
action: "list",
|
|
319
|
+
compositeWorkspaces: composites,
|
|
320
|
+
instruction,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const inventory = await workspaces.listWorkspaces({ workspaceId, mode, root, status, state, staleOnly, offset, limit }, { conversationScopeId, protectedWorkspaceIds });
|
|
325
|
+
const composites = kind === "workspace" || root !== undefined || mode !== undefined || staleOnly
|
|
326
|
+
? []
|
|
327
|
+
: compositeInventory();
|
|
328
|
+
const nextOffset = inventory.page.offset + inventory.page.limit;
|
|
329
|
+
const instruction = [
|
|
330
|
+
"Resume a selected workspaceId with open_workspace(action=\"open\", workspaceId=...).",
|
|
331
|
+
"Use close_workspace only after the user chooses cleanup; Composite close preserves identity, while action=delete dissolves only Composite-owned state. Never close inventory entries automatically.",
|
|
332
|
+
inventory.page.hasMore
|
|
333
|
+
? `More matching workspaces are available; continue with offset=${nextOffset}.`
|
|
334
|
+
: undefined,
|
|
335
|
+
].filter(Boolean).join(" ");
|
|
336
|
+
const result = [
|
|
337
|
+
`Logical workspace inventory: ${inventory.summary.matching} matching ordinary records; ${composites.length} Composite Workspace record${composites.length === 1 ? "" : "s"}.`,
|
|
338
|
+
`States: active=${inventory.summary.active}, stale=${inventory.summary.stale}, invalid=${inventory.summary.invalid}, closed=${inventory.summary.closed}.`,
|
|
339
|
+
...inventory.workspaces.map((entry) => [
|
|
340
|
+
entry.label,
|
|
341
|
+
`state=${entry.state}`,
|
|
342
|
+
`status=${entry.status}`,
|
|
343
|
+
`mode=${entry.mode}`,
|
|
344
|
+
entry.managed ? "managed" : undefined,
|
|
345
|
+
entry.current ? "current" : undefined,
|
|
346
|
+
`root=${entry.root}`,
|
|
347
|
+
`last-used=${entry.lastUsedAt}`,
|
|
348
|
+
].filter(Boolean).join(" ")),
|
|
349
|
+
...composites.map((entry) => `${entry.name} [${entry.workspaceId}] kind=composite state=${entry.state} members=${entry.members.length}`),
|
|
350
|
+
instruction,
|
|
351
|
+
].join("\n");
|
|
352
|
+
logToolCall(config, {
|
|
353
|
+
tool: "open_workspace",
|
|
354
|
+
action: "list",
|
|
355
|
+
path: root,
|
|
356
|
+
success: true,
|
|
357
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
358
|
+
});
|
|
359
|
+
return {
|
|
360
|
+
content: [textBlock(result)],
|
|
361
|
+
structuredContent: {
|
|
362
|
+
action: "list",
|
|
363
|
+
...inventory,
|
|
364
|
+
...(composites.length > 0 ? { compositeWorkspaces: composites } : {}),
|
|
365
|
+
instruction,
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (root !== undefined || status !== undefined || state !== undefined ||
|
|
370
|
+
staleOnly !== undefined || offset !== undefined || limit !== undefined) {
|
|
371
|
+
throw new Error("open_workspace inventory filters root, status, state, staleOnly, offset, and limit are only valid with action=list.");
|
|
372
|
+
}
|
|
373
|
+
const openingComposite = kind === "composite" ||
|
|
374
|
+
(workspaceId !== undefined && compositeWorkspaces.has(workspaceId));
|
|
375
|
+
if (openingComposite) {
|
|
376
|
+
if (relay !== undefined || path !== undefined || mode !== undefined || baseRef !== undefined ||
|
|
377
|
+
newWorktree !== undefined || newWorkspace !== undefined) {
|
|
378
|
+
throw new Error("Composite Workspace open accepts name/workspaceId/context only; members are attached separately and keep their own Workspace definitions.");
|
|
379
|
+
}
|
|
380
|
+
if (workspaceId !== undefined && kind === "workspace") {
|
|
381
|
+
throw new Error(`${workspaceId} is a Composite Workspace, not an ordinary Workspace.`);
|
|
382
|
+
}
|
|
383
|
+
const composite = workspaceId !== undefined
|
|
384
|
+
? compositeWorkspaces.open(workspaceId)
|
|
385
|
+
: compositeWorkspaces.create(name ?? "");
|
|
386
|
+
workspaceTasks.initializeWorkspace(composite.id);
|
|
387
|
+
const compositeTaskContext = compositeCapabilityContext(composite.id, compositeTaskGuides);
|
|
388
|
+
const compositeCapabilityCatalog = capabilityRegistry.catalog(compositeTaskContext);
|
|
389
|
+
const compositeCapabilityGuides = compositeTaskGuides.map((guide) => ({
|
|
390
|
+
name: guide.name,
|
|
391
|
+
description: guide.description,
|
|
392
|
+
whenToRead: guide.whenToRead,
|
|
393
|
+
path: formatPathForPrompt(guide.filePath),
|
|
394
|
+
}));
|
|
395
|
+
const memberContext = memberName
|
|
396
|
+
? await loadCompositeMemberContext(composite.id, memberName, context ?? "auto", conversationScopeId, protectedWorkspaceIds)
|
|
397
|
+
: undefined;
|
|
398
|
+
const instruction = [
|
|
399
|
+
`This is Composite Workspace ${composite.name} (${composite.id}).`,
|
|
400
|
+
"It has no mounted working directory of its own. Use the Composite workspaceId as the top-level Workspace handle and explicitly select one named member for member-scoped work operations.",
|
|
401
|
+
composite.members.length > 0
|
|
402
|
+
? `Members: ${composite.members.map((member) => `${member.name} — ${member.purpose}`).join("; ")}.`
|
|
403
|
+
: "This Composite Workspace currently has no members.",
|
|
404
|
+
"Member names and purposes are structural context and are always returned when this Composite Workspace is opened. context=auto/full/none controls only heavy member bootstrap context, not this Composite identity.",
|
|
405
|
+
compositeCapabilityCatalog.length > 0
|
|
406
|
+
? `Composite-owned capabilities: ${compositeCapabilityCatalog.map((entry) => entry.name).join(", ")}. Use these without member because their state belongs to the Composite Workspace itself.`
|
|
407
|
+
: undefined,
|
|
408
|
+
compositeCapabilityCatalog.some((entry) => entry.name === "workspace.tasks")
|
|
409
|
+
? workspaceTaskUsageInstruction
|
|
410
|
+
: undefined,
|
|
411
|
+
composite.members.length > 0
|
|
412
|
+
? "Before first work on a member, reopen this Composite Workspace with memberName=<member> and context=auto to receive that member's project bootstrap without creating an implicit current member."
|
|
413
|
+
: undefined,
|
|
414
|
+
"close_workspace preserves this Composite identity for later reopen. Use action=delete only when the user explicitly wants to dissolve the Composite relationship; neither operation closes or cleans up member Workspaces.",
|
|
415
|
+
].join("\n\n");
|
|
416
|
+
const response = {
|
|
417
|
+
content: [textBlock(instruction)],
|
|
418
|
+
_meta: {
|
|
419
|
+
tool: "open_workspace",
|
|
420
|
+
card: {
|
|
421
|
+
workspaceId: composite.id,
|
|
422
|
+
kind: "composite",
|
|
423
|
+
name: composite.name,
|
|
424
|
+
path: composite.name,
|
|
425
|
+
members: composite.members,
|
|
426
|
+
instruction,
|
|
427
|
+
summary: { members: composite.members.length, status: composite.status },
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
structuredContent: {
|
|
431
|
+
action: "open",
|
|
432
|
+
workspaceId: composite.id,
|
|
433
|
+
kind: "composite",
|
|
434
|
+
name: composite.name,
|
|
435
|
+
status: composite.status,
|
|
436
|
+
state: composite.status,
|
|
437
|
+
members: composite.members,
|
|
438
|
+
capabilityCatalog: compositeCapabilityCatalog,
|
|
439
|
+
capabilityGuides: compositeCapabilityGuides,
|
|
440
|
+
...(memberContext ? { memberContext } : {}),
|
|
441
|
+
instruction,
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
logToolCall(config, {
|
|
445
|
+
tool: "open_workspace",
|
|
446
|
+
action: "composite",
|
|
447
|
+
success: true,
|
|
448
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
449
|
+
});
|
|
450
|
+
rememberWorkspacePanelState(composite.id, response);
|
|
451
|
+
return response;
|
|
452
|
+
}
|
|
453
|
+
if (name !== undefined || memberName !== undefined) {
|
|
454
|
+
throw new Error("open_workspace name/memberName are only valid for a Composite Workspace.");
|
|
455
|
+
}
|
|
456
|
+
if (workspaceId !== undefined && remoteWorkspaces.has(workspaceId)) {
|
|
457
|
+
if (path !== undefined || relay !== undefined || mode !== undefined || baseRef !== undefined ||
|
|
458
|
+
newWorktree !== undefined || newWorkspace !== undefined) {
|
|
459
|
+
throw new Error("Resuming a relayed Workspace by workspaceId accepts context only.");
|
|
460
|
+
}
|
|
461
|
+
const resumed = await remoteWorkspaces.resumeWorkspace(workspaceId, context ?? "auto", hostScopeIdFor(_meta, sessionId));
|
|
462
|
+
rememberWorkspacePanelState(workspaceId, resumed);
|
|
463
|
+
return resumed;
|
|
464
|
+
}
|
|
465
|
+
if (relay !== undefined) {
|
|
466
|
+
if (!path)
|
|
467
|
+
throw new Error("Relayed open_workspace requires path.");
|
|
468
|
+
const opened = await remoteWorkspaces.openWorkspace(relay, {
|
|
469
|
+
path,
|
|
470
|
+
mode,
|
|
471
|
+
baseRef,
|
|
472
|
+
newWorktree,
|
|
473
|
+
newWorkspace,
|
|
474
|
+
context,
|
|
475
|
+
}, hostScopeIdFor(_meta, sessionId));
|
|
476
|
+
const relayedSkills = Array.isArray(opened.skills)
|
|
477
|
+
? opened.skills
|
|
478
|
+
: [];
|
|
479
|
+
const relayedCapabilities = Array.isArray(opened.capabilityCatalog)
|
|
480
|
+
? opened.capabilityCatalog
|
|
481
|
+
: [];
|
|
482
|
+
const result = [
|
|
483
|
+
`Opened relayed workspace ${opened.workspaceId}.`,
|
|
484
|
+
`Execution remote: ${relay}`,
|
|
485
|
+
`Root: ${opened.root}`,
|
|
486
|
+
`Mode: ${opened.mode}`,
|
|
487
|
+
relayedSkills.length > 0
|
|
488
|
+
? `Available skills: ${relayedSkills.map((skill) => String(skill.name ?? "")).filter(Boolean).join(", ")}`
|
|
489
|
+
: undefined,
|
|
490
|
+
relayedCapabilities.length > 0
|
|
491
|
+
? `Optional capabilities: ${relayedCapabilities.map((entry) => String(entry.name ?? "")).filter(Boolean).join(", ")}`
|
|
492
|
+
: undefined,
|
|
493
|
+
opened.instruction,
|
|
494
|
+
].filter(Boolean).join("\n");
|
|
495
|
+
const response = {
|
|
496
|
+
content: [textBlock(result)],
|
|
497
|
+
_meta: {
|
|
498
|
+
tool: "open_workspace",
|
|
499
|
+
card: {
|
|
500
|
+
workspaceId: opened.workspaceId,
|
|
501
|
+
kind: "workspace",
|
|
502
|
+
root: opened.root,
|
|
503
|
+
path: opened.root,
|
|
504
|
+
mode: opened.mode,
|
|
505
|
+
relay,
|
|
506
|
+
instruction: opened.instruction,
|
|
507
|
+
summary: { mode: opened.mode, relay },
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
structuredContent: {
|
|
511
|
+
action: "open",
|
|
512
|
+
workspaceId: opened.workspaceId,
|
|
513
|
+
kind: "workspace",
|
|
514
|
+
root: opened.root,
|
|
515
|
+
mode: opened.mode,
|
|
516
|
+
...(opened.sourceRoot ? { sourceRoot: opened.sourceRoot } : {}),
|
|
517
|
+
...(opened.contextFingerprint !== undefined
|
|
518
|
+
? { contextFingerprint: opened.contextFingerprint }
|
|
519
|
+
: {}),
|
|
520
|
+
...(opened.capabilityFingerprint !== undefined
|
|
521
|
+
? { capabilityFingerprint: opened.capabilityFingerprint }
|
|
522
|
+
: {}),
|
|
523
|
+
...(opened.capabilityCatalog !== undefined
|
|
524
|
+
? { capabilityCatalog: opened.capabilityCatalog }
|
|
525
|
+
: {}),
|
|
526
|
+
...(opened.capabilityGuides !== undefined
|
|
527
|
+
? { capabilityGuides: opened.capabilityGuides }
|
|
528
|
+
: {}),
|
|
529
|
+
...(opened.agentsFiles !== undefined ? { agentsFiles: opened.agentsFiles } : {}),
|
|
530
|
+
...(opened.availableAgentsFiles !== undefined
|
|
531
|
+
? { availableAgentsFiles: opened.availableAgentsFiles }
|
|
532
|
+
: {}),
|
|
533
|
+
...(opened.skills !== undefined ? { skills: opened.skills } : {}),
|
|
534
|
+
...(opened.agentProviders !== undefined
|
|
535
|
+
? { agentProviders: opened.agentProviders }
|
|
536
|
+
: {}),
|
|
537
|
+
...(opened.agents !== undefined ? { agents: opened.agents } : {}),
|
|
538
|
+
...(opened.skillDiagnostics !== undefined
|
|
539
|
+
? { skillDiagnostics: opened.skillDiagnostics }
|
|
540
|
+
: {}),
|
|
541
|
+
instruction: opened.instruction,
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
logToolCall(config, {
|
|
545
|
+
tool: "open_workspace",
|
|
546
|
+
action: "relay",
|
|
547
|
+
path: opened.root,
|
|
548
|
+
success: true,
|
|
549
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
550
|
+
});
|
|
551
|
+
rememberWorkspacePanelState(opened.workspaceId, response);
|
|
552
|
+
return response;
|
|
553
|
+
}
|
|
554
|
+
return presentLocalWorkspaceOpen(presentation, input, { conversationScopeId, protectedWorkspaceIds, startedAt, sessionId });
|
|
555
|
+
}
|