@akira-tl/forgerelay 0.8.8 → 0.8.9
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 +12 -0
- package/README.md +10 -18
- package/capabilities/host-integration/GUIDE.md +2 -2
- 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/setup-support.js +116 -0
- package/dist/cli.js +18 -126
- package/dist/lsp/code-intelligence.js +1 -1
- package/dist/lsp/test-support/server-fixture.js +9 -9
- 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/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} +2 -2
- 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 +207 -0
- package/dist/mcp/server/core/version.js +9 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +400 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +523 -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 +222 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +552 -0
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
- package/dist/{config.js → runtime/config/config.js} +3 -3
- package/dist/{user-config.js → runtime/config/user-config.js} +14 -54
- 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 +116 -4202
- 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 +302 -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/workspaces/relay/types.js +1 -0
- package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
- package/dist/workspaces/resources/skills.js +285 -0
- package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
- package/dist/workspaces/sessions.js +457 -0
- package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
- package/dist/workspaces.js +68 -995
- package/docs/chatgpt-coding-workflow.md +3 -11
- package/docs/configuration.md +12 -25
- 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 -8
- 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/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/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
- /package/capabilities/{workspace-tasks → workspace/workspace-tasks}/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/{process-platform.js → mcp/process/process-platform.js} +0 -0
- /package/dist/{process-sessions.js → mcp/process/process-sessions.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/{remote-transport.js → workspaces/relay/transport/remote-transport.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
package/dist/server.js
CHANGED
|
@@ -1,1348 +1,47 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import { access, realpath } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
6
3
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { McpServer
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import express from "express";
|
|
16
|
-
import * as z from "zod/v4";
|
|
17
|
-
import { applyPatch } from "./apply-patch.js";
|
|
18
|
-
import { ActivityAuditStore } from "./activity/audit-store.js";
|
|
19
|
-
import { BashOutputStore } from "./activity/bash-output-store.js";
|
|
20
|
-
import { HostTurnStore } from "./activity/host-turn-store.js";
|
|
21
|
-
import { registerActivityQueryTools } from "./activity/mcp-query-tools.js";
|
|
22
|
-
import { ActivityLifecycle, } from "./activity/lifecycle.js";
|
|
23
|
-
import { ActivityQueryService } from "./activity/query-service.js";
|
|
24
|
-
import { buildCapabilityFingerprint, loadCapabilityGuides } from "./capabilities.js";
|
|
25
|
-
import { CapabilityError, createCapabilityRegistry, } from "./capability-registry.js";
|
|
26
|
-
import { deletePath, renamePath } from "./file-mutations.js";
|
|
27
|
-
import { downloadIncomingArtifact, isArtifactDownloadSupportedPlatform, } from "./artifact-tools.js";
|
|
28
|
-
import { ArtifactError } from "./artifact-error.js";
|
|
29
|
-
import { loadConfig } from "./config.js";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { registerAppResource } from "@modelcontextprotocol/ext-apps/server";
|
|
6
|
+
import { registerActivityQueryTools } from "./activity/runtime/mcp-query-tools.js";
|
|
7
|
+
import { buildCapabilityFingerprint, loadCapabilityGuides } from "./mcp/server/core/capabilities.js";
|
|
8
|
+
import { CapabilityError, createCapabilityRegistry } from "./mcp/server/core/capability-registry.js";
|
|
9
|
+
import { downloadIncomingArtifact, isArtifactDownloadSupportedPlatform } from "./mcp/artifacts/artifact-tools.js";
|
|
10
|
+
import { ArtifactError } from "./mcp/artifacts/artifact-error.js";
|
|
11
|
+
import { loadConfig } from "./runtime/config/config.js";
|
|
30
12
|
import { CodeIntelligenceError } from "./lsp/code-intelligence.js";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import { RemoteWorkspaceRelay } from "./remote-workspace-relay.js";
|
|
51
|
-
import { hostConversationScopeId, openAiConversationScopeId } from "./request-meta.js";
|
|
52
|
-
import { ACTIVITY_PANEL_APP_LEGACY_URI, ACTIVITY_PANEL_APP_URI_TEMPLATE, MCP_APP_RESOURCE_TEMPLATE_REVISION, readActivityPanelAppManifestEntry, readWorkspaceAppManifestEntry, readWorkspaceLifecycleAppManifestEntry, resolveActivityPanelAppIdentity, resolveWorkspaceAppIdentity, resolveWorkspaceLifecycleAppIdentity, WORKSPACE_APP_LEGACY_URI, WORKSPACE_APP_URI_TEMPLATE, WORKSPACE_LIFECYCLE_APP_LEGACY_URI, WORKSPACE_LIFECYCLE_APP_URI_TEMPLATE, } from "./mcp-app-template.js";
|
|
53
|
-
import { shutdownHttpServer } from "./server-shutdown.js";
|
|
54
|
-
import { formatPathForPrompt } from "./skills.js";
|
|
55
|
-
import { createWorkspaceStore } from "./workspace-store.js";
|
|
56
|
-
import { WorkspaceTaskReminderTracker } from "./workspace-task-reminders.js";
|
|
57
|
-
import { WorkspaceTaskStore } from "./workspace-tasks.js";
|
|
58
|
-
import { compactWorkspacePresentation } from "./workspace-presentation.js";
|
|
59
|
-
import { formatAgentsPath, WorkspaceRegistry, } from "./workspaces.js";
|
|
60
|
-
import { formatAvailableSubagentProfile, summarizeSubagentProfile } from "./subagents/profiles.js";
|
|
61
|
-
import { formatSubagentProviderAvailabilitySummary, formatUnavailableSubagentProvider, getSubagentProviderAvailabilitySnapshot, } from "./subagents/providers/availability.js";
|
|
62
|
-
import { capabilityActivityAuditRequest, capabilityActivityAuditResult } from "./subagents/sessions/mcp/audit.js";
|
|
13
|
+
import { HookRunner } from "./mcp/hooks/hooks.js";
|
|
14
|
+
import { checkHookConfiguration } from "./mcp/hooks/hook-cli.js";
|
|
15
|
+
import { buildServerInstructions, buildToolDescriptions, toolNames } from "./mcp/server-instructions.js";
|
|
16
|
+
import { IncomingArtifactAdapterRegistry } from "./mcp/artifacts/incoming-artifacts.js";
|
|
17
|
+
import { registerProcessTools } from "./mcp/process/tools.js";
|
|
18
|
+
import { attachCompletedProcessNotices, recordBashCompletion } from "./mcp/process/runtime.js";
|
|
19
|
+
import { CompositeActivityCoordinator } from "./workspaces/composite/composite-activity.js";
|
|
20
|
+
import { CompositeWorkspaceRegistry } from "./workspaces/composite/composite-workspaces.js";
|
|
21
|
+
import { RemoteWorkspaceRelay } from "./workspaces/relay/workspace-relay.js";
|
|
22
|
+
import { hostConversationScopeId } from "./mcp/request-meta.js";
|
|
23
|
+
import { createActivityPanelApp } from "./mcp/panel/app.js";
|
|
24
|
+
import { shutdownHttpServer } from "./mcp/server/transport/server-shutdown.js";
|
|
25
|
+
import { formatPathForPrompt } from "./workspaces/resources/skills.js";
|
|
26
|
+
import { WorkspaceTaskReminderTracker } from "./workspaces/tasks/workspace-task-reminders.js";
|
|
27
|
+
import { WorkspaceTaskStore } from "./workspaces/tasks/workspace-tasks.js";
|
|
28
|
+
import { compactWorkspacePresentation } from "./workspaces/presentation/workspace-presentation.js";
|
|
29
|
+
import { formatAgentsPath } from "./workspaces.js";
|
|
30
|
+
import { summarizeSubagentProfile } from "./subagents/profiles.js";
|
|
31
|
+
import { formatSubagentProviderAvailabilitySummary } from "./subagents/providers/availability.js";
|
|
63
32
|
import { createSubagentMcpRuntime } from "./subagents/sessions/mcp/runtime.js";
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
destructiveHint: true,
|
|
74
|
-
idempotentHint: false,
|
|
75
|
-
openWorldHint: false,
|
|
76
|
-
};
|
|
77
|
-
const EDIT_TOOL_ANNOTATIONS = {
|
|
78
|
-
readOnlyHint: false,
|
|
79
|
-
destructiveHint: true,
|
|
80
|
-
idempotentHint: false,
|
|
81
|
-
openWorldHint: false,
|
|
82
|
-
};
|
|
83
|
-
const SHELL_TOOL_ANNOTATIONS = {
|
|
84
|
-
readOnlyHint: false,
|
|
85
|
-
destructiveHint: true,
|
|
86
|
-
idempotentHint: false,
|
|
87
|
-
openWorldHint: true,
|
|
88
|
-
};
|
|
89
|
-
function shouldAttachWidget(mode, kind) {
|
|
90
|
-
if (mode === "off")
|
|
91
|
-
return false;
|
|
92
|
-
return kind === "activity";
|
|
93
|
-
}
|
|
94
|
-
function toolWidgetDescriptorMeta(config, kind) {
|
|
95
|
-
if (!shouldAttachWidget(config.widgets, kind))
|
|
96
|
-
return { _meta: {} };
|
|
97
|
-
const resourceUri = kind === "activity"
|
|
98
|
-
? currentActivityPanelAppIdentity(config).uri
|
|
99
|
-
: currentWorkspaceLifecycleAppIdentity(config).uri;
|
|
100
|
-
return {
|
|
101
|
-
_meta: {
|
|
102
|
-
ui: {
|
|
103
|
-
resourceUri,
|
|
104
|
-
visibility: ["model", "app"],
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function workspaceLogContext(workspace, _transportSessionId) {
|
|
110
|
-
return {
|
|
111
|
-
workspaceId: workspace.id,
|
|
112
|
-
workspace: workspaceLogLabel(workspace.root, workspace.id),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function formatDiscoveredWorkspaceInstructions(files, workspaceRoot) {
|
|
116
|
-
return [
|
|
117
|
-
"Workspace instructions discovered for this path. Apply them to follow-up work under their directories:",
|
|
118
|
-
...files.flatMap((file) => [
|
|
119
|
-
`--- ${formatAgentsPath(file.path, workspaceRoot)} ---`,
|
|
120
|
-
file.content.trimEnd(),
|
|
121
|
-
]),
|
|
122
|
-
].join("\n");
|
|
123
|
-
}
|
|
124
|
-
async function assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, paths) {
|
|
125
|
-
const discovered = new Map();
|
|
126
|
-
for (const path of paths) {
|
|
127
|
-
const absolutePath = resolve(workspace.root, path);
|
|
128
|
-
for (const file of await workspaces.discoverPathInstructions(workspace, absolutePath)) {
|
|
129
|
-
discovered.set(file.path, file);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (discovered.size === 0)
|
|
133
|
-
return;
|
|
134
|
-
throw new Error([
|
|
135
|
-
formatDiscoveredWorkspaceInstructions([...discovered.values()], workspace.root),
|
|
136
|
-
"Apply these instructions, then retry this tool call. No mutation or command was executed.",
|
|
137
|
-
].join("\n"));
|
|
138
|
-
}
|
|
139
|
-
function resultOutputSchema(extra = {}) {
|
|
140
|
-
return {
|
|
141
|
-
result: z
|
|
142
|
-
.string()
|
|
143
|
-
.describe("Model-readable result text for follow-up reasoning and plain MCP hosts."),
|
|
144
|
-
...extra,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
const workspaceSkillOutputSchema = z.object({
|
|
148
|
-
name: z.string(),
|
|
149
|
-
description: z.string(),
|
|
150
|
-
});
|
|
151
|
-
const workspaceSkillDiagnosticOutputSchema = z.object({
|
|
152
|
-
type: z.enum(["warning", "error", "collision"]),
|
|
153
|
-
message: z.string(),
|
|
154
|
-
collision: z.object({
|
|
155
|
-
resourceType: z.enum(["extension", "skill", "prompt", "theme"]),
|
|
156
|
-
name: z.string(),
|
|
157
|
-
}).optional(),
|
|
158
|
-
});
|
|
159
|
-
function redactSkillDiagnosticPaths(diagnostics) {
|
|
160
|
-
return diagnostics.map((diagnostic) => {
|
|
161
|
-
let message = diagnostic.message;
|
|
162
|
-
const hiddenPaths = [
|
|
163
|
-
diagnostic.path,
|
|
164
|
-
diagnostic.collision?.winnerPath,
|
|
165
|
-
diagnostic.collision?.loserPath,
|
|
166
|
-
].filter((path) => Boolean(path));
|
|
167
|
-
for (const path of hiddenPaths) {
|
|
168
|
-
message = message.split(path).join("<skill-path>");
|
|
169
|
-
}
|
|
170
|
-
return {
|
|
171
|
-
type: diagnostic.type,
|
|
172
|
-
message,
|
|
173
|
-
...(diagnostic.collision
|
|
174
|
-
? {
|
|
175
|
-
collision: {
|
|
176
|
-
resourceType: diagnostic.collision.resourceType,
|
|
177
|
-
name: diagnostic.collision.name,
|
|
178
|
-
},
|
|
179
|
-
}
|
|
180
|
-
: {}),
|
|
181
|
-
};
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
const capabilityFingerprintOutputSchema = z.object({
|
|
185
|
-
version: z.string(),
|
|
186
|
-
toolMode: z.enum(["minimal", "full", "codex"]),
|
|
187
|
-
capabilities: z.array(z.string()),
|
|
188
|
-
});
|
|
189
|
-
const capabilityGuideOutputSchema = z.object({
|
|
190
|
-
name: z.string(),
|
|
191
|
-
description: z.string(),
|
|
192
|
-
whenToRead: z.string(),
|
|
193
|
-
path: z.string(),
|
|
194
|
-
});
|
|
195
|
-
const capabilityCatalogGuideOutputSchema = z.object({
|
|
196
|
-
name: z.string(),
|
|
197
|
-
path: z.string(),
|
|
198
|
-
readBeforeFirstUse: z.boolean(),
|
|
199
|
-
});
|
|
200
|
-
const capabilityCatalogOutputSchema = z.object({
|
|
201
|
-
name: z.string(),
|
|
202
|
-
description: z.string(),
|
|
203
|
-
available: z.boolean(),
|
|
204
|
-
unavailableReason: z.string().optional(),
|
|
205
|
-
batchPolicy: z.enum(["parallel", "serial", "unsupported"]),
|
|
206
|
-
guide: capabilityCatalogGuideOutputSchema,
|
|
207
|
-
});
|
|
208
|
-
const capabilityErrorOutputSchema = z.object({
|
|
209
|
-
code: z.string(),
|
|
210
|
-
message: z.string(),
|
|
211
|
-
});
|
|
212
|
-
const workspaceAgentsFileOutputSchema = z.object({
|
|
213
|
-
path: z.string(),
|
|
214
|
-
content: z.string(),
|
|
215
|
-
});
|
|
216
|
-
const workspaceLocalAgentOutputSchema = z.object({
|
|
217
|
-
name: z.string(),
|
|
218
|
-
description: z.string(),
|
|
219
|
-
provider: z.string(),
|
|
220
|
-
model: z.string().optional(),
|
|
221
|
-
thinking: z.string().optional(),
|
|
222
|
-
providerAvailable: z.boolean().optional(),
|
|
223
|
-
providerUnavailableReason: z.string().optional(),
|
|
224
|
-
});
|
|
225
|
-
const workspaceSubagentProviderOutputSchema = z.object({
|
|
226
|
-
name: z.string(),
|
|
227
|
-
available: z.boolean(),
|
|
228
|
-
continuationSupported: z.boolean(),
|
|
229
|
-
reason: z.string().optional(),
|
|
230
|
-
});
|
|
231
|
-
const workspaceAvailableAgentsFileOutputSchema = z.object({
|
|
232
|
-
path: z.string(),
|
|
233
|
-
});
|
|
234
|
-
const workspaceInventoryEntryOutputSchema = z.object({
|
|
235
|
-
label: z.string(),
|
|
236
|
-
workspaceId: z.string(),
|
|
237
|
-
root: z.string(),
|
|
238
|
-
status: z.string(),
|
|
239
|
-
state: z.enum(["active", "stale", "invalid", "closed"]),
|
|
240
|
-
mode: z.enum(["checkout", "worktree"]),
|
|
241
|
-
sourceRoot: z.string().optional(),
|
|
242
|
-
branch: z.string().optional(),
|
|
243
|
-
targetBranch: z.string().optional(),
|
|
244
|
-
managed: z.boolean(),
|
|
245
|
-
createdAt: z.string(),
|
|
246
|
-
lastUsedAt: z.string(),
|
|
247
|
-
idleMs: z.number().nonnegative(),
|
|
248
|
-
rootValid: z.boolean(),
|
|
249
|
-
current: z.boolean(),
|
|
250
|
-
});
|
|
251
|
-
const workspaceInventorySummaryOutputSchema = z.object({
|
|
252
|
-
total: z.number().int().nonnegative(),
|
|
253
|
-
matching: z.number().int().nonnegative(),
|
|
254
|
-
active: z.number().int().nonnegative(),
|
|
255
|
-
stale: z.number().int().nonnegative(),
|
|
256
|
-
invalid: z.number().int().nonnegative(),
|
|
257
|
-
closed: z.number().int().nonnegative(),
|
|
258
|
-
});
|
|
259
|
-
const workspaceInventoryPageOutputSchema = z.object({
|
|
260
|
-
offset: z.number().int().nonnegative(),
|
|
261
|
-
limit: z.number().int().positive(),
|
|
262
|
-
hasMore: z.boolean(),
|
|
263
|
-
});
|
|
264
|
-
const workspaceTaskInspectionSummaryOutputSchema = z.object({
|
|
265
|
-
level: z.literal("summary"),
|
|
266
|
-
version: z.literal(1),
|
|
267
|
-
revision: z.number().int().nonnegative(),
|
|
268
|
-
lists: z.array(z.object({
|
|
269
|
-
id: z.string(),
|
|
270
|
-
name: z.string(),
|
|
271
|
-
state: z.enum(["active", "archived"]),
|
|
272
|
-
revision: z.number().int().positive(),
|
|
273
|
-
taskCount: z.number().int().nonnegative(),
|
|
274
|
-
unfinishedTaskCount: z.number().int().nonnegative(),
|
|
275
|
-
})),
|
|
276
|
-
});
|
|
277
|
-
const workspaceInspectionMemberOutputSchema = z.object({
|
|
278
|
-
name: z.string(),
|
|
279
|
-
purpose: z.string(),
|
|
280
|
-
workspaceId: z.string(),
|
|
281
|
-
known: z.boolean(),
|
|
282
|
-
location: z.enum(["local", "relay"]).optional(),
|
|
283
|
-
state: z.enum(["active", "stale", "invalid", "closed"]).optional(),
|
|
284
|
-
status: z.string().optional(),
|
|
285
|
-
routeState: z.literal("known").optional(),
|
|
286
|
-
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
287
|
-
rootValid: z.boolean().optional(),
|
|
288
|
-
});
|
|
289
|
-
const workspaceInspectionOutputSchema = z.union([
|
|
290
|
-
z.object({
|
|
291
|
-
workspaceId: z.string(),
|
|
292
|
-
kind: z.literal("workspace"),
|
|
293
|
-
location: z.literal("local"),
|
|
294
|
-
label: z.string(),
|
|
295
|
-
root: z.string(),
|
|
296
|
-
status: z.string(),
|
|
297
|
-
state: z.enum(["active", "stale", "invalid", "closed"]),
|
|
298
|
-
mode: z.enum(["checkout", "worktree"]),
|
|
299
|
-
sourceRoot: z.string().optional(),
|
|
300
|
-
branch: z.string().optional(),
|
|
301
|
-
targetBranch: z.string().optional(),
|
|
302
|
-
managed: z.boolean(),
|
|
303
|
-
createdAt: z.string(),
|
|
304
|
-
lastUsedAt: z.string(),
|
|
305
|
-
idleMs: z.number().nonnegative(),
|
|
306
|
-
rootValid: z.boolean(),
|
|
307
|
-
taskSummary: workspaceTaskInspectionSummaryOutputSchema.optional(),
|
|
308
|
-
}),
|
|
309
|
-
z.object({
|
|
310
|
-
workspaceId: z.string(),
|
|
311
|
-
kind: z.literal("workspace"),
|
|
312
|
-
location: z.literal("relay"),
|
|
313
|
-
root: z.string(),
|
|
314
|
-
routeState: z.literal("known"),
|
|
315
|
-
status: z.string().optional(),
|
|
316
|
-
state: z.enum(["active", "stale", "invalid", "closed"]).optional(),
|
|
317
|
-
mode: z.enum(["checkout", "worktree"]),
|
|
318
|
-
sourceRoot: z.string().optional(),
|
|
319
|
-
branch: z.string().optional(),
|
|
320
|
-
targetBranch: z.string().optional(),
|
|
321
|
-
managed: z.boolean().optional(),
|
|
322
|
-
createdAt: z.string().optional(),
|
|
323
|
-
lastUsedAt: z.string().optional(),
|
|
324
|
-
idleMs: z.number().nonnegative().optional(),
|
|
325
|
-
rootValid: z.boolean().optional(),
|
|
326
|
-
taskSummary: workspaceTaskInspectionSummaryOutputSchema.optional(),
|
|
327
|
-
relay: z.string(),
|
|
328
|
-
executionLocation: z.string(),
|
|
329
|
-
}),
|
|
330
|
-
z.object({
|
|
331
|
-
workspaceId: z.string(),
|
|
332
|
-
kind: z.literal("composite"),
|
|
333
|
-
name: z.string(),
|
|
334
|
-
status: z.enum(["active", "closed"]),
|
|
335
|
-
state: z.enum(["active", "closed"]),
|
|
336
|
-
createdAt: z.string(),
|
|
337
|
-
lastUsedAt: z.string(),
|
|
338
|
-
members: z.array(workspaceInspectionMemberOutputSchema),
|
|
339
|
-
taskSummary: workspaceTaskInspectionSummaryOutputSchema.optional(),
|
|
340
|
-
}),
|
|
341
|
-
]);
|
|
342
|
-
const reviewFileOutputSchema = z.object({
|
|
343
|
-
path: z.string(),
|
|
344
|
-
previousPath: z.string().optional(),
|
|
345
|
-
type: z.enum(["change", "rename-pure", "rename-changed", "new", "deleted"]),
|
|
346
|
-
additions: z.number(),
|
|
347
|
-
removals: z.number(),
|
|
348
|
-
});
|
|
349
|
-
const reviewSummaryOutputSchema = z.object({
|
|
350
|
-
files: z.number(),
|
|
351
|
-
additions: z.number(),
|
|
352
|
-
removals: z.number(),
|
|
353
|
-
});
|
|
354
|
-
function sendJsonRpcError(res, status, code, message) {
|
|
355
|
-
res.status(status).json({
|
|
356
|
-
jsonrpc: "2.0",
|
|
357
|
-
error: { code, message },
|
|
358
|
-
id: null,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
function requestLogFields(req, config) {
|
|
362
|
-
return {
|
|
363
|
-
ip: requestIp(req),
|
|
364
|
-
host: req.header("host"),
|
|
365
|
-
userAgent: req.header("user-agent"),
|
|
366
|
-
origin: req.header("origin"),
|
|
367
|
-
referer: req.header("referer"),
|
|
368
|
-
contentLength: req.header("content-length"),
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
function mcpRequestDebugFields(body) {
|
|
372
|
-
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
373
|
-
return {};
|
|
374
|
-
const request = body;
|
|
375
|
-
const rpcMethod = typeof request.method === "string" ? request.method : undefined;
|
|
376
|
-
const params = request.params && typeof request.params === "object" && !Array.isArray(request.params)
|
|
377
|
-
? request.params
|
|
378
|
-
: undefined;
|
|
379
|
-
const rpcMeta = params?._meta && typeof params._meta === "object" && !Array.isArray(params._meta)
|
|
380
|
-
? params._meta
|
|
381
|
-
: undefined;
|
|
382
|
-
const rpcMetaKeys = rpcMeta ? Object.keys(rpcMeta).sort() : [];
|
|
383
|
-
let rpcTarget;
|
|
384
|
-
if (rpcMethod === "resources/read" && typeof params?.uri === "string") {
|
|
385
|
-
rpcTarget = params.uri;
|
|
386
|
-
}
|
|
387
|
-
else if (rpcMethod === "tools/call" && typeof params?.name === "string") {
|
|
388
|
-
rpcTarget = params.name;
|
|
389
|
-
}
|
|
390
|
-
return {
|
|
391
|
-
rpcMethod,
|
|
392
|
-
rpcTarget,
|
|
393
|
-
...(rpcMetaKeys.length > 0 ? { rpcMetaKeys } : {}),
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
function logToolCall(config, fields) {
|
|
397
|
-
if (!config.logging.toolCalls)
|
|
398
|
-
return;
|
|
399
|
-
const { command, ...safeFields } = fields;
|
|
400
|
-
logEvent(config.logging, fields.success ? "info" : "warn", "tool_call", {
|
|
401
|
-
...safeFields,
|
|
402
|
-
commandPreview: config.logging.shellCommands && command ? commandPreview(command) : undefined,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
function contentText(content) {
|
|
406
|
-
return content
|
|
407
|
-
.filter((item) => item.type === "text")
|
|
408
|
-
.map((item) => item.text)
|
|
409
|
-
.join("\n");
|
|
410
|
-
}
|
|
411
|
-
function toolErrorPreview(content) {
|
|
412
|
-
const text = contentText(content).replace(/\s+/g, " ").trim();
|
|
413
|
-
if (!text)
|
|
414
|
-
return undefined;
|
|
415
|
-
return text.length > 240 ? `${text.slice(0, 237)}...` : text;
|
|
416
|
-
}
|
|
417
|
-
function logFailedToolResponse(config, fields, content, startedAt) {
|
|
418
|
-
logToolCall(config, {
|
|
419
|
-
...fields,
|
|
420
|
-
success: false,
|
|
421
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
422
|
-
error: toolErrorPreview(content),
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
function textBlock(text) {
|
|
426
|
-
return { type: "text", text };
|
|
427
|
-
}
|
|
428
|
-
function attachWorkspaceTaskReminder(result, reminder) {
|
|
429
|
-
if (!reminder || toolResultIsError(result) || typeof result !== "object" || result === null)
|
|
430
|
-
return result;
|
|
431
|
-
const content = result.content;
|
|
432
|
-
if (!Array.isArray(content))
|
|
433
|
-
return result;
|
|
434
|
-
return {
|
|
435
|
-
...result,
|
|
436
|
-
content: [...content, textBlock(reminder)],
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
function textSummary(content) {
|
|
440
|
-
const text = contentText(content);
|
|
441
|
-
return {
|
|
442
|
-
lines: text.length === 0 ? 0 : text.split("\n").length,
|
|
443
|
-
characters: text.length,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
function contentLineCount(content) {
|
|
447
|
-
if (content.length === 0)
|
|
448
|
-
return 0;
|
|
449
|
-
return content.endsWith("\n")
|
|
450
|
-
? content.slice(0, -1).split("\n").length
|
|
451
|
-
: content.split("\n").length;
|
|
452
|
-
}
|
|
453
|
-
function countDiffStats(diff) {
|
|
454
|
-
if (!diff)
|
|
455
|
-
return { additions: 0, removals: 0 };
|
|
456
|
-
let additions = 0;
|
|
457
|
-
let removals = 0;
|
|
458
|
-
for (const line of diff.split("\n")) {
|
|
459
|
-
if (line.startsWith("+") && !line.startsWith("+++"))
|
|
460
|
-
additions++;
|
|
461
|
-
if (line.startsWith("-") && !line.startsWith("---"))
|
|
462
|
-
removals++;
|
|
463
|
-
}
|
|
464
|
-
return { additions, removals };
|
|
465
|
-
}
|
|
466
|
-
function newFilePatch(path, content) {
|
|
467
|
-
const lines = content.length === 0
|
|
468
|
-
? []
|
|
469
|
-
: content.endsWith("\n")
|
|
470
|
-
? content.slice(0, -1).split("\n")
|
|
471
|
-
: content.split("\n");
|
|
472
|
-
const hunkLength = lines.length;
|
|
473
|
-
const hunkRange = hunkLength === 0 ? "+0,0" : `+1,${hunkLength}`;
|
|
474
|
-
const body = lines.map((line) => `+${line}`).join("\n");
|
|
475
|
-
return [
|
|
476
|
-
`diff --git a/${path} b/${path}`,
|
|
477
|
-
"new file mode 100644",
|
|
478
|
-
"index 0000000..0000000",
|
|
479
|
-
"--- /dev/null",
|
|
480
|
-
`+++ b/${path}`,
|
|
481
|
-
`@@ -0,0 ${hunkRange} @@`,
|
|
482
|
-
body,
|
|
483
|
-
]
|
|
484
|
-
.filter((line) => line.length > 0)
|
|
485
|
-
.join("\n");
|
|
486
|
-
}
|
|
487
|
-
function assetBaseUrl(config) {
|
|
488
|
-
return `${config.publicBaseUrl.replace(/\/+$/, "")}/mcp-app-assets`;
|
|
489
|
-
}
|
|
490
|
-
function uiManifestUrl() {
|
|
491
|
-
return new URL("../dist/ui/.vite/manifest.json", import.meta.url);
|
|
492
|
-
}
|
|
493
|
-
function uiBuildDirectoryUrl() {
|
|
494
|
-
return new URL("../dist/ui/", import.meta.url);
|
|
495
|
-
}
|
|
496
|
-
const cachedWorkspaceAppIdentities = new Map();
|
|
497
|
-
const cachedWorkspaceLifecycleAppIdentities = new Map();
|
|
498
|
-
const cachedActivityPanelAppIdentities = new Map();
|
|
499
|
-
function appResourceContractRevision(config) {
|
|
500
|
-
return [
|
|
501
|
-
MCP_APP_RESOURCE_TEMPLATE_REVISION,
|
|
502
|
-
`publicBaseUrls=${JSON.stringify(config.publicBaseUrls)}`,
|
|
503
|
-
].join("\0");
|
|
504
|
-
}
|
|
505
|
-
function appIdentityOptions(config) {
|
|
506
|
-
return {
|
|
507
|
-
manifestUrl: uiManifestUrl(),
|
|
508
|
-
buildDirectoryUrl: uiBuildDirectoryUrl(),
|
|
509
|
-
fallbackRevision: FORGERELAY_VERSION,
|
|
510
|
-
resourceTemplateRevision: appResourceContractRevision(config),
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
function currentWorkspaceAppIdentity(config) {
|
|
514
|
-
const key = appResourceContractRevision(config);
|
|
515
|
-
let identity = cachedWorkspaceAppIdentities.get(key);
|
|
516
|
-
if (!identity) {
|
|
517
|
-
identity = resolveWorkspaceAppIdentity(appIdentityOptions(config));
|
|
518
|
-
cachedWorkspaceAppIdentities.set(key, identity);
|
|
519
|
-
}
|
|
520
|
-
return identity;
|
|
521
|
-
}
|
|
522
|
-
function currentWorkspaceLifecycleAppIdentity(config) {
|
|
523
|
-
const key = appResourceContractRevision(config);
|
|
524
|
-
let identity = cachedWorkspaceLifecycleAppIdentities.get(key);
|
|
525
|
-
if (!identity) {
|
|
526
|
-
identity = resolveWorkspaceLifecycleAppIdentity(appIdentityOptions(config));
|
|
527
|
-
cachedWorkspaceLifecycleAppIdentities.set(key, identity);
|
|
528
|
-
}
|
|
529
|
-
return identity;
|
|
530
|
-
}
|
|
531
|
-
function currentActivityPanelAppIdentity(config) {
|
|
532
|
-
const key = appResourceContractRevision(config);
|
|
533
|
-
let identity = cachedActivityPanelAppIdentities.get(key);
|
|
534
|
-
if (!identity) {
|
|
535
|
-
identity = resolveActivityPanelAppIdentity(appIdentityOptions(config));
|
|
536
|
-
cachedActivityPanelAppIdentities.set(key, identity);
|
|
537
|
-
}
|
|
538
|
-
return identity;
|
|
539
|
-
}
|
|
540
|
-
function getWorkspaceAppManifestEntry() {
|
|
541
|
-
return readWorkspaceAppManifestEntry(uiManifestUrl());
|
|
542
|
-
}
|
|
543
|
-
function getWorkspaceLifecycleAppManifestEntry() {
|
|
544
|
-
return readWorkspaceLifecycleAppManifestEntry(uiManifestUrl());
|
|
545
|
-
}
|
|
546
|
-
function getActivityPanelAppManifestEntry() {
|
|
547
|
-
return readActivityPanelAppManifestEntry(uiManifestUrl());
|
|
548
|
-
}
|
|
549
|
-
function assetUrl(baseUrl, assetPath) {
|
|
550
|
-
return `${baseUrl}/${assetPath.replace(/^\/+/, "")}`;
|
|
551
|
-
}
|
|
552
|
-
function mcpAppHtml(config, entry, title, waitingMessage) {
|
|
553
|
-
const baseUrl = assetBaseUrl(config);
|
|
554
|
-
const stylesheets = (entry.css ?? [])
|
|
555
|
-
.map((stylesheet) => ` <link rel="stylesheet" crossorigin href="${assetUrl(baseUrl, stylesheet)}" />`)
|
|
556
|
-
.join("\n");
|
|
557
|
-
return `<!doctype html>
|
|
558
|
-
<html lang="en">
|
|
559
|
-
<head>
|
|
560
|
-
<meta charset="UTF-8" />
|
|
561
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
562
|
-
<title>${title}</title>
|
|
563
|
-
<script type="module" crossorigin src="${assetUrl(baseUrl, entry.file)}"></script>
|
|
564
|
-
${stylesheets}
|
|
565
|
-
</head>
|
|
566
|
-
<body>
|
|
567
|
-
<main id="app" class="shell">
|
|
568
|
-
<section class="empty">${waitingMessage}</section>
|
|
569
|
-
</main>
|
|
570
|
-
</body>
|
|
571
|
-
</html>`;
|
|
572
|
-
}
|
|
573
|
-
function workspaceAppHtml(config) {
|
|
574
|
-
return mcpAppHtml(config, getWorkspaceAppManifestEntry(), "ForgeRelay Workspace", "Waiting for a workspace result.");
|
|
575
|
-
}
|
|
576
|
-
function workspaceLifecycleAppHtml(config) {
|
|
577
|
-
return mcpAppHtml(config, getWorkspaceLifecycleAppManifestEntry(), "ForgeRelay Workspace Lifecycle", "Waiting for a workspace result.");
|
|
578
|
-
}
|
|
579
|
-
function activityPanelAppHtml(config) {
|
|
580
|
-
return mcpAppHtml(config, getActivityPanelAppManifestEntry(), "ForgeRelay Activity Panel", "Waiting for Activity Panel state.");
|
|
581
|
-
}
|
|
582
|
-
function appDomain(config) {
|
|
583
|
-
return new URL(config.publicBaseUrl).origin;
|
|
584
|
-
}
|
|
585
|
-
function appCsp(config) {
|
|
586
|
-
return {
|
|
587
|
-
resourceDomains: [...config.publicBaseUrls],
|
|
588
|
-
connectDomains: [...config.publicBaseUrls],
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
function uiBuildDirectory() {
|
|
592
|
-
return fileURLToPath(new URL("../dist/ui", import.meta.url));
|
|
593
|
-
}
|
|
594
|
-
function setAssetHeaders(res) {
|
|
595
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
596
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
|
|
597
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Range");
|
|
598
|
-
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
599
|
-
}
|
|
600
|
-
async function assertMcpAppAssets(entry) {
|
|
601
|
-
const candidates = [entry.file, ...(entry.css ?? [])].map((assetPath) => new URL(`../dist/ui/${assetPath}`, import.meta.url));
|
|
602
|
-
for (const candidate of candidates)
|
|
603
|
-
await access(candidate);
|
|
604
|
-
}
|
|
605
|
-
function appResourceVariant(requestedUri, currentUri, legacyUri) {
|
|
606
|
-
if (requestedUri === currentUri)
|
|
607
|
-
return "current";
|
|
608
|
-
if (requestedUri === legacyUri)
|
|
609
|
-
return "legacy";
|
|
610
|
-
return "historical";
|
|
611
|
-
}
|
|
612
|
-
async function readMcpAppResource(config, options) {
|
|
613
|
-
const compatibility = appResourceVariant(options.requestedUri, options.currentUri, options.legacyUri);
|
|
614
|
-
try {
|
|
615
|
-
await assertMcpAppAssets(options.entry);
|
|
616
|
-
const result = {
|
|
617
|
-
contents: [{
|
|
618
|
-
uri: options.requestedUri,
|
|
619
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
620
|
-
text: options.html,
|
|
621
|
-
_meta: {
|
|
622
|
-
ui: {
|
|
623
|
-
domain: appDomain(config),
|
|
624
|
-
csp: appCsp(config),
|
|
625
|
-
},
|
|
626
|
-
// MCP Apps defines resource metadata under `_meta.ui`. Inspector 2.3.0
|
|
627
|
-
// reads the content-item CSP/domain from `_meta` directly, so mirror
|
|
628
|
-
// these values until that installed-host compatibility gap is gone.
|
|
629
|
-
domain: appDomain(config),
|
|
630
|
-
csp: appCsp(config),
|
|
631
|
-
},
|
|
632
|
-
}],
|
|
633
|
-
};
|
|
634
|
-
logEvent(config.logging, "debug", "mcp_app_template_read", {
|
|
635
|
-
requestedUri: options.requestedUri,
|
|
636
|
-
currentUri: options.currentUri,
|
|
637
|
-
compatibility,
|
|
638
|
-
transportSessionIdPrefix: transportSessionIdPrefix(options.transportSessionId),
|
|
639
|
-
});
|
|
640
|
-
return result;
|
|
641
|
-
}
|
|
642
|
-
catch (error) {
|
|
643
|
-
logEvent(config.logging, "warn", "mcp_app_template_read_failed", {
|
|
644
|
-
requestedUri: options.requestedUri,
|
|
645
|
-
currentUri: options.currentUri,
|
|
646
|
-
compatibility,
|
|
647
|
-
error: error instanceof Error ? error.message : String(error),
|
|
648
|
-
transportSessionIdPrefix: transportSessionIdPrefix(options.transportSessionId),
|
|
649
|
-
});
|
|
650
|
-
throw error;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
function readWorkspaceAppResource(config, requestedUri, transportSessionId, currentUri = currentWorkspaceAppIdentity(config).uri, legacyUri = WORKSPACE_APP_LEGACY_URI) {
|
|
654
|
-
return readMcpAppResource(config, {
|
|
655
|
-
requestedUri,
|
|
656
|
-
currentUri,
|
|
657
|
-
legacyUri,
|
|
658
|
-
entry: getWorkspaceAppManifestEntry(),
|
|
659
|
-
html: workspaceAppHtml(config),
|
|
660
|
-
transportSessionId,
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
function readWorkspaceLifecycleAppResource(config, requestedUri, transportSessionId) {
|
|
664
|
-
return readMcpAppResource(config, {
|
|
665
|
-
requestedUri,
|
|
666
|
-
currentUri: currentWorkspaceLifecycleAppIdentity(config).uri,
|
|
667
|
-
legacyUri: WORKSPACE_LIFECYCLE_APP_LEGACY_URI,
|
|
668
|
-
entry: getWorkspaceLifecycleAppManifestEntry(),
|
|
669
|
-
html: workspaceLifecycleAppHtml(config),
|
|
670
|
-
transportSessionId,
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
function readActivityPanelAppResource(config, requestedUri, transportSessionId) {
|
|
674
|
-
return readMcpAppResource(config, {
|
|
675
|
-
requestedUri,
|
|
676
|
-
currentUri: currentActivityPanelAppIdentity(config).uri,
|
|
677
|
-
legacyUri: ACTIVITY_PANEL_APP_LEGACY_URI,
|
|
678
|
-
entry: getActivityPanelAppManifestEntry(),
|
|
679
|
-
html: activityPanelAppHtml(config),
|
|
680
|
-
transportSessionId,
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
const PROCESS_RESPONSE_OUTPUT_LINES = 10;
|
|
684
|
-
function compactProcessOutput(output) {
|
|
685
|
-
if (!output)
|
|
686
|
-
return { output: "", truncated: false };
|
|
687
|
-
const trailingNewline = output.endsWith("\n");
|
|
688
|
-
const body = trailingNewline ? output.slice(0, -1) : output;
|
|
689
|
-
const lines = body.split("\n");
|
|
690
|
-
if (lines.length <= PROCESS_RESPONSE_OUTPUT_LINES)
|
|
691
|
-
return { output, truncated: false };
|
|
692
|
-
const compact = lines.slice(-PROCESS_RESPONSE_OUTPUT_LINES).join("\n");
|
|
693
|
-
return {
|
|
694
|
-
output: trailingNewline ? `${compact}\n` : compact,
|
|
695
|
-
truncated: true,
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
function outputIdNotice(outputId) {
|
|
699
|
-
return outputId ? `Full output ID: ${outputId}.` : "";
|
|
700
|
-
}
|
|
701
|
-
function processResult(snapshot) {
|
|
702
|
-
const status = snapshot.running
|
|
703
|
-
? `Process running with process ID ${snapshot.processId}.`
|
|
704
|
-
: snapshot.timedOut
|
|
705
|
-
? "Process timed out and was terminated."
|
|
706
|
-
: snapshot.signal
|
|
707
|
-
? `Process exited after signal ${snapshot.signal}.`
|
|
708
|
-
: `Process exited with code ${snapshot.exitCode ?? "unknown"}.`;
|
|
709
|
-
const compact = compactProcessOutput(snapshot.output).output.replace(/\n$/, "");
|
|
710
|
-
return [compact, status, outputIdNotice(snapshot.outputId)].filter(Boolean).join("\n");
|
|
711
|
-
}
|
|
712
|
-
function completedProcessResult(snapshot) {
|
|
713
|
-
const status = snapshot.timedOut
|
|
714
|
-
? `Background process ${snapshot.processId} timed out and was terminated.`
|
|
715
|
-
: snapshot.signal
|
|
716
|
-
? `Background process ${snapshot.processId} exited after signal ${snapshot.signal}.`
|
|
717
|
-
: `Background process ${snapshot.processId} exited with code ${snapshot.exitCode ?? "unknown"}.`;
|
|
718
|
-
const command = `Command: ${snapshot.command}`;
|
|
719
|
-
const output = compactProcessOutput(snapshot.output).output.replace(/\n$/, "");
|
|
720
|
-
return [status, command, output, outputIdNotice(snapshot.outputId)].filter(Boolean).join("\n");
|
|
721
|
-
}
|
|
722
|
-
function attachCompletedProcessNotices(processSessions, workspaceId, result, onCompleted) {
|
|
723
|
-
if (result instanceof Error) {
|
|
724
|
-
const completed = processSessions.takeCompleted(workspaceId);
|
|
725
|
-
for (const snapshot of completed)
|
|
726
|
-
onCompleted?.(snapshot);
|
|
727
|
-
if (completed.length > 0) {
|
|
728
|
-
result.message = [
|
|
729
|
-
result.message,
|
|
730
|
-
...completed.map((snapshot) => completedProcessResult(snapshot)),
|
|
731
|
-
].join("\n\n");
|
|
732
|
-
}
|
|
733
|
-
return result;
|
|
734
|
-
}
|
|
735
|
-
if (typeof result !== "object" || result === null)
|
|
736
|
-
return result;
|
|
737
|
-
const content = result.content;
|
|
738
|
-
if (!Array.isArray(content))
|
|
739
|
-
return result;
|
|
740
|
-
const structured = result.structuredContent;
|
|
741
|
-
const currentProcessId = structured?.running === true
|
|
742
|
-
? typeof structured.processId === "number"
|
|
743
|
-
? structured.processId
|
|
744
|
-
: typeof structured.sessionId === "number"
|
|
745
|
-
? structured.sessionId
|
|
746
|
-
: undefined
|
|
747
|
-
: undefined;
|
|
748
|
-
const completed = processSessions.takeCompleted(workspaceId, undefined, currentProcessId);
|
|
749
|
-
for (const snapshot of completed)
|
|
750
|
-
onCompleted?.(snapshot);
|
|
751
|
-
if (completed.length === 0)
|
|
752
|
-
return result;
|
|
753
|
-
return {
|
|
754
|
-
...result,
|
|
755
|
-
content: [
|
|
756
|
-
...content,
|
|
757
|
-
...completed.map((snapshot) => textBlock(completedProcessResult(snapshot))),
|
|
758
|
-
],
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
function processOutputSchema() {
|
|
762
|
-
return resultOutputSchema({
|
|
763
|
-
processId: z.number().int().positive().optional().describe("Canonical process handle for bash(action=\"process\") or the active command adapter."),
|
|
764
|
-
sessionId: z.number().int().positive().optional().describe("Deprecated alias of processId for compatibility."),
|
|
765
|
-
outputId: z.string().optional().describe("Stable local audit identifier for retrieving the complete original process output."),
|
|
766
|
-
running: z.boolean(),
|
|
767
|
-
exitCode: z.number().int().optional(),
|
|
768
|
-
signal: z.string().optional(),
|
|
769
|
-
timedOut: z.boolean(),
|
|
770
|
-
wallTimeMs: z.number().nonnegative(),
|
|
771
|
-
outputTruncated: z.boolean(),
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
function readForgeRelayVersion() {
|
|
775
|
-
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
776
|
-
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
777
|
-
throw new Error("Unable to read ForgeRelay package version.");
|
|
778
|
-
}
|
|
779
|
-
return packageJson.version;
|
|
780
|
-
}
|
|
781
|
-
function processToolResponse(tool, workspaceId, snapshot, summary) {
|
|
782
|
-
const compact = compactProcessOutput(snapshot.output);
|
|
783
|
-
const result = processResult(snapshot);
|
|
784
|
-
const content = [textBlock(result)];
|
|
785
|
-
const outputSummary = textSummary(compact.output ? [textBlock(compact.output)] : []);
|
|
786
|
-
return {
|
|
787
|
-
content,
|
|
788
|
-
_meta: {
|
|
789
|
-
tool,
|
|
790
|
-
card: {
|
|
791
|
-
workspaceId,
|
|
792
|
-
summary: { ...summary, ...outputSummary },
|
|
793
|
-
payload: { content },
|
|
794
|
-
},
|
|
795
|
-
},
|
|
796
|
-
structuredContent: {
|
|
797
|
-
result,
|
|
798
|
-
processId: snapshot.processId,
|
|
799
|
-
sessionId: snapshot.sessionId,
|
|
800
|
-
outputId: snapshot.outputId,
|
|
801
|
-
running: snapshot.running,
|
|
802
|
-
exitCode: snapshot.exitCode,
|
|
803
|
-
signal: snapshot.signal,
|
|
804
|
-
timedOut: snapshot.timedOut,
|
|
805
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
806
|
-
outputTruncated: snapshot.outputTruncated || compact.truncated,
|
|
807
|
-
},
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
function durableOutputResult(record) {
|
|
811
|
-
const status = record.status === "running"
|
|
812
|
-
? `Process ${record.processId} is still running.`
|
|
813
|
-
: record.timedOut
|
|
814
|
-
? `Process ${record.processId} timed out and was terminated.`
|
|
815
|
-
: record.signal
|
|
816
|
-
? `Process ${record.processId} exited after signal ${record.signal}.`
|
|
817
|
-
: `Process ${record.processId} exited with code ${record.exitCode ?? "unknown"}.`;
|
|
818
|
-
return [record.output.replace(/\n$/, ""), status, `Full output ID: ${record.outputId}.`]
|
|
819
|
-
.filter(Boolean)
|
|
820
|
-
.join("\n");
|
|
821
|
-
}
|
|
822
|
-
function durableOutputResponse(tool, workspaceId, record) {
|
|
823
|
-
const result = durableOutputResult(record);
|
|
824
|
-
const content = [textBlock(result)];
|
|
825
|
-
const finishedAt = record.finishedAt ? Date.parse(record.finishedAt) : Date.now();
|
|
826
|
-
const startedAt = Date.parse(record.startedAt);
|
|
827
|
-
return {
|
|
828
|
-
content,
|
|
829
|
-
_meta: {
|
|
830
|
-
tool,
|
|
831
|
-
card: {
|
|
832
|
-
workspaceId,
|
|
833
|
-
summary: textSummary(record.output ? [textBlock(record.output)] : []),
|
|
834
|
-
payload: { content },
|
|
835
|
-
},
|
|
836
|
-
},
|
|
837
|
-
structuredContent: {
|
|
838
|
-
result,
|
|
839
|
-
processId: record.processId,
|
|
840
|
-
sessionId: record.processId,
|
|
841
|
-
outputId: record.outputId,
|
|
842
|
-
running: record.status === "running",
|
|
843
|
-
exitCode: record.exitCode,
|
|
844
|
-
signal: record.signal,
|
|
845
|
-
timedOut: record.timedOut,
|
|
846
|
-
wallTimeMs: Math.max(0, Number.isFinite(finishedAt - startedAt) ? finishedAt - startedAt : 0),
|
|
847
|
-
outputTruncated: false,
|
|
848
|
-
},
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
function markReturnedOutput(store, result) {
|
|
852
|
-
if (typeof result !== "object" || result === null)
|
|
853
|
-
return;
|
|
854
|
-
const structured = result.structuredContent;
|
|
855
|
-
if (typeof structured !== "object" || structured === null)
|
|
856
|
-
return;
|
|
857
|
-
const record = structured;
|
|
858
|
-
if (record.running === true && typeof record.outputId === "string") {
|
|
859
|
-
store.markReturned(record.outputId);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
function readWorkspaceBashOutput(store, workspaceId, outputId) {
|
|
863
|
-
const record = store.read(outputId);
|
|
864
|
-
if (!record)
|
|
865
|
-
throw new Error(`Unknown Bash output: ${outputId}`);
|
|
866
|
-
if (record.workspaceId !== workspaceId) {
|
|
867
|
-
throw new Error(`Bash output ${outputId} does not belong to workspace ${workspaceId}.`);
|
|
868
|
-
}
|
|
869
|
-
return record;
|
|
870
|
-
}
|
|
871
|
-
function bashCompletionError(record) {
|
|
872
|
-
if (record.error)
|
|
873
|
-
return record.error;
|
|
874
|
-
if (record.timedOut)
|
|
875
|
-
return `Background process ${record.processId} timed out.`;
|
|
876
|
-
if (record.signal)
|
|
877
|
-
return `Background process ${record.processId} exited after signal ${record.signal}.`;
|
|
878
|
-
return `Background process ${record.processId} exited with code ${record.exitCode ?? "unknown"}.`;
|
|
879
|
-
}
|
|
880
|
-
function recordBashCompletion(lifecycle, store, outputId) {
|
|
881
|
-
if (!outputId)
|
|
882
|
-
return;
|
|
883
|
-
const completion = store.claimCompletion(outputId);
|
|
884
|
-
if (!completion)
|
|
885
|
-
return;
|
|
886
|
-
lifecycle.recordLinked({
|
|
887
|
-
sourceActivityId: completion.activityId,
|
|
888
|
-
tool: "bash_result",
|
|
889
|
-
request: {
|
|
890
|
-
processId: completion.processId,
|
|
891
|
-
outputId: completion.outputId,
|
|
892
|
-
},
|
|
893
|
-
result: {
|
|
894
|
-
processId: completion.processId,
|
|
895
|
-
outputId: completion.outputId,
|
|
896
|
-
exitCode: completion.exitCode,
|
|
897
|
-
signal: completion.signal,
|
|
898
|
-
timedOut: completion.timedOut,
|
|
899
|
-
},
|
|
900
|
-
outcome: completion.status === "failed"
|
|
901
|
-
? { type: "failed", error: bashCompletionError(completion) }
|
|
902
|
-
: { type: "succeeded" },
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
function workspaceHookInvocation(workspace) {
|
|
906
|
-
return {
|
|
907
|
-
workspaceId: workspace.id,
|
|
908
|
-
workspaceRoot: workspace.root,
|
|
909
|
-
workspaceMode: workspace.mode,
|
|
910
|
-
sourceRoot: workspace.sourceRoot,
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
function capabilityContextFor(workspace) {
|
|
914
|
-
return {
|
|
915
|
-
workspaceId: workspace.id,
|
|
916
|
-
workspaceKind: "workspace",
|
|
917
|
-
workspaceRoot: workspace.root,
|
|
918
|
-
guides: workspace.capabilityGuides.map((guide) => ({
|
|
919
|
-
name: guide.name,
|
|
920
|
-
description: guide.description,
|
|
921
|
-
whenToRead: guide.whenToRead,
|
|
922
|
-
path: formatPathForPrompt(guide.filePath),
|
|
923
|
-
})),
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
function compositeCapabilityContext(workspaceId, guides) {
|
|
927
|
-
return {
|
|
928
|
-
workspaceId,
|
|
929
|
-
workspaceKind: "composite",
|
|
930
|
-
guides: guides.map((guide) => ({
|
|
931
|
-
name: guide.name,
|
|
932
|
-
description: guide.description,
|
|
933
|
-
whenToRead: guide.whenToRead,
|
|
934
|
-
path: formatPathForPrompt(guide.filePath),
|
|
935
|
-
})),
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
function requireCapabilityWorkspaceRoot(context) {
|
|
939
|
-
if (!context.workspaceRoot) {
|
|
940
|
-
throw new CapabilityError("capability_unavailable", `Capability execution requires a filesystem-backed Workspace; ${context.workspaceId} is ${context.workspaceKind}.`);
|
|
941
|
-
}
|
|
942
|
-
return context.workspaceRoot;
|
|
943
|
-
}
|
|
944
|
-
function runWorkspaceTasksCapability(store, workspaceId, input) {
|
|
945
|
-
switch (input.operation) {
|
|
946
|
-
case "get":
|
|
947
|
-
if (input.level === "headers")
|
|
948
|
-
return store.readHeaders(workspaceId, input.listId);
|
|
949
|
-
if (input.level === "detail")
|
|
950
|
-
return store.readTaskDetail(workspaceId, input.listId, input.taskId);
|
|
951
|
-
return store.readSummary(workspaceId);
|
|
952
|
-
case "list.create":
|
|
953
|
-
store.createList(workspaceId, { name: input.name, position: input.position });
|
|
954
|
-
return store.readSummary(workspaceId);
|
|
955
|
-
case "list.update":
|
|
956
|
-
store.updateList(workspaceId, input.listId, {
|
|
957
|
-
name: input.name,
|
|
958
|
-
state: input.state,
|
|
959
|
-
position: input.position,
|
|
960
|
-
});
|
|
961
|
-
return store.readSummary(workspaceId);
|
|
962
|
-
case "list.delete":
|
|
963
|
-
store.deleteList(workspaceId, input.listId);
|
|
964
|
-
return store.readSummary(workspaceId);
|
|
965
|
-
case "task.create":
|
|
966
|
-
store.createTask(workspaceId, input.listId, {
|
|
967
|
-
subject: input.subject,
|
|
968
|
-
content: input.content,
|
|
969
|
-
status: input.status,
|
|
970
|
-
position: input.position,
|
|
971
|
-
});
|
|
972
|
-
return store.readHeaders(workspaceId, input.listId);
|
|
973
|
-
case "task.update":
|
|
974
|
-
store.updateTask(workspaceId, input.listId, input.taskId, {
|
|
975
|
-
subject: input.subject,
|
|
976
|
-
content: input.content,
|
|
977
|
-
status: input.status,
|
|
978
|
-
position: input.position,
|
|
979
|
-
});
|
|
980
|
-
return store.readHeaders(workspaceId, input.listId);
|
|
981
|
-
case "task.delete":
|
|
982
|
-
store.deleteTask(workspaceId, input.listId, input.taskId);
|
|
983
|
-
return store.readHeaders(workspaceId, input.listId);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
async function reviewWorkspaceChanges(reviewCheckpoints, workspace) {
|
|
987
|
-
return reviewCheckpoints.reviewChanges({
|
|
988
|
-
workspaceId: workspace.id,
|
|
989
|
-
root: workspace.root,
|
|
990
|
-
markReviewed: true,
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
function toolResultIsError(result) {
|
|
994
|
-
return typeof result === "object" && result !== null && result.isError === true;
|
|
995
|
-
}
|
|
996
|
-
function toolResultText(result) {
|
|
997
|
-
if (typeof result !== "object" || result === null)
|
|
998
|
-
return String(result ?? "");
|
|
999
|
-
const record = result;
|
|
1000
|
-
if (Array.isArray(record.content)) {
|
|
1001
|
-
const text = record.content
|
|
1002
|
-
.map((entry) => {
|
|
1003
|
-
if (typeof entry !== "object" || entry === null)
|
|
1004
|
-
return "";
|
|
1005
|
-
const value = entry.text;
|
|
1006
|
-
return typeof value === "string" ? value : "";
|
|
1007
|
-
})
|
|
1008
|
-
.filter(Boolean)
|
|
1009
|
-
.join("\n");
|
|
1010
|
-
if (text)
|
|
1011
|
-
return text;
|
|
1012
|
-
}
|
|
1013
|
-
if (typeof record.structuredContent === "object" && record.structuredContent !== null) {
|
|
1014
|
-
const value = record.structuredContent.result;
|
|
1015
|
-
if (typeof value === "string")
|
|
1016
|
-
return value;
|
|
1017
|
-
}
|
|
1018
|
-
return "";
|
|
1019
|
-
}
|
|
1020
|
-
function toolResultContent(result) {
|
|
1021
|
-
if (typeof result !== "object" || result === null)
|
|
1022
|
-
return [];
|
|
1023
|
-
const content = result.content;
|
|
1024
|
-
return Array.isArray(content) ? content : [];
|
|
1025
|
-
}
|
|
1026
|
-
function remapCompositeToolResult(result, executionWorkspaceId, compositeWorkspaceId, member) {
|
|
1027
|
-
if (typeof result !== "object" || result === null)
|
|
1028
|
-
return result;
|
|
1029
|
-
const record = result;
|
|
1030
|
-
const remapped = replaceWorkspaceIdentity(record, executionWorkspaceId, compositeWorkspaceId);
|
|
1031
|
-
const meta = typeof remapped._meta === "object" && remapped._meta !== null
|
|
1032
|
-
? { ...remapped._meta }
|
|
1033
|
-
: undefined;
|
|
1034
|
-
if (meta) {
|
|
1035
|
-
const card = typeof meta.card === "object" && meta.card !== null
|
|
1036
|
-
? { ...meta.card, workspaceId: compositeWorkspaceId, member }
|
|
1037
|
-
: undefined;
|
|
1038
|
-
if (card)
|
|
1039
|
-
meta.card = card;
|
|
1040
|
-
remapped._meta = meta;
|
|
1041
|
-
}
|
|
1042
|
-
const structured = typeof remapped.structuredContent === "object" && remapped.structuredContent !== null
|
|
1043
|
-
? { ...remapped.structuredContent, member }
|
|
1044
|
-
: undefined;
|
|
1045
|
-
if (structured)
|
|
1046
|
-
remapped.structuredContent = structured;
|
|
1047
|
-
return remapped;
|
|
1048
|
-
}
|
|
1049
|
-
function replaceWorkspaceIdentity(value, from, to) {
|
|
1050
|
-
if (typeof value === "string")
|
|
1051
|
-
return value.split(from).join(to);
|
|
1052
|
-
if (Array.isArray(value))
|
|
1053
|
-
return value.map((entry) => replaceWorkspaceIdentity(entry, from, to));
|
|
1054
|
-
if (!value || typeof value !== "object")
|
|
1055
|
-
return value;
|
|
1056
|
-
return Object.fromEntries(Object.entries(value)
|
|
1057
|
-
.map(([key, entry]) => [key, replaceWorkspaceIdentity(entry, from, to)]));
|
|
1058
|
-
}
|
|
1059
|
-
function toolResultAgentsFiles(result) {
|
|
1060
|
-
if (typeof result !== "object" || result === null)
|
|
1061
|
-
return [];
|
|
1062
|
-
const structured = result.structuredContent;
|
|
1063
|
-
if (typeof structured !== "object" || structured === null)
|
|
1064
|
-
return [];
|
|
1065
|
-
const agentsFiles = structured.agentsFiles;
|
|
1066
|
-
if (!Array.isArray(agentsFiles))
|
|
1067
|
-
return [];
|
|
1068
|
-
return agentsFiles.flatMap((entry) => {
|
|
1069
|
-
if (typeof entry !== "object" || entry === null)
|
|
1070
|
-
return [];
|
|
1071
|
-
const path = entry.path;
|
|
1072
|
-
const content = entry.content;
|
|
1073
|
-
return typeof path === "string" && typeof content === "string" ? [{ path, content }] : [];
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
1076
|
-
function workspaceActivitySnapshot(workspace) {
|
|
1077
|
-
return {
|
|
1078
|
-
id: workspace.id,
|
|
1079
|
-
root: workspace.root,
|
|
1080
|
-
mode: workspace.mode,
|
|
1081
|
-
...(workspace.sourceRoot ? { sourceRoot: workspace.sourceRoot } : {}),
|
|
1082
|
-
...(workspace.worktree?.branch ? { branch: workspace.worktree.branch } : {}),
|
|
1083
|
-
...(workspace.worktree?.targetBranch ? { targetBranch: workspace.worktree.targetBranch } : {}),
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
function activityFailureMessage(result) {
|
|
1087
|
-
if (typeof result !== "object" || result === null)
|
|
1088
|
-
return "Tool returned a failed result.";
|
|
1089
|
-
const record = result;
|
|
1090
|
-
if (Array.isArray(record.content)) {
|
|
1091
|
-
const text = record.content
|
|
1092
|
-
.map((entry) => {
|
|
1093
|
-
if (typeof entry !== "object" || entry === null)
|
|
1094
|
-
return "";
|
|
1095
|
-
const value = entry.text;
|
|
1096
|
-
return typeof value === "string" ? value : "";
|
|
1097
|
-
})
|
|
1098
|
-
.filter(Boolean)
|
|
1099
|
-
.join("\n");
|
|
1100
|
-
if (text)
|
|
1101
|
-
return text;
|
|
1102
|
-
}
|
|
1103
|
-
if (typeof record.structuredContent === "object" && record.structuredContent !== null) {
|
|
1104
|
-
const value = record.structuredContent.result;
|
|
1105
|
-
if (typeof value === "string" && value)
|
|
1106
|
-
return value;
|
|
1107
|
-
}
|
|
1108
|
-
return "Tool returned a failed result.";
|
|
1109
|
-
}
|
|
1110
|
-
function standardActivityOutcome(result) {
|
|
1111
|
-
return toolResultIsError(result)
|
|
1112
|
-
? { type: "failed", error: activityFailureMessage(result) }
|
|
1113
|
-
: { type: "succeeded" };
|
|
1114
|
-
}
|
|
1115
|
-
function processActivityOutcome(result) {
|
|
1116
|
-
if (toolResultIsError(result))
|
|
1117
|
-
return { type: "failed", error: activityFailureMessage(result) };
|
|
1118
|
-
if (typeof result !== "object" || result === null)
|
|
1119
|
-
return { type: "succeeded" };
|
|
1120
|
-
const structured = result.structuredContent;
|
|
1121
|
-
if (typeof structured !== "object" || structured === null)
|
|
1122
|
-
return { type: "succeeded" };
|
|
1123
|
-
const process = structured;
|
|
1124
|
-
if (process.running === true)
|
|
1125
|
-
return { type: "returned" };
|
|
1126
|
-
if (process.timedOut === true ||
|
|
1127
|
-
typeof process.signal === "string" ||
|
|
1128
|
-
(typeof process.exitCode === "number" && process.exitCode !== 0)) {
|
|
1129
|
-
return { type: "failed", error: activityFailureMessage(result) };
|
|
1130
|
-
}
|
|
1131
|
-
return { type: "succeeded" };
|
|
1132
|
-
}
|
|
1133
|
-
function activityRelationFor(context) {
|
|
1134
|
-
return {
|
|
1135
|
-
...(context.parentActivityId ? { parentActivityId: context.parentActivityId } : {}),
|
|
1136
|
-
...(context.turnId ? { turnId: context.turnId } : {}),
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1139
|
-
function activityRequestFor(input, context) {
|
|
1140
|
-
if (!context.activityMember || !input || typeof input !== "object" || Array.isArray(input))
|
|
1141
|
-
return input;
|
|
1142
|
-
return {
|
|
1143
|
-
...input,
|
|
1144
|
-
member: context.activityMember,
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1147
|
-
function runActivityTool(lifecycle, workspace, conversationScopeId, tool, request, operation, outcome = standardActivityOutcome, relation = {}, auditResult) {
|
|
1148
|
-
return lifecycle.run({
|
|
1149
|
-
tool,
|
|
1150
|
-
workspace: workspaceActivitySnapshot(workspace),
|
|
1151
|
-
conversationScopeId,
|
|
1152
|
-
request,
|
|
1153
|
-
operation,
|
|
1154
|
-
outcome,
|
|
1155
|
-
...(auditResult ? { auditResult } : {}),
|
|
1156
|
-
...relation,
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
function runActivityToolWithHooks(lifecycle, hooks, workspace, conversationScopeId, request, hookOptions, relation = {}) {
|
|
1160
|
-
return runActivityTool(lifecycle, workspace, conversationScopeId, hookOptions.tool, request, () => runToolWithHooks(hooks, hookOptions), standardActivityOutcome, relation);
|
|
1161
|
-
}
|
|
1162
|
-
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun, routing) {
|
|
1163
|
-
if (config.toolMode === "codex") {
|
|
1164
|
-
registerAppTool(server, "exec_command", {
|
|
1165
|
-
title: "Execute command",
|
|
1166
|
-
description: `Run a command inside an open workspace. Returns its result when it exits during the yield window, otherwise returns a processId for write_stdin. Use this for file inspection, tests, builds, package scripts, generators, formatters, and long-running processes. ${buildShellMutationPolicy()} Call open_workspace first and pass workspaceId.`,
|
|
1167
|
-
inputSchema: {
|
|
1168
|
-
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
1169
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this process."),
|
|
1170
|
-
cmd: z.string().min(1).describe("Shell command to execute."),
|
|
1171
|
-
tty: z
|
|
1172
|
-
.boolean()
|
|
1173
|
-
.optional()
|
|
1174
|
-
.describe("Allocate a pseudo-terminal for interactive commands. Defaults to false."),
|
|
1175
|
-
columns: z.number().int().min(1).max(1_000).optional().describe("Initial PTY width. Defaults to 80."),
|
|
1176
|
-
rows: z.number().int().min(1).max(1_000).optional().describe("Initial PTY height. Defaults to 24."),
|
|
1177
|
-
workingDirectory: z
|
|
1178
|
-
.string()
|
|
1179
|
-
.optional()
|
|
1180
|
-
.describe("Working directory relative to the workspace root. Defaults to the workspace root."),
|
|
1181
|
-
yieldTimeMs: z
|
|
1182
|
-
.number()
|
|
1183
|
-
.int()
|
|
1184
|
-
.min(0)
|
|
1185
|
-
.max(300_000)
|
|
1186
|
-
.optional()
|
|
1187
|
-
.describe("Feedback window before returning a processId. Use 0 for immediate background handoff. Defaults to 10000ms."),
|
|
1188
|
-
timeoutMs: z
|
|
1189
|
-
.number()
|
|
1190
|
-
.int()
|
|
1191
|
-
.min(1)
|
|
1192
|
-
.max(86_400_000)
|
|
1193
|
-
.optional()
|
|
1194
|
-
.describe("Total execution timeout from process start. On expiry ForgeRelay terminates the process. Omit for no ForgeRelay execution deadline."),
|
|
1195
|
-
maxOutputTokens: z
|
|
1196
|
-
.number()
|
|
1197
|
-
.int()
|
|
1198
|
-
.positive()
|
|
1199
|
-
.max(100_000)
|
|
1200
|
-
.optional()
|
|
1201
|
-
.describe("Approximate output token budget. Defaults to 10000."),
|
|
1202
|
-
},
|
|
1203
|
-
outputSchema: processOutputSchema(),
|
|
1204
|
-
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1205
|
-
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1206
|
-
}, async ({ workspaceId, member, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) => {
|
|
1207
|
-
const target = routing.resolve(workspaceId, member);
|
|
1208
|
-
const context = await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
|
|
1209
|
-
if (routing.isRemote(target.executionWorkspaceId)) {
|
|
1210
|
-
return routing.presentSemantic(await routing.execCommandRemote(target.executionWorkspaceId, {
|
|
1211
|
-
cmd,
|
|
1212
|
-
...(tty !== undefined ? { tty } : {}),
|
|
1213
|
-
...(columns !== undefined ? { columns } : {}),
|
|
1214
|
-
...(rows !== undefined ? { rows } : {}),
|
|
1215
|
-
...(workingDirectory !== undefined ? { workingDirectory } : {}),
|
|
1216
|
-
...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
|
|
1217
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
1218
|
-
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1219
|
-
}, routing.hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
1220
|
-
}
|
|
1221
|
-
return routing.presentSemantic(await shellRun({
|
|
1222
|
-
workspaceId: target.executionWorkspaceId,
|
|
1223
|
-
command: cmd,
|
|
1224
|
-
surface: "exec_command",
|
|
1225
|
-
tty,
|
|
1226
|
-
columns,
|
|
1227
|
-
rows,
|
|
1228
|
-
workingDirectory,
|
|
1229
|
-
yieldTimeMs,
|
|
1230
|
-
timeoutMs,
|
|
1231
|
-
maxOutputTokens,
|
|
1232
|
-
}, context), target);
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
if (config.toolMode !== "codex")
|
|
1236
|
-
return;
|
|
1237
|
-
registerAppTool(server, "write_stdin", {
|
|
1238
|
-
title: "Write to process",
|
|
1239
|
-
description: "Poll or write characters to a running process returned by exec_command, or retrieve complete durable process output by outputId. Omit chars or pass an empty string to poll. Waiting never kills the process; pass \\u0003 to explicitly send Ctrl-C.",
|
|
1240
|
-
inputSchema: {
|
|
1241
|
-
workspaceId: z.string().describe("Workspace identifier used to start the process."),
|
|
1242
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns the process."),
|
|
1243
|
-
processId: z.number().int().positive().optional().describe("Canonical process identifier returned by bash or exec_command."),
|
|
1244
|
-
sessionId: z.number().int().positive().optional().describe("Deprecated alias for processId. Retained for compatibility."),
|
|
1245
|
-
outputId: z.string().optional().describe("Stable output identifier returned by exec_command. When supplied, retrieve the complete durable output instead of controlling a process."),
|
|
1246
|
-
chars: z.string().optional().describe("Characters to write. Omit or pass an empty string to poll."),
|
|
1247
|
-
columns: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this width."),
|
|
1248
|
-
rows: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this height."),
|
|
1249
|
-
yieldTimeMs: z
|
|
1250
|
-
.number()
|
|
1251
|
-
.int()
|
|
1252
|
-
.min(0)
|
|
1253
|
-
.max(300_000)
|
|
1254
|
-
.optional()
|
|
1255
|
-
.describe("Milliseconds to keep waiting before returning again, max 300000. Polling defaults to 5000; interaction defaults to 250."),
|
|
1256
|
-
maxOutputTokens: z
|
|
1257
|
-
.number()
|
|
1258
|
-
.int()
|
|
1259
|
-
.positive()
|
|
1260
|
-
.max(100_000)
|
|
1261
|
-
.optional()
|
|
1262
|
-
.describe("Approximate output token budget. Defaults to 10000."),
|
|
1263
|
-
},
|
|
1264
|
-
outputSchema: processOutputSchema(),
|
|
1265
|
-
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1266
|
-
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1267
|
-
}, async ({ workspaceId, member, processId, sessionId, outputId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
|
|
1268
|
-
const target = routing.resolve(workspaceId, member);
|
|
1269
|
-
await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
|
|
1270
|
-
if (routing.isRemote(target.executionWorkspaceId)) {
|
|
1271
|
-
return routing.present(await routing.writeStdinRemote(target.executionWorkspaceId, {
|
|
1272
|
-
...(processId !== undefined ? { processId } : {}),
|
|
1273
|
-
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1274
|
-
...(outputId !== undefined ? { outputId } : {}),
|
|
1275
|
-
...(chars !== undefined ? { chars } : {}),
|
|
1276
|
-
...(columns !== undefined ? { columns } : {}),
|
|
1277
|
-
...(rows !== undefined ? { rows } : {}),
|
|
1278
|
-
...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
|
|
1279
|
-
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1280
|
-
}, routing.hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
1281
|
-
}
|
|
1282
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
1283
|
-
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
1284
|
-
if (outputId !== undefined) {
|
|
1285
|
-
if (processId !== undefined || sessionId !== undefined || chars !== undefined || columns !== undefined ||
|
|
1286
|
-
rows !== undefined || yieldTimeMs !== undefined || maxOutputTokens !== undefined) {
|
|
1287
|
-
throw new Error("write_stdin outputId lookup cannot be combined with process control fields.");
|
|
1288
|
-
}
|
|
1289
|
-
return runToolWithHooks(hooks, {
|
|
1290
|
-
signal: extra.signal,
|
|
1291
|
-
tool: "write_stdin",
|
|
1292
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1293
|
-
payload: { outputId },
|
|
1294
|
-
operation: async () => durableOutputResponse("write_stdin", executionWorkspaceId, readWorkspaceBashOutput(bashOutputStore, executionWorkspaceId, outputId)),
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
const resolvedProcessId = resolveProcessId(processId, sessionId);
|
|
1298
|
-
return runToolWithHooks(hooks, {
|
|
1299
|
-
signal: extra.signal,
|
|
1300
|
-
tool: "write_stdin",
|
|
1301
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1302
|
-
payload: {
|
|
1303
|
-
processId: resolvedProcessId,
|
|
1304
|
-
charactersWritten: chars?.length ?? 0,
|
|
1305
|
-
columns,
|
|
1306
|
-
rows,
|
|
1307
|
-
},
|
|
1308
|
-
operation: async () => {
|
|
1309
|
-
const startedAt = performance.now();
|
|
1310
|
-
const snapshot = await processSessions.write({
|
|
1311
|
-
workspaceId: executionWorkspaceId,
|
|
1312
|
-
processId: resolvedProcessId,
|
|
1313
|
-
chars,
|
|
1314
|
-
columns,
|
|
1315
|
-
rows,
|
|
1316
|
-
yieldTimeMs,
|
|
1317
|
-
maxOutputTokens,
|
|
1318
|
-
signal: extra.signal,
|
|
1319
|
-
});
|
|
1320
|
-
logToolCall(config, {
|
|
1321
|
-
tool: "write_stdin",
|
|
1322
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1323
|
-
exitCode: snapshot.exitCode,
|
|
1324
|
-
running: snapshot.running,
|
|
1325
|
-
processId: snapshot.processId,
|
|
1326
|
-
success: snapshot.running || snapshot.exitCode === 0,
|
|
1327
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1328
|
-
});
|
|
1329
|
-
const response = processToolResponse("write_stdin", executionWorkspaceId, snapshot, {
|
|
1330
|
-
processId: resolvedProcessId,
|
|
1331
|
-
charactersWritten: chars?.length ?? 0,
|
|
1332
|
-
running: snapshot.running,
|
|
1333
|
-
exitCode: snapshot.exitCode,
|
|
1334
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
1335
|
-
});
|
|
1336
|
-
if (!snapshot.running) {
|
|
1337
|
-
recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId);
|
|
1338
|
-
}
|
|
1339
|
-
return response;
|
|
1340
|
-
},
|
|
1341
|
-
}).then((result) => routing.present(result, target));
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
33
|
+
import { FORGERELAY_VERSION } from "./mcp/server/core/version.js";
|
|
34
|
+
import { capabilityContextFor, requireCapabilityWorkspaceRoot, reviewWorkspaceChanges, runWorkspaceTasksCapability } from "./mcp/server/core/capability-support.js";
|
|
35
|
+
import { createHttpServer } from "./mcp/server/transport/http-server.js";
|
|
36
|
+
import { registerFilesystemTools } from "./mcp/server/operations/runtime/filesystem-tools.js";
|
|
37
|
+
import { createOperationRuntime } from "./mcp/server/operations/runtime/operation-runtime.js";
|
|
38
|
+
import { registerWorkspaceAuxiliaryTools } from "./mcp/server/workspace/runtime/workspace-tools.js";
|
|
39
|
+
import { registerOpenWorkspaceTool } from "./mcp/server/workspace/runtime/workspace-open.js";
|
|
40
|
+
import { redactSkillDiagnosticPaths } from "./mcp/server/core/schemas.js";
|
|
41
|
+
import { attachWorkspaceTaskReminder, remapCompositeToolResult, toolResultIsError } from "./mcp/server/core/tool-support.js";
|
|
1344
42
|
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, subagentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries, options = {}) {
|
|
1345
43
|
const connectionScopeId = `mcp-connection:${randomUUID()}`;
|
|
44
|
+
const activityPanelApp = createActivityPanelApp(config, FORGERELAY_VERSION);
|
|
1346
45
|
const ownsRemoteWorkspaces = options.remoteWorkspaces === undefined;
|
|
1347
46
|
const remoteWorkspaces = options.remoteWorkspaces
|
|
1348
47
|
?? new RemoteWorkspaceRelay(config.configDir, config.stateDir);
|
|
@@ -1591,511 +290,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1591
290
|
: `Composite member ${memberName} context was already delivered for this Host context; keep using Composite workspaceId ${compositeWorkspaceId} with member=${memberName}.`,
|
|
1592
291
|
};
|
|
1593
292
|
};
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
const { workspaceId, ...readInput } = input;
|
|
1597
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1598
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1599
|
-
signal: context.signal,
|
|
1600
|
-
tool: toolNames.read,
|
|
1601
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1602
|
-
payload: { path: readInput.path, offset: readInput.offset, limit: readInput.limit },
|
|
1603
|
-
isFailure: toolResultIsError,
|
|
1604
|
-
operation: async () => {
|
|
1605
|
-
const startedAt = performance.now();
|
|
1606
|
-
const readPath = workspaces.resolveReadPath(workspace, readInput.path);
|
|
1607
|
-
const discoveredInstructions = (await workspaces.discoverPathInstructions(workspace, readPath.absolutePath)).filter((file) => file.path !== readPath.absolutePath);
|
|
1608
|
-
const response = await readFileTool({ ...readInput, path: readPath.absolutePath }, {
|
|
1609
|
-
cwd: workspace.root,
|
|
1610
|
-
root: workspace.root,
|
|
1611
|
-
readRoots: readPath.readRoots,
|
|
1612
|
-
});
|
|
1613
|
-
if (response.isError) {
|
|
1614
|
-
logFailedToolResponse(config, {
|
|
1615
|
-
tool: toolNames.read,
|
|
1616
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1617
|
-
path: readInput.path,
|
|
1618
|
-
}, response.content, startedAt);
|
|
1619
|
-
return response;
|
|
1620
|
-
}
|
|
1621
|
-
workspaces.markReadPathLoaded(workspace, readPath);
|
|
1622
|
-
const discoveredInstructionContent = discoveredInstructions.length > 0
|
|
1623
|
-
? textBlock(formatDiscoveredWorkspaceInstructions(discoveredInstructions, workspace.root))
|
|
1624
|
-
: undefined;
|
|
1625
|
-
const content = discoveredInstructionContent
|
|
1626
|
-
? [discoveredInstructionContent, ...response.content]
|
|
1627
|
-
: response.content;
|
|
1628
|
-
const summary = {
|
|
1629
|
-
...textSummary(response.content),
|
|
1630
|
-
offset: readInput.offset ?? 1,
|
|
1631
|
-
limited: readInput.limit !== undefined,
|
|
1632
|
-
};
|
|
1633
|
-
logToolCall(config, {
|
|
1634
|
-
tool: toolNames.read,
|
|
1635
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1636
|
-
path: readInput.path,
|
|
1637
|
-
success: true,
|
|
1638
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1639
|
-
});
|
|
1640
|
-
return {
|
|
1641
|
-
...response,
|
|
1642
|
-
content,
|
|
1643
|
-
_meta: {
|
|
1644
|
-
tool: toolNames.read,
|
|
1645
|
-
card: {
|
|
1646
|
-
workspaceId,
|
|
1647
|
-
path: readInput.path,
|
|
1648
|
-
summary,
|
|
1649
|
-
payload: { content: response.content },
|
|
1650
|
-
},
|
|
1651
|
-
},
|
|
1652
|
-
structuredContent: {
|
|
1653
|
-
result: contentText(content),
|
|
1654
|
-
...(discoveredInstructions.length > 0
|
|
1655
|
-
? {
|
|
1656
|
-
agentsFiles: discoveredInstructions.map((file) => ({
|
|
1657
|
-
path: formatAgentsPath(file.path, workspace.root),
|
|
1658
|
-
content: file.content,
|
|
1659
|
-
})),
|
|
1660
|
-
}
|
|
1661
|
-
: {}),
|
|
1662
|
-
},
|
|
1663
|
-
};
|
|
1664
|
-
},
|
|
1665
|
-
}, activityRelationFor(context));
|
|
1666
|
-
},
|
|
1667
|
-
write: async (input, context) => {
|
|
1668
|
-
const { workspaceId, ...writeInput } = input;
|
|
1669
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1670
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1671
|
-
signal: context.signal,
|
|
1672
|
-
tool: toolNames.write,
|
|
1673
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1674
|
-
payload: { path: writeInput.path },
|
|
1675
|
-
isFailure: toolResultIsError,
|
|
1676
|
-
changedPaths: (result) => toolResultIsError(result) ? [] : [writeInput.path],
|
|
1677
|
-
operation: async () => {
|
|
1678
|
-
const startedAt = performance.now();
|
|
1679
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [writeInput.path]);
|
|
1680
|
-
const response = await writeFileTool(writeInput, {
|
|
1681
|
-
cwd: workspace.root,
|
|
1682
|
-
root: workspace.root,
|
|
1683
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1684
|
-
});
|
|
1685
|
-
if (response.isError) {
|
|
1686
|
-
logFailedToolResponse(config, {
|
|
1687
|
-
tool: toolNames.write,
|
|
1688
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1689
|
-
path: writeInput.path,
|
|
1690
|
-
}, response.content, startedAt);
|
|
1691
|
-
return response;
|
|
1692
|
-
}
|
|
1693
|
-
const patch = newFilePatch(writeInput.path, writeInput.content);
|
|
1694
|
-
const stats = countDiffStats(patch);
|
|
1695
|
-
const summary = {
|
|
1696
|
-
...stats,
|
|
1697
|
-
lines: contentLineCount(writeInput.content),
|
|
1698
|
-
characters: writeInput.content.length,
|
|
1699
|
-
};
|
|
1700
|
-
logToolCall(config, {
|
|
1701
|
-
tool: toolNames.write,
|
|
1702
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1703
|
-
path: writeInput.path,
|
|
1704
|
-
success: true,
|
|
1705
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1706
|
-
});
|
|
1707
|
-
return {
|
|
1708
|
-
...response,
|
|
1709
|
-
_meta: {
|
|
1710
|
-
tool: toolNames.write,
|
|
1711
|
-
card: {
|
|
1712
|
-
workspaceId,
|
|
1713
|
-
path: writeInput.path,
|
|
1714
|
-
summary,
|
|
1715
|
-
payload: {
|
|
1716
|
-
content: response.content,
|
|
1717
|
-
patch,
|
|
1718
|
-
},
|
|
1719
|
-
},
|
|
1720
|
-
},
|
|
1721
|
-
structuredContent: {
|
|
1722
|
-
result: contentText(response.content),
|
|
1723
|
-
},
|
|
1724
|
-
};
|
|
1725
|
-
},
|
|
1726
|
-
}, activityRelationFor(context));
|
|
1727
|
-
},
|
|
1728
|
-
edit: async (input, context) => {
|
|
1729
|
-
const { workspaceId, ...editInput } = input;
|
|
1730
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1731
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1732
|
-
signal: context.signal,
|
|
1733
|
-
tool: toolNames.edit,
|
|
1734
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1735
|
-
payload: { path: editInput.path, editCount: editInput.edits.length },
|
|
1736
|
-
isFailure: toolResultIsError,
|
|
1737
|
-
changedPaths: (result) => toolResultIsError(result) ? [] : [editInput.path],
|
|
1738
|
-
operation: async () => {
|
|
1739
|
-
const startedAt = performance.now();
|
|
1740
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [editInput.path]);
|
|
1741
|
-
const response = await editFileTool(editInput, {
|
|
1742
|
-
cwd: workspace.root,
|
|
1743
|
-
root: workspace.root,
|
|
1744
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1745
|
-
});
|
|
1746
|
-
if (response.isError) {
|
|
1747
|
-
logFailedToolResponse(config, {
|
|
1748
|
-
tool: toolNames.edit,
|
|
1749
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1750
|
-
path: editInput.path,
|
|
1751
|
-
}, response.content, startedAt);
|
|
1752
|
-
return response;
|
|
1753
|
-
}
|
|
1754
|
-
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
1755
|
-
const summary = {
|
|
1756
|
-
...stats,
|
|
1757
|
-
editCount: editInput.edits.length,
|
|
1758
|
-
};
|
|
1759
|
-
const editResultText = `Edited ${editInput.path} (+${stats.additions} -${stats.removals}).`;
|
|
1760
|
-
const editContent = [textBlock(editResultText)];
|
|
1761
|
-
logToolCall(config, {
|
|
1762
|
-
tool: toolNames.edit,
|
|
1763
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1764
|
-
path: editInput.path,
|
|
1765
|
-
success: true,
|
|
1766
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1767
|
-
});
|
|
1768
|
-
return {
|
|
1769
|
-
content: editContent,
|
|
1770
|
-
_meta: {
|
|
1771
|
-
tool: toolNames.edit,
|
|
1772
|
-
card: {
|
|
1773
|
-
workspaceId,
|
|
1774
|
-
path: editInput.path,
|
|
1775
|
-
summary,
|
|
1776
|
-
payload: {
|
|
1777
|
-
diff: response.details?.diff,
|
|
1778
|
-
patch: response.details?.patch,
|
|
1779
|
-
},
|
|
1780
|
-
},
|
|
1781
|
-
},
|
|
1782
|
-
structuredContent: {
|
|
1783
|
-
status: "applied",
|
|
1784
|
-
result: contentText(editContent),
|
|
1785
|
-
},
|
|
1786
|
-
};
|
|
1787
|
-
},
|
|
1788
|
-
}, activityRelationFor(context));
|
|
1789
|
-
},
|
|
1790
|
-
rename: async (input, context) => {
|
|
1791
|
-
const { workspaceId, path, newPath } = input;
|
|
1792
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1793
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1794
|
-
signal: context.signal,
|
|
1795
|
-
tool: toolNames.rename,
|
|
1796
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1797
|
-
payload: { path, newPath, paths: [path, newPath] },
|
|
1798
|
-
changedPaths: () => [path, newPath],
|
|
1799
|
-
operation: async () => {
|
|
1800
|
-
const startedAt = performance.now();
|
|
1801
|
-
try {
|
|
1802
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path, newPath]);
|
|
1803
|
-
await renamePath({ path, newPath }, {
|
|
1804
|
-
cwd: workspace.root,
|
|
1805
|
-
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
1806
|
-
});
|
|
1807
|
-
const result = `Renamed ${path} to ${newPath}.`;
|
|
1808
|
-
const content = [textBlock(result)];
|
|
1809
|
-
logToolCall(config, {
|
|
1810
|
-
tool: toolNames.rename,
|
|
1811
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1812
|
-
path: `${path} -> ${newPath}`,
|
|
1813
|
-
success: true,
|
|
1814
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1815
|
-
});
|
|
1816
|
-
return {
|
|
1817
|
-
content,
|
|
1818
|
-
_meta: {
|
|
1819
|
-
tool: toolNames.rename,
|
|
1820
|
-
card: {
|
|
1821
|
-
workspaceId,
|
|
1822
|
-
path: newPath,
|
|
1823
|
-
summary: { previousPath: path },
|
|
1824
|
-
payload: { content },
|
|
1825
|
-
},
|
|
1826
|
-
},
|
|
1827
|
-
structuredContent: {
|
|
1828
|
-
result,
|
|
1829
|
-
status: "renamed",
|
|
1830
|
-
path,
|
|
1831
|
-
newPath,
|
|
1832
|
-
},
|
|
1833
|
-
};
|
|
1834
|
-
}
|
|
1835
|
-
catch (error) {
|
|
1836
|
-
logToolCall(config, {
|
|
1837
|
-
tool: toolNames.rename,
|
|
1838
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1839
|
-
path: `${path} -> ${newPath}`,
|
|
1840
|
-
success: false,
|
|
1841
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1842
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1843
|
-
});
|
|
1844
|
-
throw error;
|
|
1845
|
-
}
|
|
1846
|
-
},
|
|
1847
|
-
}, activityRelationFor(context));
|
|
1848
|
-
},
|
|
1849
|
-
delete: async (input, context) => {
|
|
1850
|
-
const { workspaceId, path, recursive } = input;
|
|
1851
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1852
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1853
|
-
signal: context.signal,
|
|
1854
|
-
tool: toolNames.delete,
|
|
1855
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1856
|
-
payload: { path, recursive: recursive ?? false },
|
|
1857
|
-
changedPaths: () => [path],
|
|
1858
|
-
operation: async () => {
|
|
1859
|
-
const startedAt = performance.now();
|
|
1860
|
-
try {
|
|
1861
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path]);
|
|
1862
|
-
const deleted = await deletePath({ path, recursive }, {
|
|
1863
|
-
cwd: workspace.root,
|
|
1864
|
-
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
1865
|
-
});
|
|
1866
|
-
const result = `Deleted ${path}${deleted.recursive ? " recursively" : ""}.`;
|
|
1867
|
-
const content = [textBlock(result)];
|
|
1868
|
-
logToolCall(config, {
|
|
1869
|
-
tool: toolNames.delete,
|
|
1870
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1871
|
-
path,
|
|
1872
|
-
success: true,
|
|
1873
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1874
|
-
});
|
|
1875
|
-
return {
|
|
1876
|
-
content,
|
|
1877
|
-
_meta: {
|
|
1878
|
-
tool: toolNames.delete,
|
|
1879
|
-
card: {
|
|
1880
|
-
workspaceId,
|
|
1881
|
-
path,
|
|
1882
|
-
summary: { recursive: deleted.recursive },
|
|
1883
|
-
payload: { content },
|
|
1884
|
-
},
|
|
1885
|
-
},
|
|
1886
|
-
structuredContent: {
|
|
1887
|
-
result,
|
|
1888
|
-
status: "deleted",
|
|
1889
|
-
path,
|
|
1890
|
-
recursive: deleted.recursive,
|
|
1891
|
-
},
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
1894
|
-
catch (error) {
|
|
1895
|
-
logToolCall(config, {
|
|
1896
|
-
tool: toolNames.delete,
|
|
1897
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1898
|
-
path,
|
|
1899
|
-
success: false,
|
|
1900
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1901
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1902
|
-
});
|
|
1903
|
-
throw error;
|
|
1904
|
-
}
|
|
1905
|
-
},
|
|
1906
|
-
}, activityRelationFor(context));
|
|
1907
|
-
},
|
|
1908
|
-
shellRun: async (input, context) => {
|
|
1909
|
-
const { workspaceId, command, surface, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, } = input;
|
|
1910
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1911
|
-
const activityRequest = surface === "exec_command"
|
|
1912
|
-
? {
|
|
1913
|
-
workspaceId,
|
|
1914
|
-
cmd: command,
|
|
1915
|
-
tty,
|
|
1916
|
-
columns,
|
|
1917
|
-
rows,
|
|
1918
|
-
workingDirectory,
|
|
1919
|
-
yieldTimeMs,
|
|
1920
|
-
timeoutMs,
|
|
1921
|
-
maxOutputTokens,
|
|
1922
|
-
}
|
|
1923
|
-
: {
|
|
1924
|
-
workspaceId,
|
|
1925
|
-
action: "run",
|
|
1926
|
-
command,
|
|
1927
|
-
tty,
|
|
1928
|
-
columns,
|
|
1929
|
-
rows,
|
|
1930
|
-
workingDirectory,
|
|
1931
|
-
yieldTimeMs,
|
|
1932
|
-
timeoutMs,
|
|
1933
|
-
maxOutputTokens,
|
|
1934
|
-
};
|
|
1935
|
-
let undeliveredProcessId;
|
|
1936
|
-
const activityResult = await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), surface, activityRequestFor(activityRequest, context), async (activityContext) => {
|
|
1937
|
-
try {
|
|
1938
|
-
const result = await runToolWithHooks(hooks, {
|
|
1939
|
-
signal: context.signal,
|
|
1940
|
-
tool: surface,
|
|
1941
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1942
|
-
payload: surface === "exec_command"
|
|
1943
|
-
? { command, workingDirectory: workingDirectory ?? "." }
|
|
1944
|
-
: { action: "run", command, workingDirectory: workingDirectory ?? "." },
|
|
1945
|
-
...(surface === "bash" ? { isFailure: toolResultIsError } : {}),
|
|
1946
|
-
operation: async () => {
|
|
1947
|
-
const startedAt = performance.now();
|
|
1948
|
-
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
1949
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [cwd]);
|
|
1950
|
-
const snapshot = await processSessions.start({
|
|
1951
|
-
workspaceId,
|
|
1952
|
-
command,
|
|
1953
|
-
cwd,
|
|
1954
|
-
workspaceRoot: workspace.root,
|
|
1955
|
-
tty,
|
|
1956
|
-
columns,
|
|
1957
|
-
rows,
|
|
1958
|
-
yieldTimeMs,
|
|
1959
|
-
timeoutMs,
|
|
1960
|
-
maxOutputTokens,
|
|
1961
|
-
...(surface === "exec_command" ? { codexCi: true } : {}),
|
|
1962
|
-
signal: context.signal,
|
|
1963
|
-
audit: activityContext,
|
|
1964
|
-
});
|
|
1965
|
-
undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
|
|
1966
|
-
logToolCall(config, {
|
|
1967
|
-
tool: surface,
|
|
1968
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
1969
|
-
workingDirectory: workingDirectory ?? ".",
|
|
1970
|
-
command,
|
|
1971
|
-
commandLength: command.length,
|
|
1972
|
-
exitCode: snapshot.exitCode,
|
|
1973
|
-
running: snapshot.running,
|
|
1974
|
-
processId: snapshot.processId,
|
|
1975
|
-
success: surface === "exec_command"
|
|
1976
|
-
? snapshot.running || snapshot.exitCode === 0
|
|
1977
|
-
: snapshot.running || (snapshot.exitCode === 0 && !snapshot.signal),
|
|
1978
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1979
|
-
});
|
|
1980
|
-
const response = processToolResponse(surface, workspaceId, snapshot, {
|
|
1981
|
-
...(surface === "bash" ? { action: "run" } : {}),
|
|
1982
|
-
command,
|
|
1983
|
-
workingDirectory: workingDirectory ?? ".",
|
|
1984
|
-
running: snapshot.running,
|
|
1985
|
-
exitCode: snapshot.exitCode,
|
|
1986
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
1987
|
-
});
|
|
1988
|
-
return surface === "bash" && !snapshot.running && (snapshot.signal || snapshot.exitCode !== 0)
|
|
1989
|
-
? { ...response, isError: true }
|
|
1990
|
-
: response;
|
|
1991
|
-
},
|
|
1992
|
-
});
|
|
1993
|
-
context.signal?.throwIfAborted();
|
|
1994
|
-
return result;
|
|
1995
|
-
}
|
|
1996
|
-
catch (error) {
|
|
1997
|
-
if (undeliveredProcessId !== undefined) {
|
|
1998
|
-
processSessions.discardUndelivered(workspaceId, undeliveredProcessId);
|
|
1999
|
-
}
|
|
2000
|
-
throw error;
|
|
2001
|
-
}
|
|
2002
|
-
}, processActivityOutcome, activityRelationFor(context));
|
|
2003
|
-
markReturnedOutput(bashOutputStore, activityResult);
|
|
2004
|
-
return activityResult;
|
|
2005
|
-
},
|
|
2006
|
-
capabilityRun: async (input, context) => {
|
|
2007
|
-
const { workspaceId, name, arguments: capabilityArguments, file } = input;
|
|
2008
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
2009
|
-
let changedPaths = [];
|
|
2010
|
-
return runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), toolNames.capability, activityRequestFor(capabilityActivityAuditRequest(input), context), (activityContext) => runToolWithHooks(hooks, {
|
|
2011
|
-
signal: context.signal,
|
|
2012
|
-
tool: toolNames.capability,
|
|
2013
|
-
invocation: workspaceHookInvocation(workspace),
|
|
2014
|
-
payload: { name, action: "run" },
|
|
2015
|
-
isFailure: toolResultIsError,
|
|
2016
|
-
changedPaths: () => changedPaths,
|
|
2017
|
-
operation: async () => {
|
|
2018
|
-
const startedAt = performance.now();
|
|
2019
|
-
try {
|
|
2020
|
-
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, capabilityContextFor(workspace), {
|
|
2021
|
-
nativeFile: file,
|
|
2022
|
-
signal: context.signal,
|
|
2023
|
-
requestMeta: context.requestMeta,
|
|
2024
|
-
sessionId: context.sessionId,
|
|
2025
|
-
batch: context.batch,
|
|
2026
|
-
activityId: activityContext.activityId,
|
|
2027
|
-
});
|
|
2028
|
-
changedPaths = execution.changedPaths ?? [];
|
|
2029
|
-
const result = {
|
|
2030
|
-
content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
|
|
2031
|
-
...(execution.card
|
|
2032
|
-
? {
|
|
2033
|
-
_meta: {
|
|
2034
|
-
tool: toolNames.capability,
|
|
2035
|
-
card: {
|
|
2036
|
-
workspaceId,
|
|
2037
|
-
capabilityName: name,
|
|
2038
|
-
summary: execution.card.summary ?? {},
|
|
2039
|
-
files: execution.card.files,
|
|
2040
|
-
payload: execution.card.payload ?? {},
|
|
2041
|
-
},
|
|
2042
|
-
},
|
|
2043
|
-
}
|
|
2044
|
-
: {}),
|
|
2045
|
-
structuredContent: { name, action: "run", result: execution.value },
|
|
2046
|
-
};
|
|
2047
|
-
logToolCall(config, {
|
|
2048
|
-
tool: toolNames.capability,
|
|
2049
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
2050
|
-
capability: name,
|
|
2051
|
-
action: "run",
|
|
2052
|
-
success: true,
|
|
2053
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2054
|
-
});
|
|
2055
|
-
return result;
|
|
2056
|
-
}
|
|
2057
|
-
catch (error) {
|
|
2058
|
-
const capabilityError = error instanceof CapabilityError
|
|
2059
|
-
? error
|
|
2060
|
-
: new CapabilityError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
2061
|
-
const result = {
|
|
2062
|
-
content: [textBlock(`${capabilityError.code}: ${capabilityError.message}`)],
|
|
2063
|
-
structuredContent: {
|
|
2064
|
-
name,
|
|
2065
|
-
action: "run",
|
|
2066
|
-
error: { code: capabilityError.code, message: capabilityError.message },
|
|
2067
|
-
},
|
|
2068
|
-
isError: true,
|
|
2069
|
-
};
|
|
2070
|
-
logFailedToolResponse(config, {
|
|
2071
|
-
tool: toolNames.capability,
|
|
2072
|
-
...workspaceLogContext(workspace, context.sessionId),
|
|
2073
|
-
capability: name,
|
|
2074
|
-
action: "run",
|
|
2075
|
-
}, result.content, startedAt);
|
|
2076
|
-
return result;
|
|
2077
|
-
}
|
|
2078
|
-
},
|
|
2079
|
-
}), standardActivityOutcome, activityRelationFor(context), (result) => capabilityActivityAuditResult(name, result));
|
|
2080
|
-
},
|
|
2081
|
-
});
|
|
2082
|
-
batchExecutor = new BatchExecutor({
|
|
2083
|
-
lifecycle: activityLifecycle,
|
|
2084
|
-
workspaces,
|
|
2085
|
-
coreOperations,
|
|
2086
|
-
resultIsError: toolResultIsError,
|
|
2087
|
-
capabilityBatchPolicy: (name) => capabilityRegistry.batchPolicy(name),
|
|
2088
|
-
shellSurface: "bash",
|
|
2089
|
-
});
|
|
2090
|
-
const nativeBulkMutations = new NativeBulkMutationExecutor({
|
|
2091
|
-
lifecycle: activityLifecycle,
|
|
2092
|
-
workspaces,
|
|
2093
|
-
coreOperations,
|
|
2094
|
-
preflightInstructions: (workspace, paths) => assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, paths),
|
|
2095
|
-
resultIsError: toolResultIsError,
|
|
2096
|
-
resultText: toolResultText,
|
|
2097
|
-
resultContent: toolResultContent,
|
|
293
|
+
const operationRuntime = createOperationRuntime({
|
|
294
|
+
config, workspaces, activityLifecycle, hooks, processSessions, bashOutputStore, capabilityRegistry, hostScopeIdFor,
|
|
2098
295
|
});
|
|
296
|
+
batchExecutor = operationRuntime.batchExecutor;
|
|
297
|
+
const { coreOperations, nativeBulkMutations } = operationRuntime;
|
|
2099
298
|
const server = new McpServer({
|
|
2100
299
|
name: "forgerelay",
|
|
2101
300
|
title: "ForgeRelay",
|
|
@@ -2173,982 +372,38 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2173
372
|
...compact,
|
|
2174
373
|
});
|
|
2175
374
|
};
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
375
|
+
registerAppResource(server, "ForgeRelay Activity Panel", activityPanelApp.uri, activityPanelApp.resourceMetadata, async (uri, extra) => activityPanelApp.readResource(uri.toString(), extra.sessionId));
|
|
376
|
+
registerOpenWorkspaceTool({
|
|
377
|
+
server, config, workspaces, remoteWorkspaces, compositeWorkspaces, workspaceTasks, processSessions,
|
|
378
|
+
capabilityRegistry, compositeTaskGuides, loadCompositeMemberContext, rememberWorkspacePanelState, hostScopeIdFor,
|
|
379
|
+
presentation: {
|
|
380
|
+
config, forgerelayVersion: FORGERELAY_VERSION, workspaces, workspaceTasks, reviewCheckpoints,
|
|
381
|
+
capabilityRegistry, subagentProviders, hooks, rememberWorkspacePanelState,
|
|
2183
382
|
},
|
|
2184
|
-
};
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
const currentWorkspaceAppUri = currentWorkspaceAppIdentity(config).uri;
|
|
2194
|
-
registerAppResource(server, "ForgeRelay historical tool card", currentWorkspaceAppUri, workspaceAppResourceMetadata, async (uri, extra) => readWorkspaceAppResource(config, uri.toString(), extra.sessionId));
|
|
2195
|
-
registerAppResource(server, "ForgeRelay historical tool card legacy", WORKSPACE_APP_LEGACY_URI, workspaceAppResourceMetadata, async (uri, extra) => readWorkspaceAppResource(config, uri.toString(), extra.sessionId));
|
|
2196
|
-
server.registerResource("ForgeRelay historical tool card compatibility", new ResourceTemplate(WORKSPACE_APP_URI_TEMPLATE, { list: undefined }), { ...workspaceAppResourceMetadata, mimeType: RESOURCE_MIME_TYPE }, async (uri, _variables, extra) => readWorkspaceAppResource(config, uri.toString(), extra.sessionId));
|
|
2197
|
-
server.registerResource("ForgeRelay historical Workspace Lifecycle compatibility", new ResourceTemplate(WORKSPACE_LIFECYCLE_APP_URI_TEMPLATE, { list: undefined }), { ...workspaceLifecycleResourceMetadata, mimeType: RESOURCE_MIME_TYPE }, async (uri, _variables, extra) => readWorkspaceLifecycleAppResource(config, uri.toString(), extra.sessionId));
|
|
2198
|
-
const currentActivityPanelAppUri = currentActivityPanelAppIdentity(config).uri;
|
|
2199
|
-
registerAppResource(server, "ForgeRelay Activity Panel", currentActivityPanelAppUri, activityPanelResourceMetadata, async (uri, extra) => readActivityPanelAppResource(config, uri.toString(), extra.sessionId));
|
|
2200
|
-
registerAppResource(server, "ForgeRelay Activity Panel legacy", ACTIVITY_PANEL_APP_LEGACY_URI, activityPanelResourceMetadata, async (uri, extra) => readActivityPanelAppResource(config, uri.toString(), extra.sessionId));
|
|
2201
|
-
server.registerResource("ForgeRelay Activity Panel compatibility", new ResourceTemplate(ACTIVITY_PANEL_APP_URI_TEMPLATE, { list: undefined }), { ...activityPanelResourceMetadata, mimeType: RESOURCE_MIME_TYPE }, async (uri, _variables, extra) => readActivityPanelAppResource(config, uri.toString(), extra.sessionId));
|
|
2202
|
-
registerAppTool(server, "open_workspace", {
|
|
2203
|
-
title: "Open workspace",
|
|
2204
|
-
description: "Open or resume a ForgeRelay Workspace. Ordinary workspaces default to local execution; relay may name a registered remote ForgeRelay. Composite Workspaces use the same open lifecycle but have kind=\"composite\" and a name instead of a mounted root. Reuse the returned workspaceId for later calls. Bootstrap context is delivered automatically only when needed and can be suppressed or refreshed.",
|
|
2205
|
-
inputSchema: {
|
|
2206
|
-
action: z
|
|
2207
|
-
.enum(["open", "list", "inspect", "member"])
|
|
2208
|
-
.optional()
|
|
2209
|
-
.describe("Defaults to open. Use list for lightweight inventory, inspect for bounded read-only metadata about one known Workspace without opening/resuming it, or member to change Composite membership."),
|
|
2210
|
-
memberAction: z
|
|
2211
|
-
.enum(["add", "update", "remove"])
|
|
2212
|
-
.optional()
|
|
2213
|
-
.describe("Required with action=member."),
|
|
2214
|
-
member: z.object({
|
|
2215
|
-
name: z.string().describe("Stable member name such as code or compute. For update/remove this identifies the existing member."),
|
|
2216
|
-
newName: z.string().optional().describe("Optional replacement member name for memberAction=update."),
|
|
2217
|
-
purpose: z.string().optional().describe("Agent-facing purpose. Required when adding a member; optional replacement when updating."),
|
|
2218
|
-
workspaceId: z.string().optional().describe("Existing ordinary or relayed Workspace to mount. Mutually exclusive with path."),
|
|
2219
|
-
path: z.string().optional().describe("Workspace path to open internally and mount. Mutually exclusive with workspaceId."),
|
|
2220
|
-
relay: z.string().optional().describe("Optional registered remote ForgeRelay alias for a path-backed member."),
|
|
2221
|
-
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
2222
|
-
baseRef: z.string().optional(),
|
|
2223
|
-
newWorktree: z.boolean().optional(),
|
|
2224
|
-
newWorkspace: z.boolean().optional(),
|
|
2225
|
-
}).optional().describe("Composite member definition used by action=member."),
|
|
2226
|
-
kind: z
|
|
2227
|
-
.enum(["workspace", "composite"])
|
|
2228
|
-
.optional()
|
|
2229
|
-
.describe("Workspace kind for action=open. Defaults to workspace. Use composite with name and no path to create or reopen a named Composite Workspace."),
|
|
2230
|
-
name: z
|
|
2231
|
-
.string()
|
|
2232
|
-
.optional()
|
|
2233
|
-
.describe("Composite Workspace name. Used only with kind=\"composite\" when creating/opening by name."),
|
|
2234
|
-
memberName: z
|
|
2235
|
-
.string()
|
|
2236
|
-
.optional()
|
|
2237
|
-
.describe("For action=open on a Composite Workspace, load bootstrap context for this named member without making it an implicit current member."),
|
|
2238
|
-
path: z
|
|
2239
|
-
.string()
|
|
2240
|
-
.optional()
|
|
2241
|
-
.describe("Project path to open for an ordinary Workspace. Required for action=open unless workspaceId is supplied or kind=\"composite\" with name is used. With mode=\"worktree\", this may also be a managed worktree path previously returned by ForgeRelay."),
|
|
2242
|
-
relay: z
|
|
2243
|
-
.string()
|
|
2244
|
-
.optional()
|
|
2245
|
-
.describe("Optional registered remote ForgeRelay alias. When supplied for action=open, the workspace is opened and executed on that remote instance while this Gateway returns its own workspaceId."),
|
|
2246
|
-
workspaceId: z
|
|
2247
|
-
.string()
|
|
2248
|
-
.optional()
|
|
2249
|
-
.describe("For action=open, an existing Workspace ID to resume or reuse. Historical duplicate IDs from earlier ForgeRelay versions may resolve to the canonical Workspace ID. For action=list, filters inventory. For action=inspect, identifies the single Workspace to inspect without opening or binding it."),
|
|
2250
|
-
mode: z
|
|
2251
|
-
.enum(["checkout", "worktree"])
|
|
2252
|
-
.optional()
|
|
2253
|
-
.describe("For action=open, defaults to checkout and uses the actual directory unless worktree isolation is explicitly requested. For action=list, filters by workspace mode."),
|
|
2254
|
-
baseRef: z
|
|
2255
|
-
.string()
|
|
2256
|
-
.optional()
|
|
2257
|
-
.describe("Local branch to base a managed worktree on and eventually merge back into. Only used with mode=\"worktree\". Defaults to the source checkout's current branch."),
|
|
2258
|
-
newWorktree: z
|
|
2259
|
-
.boolean()
|
|
2260
|
-
.optional()
|
|
2261
|
-
.describe("When true, create another isolated managed Git worktree instead of reusing the existing physical worktree. Use only when the user explicitly requests separate Git isolation."),
|
|
2262
|
-
newWorkspace: z
|
|
2263
|
-
.boolean()
|
|
2264
|
-
.optional()
|
|
2265
|
-
.describe("Deprecated compatibility flag. It no longer creates another Workspace identity for the same physical checkout or managed worktree; ForgeRelay reuses that target's canonical Workspace. Use newWorktree=true when the user explicitly needs separate Git isolation."),
|
|
2266
|
-
context: z
|
|
2267
|
-
.enum(["auto", "full", "none"])
|
|
2268
|
-
.optional()
|
|
2269
|
-
.describe("Bootstrap context policy for action=open. auto (default) sends full project context only when this conversation has not received the current context fingerprint; full forces a refresh; none opens/resumes without returning the full bootstrap context."),
|
|
2270
|
-
root: z
|
|
2271
|
-
.string()
|
|
2272
|
-
.optional()
|
|
2273
|
-
.describe("For action=list, filter by canonical workspace root or source root."),
|
|
2274
|
-
status: z
|
|
2275
|
-
.string()
|
|
2276
|
-
.optional()
|
|
2277
|
-
.describe("For action=list, filter by persisted workspace status such as active or closed."),
|
|
2278
|
-
state: z
|
|
2279
|
-
.enum(["active", "stale", "invalid", "closed"])
|
|
2280
|
-
.optional()
|
|
2281
|
-
.describe("For action=list, filter by derived lifecycle state."),
|
|
2282
|
-
staleOnly: z
|
|
2283
|
-
.boolean()
|
|
2284
|
-
.optional()
|
|
2285
|
-
.describe("For action=list, return only active Workspaces idle for more than two days."),
|
|
2286
|
-
offset: z
|
|
2287
|
-
.number()
|
|
2288
|
-
.int()
|
|
2289
|
-
.nonnegative()
|
|
2290
|
-
.optional()
|
|
2291
|
-
.describe("For action=list, zero-based inventory offset. Defaults to 0."),
|
|
2292
|
-
limit: z
|
|
2293
|
-
.number()
|
|
2294
|
-
.int()
|
|
2295
|
-
.min(1)
|
|
2296
|
-
.max(100)
|
|
2297
|
-
.optional()
|
|
2298
|
-
.describe("For action=list, maximum records to return. Defaults to 50; maximum 100."),
|
|
383
|
+
});
|
|
384
|
+
registerActivityQueryTools(server, activityQueries, connectionScopeId, activityPanelApp.toolMeta._meta, config.activityPanelExpanded, config.logging, workspacePanelState, {
|
|
385
|
+
panel: async (workspaceId, conversationScopeId) => {
|
|
386
|
+
if (compositeWorkspaces.has(workspaceId)) {
|
|
387
|
+
return compositeActivity.beginPanel(workspaceId, conversationScopeId);
|
|
388
|
+
}
|
|
389
|
+
return remoteWorkspaces.has(workspaceId)
|
|
390
|
+
? remoteWorkspaces.activityPanel(workspaceId, conversationScopeId)
|
|
391
|
+
: undefined;
|
|
2299
392
|
},
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
purpose: z.string(),
|
|
2311
|
-
workspaceId: z.string(),
|
|
2312
|
-
})).optional(),
|
|
2313
|
-
memberContext: z.unknown().optional(),
|
|
2314
|
-
root: z.string().optional(),
|
|
2315
|
-
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
2316
|
-
sourceRoot: z.string().optional(),
|
|
2317
|
-
worktree: z
|
|
2318
|
-
.object({
|
|
2319
|
-
path: z.string(),
|
|
2320
|
-
baseRef: z.string(),
|
|
2321
|
-
baseSha: z.string(),
|
|
2322
|
-
branch: z.string().optional(),
|
|
2323
|
-
targetBranch: z.string().optional(),
|
|
2324
|
-
dirtySource: z.boolean(),
|
|
2325
|
-
detached: z.boolean(),
|
|
2326
|
-
managed: z.boolean(),
|
|
2327
|
-
})
|
|
2328
|
-
.optional(),
|
|
2329
|
-
worktrees: z.array(z.object({
|
|
2330
|
-
workspaceId: z.string(),
|
|
2331
|
-
path: z.string(),
|
|
2332
|
-
baseRef: z.string(),
|
|
2333
|
-
baseSha: z.string(),
|
|
2334
|
-
branch: z.string().optional(),
|
|
2335
|
-
targetBranch: z.string().optional(),
|
|
2336
|
-
managed: z.boolean(),
|
|
2337
|
-
current: z.boolean(),
|
|
2338
|
-
})).optional(),
|
|
2339
|
-
staleWorkspaces: z.array(z.object({
|
|
2340
|
-
workspaceId: z.string(),
|
|
2341
|
-
root: z.string(),
|
|
2342
|
-
mode: z.enum(["checkout", "worktree"]),
|
|
2343
|
-
lastUsedAt: z.string(),
|
|
2344
|
-
idleMs: z.number().nonnegative(),
|
|
2345
|
-
branch: z.string().optional(),
|
|
2346
|
-
targetBranch: z.string().optional(),
|
|
2347
|
-
managed: z.boolean(),
|
|
2348
|
-
})).optional(),
|
|
2349
|
-
capabilityFingerprint: capabilityFingerprintOutputSchema.optional(),
|
|
2350
|
-
contextFingerprint: z.string().optional(),
|
|
2351
|
-
capabilityCatalog: z.array(capabilityCatalogOutputSchema).optional(),
|
|
2352
|
-
capabilityGuides: z.array(capabilityGuideOutputSchema).optional(),
|
|
2353
|
-
agentsFiles: z.array(workspaceAgentsFileOutputSchema).optional(),
|
|
2354
|
-
availableAgentsFiles: z.array(workspaceAvailableAgentsFileOutputSchema).optional(),
|
|
2355
|
-
skills: z.array(workspaceSkillOutputSchema).optional(),
|
|
2356
|
-
agentProviders: z.array(workspaceSubagentProviderOutputSchema).optional(),
|
|
2357
|
-
agents: z.array(workspaceLocalAgentOutputSchema).optional(),
|
|
2358
|
-
skillDiagnostics: z.array(workspaceSkillDiagnosticOutputSchema).optional(),
|
|
2359
|
-
workspaces: z.array(workspaceInventoryEntryOutputSchema).optional(),
|
|
2360
|
-
compositeWorkspaces: z.array(z.object({
|
|
2361
|
-
workspaceId: z.string(),
|
|
2362
|
-
kind: z.literal("composite"),
|
|
2363
|
-
name: z.string(),
|
|
2364
|
-
status: z.enum(["active", "closed"]),
|
|
2365
|
-
state: z.enum(["active", "closed"]),
|
|
2366
|
-
members: z.array(z.object({
|
|
2367
|
-
name: z.string(),
|
|
2368
|
-
purpose: z.string(),
|
|
2369
|
-
workspaceId: z.string(),
|
|
2370
|
-
})),
|
|
2371
|
-
createdAt: z.string(),
|
|
2372
|
-
lastUsedAt: z.string(),
|
|
2373
|
-
})).optional(),
|
|
2374
|
-
summary: workspaceInventorySummaryOutputSchema.optional(),
|
|
2375
|
-
page: workspaceInventoryPageOutputSchema.optional(),
|
|
2376
|
-
inspection: workspaceInspectionOutputSchema.optional(),
|
|
2377
|
-
instruction: z.string(),
|
|
393
|
+
snapshot: async (input, conversationScopeId) => {
|
|
394
|
+
const compositeTurnId = input.turnId ?? (input.workspaceId && compositeWorkspaces.has(input.workspaceId)
|
|
395
|
+
? compositeActivity.currentTurnId(conversationScopeId, input.workspaceId)
|
|
396
|
+
: undefined);
|
|
397
|
+
if (compositeTurnId) {
|
|
398
|
+
const composite = await compositeActivity.snapshot(compositeTurnId, input.knownRevision);
|
|
399
|
+
if (composite)
|
|
400
|
+
return composite;
|
|
401
|
+
}
|
|
402
|
+
return remoteWorkspaces.activitySnapshot(input, conversationScopeId);
|
|
2378
403
|
},
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
destructiveHint: false,
|
|
2383
|
-
idempotentHint: false,
|
|
2384
|
-
openWorldHint: false,
|
|
2385
|
-
},
|
|
2386
|
-
}, async ({ action = "open", memberAction, member, kind, name, memberName, path, relay, workspaceId, mode, baseRef, newWorktree, newWorkspace, context, root, status, state, staleOnly, offset, limit, }, { _meta, sessionId }) => {
|
|
2387
|
-
const startedAt = performance.now();
|
|
2388
|
-
const conversationScopeId = openAiConversationScopeId(_meta);
|
|
2389
|
-
const protectedWorkspaceIds = processSessions.activeWorkspaceIds();
|
|
2390
|
-
const inspectTaskSummary = (targetWorkspaceId) => {
|
|
2391
|
-
try {
|
|
2392
|
-
const summary = workspaceTasks.inspectSummary(targetWorkspaceId);
|
|
2393
|
-
if (!summary)
|
|
2394
|
-
return undefined;
|
|
2395
|
-
const { fingerprint: _fingerprint, ...inspectionSummary } = summary;
|
|
2396
|
-
return inspectionSummary;
|
|
2397
|
-
}
|
|
2398
|
-
catch {
|
|
2399
|
-
return undefined;
|
|
2400
|
-
}
|
|
2401
|
-
};
|
|
2402
|
-
const inspectCompositeMember = async (entry) => {
|
|
2403
|
-
if (remoteWorkspaces.has(entry.workspaceId)) {
|
|
2404
|
-
try {
|
|
2405
|
-
const inspected = await remoteWorkspaces.inspectWorkspace(entry.workspaceId);
|
|
2406
|
-
return {
|
|
2407
|
-
name: entry.name,
|
|
2408
|
-
purpose: entry.purpose,
|
|
2409
|
-
workspaceId: entry.workspaceId,
|
|
2410
|
-
known: true,
|
|
2411
|
-
location: inspected.location,
|
|
2412
|
-
routeState: inspected.routeState,
|
|
2413
|
-
state: inspected.state,
|
|
2414
|
-
status: inspected.status,
|
|
2415
|
-
mode: inspected.mode,
|
|
2416
|
-
rootValid: inspected.rootValid,
|
|
2417
|
-
};
|
|
2418
|
-
}
|
|
2419
|
-
catch {
|
|
2420
|
-
return {
|
|
2421
|
-
name: entry.name,
|
|
2422
|
-
purpose: entry.purpose,
|
|
2423
|
-
workspaceId: entry.workspaceId,
|
|
2424
|
-
known: false,
|
|
2425
|
-
};
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
try {
|
|
2429
|
-
const inspected = await workspaces.inspectWorkspace(entry.workspaceId);
|
|
2430
|
-
return {
|
|
2431
|
-
name: entry.name,
|
|
2432
|
-
purpose: entry.purpose,
|
|
2433
|
-
workspaceId: entry.workspaceId,
|
|
2434
|
-
known: true,
|
|
2435
|
-
location: inspected.location,
|
|
2436
|
-
state: inspected.state,
|
|
2437
|
-
status: inspected.status,
|
|
2438
|
-
mode: inspected.mode,
|
|
2439
|
-
rootValid: inspected.rootValid,
|
|
2440
|
-
};
|
|
2441
|
-
}
|
|
2442
|
-
catch {
|
|
2443
|
-
return {
|
|
2444
|
-
name: entry.name,
|
|
2445
|
-
purpose: entry.purpose,
|
|
2446
|
-
workspaceId: entry.workspaceId,
|
|
2447
|
-
known: false,
|
|
2448
|
-
};
|
|
2449
|
-
}
|
|
2450
|
-
};
|
|
2451
|
-
if (action === "inspect") {
|
|
2452
|
-
if (!workspaceId) {
|
|
2453
|
-
throw new Error("open_workspace action=inspect requires workspaceId.");
|
|
2454
|
-
}
|
|
2455
|
-
if (memberAction !== undefined || member !== undefined || kind !== undefined || name !== undefined ||
|
|
2456
|
-
memberName !== undefined || path !== undefined || relay !== undefined || mode !== undefined ||
|
|
2457
|
-
baseRef !== undefined || newWorktree !== undefined || newWorkspace !== undefined || context !== undefined ||
|
|
2458
|
-
root !== undefined || status !== undefined || state !== undefined || staleOnly !== undefined ||
|
|
2459
|
-
offset !== undefined || limit !== undefined) {
|
|
2460
|
-
throw new Error("open_workspace action=inspect accepts only workspaceId. It never opens, resumes, binds, or mutates the inspected Workspace.");
|
|
2461
|
-
}
|
|
2462
|
-
let inspection;
|
|
2463
|
-
if (compositeWorkspaces.has(workspaceId)) {
|
|
2464
|
-
const composite = compositeWorkspaces.get(workspaceId);
|
|
2465
|
-
const members = await Promise.all(composite.members.map(inspectCompositeMember));
|
|
2466
|
-
const taskSummary = inspectTaskSummary(composite.id);
|
|
2467
|
-
inspection = {
|
|
2468
|
-
workspaceId: composite.id,
|
|
2469
|
-
kind: "composite",
|
|
2470
|
-
name: composite.name,
|
|
2471
|
-
status: composite.status,
|
|
2472
|
-
state: composite.status,
|
|
2473
|
-
createdAt: composite.createdAt,
|
|
2474
|
-
lastUsedAt: composite.lastUsedAt,
|
|
2475
|
-
members,
|
|
2476
|
-
...(taskSummary ? { taskSummary } : {}),
|
|
2477
|
-
};
|
|
2478
|
-
}
|
|
2479
|
-
else if (remoteWorkspaces.has(workspaceId)) {
|
|
2480
|
-
inspection = await remoteWorkspaces.inspectWorkspace(workspaceId);
|
|
2481
|
-
}
|
|
2482
|
-
else {
|
|
2483
|
-
const inspected = await workspaces.inspectWorkspace(workspaceId);
|
|
2484
|
-
const taskSummary = inspectTaskSummary(inspected.workspaceId);
|
|
2485
|
-
inspection = {
|
|
2486
|
-
...inspected,
|
|
2487
|
-
...(taskSummary ? { taskSummary } : {}),
|
|
2488
|
-
};
|
|
2489
|
-
}
|
|
2490
|
-
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.";
|
|
2491
|
-
const result = [
|
|
2492
|
-
`Inspected Workspace ${inspection.workspaceId} (${inspection.kind}).`,
|
|
2493
|
-
inspection.kind === "composite"
|
|
2494
|
-
? `State: ${inspection.state}; members=${inspection.members.length}.`
|
|
2495
|
-
: inspection.location === "relay"
|
|
2496
|
-
? inspection.state
|
|
2497
|
-
? `State: ${inspection.state}; route=${inspection.routeState}; mode=${inspection.mode}; location=${inspection.location}. Lifecycle and Task facts come from the Execution ForgeRelay.`
|
|
2498
|
-
: `Route: ${inspection.routeState}; mode=${inspection.mode}; location=${inspection.location}.`
|
|
2499
|
-
: `State: ${inspection.state}; mode=${inspection.mode}; location=${inspection.location}.`,
|
|
2500
|
-
"Task summary is included only when durable Task state already exists on the owning Workspace; Task bodies are never returned.",
|
|
2501
|
-
instruction,
|
|
2502
|
-
].join("\n");
|
|
2503
|
-
logToolCall(config, {
|
|
2504
|
-
tool: "open_workspace",
|
|
2505
|
-
action: "inspect",
|
|
2506
|
-
success: true,
|
|
2507
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2508
|
-
});
|
|
2509
|
-
return {
|
|
2510
|
-
content: [textBlock(result)],
|
|
2511
|
-
structuredContent: {
|
|
2512
|
-
action: "inspect",
|
|
2513
|
-
workspaceId: inspection.workspaceId,
|
|
2514
|
-
kind: inspection.kind,
|
|
2515
|
-
inspection,
|
|
2516
|
-
instruction,
|
|
2517
|
-
},
|
|
2518
|
-
};
|
|
2519
|
-
}
|
|
2520
|
-
if (action === "member") {
|
|
2521
|
-
if (!workspaceId || !compositeWorkspaces.has(workspaceId)) {
|
|
2522
|
-
throw new Error("open_workspace action=member requires an existing Composite Workspace workspaceId.");
|
|
2523
|
-
}
|
|
2524
|
-
if (!compositeWorkspaces.isActive(workspaceId)) {
|
|
2525
|
-
throw new Error(`Composite Workspace ${workspaceId} is closed. Reopen it with open_workspace before changing members.`);
|
|
2526
|
-
}
|
|
2527
|
-
if (!memberAction || !member) {
|
|
2528
|
-
throw new Error("open_workspace action=member requires memberAction and member.");
|
|
2529
|
-
}
|
|
2530
|
-
if (kind !== undefined || name !== undefined || memberName !== undefined || path !== undefined || relay !== undefined || mode !== undefined ||
|
|
2531
|
-
baseRef !== undefined || newWorktree !== undefined || newWorkspace !== undefined || context !== undefined ||
|
|
2532
|
-
root !== undefined || status !== undefined || state !== undefined || staleOnly !== undefined ||
|
|
2533
|
-
offset !== undefined || limit !== undefined) {
|
|
2534
|
-
throw new Error("open_workspace action=member accepts workspaceId, memberAction, and member only. Put any Workspace open definition inside member.");
|
|
2535
|
-
}
|
|
2536
|
-
const resolveMemberTargetWorkspaceId = async () => {
|
|
2537
|
-
const byWorkspaceId = typeof member.workspaceId === "string" && member.workspaceId.length > 0;
|
|
2538
|
-
const byPath = typeof member.path === "string" && member.path.length > 0;
|
|
2539
|
-
if (byWorkspaceId === byPath) {
|
|
2540
|
-
throw new Error("A Composite Workspace member target requires exactly one of member.workspaceId or member.path.");
|
|
2541
|
-
}
|
|
2542
|
-
if (byWorkspaceId) {
|
|
2543
|
-
const targetWorkspaceId = member.workspaceId;
|
|
2544
|
-
if (compositeWorkspaces.has(targetWorkspaceId)) {
|
|
2545
|
-
throw new Error("A Composite Workspace cannot be mounted as a Composite Workspace member.");
|
|
2546
|
-
}
|
|
2547
|
-
if (member.relay !== undefined || member.mode !== undefined || member.baseRef !== undefined ||
|
|
2548
|
-
member.newWorktree !== undefined || member.newWorkspace !== undefined) {
|
|
2549
|
-
throw new Error("member.workspaceId cannot be combined with relay/mode/baseRef/newWorktree/newWorkspace.");
|
|
2550
|
-
}
|
|
2551
|
-
if (!remoteWorkspaces.has(targetWorkspaceId))
|
|
2552
|
-
workspaces.getWorkspace(targetWorkspaceId);
|
|
2553
|
-
return targetWorkspaceId;
|
|
2554
|
-
}
|
|
2555
|
-
if (member.relay !== undefined) {
|
|
2556
|
-
const opened = await remoteWorkspaces.openWorkspace(member.relay, {
|
|
2557
|
-
path: member.path,
|
|
2558
|
-
...(member.mode ? { mode: member.mode } : {}),
|
|
2559
|
-
...(member.baseRef ? { baseRef: member.baseRef } : {}),
|
|
2560
|
-
...(member.newWorktree !== undefined ? { newWorktree: member.newWorktree } : {}),
|
|
2561
|
-
...(member.newWorkspace !== undefined ? { newWorkspace: member.newWorkspace } : {}),
|
|
2562
|
-
context: "none",
|
|
2563
|
-
});
|
|
2564
|
-
return opened.workspaceId;
|
|
2565
|
-
}
|
|
2566
|
-
const opened = await workspaces.openWorkspace({
|
|
2567
|
-
path: member.path,
|
|
2568
|
-
...(member.mode ? { mode: member.mode } : {}),
|
|
2569
|
-
...(member.baseRef ? { baseRef: member.baseRef } : {}),
|
|
2570
|
-
...(member.newWorktree !== undefined ? { newWorktree: member.newWorktree } : {}),
|
|
2571
|
-
...(member.newWorkspace !== undefined ? { newWorkspace: member.newWorkspace } : {}),
|
|
2572
|
-
context: "none",
|
|
2573
|
-
}, { protectedWorkspaceIds });
|
|
2574
|
-
return opened.workspace.id;
|
|
2575
|
-
};
|
|
2576
|
-
let composite;
|
|
2577
|
-
if (memberAction === "add") {
|
|
2578
|
-
if (member.newName !== undefined) {
|
|
2579
|
-
throw new Error("Adding a Composite Workspace member does not accept member.newName.");
|
|
2580
|
-
}
|
|
2581
|
-
const purpose = member.purpose?.trim();
|
|
2582
|
-
if (!purpose)
|
|
2583
|
-
throw new Error("Adding a Composite Workspace member requires member.purpose.");
|
|
2584
|
-
const targetWorkspaceId = await resolveMemberTargetWorkspaceId();
|
|
2585
|
-
composite = compositeWorkspaces.addMember(workspaceId, {
|
|
2586
|
-
name: member.name,
|
|
2587
|
-
purpose,
|
|
2588
|
-
workspaceId: targetWorkspaceId,
|
|
2589
|
-
});
|
|
2590
|
-
}
|
|
2591
|
-
else if (memberAction === "update") {
|
|
2592
|
-
const targetFieldsPresent = member.workspaceId !== undefined || member.path !== undefined || member.relay !== undefined ||
|
|
2593
|
-
member.mode !== undefined || member.baseRef !== undefined || member.newWorktree !== undefined ||
|
|
2594
|
-
member.newWorkspace !== undefined;
|
|
2595
|
-
if (member.newName === undefined && member.purpose === undefined && !targetFieldsPresent) {
|
|
2596
|
-
throw new Error("Updating a Composite Workspace member requires newName, purpose, or a replacement Workspace target.");
|
|
2597
|
-
}
|
|
2598
|
-
const targetWorkspaceId = targetFieldsPresent
|
|
2599
|
-
? await resolveMemberTargetWorkspaceId()
|
|
2600
|
-
: undefined;
|
|
2601
|
-
composite = compositeWorkspaces.updateMember(workspaceId, member.name, {
|
|
2602
|
-
...(member.newName !== undefined ? { name: member.newName } : {}),
|
|
2603
|
-
...(member.purpose !== undefined ? { purpose: member.purpose } : {}),
|
|
2604
|
-
...(targetWorkspaceId !== undefined ? { workspaceId: targetWorkspaceId } : {}),
|
|
2605
|
-
});
|
|
2606
|
-
}
|
|
2607
|
-
else {
|
|
2608
|
-
if (member.newName !== undefined || member.purpose !== undefined || member.workspaceId !== undefined || member.path !== undefined ||
|
|
2609
|
-
member.relay !== undefined || member.mode !== undefined || member.baseRef !== undefined ||
|
|
2610
|
-
member.newWorktree !== undefined || member.newWorkspace !== undefined) {
|
|
2611
|
-
throw new Error("Removing a Composite Workspace member accepts only member.name.");
|
|
2612
|
-
}
|
|
2613
|
-
composite = compositeWorkspaces.removeMember(workspaceId, member.name);
|
|
2614
|
-
}
|
|
2615
|
-
const memberActionVerb = memberAction === "add"
|
|
2616
|
-
? "Added"
|
|
2617
|
-
: memberAction === "update"
|
|
2618
|
-
? "Updated"
|
|
2619
|
-
: "Removed";
|
|
2620
|
-
const memberActionPreposition = memberAction === "remove" ? "from" : "in";
|
|
2621
|
-
const instruction = [
|
|
2622
|
-
`${memberActionVerb} member ${member.name} ${memberActionPreposition} Composite Workspace ${composite.name} (${composite.id}).`,
|
|
2623
|
-
composite.members.length > 0
|
|
2624
|
-
? `Members: ${composite.members.map((entry) => `${entry.name} — ${entry.purpose}`).join("; ")}.`
|
|
2625
|
-
: "This Composite Workspace currently has no members.",
|
|
2626
|
-
"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.",
|
|
2627
|
-
].join("\n");
|
|
2628
|
-
const response = {
|
|
2629
|
-
content: [textBlock(instruction)],
|
|
2630
|
-
_meta: {
|
|
2631
|
-
tool: "open_workspace",
|
|
2632
|
-
card: {
|
|
2633
|
-
workspaceId: composite.id,
|
|
2634
|
-
kind: "composite",
|
|
2635
|
-
name: composite.name,
|
|
2636
|
-
path: composite.name,
|
|
2637
|
-
members: composite.members,
|
|
2638
|
-
instruction,
|
|
2639
|
-
summary: { members: composite.members.length },
|
|
2640
|
-
},
|
|
2641
|
-
},
|
|
2642
|
-
structuredContent: {
|
|
2643
|
-
action: "member",
|
|
2644
|
-
workspaceId: composite.id,
|
|
2645
|
-
memberAction,
|
|
2646
|
-
kind: "composite",
|
|
2647
|
-
name: composite.name,
|
|
2648
|
-
members: composite.members,
|
|
2649
|
-
instruction,
|
|
2650
|
-
},
|
|
2651
|
-
};
|
|
2652
|
-
rememberWorkspacePanelState(composite.id, response);
|
|
2653
|
-
return response;
|
|
2654
|
-
}
|
|
2655
|
-
if (action === "list") {
|
|
2656
|
-
if (path !== undefined || relay !== undefined || name !== undefined || memberName !== undefined || baseRef !== undefined || newWorktree !== undefined ||
|
|
2657
|
-
newWorkspace !== undefined || context !== undefined) {
|
|
2658
|
-
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.");
|
|
2659
|
-
}
|
|
2660
|
-
const compositeInventory = () => compositeWorkspaces.list()
|
|
2661
|
-
.filter((entry) => workspaceId === undefined || entry.id === workspaceId)
|
|
2662
|
-
.filter((entry) => status === undefined || entry.status === status)
|
|
2663
|
-
.filter((entry) => state === undefined || entry.status === state)
|
|
2664
|
-
.map((entry) => ({
|
|
2665
|
-
workspaceId: entry.id,
|
|
2666
|
-
kind: entry.kind,
|
|
2667
|
-
name: entry.name,
|
|
2668
|
-
status: entry.status,
|
|
2669
|
-
state: entry.status,
|
|
2670
|
-
members: entry.members,
|
|
2671
|
-
createdAt: entry.createdAt,
|
|
2672
|
-
lastUsedAt: entry.lastUsedAt,
|
|
2673
|
-
}));
|
|
2674
|
-
if (kind === "composite") {
|
|
2675
|
-
if (root !== undefined || mode !== undefined || staleOnly !== undefined ||
|
|
2676
|
-
offset !== undefined || limit !== undefined) {
|
|
2677
|
-
throw new Error("Composite Workspace inventory does not accept root/mode/staleOnly/offset/limit filters; use workspaceId/status/state when selecting Composite Workspaces.");
|
|
2678
|
-
}
|
|
2679
|
-
const composites = compositeInventory();
|
|
2680
|
-
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.";
|
|
2681
|
-
const result = [
|
|
2682
|
-
`Composite Workspace inventory: ${composites.length} matching record${composites.length === 1 ? "" : "s"}.`,
|
|
2683
|
-
...composites.map((entry) => `${entry.name} [${entry.workspaceId}] state=${entry.state} members=${entry.members.length} last-used=${entry.lastUsedAt}`),
|
|
2684
|
-
instruction,
|
|
2685
|
-
].join("\n");
|
|
2686
|
-
return {
|
|
2687
|
-
content: [textBlock(result)],
|
|
2688
|
-
structuredContent: {
|
|
2689
|
-
action: "list",
|
|
2690
|
-
compositeWorkspaces: composites,
|
|
2691
|
-
instruction,
|
|
2692
|
-
},
|
|
2693
|
-
};
|
|
2694
|
-
}
|
|
2695
|
-
const inventory = await workspaces.listWorkspaces({ workspaceId, mode, root, status, state, staleOnly, offset, limit }, { conversationScopeId, protectedWorkspaceIds });
|
|
2696
|
-
const composites = kind === "workspace" || root !== undefined || mode !== undefined || staleOnly
|
|
2697
|
-
? []
|
|
2698
|
-
: compositeInventory();
|
|
2699
|
-
const nextOffset = inventory.page.offset + inventory.page.limit;
|
|
2700
|
-
const instruction = [
|
|
2701
|
-
"Resume a selected workspaceId with open_workspace(action=\"open\", workspaceId=...).",
|
|
2702
|
-
"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.",
|
|
2703
|
-
inventory.page.hasMore
|
|
2704
|
-
? `More matching workspaces are available; continue with offset=${nextOffset}.`
|
|
2705
|
-
: undefined,
|
|
2706
|
-
].filter(Boolean).join(" ");
|
|
2707
|
-
const result = [
|
|
2708
|
-
`Logical workspace inventory: ${inventory.summary.matching} matching ordinary records; ${composites.length} Composite Workspace record${composites.length === 1 ? "" : "s"}.`,
|
|
2709
|
-
`States: active=${inventory.summary.active}, stale=${inventory.summary.stale}, invalid=${inventory.summary.invalid}, closed=${inventory.summary.closed}.`,
|
|
2710
|
-
...inventory.workspaces.map((entry) => [
|
|
2711
|
-
entry.label,
|
|
2712
|
-
`state=${entry.state}`,
|
|
2713
|
-
`status=${entry.status}`,
|
|
2714
|
-
`mode=${entry.mode}`,
|
|
2715
|
-
entry.managed ? "managed" : undefined,
|
|
2716
|
-
entry.current ? "current" : undefined,
|
|
2717
|
-
`root=${entry.root}`,
|
|
2718
|
-
`last-used=${entry.lastUsedAt}`,
|
|
2719
|
-
].filter(Boolean).join(" ")),
|
|
2720
|
-
...composites.map((entry) => `${entry.name} [${entry.workspaceId}] kind=composite state=${entry.state} members=${entry.members.length}`),
|
|
2721
|
-
instruction,
|
|
2722
|
-
].join("\n");
|
|
2723
|
-
logToolCall(config, {
|
|
2724
|
-
tool: "open_workspace",
|
|
2725
|
-
action: "list",
|
|
2726
|
-
path: root,
|
|
2727
|
-
success: true,
|
|
2728
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2729
|
-
});
|
|
2730
|
-
return {
|
|
2731
|
-
content: [textBlock(result)],
|
|
2732
|
-
structuredContent: {
|
|
2733
|
-
action: "list",
|
|
2734
|
-
...inventory,
|
|
2735
|
-
...(composites.length > 0 ? { compositeWorkspaces: composites } : {}),
|
|
2736
|
-
instruction,
|
|
2737
|
-
},
|
|
2738
|
-
};
|
|
2739
|
-
}
|
|
2740
|
-
if (root !== undefined || status !== undefined || state !== undefined ||
|
|
2741
|
-
staleOnly !== undefined || offset !== undefined || limit !== undefined) {
|
|
2742
|
-
throw new Error("open_workspace inventory filters root, status, state, staleOnly, offset, and limit are only valid with action=list.");
|
|
2743
|
-
}
|
|
2744
|
-
const openingComposite = kind === "composite" ||
|
|
2745
|
-
(workspaceId !== undefined && compositeWorkspaces.has(workspaceId));
|
|
2746
|
-
if (openingComposite) {
|
|
2747
|
-
if (relay !== undefined || path !== undefined || mode !== undefined || baseRef !== undefined ||
|
|
2748
|
-
newWorktree !== undefined || newWorkspace !== undefined) {
|
|
2749
|
-
throw new Error("Composite Workspace open accepts name/workspaceId/context only; members are attached separately and keep their own Workspace definitions.");
|
|
2750
|
-
}
|
|
2751
|
-
if (workspaceId !== undefined && kind === "workspace") {
|
|
2752
|
-
throw new Error(`${workspaceId} is a Composite Workspace, not an ordinary Workspace.`);
|
|
2753
|
-
}
|
|
2754
|
-
const composite = workspaceId !== undefined
|
|
2755
|
-
? compositeWorkspaces.open(workspaceId)
|
|
2756
|
-
: compositeWorkspaces.create(name ?? "");
|
|
2757
|
-
workspaceTasks.initializeWorkspace(composite.id);
|
|
2758
|
-
const compositeTaskContext = compositeCapabilityContext(composite.id, compositeTaskGuides);
|
|
2759
|
-
const compositeCapabilityCatalog = capabilityRegistry.catalog(compositeTaskContext);
|
|
2760
|
-
const compositeCapabilityGuides = compositeTaskGuides.map((guide) => ({
|
|
2761
|
-
name: guide.name,
|
|
2762
|
-
description: guide.description,
|
|
2763
|
-
whenToRead: guide.whenToRead,
|
|
2764
|
-
path: formatPathForPrompt(guide.filePath),
|
|
2765
|
-
}));
|
|
2766
|
-
const memberContext = memberName
|
|
2767
|
-
? await loadCompositeMemberContext(composite.id, memberName, context ?? "auto", conversationScopeId, protectedWorkspaceIds)
|
|
2768
|
-
: undefined;
|
|
2769
|
-
const instruction = [
|
|
2770
|
-
`This is Composite Workspace ${composite.name} (${composite.id}).`,
|
|
2771
|
-
"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.",
|
|
2772
|
-
composite.members.length > 0
|
|
2773
|
-
? `Members: ${composite.members.map((member) => `${member.name} — ${member.purpose}`).join("; ")}.`
|
|
2774
|
-
: "This Composite Workspace currently has no members.",
|
|
2775
|
-
"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.",
|
|
2776
|
-
compositeCapabilityCatalog.length > 0
|
|
2777
|
-
? `Composite-owned capabilities: ${compositeCapabilityCatalog.map((entry) => entry.name).join(", ")}. Use these without member because their state belongs to the Composite Workspace itself.`
|
|
2778
|
-
: undefined,
|
|
2779
|
-
composite.members.length > 0
|
|
2780
|
-
? "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."
|
|
2781
|
-
: undefined,
|
|
2782
|
-
"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.",
|
|
2783
|
-
].join("\n\n");
|
|
2784
|
-
const response = {
|
|
2785
|
-
content: [textBlock(instruction)],
|
|
2786
|
-
_meta: {
|
|
2787
|
-
tool: "open_workspace",
|
|
2788
|
-
card: {
|
|
2789
|
-
workspaceId: composite.id,
|
|
2790
|
-
kind: "composite",
|
|
2791
|
-
name: composite.name,
|
|
2792
|
-
path: composite.name,
|
|
2793
|
-
members: composite.members,
|
|
2794
|
-
instruction,
|
|
2795
|
-
summary: { members: composite.members.length, status: composite.status },
|
|
2796
|
-
},
|
|
2797
|
-
},
|
|
2798
|
-
structuredContent: {
|
|
2799
|
-
action: "open",
|
|
2800
|
-
workspaceId: composite.id,
|
|
2801
|
-
kind: "composite",
|
|
2802
|
-
name: composite.name,
|
|
2803
|
-
status: composite.status,
|
|
2804
|
-
state: composite.status,
|
|
2805
|
-
members: composite.members,
|
|
2806
|
-
capabilityCatalog: compositeCapabilityCatalog,
|
|
2807
|
-
capabilityGuides: compositeCapabilityGuides,
|
|
2808
|
-
...(memberContext ? { memberContext } : {}),
|
|
2809
|
-
instruction,
|
|
2810
|
-
},
|
|
2811
|
-
};
|
|
2812
|
-
logToolCall(config, {
|
|
2813
|
-
tool: "open_workspace",
|
|
2814
|
-
action: "composite",
|
|
2815
|
-
success: true,
|
|
2816
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2817
|
-
});
|
|
2818
|
-
rememberWorkspacePanelState(composite.id, response);
|
|
2819
|
-
return response;
|
|
2820
|
-
}
|
|
2821
|
-
if (name !== undefined || memberName !== undefined) {
|
|
2822
|
-
throw new Error("open_workspace name/memberName are only valid for a Composite Workspace.");
|
|
2823
|
-
}
|
|
2824
|
-
if (workspaceId !== undefined && remoteWorkspaces.has(workspaceId)) {
|
|
2825
|
-
if (path !== undefined || relay !== undefined || mode !== undefined || baseRef !== undefined ||
|
|
2826
|
-
newWorktree !== undefined || newWorkspace !== undefined) {
|
|
2827
|
-
throw new Error("Resuming a relayed Workspace by workspaceId accepts context only.");
|
|
2828
|
-
}
|
|
2829
|
-
const resumed = await remoteWorkspaces.resumeWorkspace(workspaceId, context ?? "auto", hostScopeIdFor(_meta, sessionId));
|
|
2830
|
-
rememberWorkspacePanelState(workspaceId, resumed);
|
|
2831
|
-
return resumed;
|
|
2832
|
-
}
|
|
2833
|
-
if (relay !== undefined) {
|
|
2834
|
-
if (!path)
|
|
2835
|
-
throw new Error("Relayed open_workspace requires path.");
|
|
2836
|
-
const opened = await remoteWorkspaces.openWorkspace(relay, {
|
|
2837
|
-
path,
|
|
2838
|
-
mode,
|
|
2839
|
-
baseRef,
|
|
2840
|
-
newWorktree,
|
|
2841
|
-
newWorkspace,
|
|
2842
|
-
context,
|
|
2843
|
-
}, hostScopeIdFor(_meta, sessionId));
|
|
2844
|
-
const relayedSkills = Array.isArray(opened.skills)
|
|
2845
|
-
? opened.skills
|
|
2846
|
-
: [];
|
|
2847
|
-
const relayedCapabilities = Array.isArray(opened.capabilityCatalog)
|
|
2848
|
-
? opened.capabilityCatalog
|
|
2849
|
-
: [];
|
|
2850
|
-
const result = [
|
|
2851
|
-
`Opened relayed workspace ${opened.workspaceId}.`,
|
|
2852
|
-
`Execution remote: ${relay}`,
|
|
2853
|
-
`Root: ${opened.root}`,
|
|
2854
|
-
`Mode: ${opened.mode}`,
|
|
2855
|
-
relayedSkills.length > 0
|
|
2856
|
-
? `Available skills: ${relayedSkills.map((skill) => String(skill.name ?? "")).filter(Boolean).join(", ")}`
|
|
2857
|
-
: undefined,
|
|
2858
|
-
relayedCapabilities.length > 0
|
|
2859
|
-
? `Optional capabilities: ${relayedCapabilities.map((entry) => String(entry.name ?? "")).filter(Boolean).join(", ")}`
|
|
2860
|
-
: undefined,
|
|
2861
|
-
opened.instruction,
|
|
2862
|
-
].filter(Boolean).join("\n");
|
|
2863
|
-
const response = {
|
|
2864
|
-
content: [textBlock(result)],
|
|
2865
|
-
_meta: {
|
|
2866
|
-
tool: "open_workspace",
|
|
2867
|
-
card: {
|
|
2868
|
-
workspaceId: opened.workspaceId,
|
|
2869
|
-
kind: "workspace",
|
|
2870
|
-
root: opened.root,
|
|
2871
|
-
path: opened.root,
|
|
2872
|
-
mode: opened.mode,
|
|
2873
|
-
relay,
|
|
2874
|
-
instruction: opened.instruction,
|
|
2875
|
-
summary: { mode: opened.mode, relay },
|
|
2876
|
-
},
|
|
2877
|
-
},
|
|
2878
|
-
structuredContent: {
|
|
2879
|
-
action: "open",
|
|
2880
|
-
workspaceId: opened.workspaceId,
|
|
2881
|
-
kind: "workspace",
|
|
2882
|
-
root: opened.root,
|
|
2883
|
-
mode: opened.mode,
|
|
2884
|
-
...(opened.sourceRoot ? { sourceRoot: opened.sourceRoot } : {}),
|
|
2885
|
-
...(opened.contextFingerprint !== undefined
|
|
2886
|
-
? { contextFingerprint: opened.contextFingerprint }
|
|
2887
|
-
: {}),
|
|
2888
|
-
...(opened.capabilityFingerprint !== undefined
|
|
2889
|
-
? { capabilityFingerprint: opened.capabilityFingerprint }
|
|
2890
|
-
: {}),
|
|
2891
|
-
...(opened.capabilityCatalog !== undefined
|
|
2892
|
-
? { capabilityCatalog: opened.capabilityCatalog }
|
|
2893
|
-
: {}),
|
|
2894
|
-
...(opened.capabilityGuides !== undefined
|
|
2895
|
-
? { capabilityGuides: opened.capabilityGuides }
|
|
2896
|
-
: {}),
|
|
2897
|
-
...(opened.agentsFiles !== undefined ? { agentsFiles: opened.agentsFiles } : {}),
|
|
2898
|
-
...(opened.availableAgentsFiles !== undefined
|
|
2899
|
-
? { availableAgentsFiles: opened.availableAgentsFiles }
|
|
2900
|
-
: {}),
|
|
2901
|
-
...(opened.skills !== undefined ? { skills: opened.skills } : {}),
|
|
2902
|
-
...(opened.agentProviders !== undefined
|
|
2903
|
-
? { agentProviders: opened.agentProviders }
|
|
2904
|
-
: {}),
|
|
2905
|
-
...(opened.agents !== undefined ? { agents: opened.agents } : {}),
|
|
2906
|
-
...(opened.skillDiagnostics !== undefined
|
|
2907
|
-
? { skillDiagnostics: opened.skillDiagnostics }
|
|
2908
|
-
: {}),
|
|
2909
|
-
instruction: opened.instruction,
|
|
2910
|
-
},
|
|
2911
|
-
};
|
|
2912
|
-
logToolCall(config, {
|
|
2913
|
-
tool: "open_workspace",
|
|
2914
|
-
action: "relay",
|
|
2915
|
-
path: opened.root,
|
|
2916
|
-
success: true,
|
|
2917
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2918
|
-
});
|
|
2919
|
-
rememberWorkspacePanelState(opened.workspaceId, response);
|
|
2920
|
-
return response;
|
|
2921
|
-
}
|
|
2922
|
-
const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, bootstrapContextComponents, contextFingerprint, } = await workspaces.openWorkspace({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context }, {
|
|
2923
|
-
conversationScopeId,
|
|
2924
|
-
protectedWorkspaceIds,
|
|
2925
|
-
});
|
|
2926
|
-
workspaceTasks.initializeWorkspace(workspace.id);
|
|
2927
|
-
const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
|
|
2928
|
-
const staleWorkspaces = await workspaces.listStaleWorkspaces(workspace);
|
|
2929
|
-
const capabilityFingerprint = buildCapabilityFingerprint(config, FORGERELAY_VERSION, {
|
|
2930
|
-
artifactDownloadSupported: isArtifactDownloadSupportedPlatform(),
|
|
2931
|
-
});
|
|
2932
|
-
if (config.widgets === "changes") {
|
|
2933
|
-
await reviewCheckpoints.initializeWorkspace({
|
|
2934
|
-
workspaceId: workspace.id,
|
|
2935
|
-
root: workspace.root,
|
|
2936
|
-
});
|
|
2937
|
-
}
|
|
2938
|
-
const cardSkills = workspace.skills
|
|
2939
|
-
.filter((skill) => !skill.disableModelInvocation)
|
|
2940
|
-
.map((skill) => ({
|
|
2941
|
-
name: skill.name,
|
|
2942
|
-
description: skill.description,
|
|
2943
|
-
}));
|
|
2944
|
-
const capabilityGuides = workspace.capabilityGuides.map((guide) => ({
|
|
2945
|
-
name: guide.name,
|
|
2946
|
-
description: guide.description,
|
|
2947
|
-
whenToRead: guide.whenToRead,
|
|
2948
|
-
path: formatPathForPrompt(guide.filePath),
|
|
2949
|
-
}));
|
|
2950
|
-
const capabilityCatalog = capabilityRegistry.catalog(capabilityContextFor(workspace));
|
|
2951
|
-
const cardAgentProviders = config.subagents ? subagentProviders : [];
|
|
2952
|
-
const cardAgents = workspace.agentProfiles.map((profile) => {
|
|
2953
|
-
const summary = summarizeSubagentProfile(profile);
|
|
2954
|
-
const availability = cardAgentProviders.find((provider) => provider.name === summary.provider);
|
|
2955
|
-
return {
|
|
2956
|
-
...summary,
|
|
2957
|
-
providerAvailable: availability?.available,
|
|
2958
|
-
providerUnavailableReason: availability?.reason,
|
|
2959
|
-
};
|
|
2960
|
-
});
|
|
2961
|
-
const cardAgentsFiles = agentsFiles.map((file) => ({
|
|
2962
|
-
path: formatAgentsPath(file.path, workspace.root),
|
|
2963
|
-
content: file.content,
|
|
2964
|
-
}));
|
|
2965
|
-
const cardAvailableAgentsFiles = availableAgentsFiles.map((file) => ({
|
|
2966
|
-
path: formatAgentsPath(file.path, workspace.root),
|
|
2967
|
-
}));
|
|
2968
|
-
const bootstrapComponents = new Set(bootstrapContextComponents);
|
|
2969
|
-
const visibleSkills = bootstrapComponents.has("skills") ? cardSkills : [];
|
|
2970
|
-
const visibleSkillDiagnostics = bootstrapComponents.has("skillDiagnostics")
|
|
2971
|
-
? redactSkillDiagnosticPaths(workspace.skillDiagnostics)
|
|
2972
|
-
: [];
|
|
2973
|
-
const visibleCapabilityGuides = bootstrapComponents.has("capabilityGuides") ? capabilityGuides : [];
|
|
2974
|
-
const visibleAgentProviders = bootstrapComponents.has("agentProfiles") ? cardAgentProviders : [];
|
|
2975
|
-
const visibleAgents = bootstrapComponents.has("agentProfiles") ? cardAgents : [];
|
|
2976
|
-
const loadedAgentsFiles = bootstrapComponents.has("agentsFiles") ? cardAgentsFiles : [];
|
|
2977
|
-
const availableAgentsFileOutputs = bootstrapComponents.has("availableAgentsFiles")
|
|
2978
|
-
? cardAvailableAgentsFiles
|
|
2979
|
-
: [];
|
|
2980
|
-
const workspaceContextInstruction = "For later open_workspace calls, context=\"auto\" avoids repeating unchanged bootstrap context; use context=\"none\" when only the workspace handle/metadata is needed, or context=\"full\" to force a refresh.";
|
|
2981
|
-
const workspaceManagementInstruction = "Use open_workspace(action=\"list\") for lightweight Workspace inventory. Use action=\"inspect\" with one known workspaceId for bounded read-only metadata without opening/resuming it. Explicitly open a Workspace before executing or mutating against it, and ask the user before close_workspace cleanup.";
|
|
2982
|
-
const cardInstruction = config.skillsEnabled
|
|
2983
|
-
? `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches an available skill, load it with read(path=\"skills://<name>\") before proceeding. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`
|
|
2984
|
-
: `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`;
|
|
2985
|
-
const instruction = workspaceReused
|
|
2986
|
-
? includeBootstrapContext
|
|
2987
|
-
? [
|
|
2988
|
-
`Workspace already exists as ${workspace.id} for this directory.`,
|
|
2989
|
-
"Reuse this workspaceId for subsequent tool calls.",
|
|
2990
|
-
`Project bootstrap context components included in this response: ${bootstrapContextComponents.join(", ")}. Components not listed are unchanged and are not repeated.`,
|
|
2991
|
-
workspaceContextInstruction,
|
|
2992
|
-
workspaceManagementInstruction,
|
|
2993
|
-
].join("\n\n")
|
|
2994
|
-
: [
|
|
2995
|
-
`Workspace already open as ${workspace.id}.`,
|
|
2996
|
-
"Reuse this workspaceId for subsequent tool calls. This is the same directory previously opened in this conversation.",
|
|
2997
|
-
"Continue following the project instructions, nested instruction files, skills, capability guides, agent profiles, and diagnostics previously provided for this workspace. They remain active and are not repeated here.",
|
|
2998
|
-
workspaceContextInstruction,
|
|
2999
|
-
workspaceManagementInstruction,
|
|
3000
|
-
].join("\n\n")
|
|
3001
|
-
: workspace.mode === "worktree"
|
|
3002
|
-
? "Use this workspaceId for subsequent tool calls. Follow the project instructions, nested instruction files, skills, agent profiles, and diagnostics returned for this isolated worktree."
|
|
3003
|
-
: cardInstruction;
|
|
3004
|
-
const resultContent = [
|
|
3005
|
-
{
|
|
3006
|
-
type: "text",
|
|
3007
|
-
text: [
|
|
3008
|
-
workspaceReused
|
|
3009
|
-
? `Workspace already open as ${workspace.id}.`
|
|
3010
|
-
: workspace.mode === "worktree"
|
|
3011
|
-
? `Opened isolated worktree workspace ${workspace.id}.`
|
|
3012
|
-
: `Opened workspace ${workspace.id}.`,
|
|
3013
|
-
`Root: ${workspace.root}`,
|
|
3014
|
-
`Mode: ${workspace.mode}`,
|
|
3015
|
-
loadedAgentsFiles.length > 0
|
|
3016
|
-
? `Loaded project instructions: ${loadedAgentsFiles.map((file) => file.path).join(", ")}`
|
|
3017
|
-
: undefined,
|
|
3018
|
-
availableAgentsFileOutputs.length > 0
|
|
3019
|
-
? `Available nested instructions: ${availableAgentsFileOutputs.map((file) => file.path).join(", ")}`
|
|
3020
|
-
: undefined,
|
|
3021
|
-
visibleSkills.length > 0
|
|
3022
|
-
? `Available skills: ${visibleSkills.map((skill) => skill.name).join(", ")}`
|
|
3023
|
-
: undefined,
|
|
3024
|
-
capabilityCatalog.length > 0
|
|
3025
|
-
? `Optional capabilities: ${capabilityCatalog.map((entry) => entry.name).join(", ")}`
|
|
3026
|
-
: undefined,
|
|
3027
|
-
visibleCapabilityGuides.length > 0
|
|
3028
|
-
? `Capability guides: ${visibleCapabilityGuides.map((guide) => guide.name).join(", ")}`
|
|
3029
|
-
: undefined,
|
|
3030
|
-
visibleAgentProviders.some((provider) => provider.available)
|
|
3031
|
-
? `Available subagent providers: ${visibleAgentProviders.filter((provider) => provider.available).map((provider) => provider.name).join(", ")}`
|
|
3032
|
-
: undefined,
|
|
3033
|
-
visibleAgentProviders.some((provider) => !provider.available)
|
|
3034
|
-
? `Unavailable subagent providers: ${visibleAgentProviders.filter((provider) => !provider.available).map(formatUnavailableSubagentProvider).join(", ")}`
|
|
3035
|
-
: undefined,
|
|
3036
|
-
visibleAgents.length > 0
|
|
3037
|
-
? `Available subagent profiles: ${visibleAgents.map(formatAvailableSubagentProfile).join(", ")}`
|
|
3038
|
-
: undefined,
|
|
3039
|
-
knownWorktrees.length > 0
|
|
3040
|
-
? `Known worktrees: ${knownWorktrees.map((worktree) => `${worktree.path} [${worktree.workspaceId}]${worktree.branch ? ` branch=${worktree.branch}` : ""}${worktree.targetBranch ? ` target=${worktree.targetBranch}` : ""}${worktree.current ? " (current)" : ""}`).join(", ")}`
|
|
3041
|
-
: undefined,
|
|
3042
|
-
staleWorkspaces.length > 0
|
|
3043
|
-
? `This Workspace has been idle for more than 2 days: ${staleWorkspaces.map((stale) => `${stale.workspaceId} last-used=${stale.lastUsedAt}`).join(", ")}. It remains available to resume or explicitly close; do not clean it up automatically.`
|
|
3044
|
-
: undefined,
|
|
3045
|
-
`ForgeRelay ${capabilityFingerprint.version} capabilities: ${capabilityFingerprint.capabilities.join(", ")}`,
|
|
3046
|
-
instruction,
|
|
3047
|
-
].filter(Boolean).join("\n"),
|
|
3048
|
-
},
|
|
3049
|
-
];
|
|
3050
|
-
logToolCall(config, {
|
|
3051
|
-
tool: "open_workspace",
|
|
3052
|
-
...workspaceLogContext(workspace, sessionId),
|
|
3053
|
-
path: workspace.root,
|
|
3054
|
-
success: true,
|
|
3055
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
3056
|
-
});
|
|
3057
|
-
const workspaceCard = {
|
|
3058
|
-
workspaceId: workspace.id,
|
|
3059
|
-
kind: "workspace",
|
|
3060
|
-
root: workspace.root,
|
|
3061
|
-
path: workspace.root,
|
|
3062
|
-
mode: workspace.mode,
|
|
3063
|
-
workspaceReused,
|
|
3064
|
-
includeBootstrapContext,
|
|
3065
|
-
sourceRoot: workspace.sourceRoot,
|
|
3066
|
-
worktree: workspace.worktree,
|
|
3067
|
-
worktrees: knownWorktrees,
|
|
3068
|
-
staleWorkspaces,
|
|
3069
|
-
capabilityFingerprint,
|
|
3070
|
-
contextFingerprint,
|
|
3071
|
-
capabilityCatalog,
|
|
3072
|
-
agentsFiles: cardAgentsFiles,
|
|
3073
|
-
availableAgentsFiles: cardAvailableAgentsFiles,
|
|
3074
|
-
skills: cardSkills,
|
|
3075
|
-
agentProviders: cardAgentProviders,
|
|
3076
|
-
agents: cardAgents,
|
|
3077
|
-
instruction: cardInstruction,
|
|
3078
|
-
summary: {
|
|
3079
|
-
mode: workspace.mode,
|
|
3080
|
-
agentsFiles: cardAgentsFiles.length,
|
|
3081
|
-
availableAgentsFiles: cardAvailableAgentsFiles.length,
|
|
3082
|
-
skills: cardSkills.length,
|
|
3083
|
-
capabilities: capabilityCatalog.length,
|
|
3084
|
-
agentProviders: cardAgentProviders.length,
|
|
3085
|
-
agents: cardAgents.length,
|
|
3086
|
-
},
|
|
3087
|
-
};
|
|
3088
|
-
const response = hooks.decorateResult(workspace.id, attachHookReports({
|
|
3089
|
-
content: resultContent,
|
|
3090
|
-
_meta: {
|
|
3091
|
-
tool: "open_workspace",
|
|
3092
|
-
card: includeBootstrapContext
|
|
3093
|
-
? workspaceCard
|
|
3094
|
-
: compactWorkspacePresentation(workspaceCard),
|
|
3095
|
-
},
|
|
3096
|
-
structuredContent: {
|
|
3097
|
-
action: "open",
|
|
3098
|
-
workspaceId: workspace.id,
|
|
3099
|
-
kind: "workspace",
|
|
3100
|
-
root: workspace.root,
|
|
3101
|
-
mode: workspace.mode,
|
|
3102
|
-
sourceRoot: workspace.sourceRoot,
|
|
3103
|
-
worktree: workspace.worktree,
|
|
3104
|
-
worktrees: knownWorktrees,
|
|
3105
|
-
staleWorkspaces,
|
|
3106
|
-
capabilityFingerprint,
|
|
3107
|
-
contextFingerprint,
|
|
3108
|
-
capabilityCatalog,
|
|
3109
|
-
...(bootstrapComponents.has("capabilityGuides")
|
|
3110
|
-
? { capabilityGuides: visibleCapabilityGuides }
|
|
3111
|
-
: {}),
|
|
3112
|
-
...(bootstrapComponents.has("agentsFiles") ? { agentsFiles: loadedAgentsFiles } : {}),
|
|
3113
|
-
...(bootstrapComponents.has("availableAgentsFiles")
|
|
3114
|
-
? { availableAgentsFiles: availableAgentsFileOutputs }
|
|
3115
|
-
: {}),
|
|
3116
|
-
...(bootstrapComponents.has("skills") ? { skills: visibleSkills } : {}),
|
|
3117
|
-
...(bootstrapComponents.has("agentProfiles")
|
|
3118
|
-
? { agentProviders: visibleAgentProviders, agents: visibleAgents }
|
|
3119
|
-
: {}),
|
|
3120
|
-
...(bootstrapComponents.has("skillDiagnostics")
|
|
3121
|
-
? { skillDiagnostics: visibleSkillDiagnostics }
|
|
3122
|
-
: {}),
|
|
3123
|
-
instruction,
|
|
3124
|
-
},
|
|
3125
|
-
}, hookReports));
|
|
3126
|
-
rememberWorkspacePanelState(workspace.id, response);
|
|
3127
|
-
return response;
|
|
3128
|
-
});
|
|
3129
|
-
registerActivityQueryTools(server, activityQueries, connectionScopeId, toolWidgetDescriptorMeta(config, "activity")._meta, config.activityPanelExpanded, config.logging, workspacePanelState, {
|
|
3130
|
-
panel: async (workspaceId, conversationScopeId) => {
|
|
3131
|
-
if (compositeWorkspaces.has(workspaceId)) {
|
|
3132
|
-
return compositeActivity.beginPanel(workspaceId, conversationScopeId);
|
|
3133
|
-
}
|
|
3134
|
-
return remoteWorkspaces.has(workspaceId)
|
|
3135
|
-
? remoteWorkspaces.activityPanel(workspaceId, conversationScopeId)
|
|
3136
|
-
: undefined;
|
|
3137
|
-
},
|
|
3138
|
-
snapshot: async (input, conversationScopeId) => {
|
|
3139
|
-
const compositeTurnId = input.turnId ?? (input.workspaceId && compositeWorkspaces.has(input.workspaceId)
|
|
3140
|
-
? compositeActivity.currentTurnId(conversationScopeId, input.workspaceId)
|
|
3141
|
-
: undefined);
|
|
3142
|
-
if (compositeTurnId) {
|
|
3143
|
-
const composite = await compositeActivity.snapshot(compositeTurnId, input.knownRevision);
|
|
3144
|
-
if (composite)
|
|
3145
|
-
return composite;
|
|
3146
|
-
}
|
|
3147
|
-
return remoteWorkspaces.activitySnapshot(input, conversationScopeId);
|
|
3148
|
-
},
|
|
3149
|
-
index: async (turnId, knownRevision, conversationScopeId) => {
|
|
3150
|
-
const composite = await compositeActivity.index(turnId, knownRevision);
|
|
3151
|
-
return composite ?? remoteWorkspaces.activityIndex(turnId, knownRevision, conversationScopeId);
|
|
404
|
+
index: async (turnId, knownRevision, conversationScopeId) => {
|
|
405
|
+
const composite = await compositeActivity.index(turnId, knownRevision);
|
|
406
|
+
return composite ?? remoteWorkspaces.activityIndex(turnId, knownRevision, conversationScopeId);
|
|
3152
407
|
},
|
|
3153
408
|
detail: async (turnId, activityId, conversationScopeId) => {
|
|
3154
409
|
const composite = await compositeActivity.detail(turnId, activityId);
|
|
@@ -3159,1130 +414,53 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3159
414
|
return composite ?? remoteWorkspaces.activityOutput(turnId, outputId, conversationScopeId, cursor);
|
|
3160
415
|
},
|
|
3161
416
|
});
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name whose capability surface is used."),
|
|
3168
|
-
name: z
|
|
3169
|
-
.string()
|
|
3170
|
-
.regex(/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/)
|
|
3171
|
-
.describe("Stable dotted capability name advertised by open_workspace."),
|
|
3172
|
-
action: z.enum(["describe", "run"]),
|
|
3173
|
-
arguments: z
|
|
3174
|
-
.record(z.string(), z.unknown())
|
|
3175
|
-
.optional()
|
|
3176
|
-
.describe("Capability-specific JSON arguments. Omit for describe and for capabilities with no arguments."),
|
|
3177
|
-
file: z
|
|
3178
|
-
.unknown()
|
|
3179
|
-
.optional()
|
|
3180
|
-
.describe("Host-native file value. Only capabilities whose describe result advertises native-file transport may consume it."),
|
|
3181
|
-
},
|
|
3182
|
-
outputSchema: {
|
|
3183
|
-
name: z.string(),
|
|
3184
|
-
action: z.enum(["describe", "run"]),
|
|
3185
|
-
member: z.string().optional(),
|
|
3186
|
-
capability: z.unknown().optional(),
|
|
3187
|
-
result: z.unknown().optional(),
|
|
3188
|
-
error: capabilityErrorOutputSchema.optional(),
|
|
3189
|
-
},
|
|
3190
|
-
_meta: {
|
|
3191
|
-
...toolWidgetDescriptorMeta(config, "capability")._meta,
|
|
3192
|
-
"openai/fileParams": ["file"],
|
|
3193
|
-
},
|
|
3194
|
-
annotations: {
|
|
3195
|
-
readOnlyHint: false,
|
|
3196
|
-
destructiveHint: false,
|
|
3197
|
-
idempotentHint: false,
|
|
3198
|
-
openWorldHint: true,
|
|
3199
|
-
},
|
|
3200
|
-
}, async ({ workspaceId, member, name, action, arguments: capabilityArguments, file }, extra) => {
|
|
3201
|
-
if (name === "workspace.tasks" && compositeWorkspaces.has(workspaceId)) {
|
|
3202
|
-
if (member !== undefined) {
|
|
3203
|
-
throw new Error(`workspace.tasks belongs to Composite Workspace ${workspaceId} itself and does not accept member.`);
|
|
3204
|
-
}
|
|
3205
|
-
if (!compositeWorkspaces.isActive(workspaceId)) {
|
|
3206
|
-
throw new Error(`Composite Workspace ${workspaceId} is closed. Reopen it with open_workspace before use.`);
|
|
3207
|
-
}
|
|
3208
|
-
const startedAt = performance.now();
|
|
3209
|
-
const context = compositeCapabilityContext(workspaceId, compositeTaskGuides);
|
|
3210
|
-
try {
|
|
3211
|
-
if (action === "run") {
|
|
3212
|
-
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, context, {
|
|
3213
|
-
nativeFile: file,
|
|
3214
|
-
signal: extra.signal,
|
|
3215
|
-
requestMeta: extra._meta,
|
|
3216
|
-
sessionId: extra.sessionId,
|
|
3217
|
-
});
|
|
3218
|
-
const result = {
|
|
3219
|
-
content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
|
|
3220
|
-
structuredContent: { name, action, result: execution.value },
|
|
3221
|
-
};
|
|
3222
|
-
logToolCall(config, {
|
|
3223
|
-
tool: toolNames.capability,
|
|
3224
|
-
capability: name,
|
|
3225
|
-
action,
|
|
3226
|
-
success: true,
|
|
3227
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
3228
|
-
});
|
|
3229
|
-
return result;
|
|
3230
|
-
}
|
|
3231
|
-
const capability = capabilityRegistry.describe(name, context);
|
|
3232
|
-
const result = {
|
|
3233
|
-
content: [textBlock([
|
|
3234
|
-
`${capability.name}: ${capability.description}`,
|
|
3235
|
-
`Available: ${capability.available}`,
|
|
3236
|
-
`Guide: ${capability.guide.path}`,
|
|
3237
|
-
capability.guide.readBeforeFirstUse
|
|
3238
|
-
? "Read the guide before first use when this contract is unfamiliar."
|
|
3239
|
-
: undefined,
|
|
3240
|
-
].filter(Boolean).join("\n"))],
|
|
3241
|
-
structuredContent: { name, action, capability },
|
|
3242
|
-
};
|
|
3243
|
-
logToolCall(config, {
|
|
3244
|
-
tool: toolNames.capability,
|
|
3245
|
-
capability: name,
|
|
3246
|
-
action,
|
|
3247
|
-
success: true,
|
|
3248
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
3249
|
-
});
|
|
3250
|
-
return result;
|
|
3251
|
-
}
|
|
3252
|
-
catch (error) {
|
|
3253
|
-
if (extra.signal.aborted)
|
|
3254
|
-
throw error;
|
|
3255
|
-
const capabilityError = error instanceof CapabilityError
|
|
3256
|
-
? error
|
|
3257
|
-
: new CapabilityError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
3258
|
-
return {
|
|
3259
|
-
content: [textBlock(`${capabilityError.code}: ${capabilityError.message}`)],
|
|
3260
|
-
structuredContent: {
|
|
3261
|
-
name,
|
|
3262
|
-
action,
|
|
3263
|
-
error: { code: capabilityError.code, message: capabilityError.message },
|
|
3264
|
-
},
|
|
3265
|
-
isError: true,
|
|
3266
|
-
};
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
3270
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3271
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3272
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3273
|
-
const response = await remoteWorkspaces.capability(executionWorkspaceId, {
|
|
3274
|
-
name,
|
|
3275
|
-
action,
|
|
3276
|
-
...(capabilityArguments !== undefined ? { arguments: capabilityArguments } : {}),
|
|
3277
|
-
...(file !== undefined ? { file } : {}),
|
|
3278
|
-
}, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
3279
|
-
return action === "run" && name !== "workspace.tasks"
|
|
3280
|
-
? presentSemanticWorkResult(response, target)
|
|
3281
|
-
: presentExecutionResult(response, target);
|
|
3282
|
-
}
|
|
3283
|
-
if (action === "run" && name === "batch.execute") {
|
|
3284
|
-
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3285
|
-
const startedAt = performance.now();
|
|
3286
|
-
try {
|
|
3287
|
-
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, capabilityContextFor(workspace), {
|
|
3288
|
-
nativeFile: file,
|
|
3289
|
-
signal: extra.signal,
|
|
3290
|
-
requestMeta: extra._meta,
|
|
3291
|
-
sessionId: extra.sessionId,
|
|
3292
|
-
});
|
|
3293
|
-
const result = {
|
|
3294
|
-
content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
|
|
3295
|
-
structuredContent: { name, action, result: execution.value },
|
|
3296
|
-
};
|
|
3297
|
-
logToolCall(config, {
|
|
3298
|
-
tool: toolNames.capability,
|
|
3299
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
3300
|
-
capability: name,
|
|
3301
|
-
action,
|
|
3302
|
-
success: true,
|
|
3303
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
3304
|
-
});
|
|
3305
|
-
return presentSemanticWorkResult(result, target);
|
|
3306
|
-
}
|
|
3307
|
-
catch (error) {
|
|
3308
|
-
if (extra.signal.aborted)
|
|
3309
|
-
throw error;
|
|
3310
|
-
const capabilityError = error instanceof CapabilityError
|
|
3311
|
-
? error
|
|
3312
|
-
: new CapabilityError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
3313
|
-
const result = {
|
|
3314
|
-
content: [textBlock(`${capabilityError.code}: ${capabilityError.message}`)],
|
|
3315
|
-
structuredContent: {
|
|
3316
|
-
name,
|
|
3317
|
-
action,
|
|
3318
|
-
error: { code: capabilityError.code, message: capabilityError.message },
|
|
3319
|
-
},
|
|
3320
|
-
isError: true,
|
|
3321
|
-
};
|
|
3322
|
-
logFailedToolResponse(config, {
|
|
3323
|
-
tool: toolNames.capability,
|
|
3324
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
3325
|
-
capability: name,
|
|
3326
|
-
action,
|
|
3327
|
-
}, result.content, startedAt);
|
|
3328
|
-
return presentExecutionResult(result, target);
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
if (action === "run") {
|
|
3332
|
-
const response = await coreOperations.capabilityRun({ workspaceId: executionWorkspaceId, name, arguments: capabilityArguments, file }, executionContext);
|
|
3333
|
-
return name === "workspace.tasks"
|
|
3334
|
-
? presentExecutionResult(response, target)
|
|
3335
|
-
: presentSemanticWorkResult(response, target);
|
|
3336
|
-
}
|
|
3337
|
-
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3338
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, name, action, arguments: capabilityArguments, file }, executionContext), {
|
|
3339
|
-
signal: extra.signal,
|
|
3340
|
-
tool: toolNames.capability,
|
|
3341
|
-
invocation: workspaceHookInvocation(workspace),
|
|
3342
|
-
payload: { name, action },
|
|
3343
|
-
isFailure: toolResultIsError,
|
|
3344
|
-
operation: async () => {
|
|
3345
|
-
const startedAt = performance.now();
|
|
3346
|
-
try {
|
|
3347
|
-
const capability = capabilityRegistry.describe(name, capabilityContextFor(workspace));
|
|
3348
|
-
const result = {
|
|
3349
|
-
content: [textBlock([
|
|
3350
|
-
`${capability.name}: ${capability.description}`,
|
|
3351
|
-
`Available: ${capability.available}`,
|
|
3352
|
-
`Guide: ${capability.guide.path}`,
|
|
3353
|
-
capability.guide.readBeforeFirstUse
|
|
3354
|
-
? "Read the guide before first use when this contract is unfamiliar."
|
|
3355
|
-
: undefined,
|
|
3356
|
-
].filter(Boolean).join("\n"))],
|
|
3357
|
-
structuredContent: { name, action, capability },
|
|
3358
|
-
};
|
|
3359
|
-
logToolCall(config, {
|
|
3360
|
-
tool: toolNames.capability,
|
|
3361
|
-
...workspaceLogContext(workspace),
|
|
3362
|
-
capability: name,
|
|
3363
|
-
action,
|
|
3364
|
-
success: true,
|
|
3365
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
3366
|
-
});
|
|
3367
|
-
return result;
|
|
3368
|
-
}
|
|
3369
|
-
catch (error) {
|
|
3370
|
-
const capabilityError = error instanceof CapabilityError
|
|
3371
|
-
? error
|
|
3372
|
-
: new CapabilityError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
3373
|
-
const result = {
|
|
3374
|
-
content: [textBlock(`${capabilityError.code}: ${capabilityError.message}`)],
|
|
3375
|
-
structuredContent: {
|
|
3376
|
-
name,
|
|
3377
|
-
action,
|
|
3378
|
-
error: { code: capabilityError.code, message: capabilityError.message },
|
|
3379
|
-
},
|
|
3380
|
-
isError: true,
|
|
3381
|
-
};
|
|
3382
|
-
logFailedToolResponse(config, {
|
|
3383
|
-
tool: toolNames.capability,
|
|
3384
|
-
...workspaceLogContext(workspace),
|
|
3385
|
-
capability: name,
|
|
3386
|
-
action,
|
|
3387
|
-
}, result.content, startedAt);
|
|
3388
|
-
return result;
|
|
3389
|
-
}
|
|
3390
|
-
},
|
|
3391
|
-
}, activityRelationFor(executionContext)).then((result) => presentExecutionResult(result, target));
|
|
417
|
+
registerWorkspaceAuxiliaryTools({
|
|
418
|
+
server, config, workspaces, remoteWorkspaces, compositeWorkspaces, compositeTaskGuides, capabilityRegistry,
|
|
419
|
+
coreOperations, activityLifecycle, hooks, workspaceTasks, taskReminders, activityQueries, compositeActivity,
|
|
420
|
+
workspacePanelStates, processSessions, reviewCheckpoints, codeIntelligence, resolveExecutionTarget,
|
|
421
|
+
prepareExecutionContext, hostScopeIdFor, presentExecutionResult, presentSemanticWorkResult,
|
|
3392
422
|
});
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
workspaceId: z.string(),
|
|
3410
|
-
action: z.enum(["close", "delete"]).optional(),
|
|
3411
|
-
kind: z.enum(["workspace", "composite"]).optional(),
|
|
3412
|
-
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
3413
|
-
name: z.string().optional(),
|
|
3414
|
-
members: z.array(z.object({
|
|
3415
|
-
name: z.string(),
|
|
3416
|
-
purpose: z.string(),
|
|
3417
|
-
workspaceId: z.string(),
|
|
3418
|
-
})).optional(),
|
|
3419
|
-
status: z.enum(["active", "closed"]).optional(),
|
|
3420
|
-
dissolved: z.boolean().optional(),
|
|
3421
|
-
sourceRoot: z.string().optional(),
|
|
3422
|
-
branch: z.string().optional(),
|
|
3423
|
-
targetBranch: z.string().optional(),
|
|
3424
|
-
commitSha: z.string().optional(),
|
|
3425
|
-
mergedSha: z.string().optional(),
|
|
3426
|
-
committed: z.boolean().optional(),
|
|
3427
|
-
cleanupWarning: z.string().optional(),
|
|
3428
|
-
}),
|
|
3429
|
-
_meta: {},
|
|
3430
|
-
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
3431
|
-
}, async ({ workspaceId, action = "close", commitMessage }, extra) => {
|
|
3432
|
-
if (compositeWorkspaces.has(workspaceId)) {
|
|
3433
|
-
if (commitMessage !== undefined) {
|
|
3434
|
-
throw new Error("close_workspace commitMessage is not valid for a Composite Workspace.");
|
|
3435
|
-
}
|
|
3436
|
-
const composite = action === "delete"
|
|
3437
|
-
? compositeWorkspaces.dissolve(workspaceId)
|
|
3438
|
-
: compositeWorkspaces.close(workspaceId);
|
|
3439
|
-
if (action === "delete") {
|
|
3440
|
-
workspaceTasks.deleteWorkspace(workspaceId);
|
|
3441
|
-
taskReminders.forget(workspaceId);
|
|
3442
|
-
}
|
|
3443
|
-
compositeActivity.forgetComposite(workspaceId);
|
|
3444
|
-
workspacePanelStates.delete(workspaceId);
|
|
3445
|
-
const result = [
|
|
3446
|
-
action === "delete"
|
|
3447
|
-
? `Deleted Composite Workspace ${composite.name} (${workspaceId}); its Composite relationship and ForgeRelay-owned Composite state were dissolved.`
|
|
3448
|
-
: `Closed Composite Workspace ${composite.name} (${workspaceId}); its identity and member topology were preserved for later reopen.`,
|
|
3449
|
-
composite.members.length > 0
|
|
3450
|
-
? `Preserved member Workspaces: ${composite.members.map((member) => `${member.name} [${member.workspaceId}]`).join(", ")}.`
|
|
3451
|
-
: "The Composite Workspace had no members.",
|
|
3452
|
-
"Member Workspace handles, managed worktrees, processes, files, and Workspace Relay routes were not closed, finalized, deleted, or otherwise mutated.",
|
|
3453
|
-
].join("\n");
|
|
3454
|
-
return {
|
|
3455
|
-
content: [textBlock(result)],
|
|
3456
|
-
_meta: {
|
|
3457
|
-
tool: toolNames.closeWorkspace,
|
|
3458
|
-
card: {
|
|
3459
|
-
workspaceId,
|
|
3460
|
-
action,
|
|
3461
|
-
kind: "composite",
|
|
3462
|
-
name: composite.name,
|
|
3463
|
-
members: composite.members,
|
|
3464
|
-
...(action === "close" ? { status: "closed" } : {}),
|
|
3465
|
-
dissolved: action === "delete",
|
|
3466
|
-
payload: { content: [textBlock(result)] },
|
|
3467
|
-
},
|
|
3468
|
-
},
|
|
3469
|
-
structuredContent: {
|
|
3470
|
-
result,
|
|
3471
|
-
workspaceId,
|
|
3472
|
-
action,
|
|
3473
|
-
kind: "composite",
|
|
3474
|
-
name: composite.name,
|
|
3475
|
-
members: composite.members,
|
|
3476
|
-
...(action === "close" ? { status: "closed" } : {}),
|
|
3477
|
-
dissolved: action === "delete",
|
|
3478
|
-
},
|
|
3479
|
-
};
|
|
3480
|
-
}
|
|
3481
|
-
if (remoteWorkspaces.has(workspaceId)) {
|
|
3482
|
-
const response = await remoteWorkspaces.closeWorkspace(workspaceId, { action, ...(commitMessage !== undefined ? { commitMessage } : {}) }, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
3483
|
-
workspacePanelStates.delete(workspaceId);
|
|
3484
|
-
return response;
|
|
3485
|
-
}
|
|
3486
|
-
const session = workspaces.getWorkspaceSession(workspaceId);
|
|
3487
|
-
if (action === "delete" && session.mode === "checkout") {
|
|
3488
|
-
if (commitMessage !== undefined) {
|
|
3489
|
-
throw new Error("close_workspace commitMessage is not valid with action=delete for a checkout Workspace.");
|
|
3490
|
-
}
|
|
3491
|
-
if (processSessions.activeWorkspaceIds().has(session.id)) {
|
|
3492
|
-
throw new Error(`Workspace ${session.id} still owns a running process. Poll, interrupt, or wait for it before deleting this Workspace.`);
|
|
3493
|
-
}
|
|
3494
|
-
const response = await runToolWithHooks(hooks, {
|
|
3495
|
-
signal: extra.signal,
|
|
3496
|
-
tool: toolNames.closeWorkspace,
|
|
3497
|
-
invocation: {
|
|
3498
|
-
workspaceId: session.id,
|
|
3499
|
-
workspaceRoot: session.root,
|
|
3500
|
-
workspaceMode: session.mode,
|
|
3501
|
-
sourceRoot: session.sourceRoot,
|
|
3502
|
-
},
|
|
3503
|
-
payload: { workspaceId: session.id, action: "delete", mode: session.mode },
|
|
3504
|
-
operation: async () => {
|
|
3505
|
-
workspaces.deleteWorkspace(session.id);
|
|
3506
|
-
workspaceTasks.deleteWorkspace(session.id);
|
|
3507
|
-
taskReminders.forget(session.id);
|
|
3508
|
-
await reviewCheckpoints.releaseWorkspace(session.id);
|
|
3509
|
-
const result = `Deleted ForgeRelay Workspace ${session.id}. Physical project files were not removed.`;
|
|
3510
|
-
return {
|
|
3511
|
-
content: [textBlock(result)],
|
|
3512
|
-
_meta: {
|
|
3513
|
-
tool: toolNames.closeWorkspace,
|
|
3514
|
-
card: {
|
|
3515
|
-
workspaceId: session.id,
|
|
3516
|
-
action: "delete",
|
|
3517
|
-
mode: "checkout",
|
|
3518
|
-
payload: { content: [textBlock(result)] },
|
|
3519
|
-
},
|
|
3520
|
-
},
|
|
3521
|
-
structuredContent: {
|
|
3522
|
-
result,
|
|
3523
|
-
workspaceId: session.id,
|
|
3524
|
-
action: "delete",
|
|
3525
|
-
mode: "checkout",
|
|
3526
|
-
},
|
|
3527
|
-
};
|
|
3528
|
-
},
|
|
3529
|
-
});
|
|
3530
|
-
workspacePanelStates.delete(session.id);
|
|
3531
|
-
return response;
|
|
3532
|
-
}
|
|
3533
|
-
if (action === "delete" && session.mode === "worktree" && session.status === "closed") {
|
|
3534
|
-
if (commitMessage !== undefined) {
|
|
3535
|
-
throw new Error("close_workspace commitMessage is not needed when deleting an already-closed managed-worktree Workspace.");
|
|
3536
|
-
}
|
|
3537
|
-
const hookRoot = session.sourceRoot ?? session.root;
|
|
3538
|
-
const response = await runToolWithHooks(hooks, {
|
|
3539
|
-
signal: extra.signal,
|
|
3540
|
-
tool: toolNames.closeWorkspace,
|
|
3541
|
-
invocation: {
|
|
3542
|
-
workspaceId: session.id,
|
|
3543
|
-
workspaceRoot: hookRoot,
|
|
3544
|
-
workspaceMode: session.mode,
|
|
3545
|
-
sourceRoot: session.sourceRoot,
|
|
3546
|
-
},
|
|
3547
|
-
payload: { workspaceId: session.id, action: "delete", mode: session.mode },
|
|
3548
|
-
operation: async () => {
|
|
3549
|
-
workspaces.deleteWorkspace(session.id);
|
|
3550
|
-
workspaceTasks.deleteWorkspace(session.id);
|
|
3551
|
-
taskReminders.forget(session.id);
|
|
3552
|
-
await reviewCheckpoints.releaseWorkspace(session.id);
|
|
3553
|
-
const result = `Deleted closed managed-worktree Workspace ${session.id}. Its already-removed worktree backing was not recreated.`;
|
|
3554
|
-
return {
|
|
3555
|
-
content: [textBlock(result)],
|
|
3556
|
-
_meta: {
|
|
3557
|
-
tool: toolNames.closeWorkspace,
|
|
3558
|
-
card: {
|
|
3559
|
-
workspaceId: session.id,
|
|
3560
|
-
action: "delete",
|
|
3561
|
-
mode: "worktree",
|
|
3562
|
-
sourceRoot: session.sourceRoot,
|
|
3563
|
-
targetBranch: session.targetBranch,
|
|
3564
|
-
payload: { content: [textBlock(result)] },
|
|
3565
|
-
},
|
|
3566
|
-
},
|
|
3567
|
-
structuredContent: {
|
|
3568
|
-
result,
|
|
3569
|
-
workspaceId: session.id,
|
|
3570
|
-
action: "delete",
|
|
3571
|
-
mode: "worktree",
|
|
3572
|
-
sourceRoot: session.sourceRoot,
|
|
3573
|
-
targetBranch: session.targetBranch,
|
|
3574
|
-
},
|
|
3575
|
-
};
|
|
3576
|
-
},
|
|
3577
|
-
});
|
|
3578
|
-
workspacePanelStates.delete(session.id);
|
|
3579
|
-
return response;
|
|
3580
|
-
}
|
|
3581
|
-
const workspace = workspaces.getWorkspace(session.id);
|
|
3582
|
-
const response = await runToolWithHooks(hooks, {
|
|
3583
|
-
signal: extra.signal,
|
|
3584
|
-
tool: toolNames.closeWorkspace,
|
|
3585
|
-
invocation: workspaceHookInvocation(workspace),
|
|
3586
|
-
payload: { workspaceId: workspace.id, action, commitMessage, mode: workspace.mode },
|
|
3587
|
-
afterCwd: (response) => "sourceRoot" in response.structuredContent &&
|
|
3588
|
-
typeof response.structuredContent.sourceRoot === "string"
|
|
3589
|
-
? response.structuredContent.sourceRoot
|
|
3590
|
-
: undefined,
|
|
3591
|
-
operation: async () => {
|
|
3592
|
-
if (workspace.mode === "worktree") {
|
|
3593
|
-
if (!commitMessage) {
|
|
3594
|
-
throw new Error(`Managed-worktree-backed Workspace ${workspace.id} requires commitMessage when ${action === "delete" ? "deleting active work" : "closing"}.`);
|
|
3595
|
-
}
|
|
3596
|
-
const physicalWorkspaceIds = workspaces.workspaceIdsForPhysicalWorkspace(workspace);
|
|
3597
|
-
const busyWorkspaceIds = physicalWorkspaceIds
|
|
3598
|
-
.filter((id) => processSessions.activeWorkspaceIds().has(id));
|
|
3599
|
-
if (busyWorkspaceIds.length > 0) {
|
|
3600
|
-
throw new Error(`Cannot close this worktree-backed Workspace while Workspace processes are still running: ${busyWorkspaceIds.join(", ")}.`);
|
|
3601
|
-
}
|
|
3602
|
-
const startedAt = performance.now();
|
|
3603
|
-
const retirement = await codeIntelligence.retireWorkspaceRoot(workspace.root);
|
|
3604
|
-
let closed;
|
|
3605
|
-
try {
|
|
3606
|
-
closed = await workspaces.closeWorktree(workspace.id, commitMessage);
|
|
3607
|
-
}
|
|
3608
|
-
finally {
|
|
3609
|
-
codeIntelligence.restoreWorkspaceRoot(retirement.root);
|
|
3610
|
-
}
|
|
3611
|
-
await Promise.all(physicalWorkspaceIds.map((id) => reviewCheckpoints.releaseWorkspace(id)));
|
|
3612
|
-
if (action === "delete") {
|
|
3613
|
-
workspaces.deleteWorkspace(workspace.id);
|
|
3614
|
-
workspaceTasks.deleteWorkspace(workspace.id);
|
|
3615
|
-
taskReminders.forget(workspace.id);
|
|
3616
|
-
}
|
|
3617
|
-
const result = [
|
|
3618
|
-
action === "delete"
|
|
3619
|
-
? `Safely finalized and deleted managed-worktree Workspace ${workspace.id}.`
|
|
3620
|
-
: `Closed managed-worktree-backed Workspace ${workspace.id}; its identity was preserved for later reopen.`,
|
|
3621
|
-
`Merged ${closed.branch} into ${closed.targetBranch} by fast-forward.`,
|
|
3622
|
-
`Source checkout: ${closed.sourceRoot}`,
|
|
3623
|
-
`Commit: ${closed.commitSha}`,
|
|
3624
|
-
closed.cleanupWarning
|
|
3625
|
-
? `Cleanup warning: ${closed.cleanupWarning}`
|
|
3626
|
-
: "The managed worktree directory and branch were removed.",
|
|
3627
|
-
].join("\n");
|
|
3628
|
-
logToolCall(config, {
|
|
3629
|
-
tool: toolNames.closeWorkspace,
|
|
3630
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
3631
|
-
path: closed.sourceRoot,
|
|
3632
|
-
success: true,
|
|
3633
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
3634
|
-
});
|
|
3635
|
-
return attachHookReports({
|
|
3636
|
-
content: [textBlock(result)],
|
|
3637
|
-
_meta: {
|
|
3638
|
-
tool: toolNames.closeWorkspace,
|
|
3639
|
-
card: {
|
|
3640
|
-
workspaceId: workspace.id,
|
|
3641
|
-
action,
|
|
3642
|
-
mode: "worktree",
|
|
3643
|
-
sourceRoot: closed.sourceRoot,
|
|
3644
|
-
branch: closed.branch,
|
|
3645
|
-
targetBranch: closed.targetBranch,
|
|
3646
|
-
commitSha: closed.commitSha,
|
|
3647
|
-
mergedSha: closed.mergedSha,
|
|
3648
|
-
committed: closed.committed,
|
|
3649
|
-
cleanupWarning: closed.cleanupWarning,
|
|
3650
|
-
payload: { content: [textBlock(result)] },
|
|
3651
|
-
},
|
|
3652
|
-
},
|
|
3653
|
-
structuredContent: {
|
|
3654
|
-
result,
|
|
3655
|
-
workspaceId: workspace.id,
|
|
3656
|
-
action,
|
|
3657
|
-
mode: "worktree",
|
|
3658
|
-
sourceRoot: closed.sourceRoot,
|
|
3659
|
-
branch: closed.branch,
|
|
3660
|
-
targetBranch: closed.targetBranch,
|
|
3661
|
-
commitSha: closed.commitSha,
|
|
3662
|
-
mergedSha: closed.mergedSha,
|
|
3663
|
-
committed: closed.committed,
|
|
3664
|
-
cleanupWarning: closed.cleanupWarning,
|
|
3665
|
-
},
|
|
3666
|
-
}, closed.hookReports);
|
|
3667
|
-
}
|
|
3668
|
-
if (commitMessage !== undefined) {
|
|
3669
|
-
throw new Error("close_workspace commitMessage is only valid for managed-worktree-backed workspaces.");
|
|
3670
|
-
}
|
|
3671
|
-
const checkoutWorkspaceId = workspace.id;
|
|
3672
|
-
if (processSessions.activeWorkspaceIds().has(checkoutWorkspaceId)) {
|
|
3673
|
-
throw new Error(`Workspace ${checkoutWorkspaceId} still owns a running process. Poll, interrupt, or wait for it before closing this workspace.`);
|
|
3674
|
-
}
|
|
3675
|
-
workspaces.closeWorkspace(checkoutWorkspaceId);
|
|
3676
|
-
await reviewCheckpoints.releaseWorkspace(checkoutWorkspaceId);
|
|
3677
|
-
const result = `Closed checkout-backed Workspace ${checkoutWorkspaceId}; its ForgeRelay identity was preserved for later reopen. Physical project files were not removed.`;
|
|
3678
|
-
return {
|
|
3679
|
-
content: [textBlock(result)],
|
|
3680
|
-
_meta: {
|
|
3681
|
-
tool: toolNames.closeWorkspace,
|
|
3682
|
-
card: {
|
|
3683
|
-
workspaceId: checkoutWorkspaceId,
|
|
3684
|
-
action: "close",
|
|
3685
|
-
mode: "checkout",
|
|
3686
|
-
payload: { content: [textBlock(result)] },
|
|
3687
|
-
},
|
|
3688
|
-
},
|
|
3689
|
-
structuredContent: {
|
|
3690
|
-
result,
|
|
3691
|
-
workspaceId: checkoutWorkspaceId,
|
|
3692
|
-
action: "close",
|
|
3693
|
-
mode: "checkout",
|
|
3694
|
-
},
|
|
3695
|
-
};
|
|
3696
|
-
},
|
|
3697
|
-
});
|
|
3698
|
-
workspacePanelStates.delete(workspace.id);
|
|
3699
|
-
return response;
|
|
3700
|
-
});
|
|
3701
|
-
registerAppTool(server, toolNames.read, {
|
|
3702
|
-
title: "Read file",
|
|
3703
|
-
description: toolDescriptions.read,
|
|
3704
|
-
inputSchema: {
|
|
3705
|
-
workspaceId: z
|
|
3706
|
-
.string()
|
|
3707
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
3708
|
-
member: z
|
|
3709
|
-
.string()
|
|
3710
|
-
.optional()
|
|
3711
|
-
.describe("Required for Composite member-scoped file reads. Omit only when reading an advertised Composite-owned capability guide."),
|
|
3712
|
-
path: z
|
|
3713
|
-
.string()
|
|
3714
|
-
.optional()
|
|
3715
|
-
.describe(config.skillsEnabled
|
|
3716
|
-
? "One file path to read, relative to the workspace root or absolute inside the OS temp directory. Load an available skill with skills://<name>; after loading it, read files in that skill with skills://<name>/<relative-path>. Advertised capability-guide paths from open_workspace are also readable. Use exactly one of path or paths."
|
|
3717
|
-
: "One file path to read, relative to the workspace root or absolute inside the OS temp directory. May also be an advertised capability-guide path from open_workspace. Use exactly one of path or paths."),
|
|
3718
|
-
paths: z
|
|
3719
|
-
.array(z.string())
|
|
3720
|
-
.min(1)
|
|
3721
|
-
.max(100)
|
|
3722
|
-
.optional()
|
|
3723
|
-
.describe("Multiple file paths to read in one call. Uses the same offset/limit for every file. Use exactly one of path or paths."),
|
|
3724
|
-
offset: z
|
|
3725
|
-
.number()
|
|
3726
|
-
.int()
|
|
3727
|
-
.positive()
|
|
3728
|
-
.optional()
|
|
3729
|
-
.describe("1-indexed line number to start reading from."),
|
|
3730
|
-
limit: z
|
|
3731
|
-
.number()
|
|
3732
|
-
.int()
|
|
3733
|
-
.positive()
|
|
3734
|
-
.optional()
|
|
3735
|
-
.describe("Maximum number of lines to read."),
|
|
3736
|
-
},
|
|
3737
|
-
outputSchema: resultOutputSchema({
|
|
3738
|
-
agentsFiles: z.array(workspaceAgentsFileOutputSchema).optional(),
|
|
3739
|
-
results: z.array(z.object({
|
|
3740
|
-
path: z.string(),
|
|
3741
|
-
status: z.enum(["done", "error"]),
|
|
3742
|
-
result: z.string(),
|
|
3743
|
-
})).optional(),
|
|
3744
|
-
files: z.number().int().nonnegative().optional(),
|
|
3745
|
-
failed: z.number().int().nonnegative().optional(),
|
|
3746
|
-
}),
|
|
3747
|
-
...toolWidgetDescriptorMeta(config, "read"),
|
|
3748
|
-
annotations: { readOnlyHint: true },
|
|
3749
|
-
}, async ({ workspaceId, member, path, paths, offset, limit }, extra) => {
|
|
3750
|
-
if ((path === undefined) === (paths === undefined)) {
|
|
3751
|
-
throw new Error("read requires exactly one of path or paths.");
|
|
3752
|
-
}
|
|
3753
|
-
if (compositeWorkspaces.has(workspaceId) && member === undefined && path !== undefined) {
|
|
3754
|
-
const guide = compositeTaskGuides.find((candidate) => formatPathForPrompt(candidate.filePath) === path || candidate.filePath === path);
|
|
3755
|
-
if (guide) {
|
|
3756
|
-
if (!compositeWorkspaces.isActive(workspaceId)) {
|
|
3757
|
-
throw new Error(`Composite Workspace ${workspaceId} is closed. Reopen it with open_workspace before use.`);
|
|
3758
|
-
}
|
|
3759
|
-
const startedAt = performance.now();
|
|
3760
|
-
const raw = readFileSync(guide.filePath, "utf8");
|
|
3761
|
-
const start = (offset ?? 1) - 1;
|
|
3762
|
-
const end = limit === undefined ? undefined : start + limit;
|
|
3763
|
-
const result = raw.split("\n").slice(start, end).join("\n");
|
|
3764
|
-
logToolCall(config, {
|
|
3765
|
-
tool: toolNames.read,
|
|
3766
|
-
path: formatPathForPrompt(guide.filePath),
|
|
3767
|
-
success: true,
|
|
3768
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
3769
|
-
});
|
|
3770
|
-
return {
|
|
3771
|
-
content: [textBlock(result)],
|
|
3772
|
-
structuredContent: { result },
|
|
3773
|
-
};
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
3777
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3778
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3779
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3780
|
-
return presentSemanticWorkResult(await remoteWorkspaces.read(executionWorkspaceId, { path, paths, offset, limit }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3781
|
-
}
|
|
3782
|
-
if (path !== undefined) {
|
|
3783
|
-
return presentSemanticWorkResult(await coreOperations.read({ workspaceId: executionWorkspaceId, path, offset, limit }, executionContext), target);
|
|
3784
|
-
}
|
|
3785
|
-
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3786
|
-
let response;
|
|
3787
|
-
await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(extra._meta, extra.sessionId), toolNames.read, activityRequestFor({ workspaceId: executionWorkspaceId, paths, offset, limit }, executionContext), async (parentContext) => {
|
|
3788
|
-
const execution = await executeBulkRead({
|
|
3789
|
-
paths: paths,
|
|
3790
|
-
signal: extra.signal,
|
|
3791
|
-
run: (childPath) => coreOperations.read({ workspaceId: executionWorkspaceId, path: childPath, offset, limit }, {
|
|
3792
|
-
...executionContext,
|
|
3793
|
-
parentActivityId: parentContext.activityId,
|
|
3794
|
-
turnId: parentContext.turnId,
|
|
3795
|
-
}),
|
|
3796
|
-
isError: toolResultIsError,
|
|
3797
|
-
resultText: toolResultText,
|
|
3798
|
-
});
|
|
3799
|
-
const content = execution.children.flatMap((child) => [
|
|
3800
|
-
textBlock(`--- ${child.path} · ${child.status} ---`),
|
|
3801
|
-
...(child.response
|
|
3802
|
-
? toolResultContent(child.response)
|
|
3803
|
-
: [textBlock(child.result)]),
|
|
3804
|
-
]);
|
|
3805
|
-
const seenAgentPaths = new Set();
|
|
3806
|
-
const agentsFiles = execution.children.flatMap((child) => child.response ? toolResultAgentsFiles(child.response) : []).filter((file) => {
|
|
3807
|
-
if (seenAgentPaths.has(file.path))
|
|
3808
|
-
return false;
|
|
3809
|
-
seenAgentPaths.add(file.path);
|
|
3810
|
-
return true;
|
|
3811
|
-
});
|
|
3812
|
-
response = {
|
|
3813
|
-
content,
|
|
3814
|
-
structuredContent: {
|
|
3815
|
-
result: contentText(content),
|
|
3816
|
-
results: execution.children.map(({ path: childPath, status, result }) => ({
|
|
3817
|
-
path: childPath,
|
|
3818
|
-
status,
|
|
3819
|
-
result,
|
|
3820
|
-
})),
|
|
3821
|
-
files: execution.children.length,
|
|
3822
|
-
failed: execution.failed,
|
|
3823
|
-
...(agentsFiles.length > 0 ? { agentsFiles } : {}),
|
|
3824
|
-
},
|
|
3825
|
-
};
|
|
3826
|
-
return {
|
|
3827
|
-
childCount: execution.children.length,
|
|
3828
|
-
succeeded: execution.succeeded,
|
|
3829
|
-
failed: execution.failed,
|
|
3830
|
-
};
|
|
3831
|
-
}, (summary) => summary.failed > 0
|
|
3832
|
-
? { type: "failed", error: `${summary.failed} of ${summary.childCount} child Reads failed.` }
|
|
3833
|
-
: { type: "succeeded" }, activityRelationFor(executionContext));
|
|
3834
|
-
if (!response)
|
|
3835
|
-
throw new Error("Bulk Read completed without a response.");
|
|
3836
|
-
return presentSemanticWorkResult(response, target);
|
|
3837
|
-
});
|
|
3838
|
-
if (config.toolMode !== "codex") {
|
|
3839
|
-
registerAppTool(server, toolNames.write, {
|
|
3840
|
-
title: "Write file",
|
|
3841
|
-
description: toolDescriptions.write,
|
|
3842
|
-
inputSchema: {
|
|
3843
|
-
workspaceId: z
|
|
3844
|
-
.string()
|
|
3845
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
3846
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
3847
|
-
path: z
|
|
3848
|
-
.string()
|
|
3849
|
-
.describe("File path to write, relative to the workspace root or absolute inside the OS temp directory."),
|
|
3850
|
-
content: z.string().describe("Complete new file content."),
|
|
3851
|
-
},
|
|
3852
|
-
outputSchema: resultOutputSchema(),
|
|
3853
|
-
...toolWidgetDescriptorMeta(config, "write"),
|
|
3854
|
-
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
3855
|
-
}, async ({ workspaceId, member, ...input }, extra) => {
|
|
3856
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
3857
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3858
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3859
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3860
|
-
return presentSemanticWorkResult(await remoteWorkspaces.write(executionWorkspaceId, input, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3861
|
-
}
|
|
3862
|
-
return presentSemanticWorkResult(await coreOperations.write({ workspaceId: executionWorkspaceId, ...input }, executionContext), target);
|
|
3863
|
-
});
|
|
3864
|
-
registerAppTool(server, toolNames.edit, {
|
|
3865
|
-
title: "Edit file",
|
|
3866
|
-
description: toolDescriptions.edit,
|
|
3867
|
-
inputSchema: {
|
|
3868
|
-
workspaceId: z
|
|
3869
|
-
.string()
|
|
3870
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
3871
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
3872
|
-
path: z
|
|
3873
|
-
.string()
|
|
3874
|
-
.optional()
|
|
3875
|
-
.describe("One file path to edit. Use exactly one of path or paths."),
|
|
3876
|
-
paths: z
|
|
3877
|
-
.array(z.string())
|
|
3878
|
-
.min(1)
|
|
3879
|
-
.max(100)
|
|
3880
|
-
.optional()
|
|
3881
|
-
.describe("Multiple file paths to edit with the same edits. Use exactly one of path or paths."),
|
|
3882
|
-
edits: z
|
|
3883
|
-
.array(z.object({
|
|
3884
|
-
oldText: z
|
|
3885
|
-
.string()
|
|
3886
|
-
.describe("Exact text to replace. Must match uniquely in the original file."),
|
|
3887
|
-
newText: z.string().describe("Replacement text."),
|
|
3888
|
-
}))
|
|
3889
|
-
.min(1),
|
|
3890
|
-
},
|
|
3891
|
-
outputSchema: resultOutputSchema({
|
|
3892
|
-
status: z.enum(["applied", "partial"]),
|
|
3893
|
-
results: z.array(z.object({
|
|
3894
|
-
path: z.string(),
|
|
3895
|
-
status: z.enum(["done", "error", "unexecuted"]),
|
|
3896
|
-
result: z.string().optional(),
|
|
3897
|
-
})).optional(),
|
|
3898
|
-
files: z.number().int().nonnegative().optional(),
|
|
3899
|
-
completed: z.number().int().nonnegative().optional(),
|
|
3900
|
-
failed: z.number().int().nonnegative().optional(),
|
|
3901
|
-
unexecuted: z.number().int().nonnegative().optional(),
|
|
3902
|
-
}),
|
|
3903
|
-
...toolWidgetDescriptorMeta(config, "edit"),
|
|
3904
|
-
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
3905
|
-
}, async ({ workspaceId, member, path, paths, edits }, extra) => {
|
|
3906
|
-
if ((path === undefined) === (paths === undefined)) {
|
|
3907
|
-
throw new Error("edit requires exactly one of path or paths.");
|
|
3908
|
-
}
|
|
3909
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
3910
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3911
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3912
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3913
|
-
return presentSemanticWorkResult(await remoteWorkspaces.edit(executionWorkspaceId, { path, paths, edits }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3914
|
-
}
|
|
3915
|
-
if (path !== undefined) {
|
|
3916
|
-
return presentSemanticWorkResult(await coreOperations.edit({ workspaceId: executionWorkspaceId, path, edits }, executionContext), target);
|
|
3917
|
-
}
|
|
3918
|
-
return presentSemanticWorkResult(await nativeBulkMutations.edit({ workspaceId: executionWorkspaceId, paths: paths, edits }, executionContext), target);
|
|
3919
|
-
});
|
|
3920
|
-
}
|
|
3921
|
-
registerAppTool(server, toolNames.rename, {
|
|
3922
|
-
title: "Rename path",
|
|
3923
|
-
description: toolDescriptions.rename,
|
|
3924
|
-
inputSchema: {
|
|
3925
|
-
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
3926
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
3927
|
-
path: z.string().describe("Source file or directory path relative to the workspace root, or absolute inside the OS temp directory."),
|
|
3928
|
-
newPath: z.string().describe("Destination path relative to the workspace root, or absolute inside the OS temp directory. The destination must not already exist."),
|
|
3929
|
-
},
|
|
3930
|
-
outputSchema: resultOutputSchema({
|
|
3931
|
-
status: z.literal("renamed"),
|
|
3932
|
-
path: z.string(),
|
|
3933
|
-
newPath: z.string(),
|
|
3934
|
-
}),
|
|
3935
|
-
...toolWidgetDescriptorMeta(config, "edit"),
|
|
3936
|
-
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
3937
|
-
}, async ({ workspaceId, member, path, newPath }, extra) => {
|
|
3938
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
3939
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3940
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3941
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3942
|
-
return presentSemanticWorkResult(await remoteWorkspaces.rename(executionWorkspaceId, { path, newPath }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3943
|
-
}
|
|
3944
|
-
return presentSemanticWorkResult(await coreOperations.rename({ workspaceId: executionWorkspaceId, path, newPath }, executionContext), target);
|
|
423
|
+
registerFilesystemTools({
|
|
424
|
+
server,
|
|
425
|
+
config,
|
|
426
|
+
workspaces,
|
|
427
|
+
compositeWorkspaces,
|
|
428
|
+
compositeTaskGuides,
|
|
429
|
+
remoteWorkspaces,
|
|
430
|
+
coreOperations,
|
|
431
|
+
nativeBulkMutations,
|
|
432
|
+
activityLifecycle,
|
|
433
|
+
hooks,
|
|
434
|
+
toolDescriptions,
|
|
435
|
+
resolveExecutionTarget,
|
|
436
|
+
prepareExecutionContext,
|
|
437
|
+
presentSemanticWorkResult,
|
|
438
|
+
hostScopeIdFor,
|
|
3945
439
|
});
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
440
|
+
registerProcessTools({
|
|
441
|
+
server,
|
|
442
|
+
config,
|
|
443
|
+
workspaces,
|
|
444
|
+
processSessions,
|
|
445
|
+
hooks,
|
|
446
|
+
activityLifecycle,
|
|
447
|
+
bashOutputStore,
|
|
448
|
+
shellRun: (input, context) => coreOperations.shellRun(input, context),
|
|
449
|
+
routing: {
|
|
450
|
+
resolve: resolveExecutionTarget,
|
|
451
|
+
prepare: prepareExecutionContext,
|
|
452
|
+
present: presentExecutionResult,
|
|
453
|
+
presentSemantic: presentSemanticWorkResult,
|
|
454
|
+
isRemote: (workspaceId) => remoteWorkspaces.has(workspaceId),
|
|
455
|
+
bashRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.bash(workspaceId, input, conversationScopeId),
|
|
456
|
+
execCommandRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.execCommand(workspaceId, input, conversationScopeId),
|
|
457
|
+
writeStdinRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.writeStdin(workspaceId, input, conversationScopeId),
|
|
458
|
+
hostScopeIdFor,
|
|
459
|
+
},
|
|
460
|
+
descriptions: {
|
|
461
|
+
shell: toolDescriptions.shell,
|
|
462
|
+
shellCommand: toolDescriptions.shellCommand,
|
|
3960
463
|
},
|
|
3961
|
-
outputSchema: resultOutputSchema({
|
|
3962
|
-
status: z.enum(["deleted", "partial"]),
|
|
3963
|
-
path: z.string().optional(),
|
|
3964
|
-
recursive: z.boolean().optional(),
|
|
3965
|
-
results: z.array(z.object({
|
|
3966
|
-
path: z.string(),
|
|
3967
|
-
status: z.enum(["done", "error", "unexecuted"]),
|
|
3968
|
-
result: z.string().optional(),
|
|
3969
|
-
})).optional(),
|
|
3970
|
-
paths: z.number().int().nonnegative().optional(),
|
|
3971
|
-
completed: z.number().int().nonnegative().optional(),
|
|
3972
|
-
failed: z.number().int().nonnegative().optional(),
|
|
3973
|
-
unexecuted: z.number().int().nonnegative().optional(),
|
|
3974
|
-
}),
|
|
3975
|
-
...toolWidgetDescriptorMeta(config, "edit"),
|
|
3976
|
-
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
3977
|
-
}, async ({ workspaceId, member, path, paths, recursive }, extra) => {
|
|
3978
|
-
if ((path === undefined) === (paths === undefined)) {
|
|
3979
|
-
throw new Error("delete requires exactly one of path or paths.");
|
|
3980
|
-
}
|
|
3981
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
3982
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3983
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3984
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3985
|
-
return presentSemanticWorkResult(await remoteWorkspaces.delete(executionWorkspaceId, { path, paths, recursive }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3986
|
-
}
|
|
3987
|
-
if (path !== undefined) {
|
|
3988
|
-
return presentSemanticWorkResult(await coreOperations.delete({ workspaceId: executionWorkspaceId, path, recursive }, executionContext), target);
|
|
3989
|
-
}
|
|
3990
|
-
return presentSemanticWorkResult(await nativeBulkMutations.delete({ workspaceId: executionWorkspaceId, paths: paths, recursive }, executionContext), target);
|
|
3991
|
-
});
|
|
3992
|
-
if (config.toolMode === "codex") {
|
|
3993
|
-
registerAppTool(server, "apply_patch", {
|
|
3994
|
-
title: "Apply patch",
|
|
3995
|
-
description: toolDescriptions.applyPatch,
|
|
3996
|
-
inputSchema: {
|
|
3997
|
-
workspaceId: z
|
|
3998
|
-
.string()
|
|
3999
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
4000
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this patch."),
|
|
4001
|
-
patch: z
|
|
4002
|
-
.string()
|
|
4003
|
-
.describe("Patch text enclosed by *** Begin Patch and *** End Patch markers."),
|
|
4004
|
-
},
|
|
4005
|
-
outputSchema: resultOutputSchema({
|
|
4006
|
-
additions: z.number(),
|
|
4007
|
-
removals: z.number(),
|
|
4008
|
-
files: z.array(z.object({
|
|
4009
|
-
path: z.string(),
|
|
4010
|
-
previousPath: z.string().optional(),
|
|
4011
|
-
operation: z.enum(["add", "update", "delete", "move"]),
|
|
4012
|
-
})),
|
|
4013
|
-
}),
|
|
4014
|
-
...toolWidgetDescriptorMeta(config, "edit"),
|
|
4015
|
-
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
4016
|
-
}, async ({ workspaceId, member, patch }, extra) => {
|
|
4017
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
4018
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
4019
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
4020
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
4021
|
-
return presentSemanticWorkResult(await remoteWorkspaces.applyPatch(executionWorkspaceId, { patch }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
4022
|
-
}
|
|
4023
|
-
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
4024
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, patch }, executionContext), {
|
|
4025
|
-
signal: extra.signal,
|
|
4026
|
-
tool: "apply_patch",
|
|
4027
|
-
invocation: workspaceHookInvocation(workspace),
|
|
4028
|
-
payload: { patchBytes: Buffer.byteLength(patch) },
|
|
4029
|
-
changedPaths: (response) => Array.from(new Set(response.structuredContent.files.flatMap((file) => [file.previousPath, file.path])
|
|
4030
|
-
.filter((path) => Boolean(path)))),
|
|
4031
|
-
operation: async () => {
|
|
4032
|
-
const startedAt = performance.now();
|
|
4033
|
-
const applied = await applyPatch(workspace.root, patch, [tmpdir()]);
|
|
4034
|
-
const paths = applied.files.map((file) => file.path).join(", ");
|
|
4035
|
-
const result = `Applied patch to ${applied.files.length} file(s): ${paths}`;
|
|
4036
|
-
const content = [textBlock(result)];
|
|
4037
|
-
const displayPath = applied.files.length === 1
|
|
4038
|
-
? applied.files[0]?.path
|
|
4039
|
-
: `${applied.files.length} files`;
|
|
4040
|
-
logToolCall(config, {
|
|
4041
|
-
tool: "apply_patch",
|
|
4042
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
4043
|
-
path: displayPath,
|
|
4044
|
-
success: true,
|
|
4045
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
4046
|
-
});
|
|
4047
|
-
return {
|
|
4048
|
-
content,
|
|
4049
|
-
_meta: {
|
|
4050
|
-
tool: "apply_patch",
|
|
4051
|
-
card: {
|
|
4052
|
-
workspaceId: executionWorkspaceId,
|
|
4053
|
-
path: displayPath,
|
|
4054
|
-
summary: {
|
|
4055
|
-
files: applied.files.length,
|
|
4056
|
-
additions: applied.additions,
|
|
4057
|
-
removals: applied.removals,
|
|
4058
|
-
},
|
|
4059
|
-
files: applied.files,
|
|
4060
|
-
payload: { patch: applied.patch },
|
|
4061
|
-
},
|
|
4062
|
-
},
|
|
4063
|
-
structuredContent: {
|
|
4064
|
-
result,
|
|
4065
|
-
additions: applied.additions,
|
|
4066
|
-
removals: applied.removals,
|
|
4067
|
-
files: applied.files,
|
|
4068
|
-
},
|
|
4069
|
-
};
|
|
4070
|
-
},
|
|
4071
|
-
}, activityRelationFor(executionContext)).then((result) => presentSemanticWorkResult(result, target));
|
|
4072
|
-
});
|
|
4073
|
-
}
|
|
4074
|
-
if (config.toolMode !== "codex") {
|
|
4075
|
-
registerAppTool(server, toolNames.shell, {
|
|
4076
|
-
title: "Bash",
|
|
4077
|
-
description: toolDescriptions.shell,
|
|
4078
|
-
inputSchema: {
|
|
4079
|
-
workspaceId: z
|
|
4080
|
-
.string()
|
|
4081
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
4082
|
-
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this process operation."),
|
|
4083
|
-
action: z
|
|
4084
|
-
.enum(["run", "process", "output"])
|
|
4085
|
-
.optional()
|
|
4086
|
-
.describe("Defaults to run. Use process with a returned processId to poll/interact, or output with outputId to retrieve complete durable output."),
|
|
4087
|
-
command: z
|
|
4088
|
-
.string()
|
|
4089
|
-
.optional()
|
|
4090
|
-
.describe(`${toolDescriptions.shellCommand} Required for action=run.`),
|
|
4091
|
-
processId: z
|
|
4092
|
-
.number()
|
|
4093
|
-
.int()
|
|
4094
|
-
.positive()
|
|
4095
|
-
.optional()
|
|
4096
|
-
.describe("Process identifier returned by a previous bash action=run call. Required for action=process."),
|
|
4097
|
-
outputId: z
|
|
4098
|
-
.string()
|
|
4099
|
-
.optional()
|
|
4100
|
-
.describe("Stable output identifier returned by a Bash run. Required for action=output."),
|
|
4101
|
-
input: z
|
|
4102
|
-
.string()
|
|
4103
|
-
.optional()
|
|
4104
|
-
.describe("Characters to write for action=process. Omit to poll/wait without input."),
|
|
4105
|
-
interrupt: z
|
|
4106
|
-
.boolean()
|
|
4107
|
-
.optional()
|
|
4108
|
-
.describe("For action=process, send SIGINT to the process. Cannot be combined with input."),
|
|
4109
|
-
tty: z
|
|
4110
|
-
.boolean()
|
|
4111
|
-
.optional()
|
|
4112
|
-
.describe("For action=run, allocate a pseudo-terminal for interactive commands. Defaults to false."),
|
|
4113
|
-
columns: z
|
|
4114
|
-
.number()
|
|
4115
|
-
.int()
|
|
4116
|
-
.min(1)
|
|
4117
|
-
.max(1_000)
|
|
4118
|
-
.optional()
|
|
4119
|
-
.describe("Initial PTY width for action=run, or resize width for action=process."),
|
|
4120
|
-
rows: z
|
|
4121
|
-
.number()
|
|
4122
|
-
.int()
|
|
4123
|
-
.min(1)
|
|
4124
|
-
.max(1_000)
|
|
4125
|
-
.optional()
|
|
4126
|
-
.describe("Initial PTY height for action=run, or resize height for action=process."),
|
|
4127
|
-
workingDirectory: z
|
|
4128
|
-
.string()
|
|
4129
|
-
.optional()
|
|
4130
|
-
.describe("For action=run, working directory relative to the workspace root. Defaults to the workspace root."),
|
|
4131
|
-
yieldTimeMs: z
|
|
4132
|
-
.number()
|
|
4133
|
-
.int()
|
|
4134
|
-
.min(0)
|
|
4135
|
-
.max(300_000)
|
|
4136
|
-
.optional()
|
|
4137
|
-
.describe("Maximum feedback wait, not a minimum delay: if the process finishes sooner, the call returns immediately. For long-running commands or wait-only action=process calls, set a long window near the Host request deadline (60000ms when supported) instead of repeated short polling. For action=run, use 0 for immediate background handoff; otherwise defaults to 10000ms. For action=process, wait-only calls default to 5000ms and interaction to 250ms."),
|
|
4138
|
-
timeoutMs: z
|
|
4139
|
-
.number()
|
|
4140
|
-
.int()
|
|
4141
|
-
.min(1)
|
|
4142
|
-
.max(86_400_000)
|
|
4143
|
-
.optional()
|
|
4144
|
-
.describe("For action=run, total execution timeout from process start. On expiry ForgeRelay terminates the process. Omit for no ForgeRelay execution deadline."),
|
|
4145
|
-
maxOutputTokens: z
|
|
4146
|
-
.number()
|
|
4147
|
-
.int()
|
|
4148
|
-
.positive()
|
|
4149
|
-
.max(100_000)
|
|
4150
|
-
.optional()
|
|
4151
|
-
.describe("Approximate output token budget. Defaults to 10000."),
|
|
4152
|
-
},
|
|
4153
|
-
outputSchema: processOutputSchema(),
|
|
4154
|
-
...toolWidgetDescriptorMeta(config, "shell"),
|
|
4155
|
-
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
4156
|
-
}, async ({ workspaceId, member, action = "run", command, processId, outputId, input, interrupt, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, }, extra) => {
|
|
4157
|
-
const target = resolveExecutionTarget(workspaceId, member);
|
|
4158
|
-
const executionWorkspaceId = target.executionWorkspaceId;
|
|
4159
|
-
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
4160
|
-
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
4161
|
-
const response = await remoteWorkspaces.bash(executionWorkspaceId, {
|
|
4162
|
-
action,
|
|
4163
|
-
...(command !== undefined ? { command } : {}),
|
|
4164
|
-
...(processId !== undefined ? { processId } : {}),
|
|
4165
|
-
...(outputId !== undefined ? { outputId } : {}),
|
|
4166
|
-
...(input !== undefined ? { input } : {}),
|
|
4167
|
-
...(interrupt !== undefined ? { interrupt } : {}),
|
|
4168
|
-
...(tty !== undefined ? { tty } : {}),
|
|
4169
|
-
...(columns !== undefined ? { columns } : {}),
|
|
4170
|
-
...(rows !== undefined ? { rows } : {}),
|
|
4171
|
-
...(workingDirectory !== undefined ? { workingDirectory } : {}),
|
|
4172
|
-
...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
|
|
4173
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
4174
|
-
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
4175
|
-
}, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
4176
|
-
return action === "run"
|
|
4177
|
-
? presentSemanticWorkResult(response, target)
|
|
4178
|
-
: presentExecutionResult(response, target);
|
|
4179
|
-
}
|
|
4180
|
-
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
4181
|
-
if (action === "run") {
|
|
4182
|
-
if (!command)
|
|
4183
|
-
throw new Error("bash action=run requires command.");
|
|
4184
|
-
if (processId !== undefined || outputId !== undefined || input !== undefined || interrupt !== undefined) {
|
|
4185
|
-
throw new Error("bash action=run does not accept processId, outputId, input, or interrupt.");
|
|
4186
|
-
}
|
|
4187
|
-
return presentSemanticWorkResult(await coreOperations.shellRun({
|
|
4188
|
-
workspaceId: executionWorkspaceId,
|
|
4189
|
-
command,
|
|
4190
|
-
surface: "bash",
|
|
4191
|
-
tty,
|
|
4192
|
-
columns,
|
|
4193
|
-
rows,
|
|
4194
|
-
workingDirectory,
|
|
4195
|
-
yieldTimeMs,
|
|
4196
|
-
timeoutMs,
|
|
4197
|
-
maxOutputTokens,
|
|
4198
|
-
}, executionContext), target);
|
|
4199
|
-
}
|
|
4200
|
-
if (action === "output") {
|
|
4201
|
-
if (!outputId)
|
|
4202
|
-
throw new Error("bash action=output requires outputId.");
|
|
4203
|
-
if (command !== undefined || processId !== undefined || input !== undefined || interrupt !== undefined ||
|
|
4204
|
-
tty !== undefined || columns !== undefined || rows !== undefined || workingDirectory !== undefined ||
|
|
4205
|
-
yieldTimeMs !== undefined || timeoutMs !== undefined || maxOutputTokens !== undefined) {
|
|
4206
|
-
throw new Error("bash action=output accepts only workspaceId and outputId.");
|
|
4207
|
-
}
|
|
4208
|
-
return runToolWithHooks(hooks, {
|
|
4209
|
-
signal: extra.signal,
|
|
4210
|
-
tool: toolNames.shell,
|
|
4211
|
-
invocation: workspaceHookInvocation(workspace),
|
|
4212
|
-
payload: { action, outputId },
|
|
4213
|
-
operation: async () => durableOutputResponse(toolNames.shell, executionWorkspaceId, readWorkspaceBashOutput(bashOutputStore, executionWorkspaceId, outputId)),
|
|
4214
|
-
}).then((result) => presentExecutionResult(result, target));
|
|
4215
|
-
}
|
|
4216
|
-
if (outputId !== undefined)
|
|
4217
|
-
throw new Error("bash action=process does not accept outputId.");
|
|
4218
|
-
if (command !== undefined || workingDirectory !== undefined || tty !== undefined || timeoutMs !== undefined) {
|
|
4219
|
-
throw new Error("bash action=process does not accept command, workingDirectory, tty, or timeoutMs.");
|
|
4220
|
-
}
|
|
4221
|
-
if (processId === undefined)
|
|
4222
|
-
throw new Error("bash action=process requires processId.");
|
|
4223
|
-
if (interrupt && input !== undefined) {
|
|
4224
|
-
throw new Error("bash action=process cannot combine interrupt with input.");
|
|
4225
|
-
}
|
|
4226
|
-
return runToolWithHooks(hooks, {
|
|
4227
|
-
signal: extra.signal,
|
|
4228
|
-
tool: toolNames.shell,
|
|
4229
|
-
invocation: workspaceHookInvocation(workspace),
|
|
4230
|
-
payload: {
|
|
4231
|
-
action,
|
|
4232
|
-
processId,
|
|
4233
|
-
inputLength: input?.length ?? 0,
|
|
4234
|
-
interrupt: interrupt ?? false,
|
|
4235
|
-
columns,
|
|
4236
|
-
rows,
|
|
4237
|
-
},
|
|
4238
|
-
isFailure: toolResultIsError,
|
|
4239
|
-
operation: async () => {
|
|
4240
|
-
const startedAt = performance.now();
|
|
4241
|
-
const snapshot = await processSessions.write({
|
|
4242
|
-
workspaceId: executionWorkspaceId,
|
|
4243
|
-
processId,
|
|
4244
|
-
chars: interrupt ? "\u0003" : input,
|
|
4245
|
-
columns,
|
|
4246
|
-
rows,
|
|
4247
|
-
yieldTimeMs,
|
|
4248
|
-
maxOutputTokens,
|
|
4249
|
-
signal: extra.signal,
|
|
4250
|
-
});
|
|
4251
|
-
logToolCall(config, {
|
|
4252
|
-
tool: toolNames.shell,
|
|
4253
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
4254
|
-
exitCode: snapshot.exitCode,
|
|
4255
|
-
running: snapshot.running,
|
|
4256
|
-
processId: snapshot.processId,
|
|
4257
|
-
success: snapshot.running || snapshot.exitCode === 0,
|
|
4258
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
4259
|
-
});
|
|
4260
|
-
const response = processToolResponse(toolNames.shell, executionWorkspaceId, snapshot, {
|
|
4261
|
-
action,
|
|
4262
|
-
processId,
|
|
4263
|
-
inputLength: input?.length ?? 0,
|
|
4264
|
-
interrupt: interrupt ?? false,
|
|
4265
|
-
running: snapshot.running,
|
|
4266
|
-
exitCode: snapshot.exitCode,
|
|
4267
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
4268
|
-
});
|
|
4269
|
-
if (!snapshot.running) {
|
|
4270
|
-
recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId);
|
|
4271
|
-
}
|
|
4272
|
-
return presentExecutionResult(response, target);
|
|
4273
|
-
},
|
|
4274
|
-
});
|
|
4275
|
-
});
|
|
4276
|
-
}
|
|
4277
|
-
registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, (input, context) => coreOperations.shellRun(input, context), {
|
|
4278
|
-
resolve: resolveExecutionTarget,
|
|
4279
|
-
prepare: prepareExecutionContext,
|
|
4280
|
-
present: presentExecutionResult,
|
|
4281
|
-
presentSemantic: presentSemanticWorkResult,
|
|
4282
|
-
isRemote: (workspaceId) => remoteWorkspaces.has(workspaceId),
|
|
4283
|
-
execCommandRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.execCommand(workspaceId, input, conversationScopeId),
|
|
4284
|
-
writeStdinRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.writeStdin(workspaceId, input, conversationScopeId),
|
|
4285
|
-
hostScopeIdFor,
|
|
4286
464
|
});
|
|
4287
465
|
if (ownsRemoteWorkspaces) {
|
|
4288
466
|
const closeServer = server.close.bind(server);
|
|
@@ -4302,271 +480,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
4302
480
|
return server;
|
|
4303
481
|
}
|
|
4304
482
|
export function createServer(config = loadConfig(), options = {}) {
|
|
4305
|
-
|
|
4306
|
-
?? [createOpenAIIncomingArtifactAdapter()];
|
|
4307
|
-
const allowedHosts = config.allowedHosts.includes("*")
|
|
4308
|
-
? undefined
|
|
4309
|
-
: Array.from(new Set([config.host, ...config.allowedHosts]));
|
|
4310
|
-
const app = createMcpExpressApp({
|
|
4311
|
-
host: config.host,
|
|
4312
|
-
...(allowedHosts ? { allowedHosts } : {}),
|
|
4313
|
-
});
|
|
4314
|
-
const transports = new McpTransportRegistry({
|
|
4315
|
-
maxTransports: MAX_MCP_TRANSPORT_SESSIONS,
|
|
4316
|
-
});
|
|
4317
|
-
const mcpUrl = publicEndpointUrl(config.publicBaseUrl, "mcp");
|
|
4318
|
-
const resourceServerUrl = resourceUrlFromServerUrl(mcpUrl);
|
|
4319
|
-
const oauthProvider = new SingleUserOAuthProvider(config.oauth, mcpUrl, config.stateDir);
|
|
4320
|
-
const bearerAuth = requireBearerAuth({
|
|
4321
|
-
verifier: oauthProvider,
|
|
4322
|
-
requiredScopes: [config.oauth.scopes[0] ?? "devspace"],
|
|
4323
|
-
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resourceServerUrl),
|
|
4324
|
-
});
|
|
4325
|
-
const workspaceStore = createWorkspaceStore(config.stateDir);
|
|
4326
|
-
const workspaces = new WorkspaceRegistry(config, workspaceStore);
|
|
4327
|
-
const sharedRemoteWorkspaces = new RemoteWorkspaceRelay(config.configDir, config.stateDir);
|
|
4328
|
-
const sharedCompositeWorkspaces = new CompositeWorkspaceRegistry(config.stateDir);
|
|
4329
|
-
const sharedWorkspaceTasks = new WorkspaceTaskStore(config.stateDir);
|
|
4330
|
-
const sharedTaskReminders = new WorkspaceTaskReminderTracker(config.taskReminderInterval, sharedWorkspaceTasks);
|
|
4331
|
-
const activityAuditStore = new ActivityAuditStore(config.stateDir);
|
|
4332
|
-
const bashOutputStore = new BashOutputStore(config.stateDir);
|
|
4333
|
-
const hostTurnStore = new HostTurnStore(config.stateDir);
|
|
4334
|
-
const activityQueries = new ActivityQueryService(hostTurnStore, activityAuditStore, bashOutputStore);
|
|
4335
|
-
const activityLifecycle = new ActivityLifecycle(activityAuditStore, {
|
|
4336
|
-
turnIdForConversation: (conversationScopeId, workspaceId) => activityQueries.currentTurnId(conversationScopeId, workspaceId),
|
|
4337
|
-
});
|
|
4338
|
-
const reviewCheckpoints = createReviewCheckpointManager();
|
|
4339
|
-
const processSessions = new ProcessManager({ outputAudit: bashOutputStore });
|
|
4340
|
-
const codeIntelligence = new CodeIntelligenceManager(config);
|
|
4341
|
-
const subagentProviders = config.subagents
|
|
4342
|
-
? getSubagentProviderAvailabilitySnapshot()
|
|
4343
|
-
: [];
|
|
4344
|
-
const logTransportCloseResults = (reason, results) => {
|
|
4345
|
-
let closedCount = 0;
|
|
4346
|
-
for (const result of results) {
|
|
4347
|
-
if (result.error) {
|
|
4348
|
-
logEvent(config.logging, "warn", "mcp_transport_session_close_failed", {
|
|
4349
|
-
reason,
|
|
4350
|
-
transportSessionIdPrefix: transportSessionIdPrefix(result.transportSessionId),
|
|
4351
|
-
error: result.error instanceof Error
|
|
4352
|
-
? result.error.message
|
|
4353
|
-
: String(result.error),
|
|
4354
|
-
});
|
|
4355
|
-
continue;
|
|
4356
|
-
}
|
|
4357
|
-
closedCount += 1;
|
|
4358
|
-
if (reason !== "server_shutdown") {
|
|
4359
|
-
logEvent(config.logging, "debug", "mcp_transport_session_closed", {
|
|
4360
|
-
reason,
|
|
4361
|
-
transportSessionIdPrefix: transportSessionIdPrefix(result.transportSessionId),
|
|
4362
|
-
});
|
|
4363
|
-
}
|
|
4364
|
-
}
|
|
4365
|
-
if (reason === "server_shutdown" && closedCount > 0) {
|
|
4366
|
-
logEvent(config.logging, "debug", "mcp_transport_sessions_closed", {
|
|
4367
|
-
reason,
|
|
4368
|
-
count: closedCount,
|
|
4369
|
-
});
|
|
4370
|
-
}
|
|
4371
|
-
};
|
|
4372
|
-
const logRuntimeResources = () => {
|
|
4373
|
-
const memory = process.memoryUsage();
|
|
4374
|
-
const processStats = processSessions.stats();
|
|
4375
|
-
const codeStats = codeIntelligence.stats();
|
|
4376
|
-
logEvent(config.logging, "debug", "runtime_resources", {
|
|
4377
|
-
rssBytes: memory.rss,
|
|
4378
|
-
heapUsedBytes: memory.heapUsed,
|
|
4379
|
-
heapTotalBytes: memory.heapTotal,
|
|
4380
|
-
externalBytes: memory.external,
|
|
4381
|
-
arrayBuffersBytes: memory.arrayBuffers,
|
|
4382
|
-
mcpTransports: transports.size,
|
|
4383
|
-
processesTotal: processStats.total,
|
|
4384
|
-
processesRunning: processStats.running,
|
|
4385
|
-
processesCompleted: processStats.completed,
|
|
4386
|
-
cachedWorkspaces: workspaces.cachedWorkspaceCount,
|
|
4387
|
-
reviewStates: reviewCheckpoints.stateCount,
|
|
4388
|
-
languageServices: codeStats.servicesTotal,
|
|
4389
|
-
languageServicesActive: codeStats.servicesActive,
|
|
4390
|
-
languageProcessesRunning: codeStats.processesRunning,
|
|
4391
|
-
languageOperationsInFlight: codeStats.operationsInFlight,
|
|
4392
|
-
languageRequestsActive: codeStats.semanticRequestsActive,
|
|
4393
|
-
languageRequestsQueued: codeStats.semanticRequestsQueued,
|
|
4394
|
-
languageOpenDocuments: codeStats.openDocuments,
|
|
4395
|
-
languageDiagnosticSnapshots: codeStats.diagnosticSnapshots,
|
|
4396
|
-
languageDiagnosticsRetained: codeStats.diagnosticsRetained,
|
|
4397
|
-
languageStderrBytes: codeStats.stderrBytes,
|
|
4398
|
-
languagePendingCreations: codeStats.pendingCreations,
|
|
4399
|
-
languageCrashCooldowns: codeStats.crashCooldowns,
|
|
4400
|
-
languageInvalidatedServices: codeStats.invalidatedServices,
|
|
4401
|
-
languageRetiredWorkspaceRoots: codeStats.retiredWorkspaceRoots,
|
|
4402
|
-
});
|
|
4403
|
-
};
|
|
4404
|
-
const transportCleanupTimer = setInterval(() => {
|
|
4405
|
-
void transports
|
|
4406
|
-
.closeIdle(MCP_TRANSPORT_IDLE_TIMEOUT_MS)
|
|
4407
|
-
.then((results) => logTransportCloseResults("idle_timeout", results))
|
|
4408
|
-
.finally(logRuntimeResources);
|
|
4409
|
-
}, MCP_TRANSPORT_CLEANUP_INTERVAL_MS);
|
|
4410
|
-
transportCleanupTimer.unref();
|
|
4411
|
-
logRuntimeResources();
|
|
4412
|
-
if (config.logging.trustProxy) {
|
|
4413
|
-
app.set("trust proxy", 1);
|
|
4414
|
-
}
|
|
4415
|
-
app.use((req, res, next) => {
|
|
4416
|
-
const requestId = randomUUID();
|
|
4417
|
-
const startedAt = performance.now();
|
|
4418
|
-
res.locals.requestId = requestId;
|
|
4419
|
-
res.on("finish", () => {
|
|
4420
|
-
const path = requestPath(req);
|
|
4421
|
-
if (!config.logging.requests)
|
|
4422
|
-
return;
|
|
4423
|
-
if (!config.logging.assets && path.startsWith("/mcp-app-assets"))
|
|
4424
|
-
return;
|
|
4425
|
-
logEvent(config.logging, "info", "http_request", {
|
|
4426
|
-
requestId,
|
|
4427
|
-
method: req.method,
|
|
4428
|
-
path,
|
|
4429
|
-
status: res.statusCode,
|
|
4430
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
4431
|
-
...requestLogFields(req, config),
|
|
4432
|
-
});
|
|
4433
|
-
});
|
|
4434
|
-
next();
|
|
4435
|
-
});
|
|
4436
|
-
app.use(createForgeRelayAuthRouter({
|
|
4437
|
-
provider: oauthProvider,
|
|
4438
|
-
cliAuthenticationProvider: oauthProvider,
|
|
4439
|
-
instanceId: config.instanceId,
|
|
4440
|
-
issuerUrl: new URL(config.publicBaseUrl),
|
|
4441
|
-
resourceServerUrl,
|
|
4442
|
-
scopesSupported: config.oauth.scopes,
|
|
4443
|
-
resourceName: "ForgeRelay",
|
|
4444
|
-
}));
|
|
4445
|
-
app.options("/mcp-app-assets/{*asset}", (_req, res) => {
|
|
4446
|
-
setAssetHeaders(res);
|
|
4447
|
-
res.sendStatus(204);
|
|
4448
|
-
});
|
|
4449
|
-
app.use("/mcp-app-assets", express.static(uiBuildDirectory(), {
|
|
4450
|
-
immutable: true,
|
|
4451
|
-
maxAge: "1y",
|
|
4452
|
-
fallthrough: false,
|
|
4453
|
-
setHeaders: setAssetHeaders,
|
|
4454
|
-
}));
|
|
4455
|
-
app.get("/healthz", (_req, res) => {
|
|
4456
|
-
res.json({ ok: true, name: "forgerelay" });
|
|
4457
|
-
});
|
|
4458
|
-
app.all("/mcp", async (req, res) => {
|
|
4459
|
-
const requestId = res.locals.requestId;
|
|
4460
|
-
const transportSessionId = req.header("mcp-session-id");
|
|
4461
|
-
const initializeRequest = req.method === "POST" && isInitializeRequest(req.body);
|
|
4462
|
-
await new Promise((resolve, reject) => {
|
|
4463
|
-
bearerAuth(req, res, (error) => {
|
|
4464
|
-
if (error)
|
|
4465
|
-
reject(error);
|
|
4466
|
-
else
|
|
4467
|
-
resolve();
|
|
4468
|
-
});
|
|
4469
|
-
});
|
|
4470
|
-
if (res.headersSent)
|
|
4471
|
-
return;
|
|
4472
|
-
if (!req.auth?.resource || !checkResourceAllowed({ requestedResource: req.auth.resource, configuredResource: resourceServerUrl })) {
|
|
4473
|
-
logEvent(config.logging, "warn", "auth_denied", {
|
|
4474
|
-
requestId,
|
|
4475
|
-
method: req.method,
|
|
4476
|
-
path: requestPath(req),
|
|
4477
|
-
reason: "invalid_oauth_resource",
|
|
4478
|
-
...requestLogFields(req, config),
|
|
4479
|
-
});
|
|
4480
|
-
sendJsonRpcError(res, 401, -32001, "Unauthorized");
|
|
4481
|
-
return;
|
|
4482
|
-
}
|
|
4483
|
-
logEvent(config.logging, "debug", "mcp_request", {
|
|
4484
|
-
requestId,
|
|
4485
|
-
httpMethod: req.method,
|
|
4486
|
-
transportSessionIdPresent: Boolean(transportSessionId),
|
|
4487
|
-
transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
|
|
4488
|
-
isInitialize: initializeRequest,
|
|
4489
|
-
...mcpRequestDebugFields(req.body),
|
|
4490
|
-
});
|
|
4491
|
-
try {
|
|
4492
|
-
let transport;
|
|
4493
|
-
if (transportSessionId) {
|
|
4494
|
-
transport = transports.get(transportSessionId);
|
|
4495
|
-
if (!transport) {
|
|
4496
|
-
sendJsonRpcError(res, 404, -32000, "Unknown MCP transport session");
|
|
4497
|
-
return;
|
|
4498
|
-
}
|
|
4499
|
-
}
|
|
4500
|
-
else if (initializeRequest) {
|
|
4501
|
-
transport = new StreamableHTTPServerTransport({
|
|
4502
|
-
sessionIdGenerator: () => randomUUID(),
|
|
4503
|
-
onsessioninitialized: (newTransportSessionId) => {
|
|
4504
|
-
if (transport) {
|
|
4505
|
-
void transports
|
|
4506
|
-
.register(newTransportSessionId, transport)
|
|
4507
|
-
.then((results) => logTransportCloseResults("capacity_limit", results));
|
|
4508
|
-
}
|
|
4509
|
-
logEvent(config.logging, "debug", "mcp_transport_session_created", {
|
|
4510
|
-
requestId,
|
|
4511
|
-
transportSessionIdPrefix: transportSessionIdPrefix(newTransportSessionId),
|
|
4512
|
-
...requestLogFields(req, config),
|
|
4513
|
-
});
|
|
4514
|
-
},
|
|
4515
|
-
});
|
|
4516
|
-
transport.onclose = () => {
|
|
4517
|
-
const closedTransportSessionId = transport?.sessionId;
|
|
4518
|
-
if (closedTransportSessionId && transports.remove(closedTransportSessionId)) {
|
|
4519
|
-
logEvent(config.logging, "debug", "mcp_transport_session_closed", {
|
|
4520
|
-
reason: "transport_close",
|
|
4521
|
-
transportSessionIdPrefix: transportSessionIdPrefix(closedTransportSessionId),
|
|
4522
|
-
});
|
|
4523
|
-
}
|
|
4524
|
-
};
|
|
4525
|
-
const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, subagentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries, {
|
|
4526
|
-
taskReminders: sharedTaskReminders,
|
|
4527
|
-
remoteWorkspaces: sharedRemoteWorkspaces,
|
|
4528
|
-
compositeWorkspaces: sharedCompositeWorkspaces,
|
|
4529
|
-
});
|
|
4530
|
-
await server.connect(transport);
|
|
4531
|
-
}
|
|
4532
|
-
else {
|
|
4533
|
-
sendJsonRpcError(res, 400, -32000, "No valid MCP transport session");
|
|
4534
|
-
return;
|
|
4535
|
-
}
|
|
4536
|
-
await transport.handleRequest(req, res, req.body);
|
|
4537
|
-
}
|
|
4538
|
-
catch (error) {
|
|
4539
|
-
logEvent(config.logging, "error", "mcp_request_error", {
|
|
4540
|
-
requestId,
|
|
4541
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4542
|
-
});
|
|
4543
|
-
if (!res.headersSent) {
|
|
4544
|
-
sendJsonRpcError(res, 500, -32603, "Internal server error");
|
|
4545
|
-
}
|
|
4546
|
-
}
|
|
4547
|
-
});
|
|
4548
|
-
let closePromise;
|
|
4549
|
-
return {
|
|
4550
|
-
app,
|
|
4551
|
-
config,
|
|
4552
|
-
subagentProviders,
|
|
4553
|
-
close: () => {
|
|
4554
|
-
closePromise ??= (async () => {
|
|
4555
|
-
clearInterval(transportCleanupTimer);
|
|
4556
|
-
const results = await transports.closeAll();
|
|
4557
|
-
logTransportCloseResults("server_shutdown", results);
|
|
4558
|
-
await sharedRemoteWorkspaces.shutdown();
|
|
4559
|
-
processSessions.shutdown();
|
|
4560
|
-
await codeIntelligence.shutdown();
|
|
4561
|
-
oauthProvider.close();
|
|
4562
|
-
hostTurnStore.close();
|
|
4563
|
-
bashOutputStore.close();
|
|
4564
|
-
activityAuditStore.close();
|
|
4565
|
-
workspaceStore.close?.();
|
|
4566
|
-
})();
|
|
4567
|
-
return closePromise;
|
|
4568
|
-
},
|
|
4569
|
-
};
|
|
483
|
+
return createHttpServer(config, options, createMcpServer);
|
|
4570
484
|
}
|
|
4571
485
|
async function isMainModule() {
|
|
4572
486
|
if (!process.argv[1])
|