@akira-tl/forgerelay 0.3.4 → 0.3.6
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 +28 -0
- package/capabilities/artifacts-review/GUIDE.md +2 -4
- package/dist/artifact-tools.js +0 -128
- package/dist/capabilities.js +2 -5
- package/dist/db/migrations.js +19 -0
- package/dist/db/schema.js +9 -0
- package/dist/hooks.js +12 -0
- package/dist/mcp/server-instructions.js +4 -9
- package/dist/review-checkpoints.js +4 -4
- package/dist/server.js +154 -280
- package/dist/ui/.vite/manifest.json +280 -280
- package/dist/ui/assets/{angular-html-CQmCUfkv.js → angular-html-BT2Z6jee.js} +1 -1
- package/dist/ui/assets/{angular-ts-BSbfxpMS.js → angular-ts-BwPE8YDt.js} +1 -1
- package/dist/ui/assets/{apl-CWCaH-E3.js → apl-CGbONVvU.js} +1 -1
- package/dist/ui/assets/{astro-R_p4G0xA.js → astro-DIAPy7cj.js} +1 -1
- package/dist/ui/assets/{blade-irHensDY.js → blade-Cgtd19Ky.js} +1 -1
- package/dist/ui/assets/{c-_XEQUoq6.js → c-acnvZ8tm.js} +1 -1
- package/dist/ui/assets/{cobol-Css6VQjJ.js → cobol-G-7HLVKW.js} +1 -1
- package/dist/ui/assets/{coffee-CpXUkyDm.js → coffee-DQFyKxEB.js} +1 -1
- package/dist/ui/assets/{cpp-BQHsPWwI.js → cpp-UyrmHXan.js} +1 -1
- package/dist/ui/assets/{crystal-Cbv8J9Fm.js → crystal-DuiWhEO3.js} +1 -1
- package/dist/ui/assets/{css-D8DkrS3U.js → css-D_WCbY6m.js} +1 -1
- package/dist/ui/assets/{edge-bBL_XDAG.js → edge-COkalhFi.js} +1 -1
- package/dist/ui/assets/{elixir-CQjsM9sA.js → elixir-cdlzbYR1.js} +1 -1
- package/dist/ui/assets/{elm-DLFl-jEZ.js → elm-ZwDYuYpb.js} +1 -1
- package/dist/ui/assets/{erb-BJjsdB_Q.js → erb-C8WG9TtC.js} +1 -1
- package/dist/ui/assets/{git-rebase-CqP5A2s0.js → git-rebase-CMlDVJBT.js} +1 -1
- package/dist/ui/assets/{glimmer-js-B5Hh_7xe.js → glimmer-js-DeLOQlol.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-oaeSAC8s.js → glimmer-ts-DTEM04TK.js} +1 -1
- package/dist/ui/assets/{glsl-BylkOpcF.js → glsl-IJV4SB-M.js} +1 -1
- package/dist/ui/assets/{graphql-DsVRvP6S.js → graphql-PM-WDnoB.js} +1 -1
- package/dist/ui/assets/{hack-D4N5abT_.js → hack-C14sh6Lh.js} +1 -1
- package/dist/ui/assets/{haml-CIAJQIox.js → haml-B_5nS6Kx.js} +1 -1
- package/dist/ui/assets/{handlebars-DDEZyNXR.js → handlebars-eS-J0O-M.js} +1 -1
- package/dist/ui/assets/{heavy-payload-DafM8yjB.js → heavy-payload-BKB0QDOI.js} +1 -1
- package/dist/ui/assets/{html-zDy-3lvy.js → html-C3TPjrC5.js} +1 -1
- package/dist/ui/assets/{html-derivative-CZ91Y2d5.js → html-derivative-BlVZU2id.js} +1 -1
- package/dist/ui/assets/{http-ezaodZRf.js → http-BmbZzA5x.js} +1 -1
- package/dist/ui/assets/{hurl-BmL0o7TO.js → hurl-Dnd_YpOD.js} +1 -1
- package/dist/ui/assets/{java-Q2C0vwbH.js → java-ne9HdIVn.js} +1 -1
- package/dist/ui/assets/{javascript-UDhBkHE3.js → javascript-DpHT58rr.js} +1 -1
- package/dist/ui/assets/{jinja-BWRskcYS.js → jinja-DTvkZdHA.js} +1 -1
- package/dist/ui/assets/{jison-B18BY9Ds.js → jison-DUJG-tmh.js} +1 -1
- package/dist/ui/assets/{json-DNj7MpRr.js → json-DJmnBdxW.js} +1 -1
- package/dist/ui/assets/{jsx-CVpHJ6cr.js → jsx-BclJnZnN.js} +1 -1
- package/dist/ui/assets/{julia-CJBNBS9c.js → julia-P5B4pHFa.js} +1 -1
- package/dist/ui/assets/{just-Bmu2JCQT.js → just-DYaoh7C6.js} +1 -1
- package/dist/ui/assets/{latex-BB7F93KY.js → latex-CI5lnkWV.js} +1 -1
- package/dist/ui/assets/{liquid-Btnomc6E.js → liquid-D3LnAs_z.js} +1 -1
- package/dist/ui/assets/{lua-D0FYT9Vo.js → lua-dZC7ZQIY.js} +1 -1
- package/dist/ui/assets/{marko-MHLw0ZlK.js → marko-CRDrjqfS.js} +1 -1
- package/dist/ui/assets/{mdc-CSr-suGy.js → mdc-ZzwgmJVn.js} +1 -1
- package/dist/ui/assets/{nginx-ur1l6E-g.js → nginx-BAvFXGSV.js} +1 -1
- package/dist/ui/assets/{nim-B2k0IugA.js → nim-BHIFawe9.js} +1 -1
- package/dist/ui/assets/{perl-BqOHxFDn.js → perl-C_f5AnLa.js} +1 -1
- package/dist/ui/assets/{php-DzPK6O1_.js → php-DDqpegL5.js} +1 -1
- package/dist/ui/assets/{pug-D169OZkr.js → pug-CSaAZXyd.js} +1 -1
- package/dist/ui/assets/{qml-BtemJTC4.js → qml-CNHvRs_z.js} +1 -1
- package/dist/ui/assets/{r-BGVHu0bd.js → r-CAfoQND2.js} +1 -1
- package/dist/ui/assets/{razor-Diisjg1d.js → razor-BDfAPy7I.js} +1 -1
- package/dist/ui/assets/{regexp-B2F5vMZ6.js → regexp-Mug5jltQ.js} +1 -1
- package/dist/ui/assets/{review-payload-B-3I3Oj_.js → review-payload-D7GIdYDj.js} +1 -1
- package/dist/ui/assets/{rst-BxpeZwuT.js → rst-BzPP2gle.js} +1 -1
- package/dist/ui/assets/{ruby-DrCq4riN.js → ruby-CQjzokzX.js} +1 -1
- package/dist/ui/assets/{sas-CI_2W-1n.js → sas-Bp37KGr7.js} +1 -1
- package/dist/ui/assets/{scrollbar-DHsDF5w-.js → scrollbar-HUMBdLLK.js} +3 -3
- package/dist/ui/assets/{scss-BxtyHZ1p.js → scss-C_Z1DYnw.js} +1 -1
- package/dist/ui/assets/{shellscript-BY_YYrHT.js → shellscript-CLfJpn3I.js} +1 -1
- package/dist/ui/assets/{shellsession-C6uBrLmA.js → shellsession-CNc62UhY.js} +1 -1
- package/dist/ui/assets/{soy-BHM9gdPh.js → soy-CF44SpBo.js} +1 -1
- package/dist/ui/assets/{sql-XmhfEGz_.js → sql-CYnzfiQ1.js} +1 -1
- package/dist/ui/assets/{stata-BbHY7NEo.js → stata-DVeMA68W.js} +1 -1
- package/dist/ui/assets/{surrealql-CD49DZ3E.js → surrealql-52sF96Yv.js} +1 -1
- package/dist/ui/assets/{svelte-CbYsEYJY.js → svelte-ydwT7UbF.js} +1 -1
- package/dist/ui/assets/{templ-B5JoUxEB.js → templ-BXcoVoGh.js} +1 -1
- package/dist/ui/assets/{tex-DZcOfTZK.js → tex-COsYFp7O.js} +1 -1
- package/dist/ui/assets/{ts-tags-BeU-xqc0.js → ts-tags-CF--LE1s.js} +1 -1
- package/dist/ui/assets/{tsx-V7W1xGl3.js → tsx-DLBt2-2f.js} +1 -1
- package/dist/ui/assets/{twig-MIaJo5BU.js → twig-YoM-DiI5.js} +1 -1
- package/dist/ui/assets/{typescript-C0WESuVc.js → typescript-Ce9JGkNI.js} +1 -1
- package/dist/ui/assets/{vue-eJHwc9ra.js → vue-B5LDEy4l.js} +1 -1
- package/dist/ui/assets/{vue-html-CLmBujTU.js → vue-html-CPdGGk4T.js} +1 -1
- package/dist/ui/assets/{vue-vine-BP5kjIGb.js → vue-vine-B5bjgk0a.js} +1 -1
- package/dist/ui/assets/{workspace-app-C1qR5cAY.js → workspace-app-Cm9EX5x4.js} +3 -3
- package/dist/ui/assets/{xml-j7pmyP48.js → xml-Cun8xb0T.js} +1 -1
- package/dist/ui/assets/{xsl-5VFHbg6o.js → xsl-wls_CEK_.js} +1 -1
- package/dist/ui/assets/{yaml-DxFAt-KU.js → yaml-BxuXn9OL.js} +1 -1
- package/dist/ui/workspace-app.html +1 -1
- package/dist/workspace-store.js +52 -1
- package/dist/workspaces.js +209 -29
- package/docs/artifact-exchange.md +4 -3
- package/docs/chatgpt-coding-workflow.md +70 -11
- package/docs/configuration.md +41 -16
- package/docs/debugging.md +2 -2
- package/docs/gotchas.md +3 -3
- package/docs/roadmap.md +12 -1
- package/docs/security.md +3 -2
- package/package.json +1 -1
- package/scripts/debug/accept.mjs +113 -15
package/dist/server.js
CHANGED
|
@@ -17,7 +17,7 @@ import { applyPatch } from "./apply-patch.js";
|
|
|
17
17
|
import { buildCapabilityFingerprint } from "./capabilities.js";
|
|
18
18
|
import { CapabilityError, createCapabilityRegistry, } from "./capability-registry.js";
|
|
19
19
|
import { deletePath, renamePath } from "./file-mutations.js";
|
|
20
|
-
import { downloadIncomingArtifact, isArtifactDownloadSupportedPlatform,
|
|
20
|
+
import { downloadIncomingArtifact, isArtifactDownloadSupportedPlatform, } from "./artifact-tools.js";
|
|
21
21
|
import { ArtifactError } from "./artifact-error.js";
|
|
22
22
|
import { loadConfig } from "./config.js";
|
|
23
23
|
import { attachHookReports, HookRunner, runToolWithHooks } from "./hooks.js";
|
|
@@ -25,7 +25,7 @@ import { checkHookConfiguration } from "./hook-cli.js";
|
|
|
25
25
|
import { buildServerInstructions, buildShellMutationPolicy, buildToolDescriptions, toolNames, } from "./mcp/server-instructions.js";
|
|
26
26
|
import { createOpenAIIncomingArtifactAdapter, IncomingArtifactAdapterRegistry, } from "./incoming-artifacts.js";
|
|
27
27
|
import { logEvent, requestIp, requestPath, commandPreview, transportSessionIdPrefix, workspaceLogLabel, } from "./logger.js";
|
|
28
|
-
import { editFileTool,
|
|
28
|
+
import { editFileTool, readFileTool, writeFileTool, } from "./pi-tools.js";
|
|
29
29
|
import { SingleUserOAuthProvider } from "./oauth-provider.js";
|
|
30
30
|
import { McpTransportRegistry, } from "./mcp-sessions.js";
|
|
31
31
|
import { ProcessManager, resolveProcessId, } from "./process-sessions.js";
|
|
@@ -67,7 +67,7 @@ function shouldAttachWidget(mode, kind) {
|
|
|
67
67
|
case "off":
|
|
68
68
|
return false;
|
|
69
69
|
case "changes":
|
|
70
|
-
return kind === "workspace" || kind === "
|
|
70
|
+
return kind === "workspace" || kind === "capability";
|
|
71
71
|
case "full":
|
|
72
72
|
return true;
|
|
73
73
|
}
|
|
@@ -164,6 +164,36 @@ const workspaceLocalAgentProviderOutputSchema = z.object({
|
|
|
164
164
|
const workspaceAvailableAgentsFileOutputSchema = z.object({
|
|
165
165
|
path: z.string(),
|
|
166
166
|
});
|
|
167
|
+
const workspaceInventoryEntryOutputSchema = z.object({
|
|
168
|
+
label: z.string(),
|
|
169
|
+
workspaceId: z.string(),
|
|
170
|
+
root: z.string(),
|
|
171
|
+
status: z.string(),
|
|
172
|
+
state: z.enum(["active", "stale", "invalid", "closed"]),
|
|
173
|
+
mode: z.enum(["checkout", "worktree"]),
|
|
174
|
+
sourceRoot: z.string().optional(),
|
|
175
|
+
branch: z.string().optional(),
|
|
176
|
+
targetBranch: z.string().optional(),
|
|
177
|
+
managed: z.boolean(),
|
|
178
|
+
createdAt: z.string(),
|
|
179
|
+
lastUsedAt: z.string(),
|
|
180
|
+
idleMs: z.number().nonnegative(),
|
|
181
|
+
rootValid: z.boolean(),
|
|
182
|
+
current: z.boolean(),
|
|
183
|
+
});
|
|
184
|
+
const workspaceInventorySummaryOutputSchema = z.object({
|
|
185
|
+
total: z.number().int().nonnegative(),
|
|
186
|
+
matching: z.number().int().nonnegative(),
|
|
187
|
+
active: z.number().int().nonnegative(),
|
|
188
|
+
stale: z.number().int().nonnegative(),
|
|
189
|
+
invalid: z.number().int().nonnegative(),
|
|
190
|
+
closed: z.number().int().nonnegative(),
|
|
191
|
+
});
|
|
192
|
+
const workspaceInventoryPageOutputSchema = z.object({
|
|
193
|
+
offset: z.number().int().nonnegative(),
|
|
194
|
+
limit: z.number().int().positive(),
|
|
195
|
+
hasMore: z.boolean(),
|
|
196
|
+
});
|
|
167
197
|
const reviewFileOutputSchema = z.object({
|
|
168
198
|
path: z.string(),
|
|
169
199
|
previousPath: z.string().optional(),
|
|
@@ -711,7 +741,6 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
711
741
|
files: review.files,
|
|
712
742
|
},
|
|
713
743
|
card: {
|
|
714
|
-
tool: "show_changes",
|
|
715
744
|
summary: review.summary,
|
|
716
745
|
files: review.files,
|
|
717
746
|
payload: { patch: review.patch },
|
|
@@ -776,20 +805,24 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
776
805
|
}, async (uri, _variables, extra) => readWorkspaceAppResource(config, uri.toString(), extra.sessionId));
|
|
777
806
|
registerAppTool(server, "open_workspace", {
|
|
778
807
|
title: "Open workspace",
|
|
779
|
-
description: "Open or resume a local coding workspace. Reuse the returned workspaceId for later calls. Default to checkout; use mode=\"worktree\" only when the user explicitly requests isolated or parallel Git work. Every call returns
|
|
808
|
+
description: "Open or resume a local coding workspace. Reuse the returned workspaceId for later calls. Default to checkout; use mode=\"worktree\" only when the user explicitly requests isolated or parallel Git work. Every call returns lightweight workspace metadata; bootstrap context is delivered automatically only when needed and can be explicitly suppressed or refreshed.",
|
|
780
809
|
inputSchema: {
|
|
810
|
+
action: z
|
|
811
|
+
.enum(["open", "list"])
|
|
812
|
+
.optional()
|
|
813
|
+
.describe("Defaults to open. Use list only when you need to inspect or choose logical workspaces before resuming or cleaning them up."),
|
|
781
814
|
path: z
|
|
782
815
|
.string()
|
|
783
816
|
.optional()
|
|
784
|
-
.describe("Project path to open. Required unless workspaceId is supplied. With mode=\"worktree\", this may also be a managed worktree path previously returned by ForgeRelay."),
|
|
817
|
+
.describe("Project path to open. Required for action=open unless workspaceId is supplied. With mode=\"worktree\", this may also be a managed worktree path previously returned by ForgeRelay."),
|
|
785
818
|
workspaceId: z
|
|
786
819
|
.string()
|
|
787
820
|
.optional()
|
|
788
|
-
.describe("
|
|
821
|
+
.describe("For action=open, an existing logical workspace ID to resume in this conversation. For action=list, filters inventory to one workspace ID."),
|
|
789
822
|
mode: z
|
|
790
823
|
.enum(["checkout", "worktree"])
|
|
791
824
|
.optional()
|
|
792
|
-
.describe("
|
|
825
|
+
.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."),
|
|
793
826
|
baseRef: z
|
|
794
827
|
.string()
|
|
795
828
|
.optional()
|
|
@@ -802,11 +835,45 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
802
835
|
.boolean()
|
|
803
836
|
.optional()
|
|
804
837
|
.describe("When true, allocate a fresh logical workspaceId for the same physical checkout or worktree and bind this conversation to it. Use only after the user explicitly requests a new logical workspace."),
|
|
838
|
+
context: z
|
|
839
|
+
.enum(["auto", "full", "none"])
|
|
840
|
+
.optional()
|
|
841
|
+
.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."),
|
|
842
|
+
root: z
|
|
843
|
+
.string()
|
|
844
|
+
.optional()
|
|
845
|
+
.describe("For action=list, filter by canonical workspace root or source root."),
|
|
846
|
+
status: z
|
|
847
|
+
.string()
|
|
848
|
+
.optional()
|
|
849
|
+
.describe("For action=list, filter by persisted workspace status such as active or closed."),
|
|
850
|
+
state: z
|
|
851
|
+
.enum(["active", "stale", "invalid", "closed"])
|
|
852
|
+
.optional()
|
|
853
|
+
.describe("For action=list, filter by derived lifecycle state."),
|
|
854
|
+
staleOnly: z
|
|
855
|
+
.boolean()
|
|
856
|
+
.optional()
|
|
857
|
+
.describe("For action=list, return only active logical workspaces idle for more than two days."),
|
|
858
|
+
offset: z
|
|
859
|
+
.number()
|
|
860
|
+
.int()
|
|
861
|
+
.nonnegative()
|
|
862
|
+
.optional()
|
|
863
|
+
.describe("For action=list, zero-based inventory offset. Defaults to 0."),
|
|
864
|
+
limit: z
|
|
865
|
+
.number()
|
|
866
|
+
.int()
|
|
867
|
+
.min(1)
|
|
868
|
+
.max(100)
|
|
869
|
+
.optional()
|
|
870
|
+
.describe("For action=list, maximum records to return. Defaults to 50; maximum 100."),
|
|
805
871
|
},
|
|
806
872
|
outputSchema: {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
873
|
+
action: z.enum(["open", "list"]),
|
|
874
|
+
workspaceId: z.string().optional(),
|
|
875
|
+
root: z.string().optional(),
|
|
876
|
+
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
810
877
|
sourceRoot: z.string().optional(),
|
|
811
878
|
worktree: z
|
|
812
879
|
.object({
|
|
@@ -829,7 +896,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
829
896
|
targetBranch: z.string().optional(),
|
|
830
897
|
managed: z.boolean(),
|
|
831
898
|
current: z.boolean(),
|
|
832
|
-
})),
|
|
899
|
+
})).optional(),
|
|
833
900
|
staleWorkspaces: z.array(z.object({
|
|
834
901
|
workspaceId: z.string(),
|
|
835
902
|
root: z.string(),
|
|
@@ -839,9 +906,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
839
906
|
branch: z.string().optional(),
|
|
840
907
|
targetBranch: z.string().optional(),
|
|
841
908
|
managed: z.boolean(),
|
|
842
|
-
})),
|
|
843
|
-
capabilityFingerprint: capabilityFingerprintOutputSchema,
|
|
844
|
-
|
|
909
|
+
})).optional(),
|
|
910
|
+
capabilityFingerprint: capabilityFingerprintOutputSchema.optional(),
|
|
911
|
+
contextFingerprint: z.string().optional(),
|
|
912
|
+
capabilityCatalog: z.array(capabilityCatalogOutputSchema).optional(),
|
|
845
913
|
capabilityGuides: z.array(capabilityGuideOutputSchema).optional(),
|
|
846
914
|
agentsFiles: z.array(workspaceAgentsFileOutputSchema).optional(),
|
|
847
915
|
availableAgentsFiles: z.array(workspaceAvailableAgentsFileOutputSchema).optional(),
|
|
@@ -849,6 +917,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
849
917
|
agentProviders: z.array(workspaceLocalAgentProviderOutputSchema).optional(),
|
|
850
918
|
agents: z.array(workspaceLocalAgentOutputSchema).optional(),
|
|
851
919
|
skillDiagnostics: z.array(z.unknown()).optional(),
|
|
920
|
+
workspaces: z.array(workspaceInventoryEntryOutputSchema).optional(),
|
|
921
|
+
summary: workspaceInventorySummaryOutputSchema.optional(),
|
|
922
|
+
page: workspaceInventoryPageOutputSchema.optional(),
|
|
852
923
|
instruction: z.string(),
|
|
853
924
|
},
|
|
854
925
|
...toolWidgetDescriptorMeta(config, "workspace"),
|
|
@@ -858,11 +929,62 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
858
929
|
idempotentHint: false,
|
|
859
930
|
openWorldHint: false,
|
|
860
931
|
},
|
|
861
|
-
}, async ({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace }, { _meta, sessionId }) => {
|
|
932
|
+
}, async ({ action = "open", path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context, root, status, state, staleOnly, offset, limit, }, { _meta, sessionId }) => {
|
|
862
933
|
const startedAt = performance.now();
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
934
|
+
const conversationScopeId = openAiConversationScopeId(_meta);
|
|
935
|
+
const protectedWorkspaceIds = processSessions.activeWorkspaceIds();
|
|
936
|
+
if (action === "list") {
|
|
937
|
+
if (path !== undefined || baseRef !== undefined || newWorktree !== undefined ||
|
|
938
|
+
newWorkspace !== undefined || context !== undefined) {
|
|
939
|
+
throw new Error("open_workspace action=list does not accept path, baseRef, newWorktree, newWorkspace, or context. Use root/workspaceId/mode/status/state/staleOnly for inventory filters.");
|
|
940
|
+
}
|
|
941
|
+
const inventory = await workspaces.listWorkspaces({ workspaceId, mode, root, status, state, staleOnly, offset, limit }, { conversationScopeId, protectedWorkspaceIds });
|
|
942
|
+
const nextOffset = inventory.page.offset + inventory.page.limit;
|
|
943
|
+
const instruction = [
|
|
944
|
+
"Resume a selected workspaceId with open_workspace(action=\"open\", workspaceId=...).",
|
|
945
|
+
"Use close_workspace only after the user chooses cleanup; never close inventory entries automatically.",
|
|
946
|
+
inventory.page.hasMore
|
|
947
|
+
? `More matching workspaces are available; continue with offset=${nextOffset}.`
|
|
948
|
+
: undefined,
|
|
949
|
+
].filter(Boolean).join(" ");
|
|
950
|
+
const result = [
|
|
951
|
+
`Logical workspace inventory: ${inventory.summary.matching} matching of ${inventory.summary.total} stored records.`,
|
|
952
|
+
`States: active=${inventory.summary.active}, stale=${inventory.summary.stale}, invalid=${inventory.summary.invalid}, closed=${inventory.summary.closed}.`,
|
|
953
|
+
...inventory.workspaces.map((entry) => [
|
|
954
|
+
entry.label,
|
|
955
|
+
`state=${entry.state}`,
|
|
956
|
+
`status=${entry.status}`,
|
|
957
|
+
`mode=${entry.mode}`,
|
|
958
|
+
entry.managed ? "managed" : undefined,
|
|
959
|
+
entry.current ? "current" : undefined,
|
|
960
|
+
`root=${entry.root}`,
|
|
961
|
+
`last-used=${entry.lastUsedAt}`,
|
|
962
|
+
].filter(Boolean).join(" ")),
|
|
963
|
+
instruction,
|
|
964
|
+
].join("\n");
|
|
965
|
+
logToolCall(config, {
|
|
966
|
+
tool: "open_workspace",
|
|
967
|
+
action: "list",
|
|
968
|
+
path: root,
|
|
969
|
+
success: true,
|
|
970
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
971
|
+
});
|
|
972
|
+
return {
|
|
973
|
+
content: [textBlock(result)],
|
|
974
|
+
structuredContent: {
|
|
975
|
+
action: "list",
|
|
976
|
+
...inventory,
|
|
977
|
+
instruction,
|
|
978
|
+
},
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
if (root !== undefined || status !== undefined || state !== undefined ||
|
|
982
|
+
staleOnly !== undefined || offset !== undefined || limit !== undefined) {
|
|
983
|
+
throw new Error("open_workspace inventory filters root, status, state, staleOnly, offset, and limit are only valid with action=list.");
|
|
984
|
+
}
|
|
985
|
+
const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, contextFingerprint, } = await workspaces.openWorkspace({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context }, {
|
|
986
|
+
conversationScopeId,
|
|
987
|
+
protectedWorkspaceIds,
|
|
866
988
|
});
|
|
867
989
|
const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
|
|
868
990
|
const staleWorkspaces = await workspaces.listStaleWorkspaces(workspace);
|
|
@@ -912,20 +1034,26 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
912
1034
|
const visibleAgents = includeBootstrapContext ? cardAgents : [];
|
|
913
1035
|
const loadedAgentsFiles = includeBootstrapContext ? cardAgentsFiles : [];
|
|
914
1036
|
const availableAgentsFileOutputs = includeBootstrapContext ? cardAvailableAgentsFiles : [];
|
|
1037
|
+
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.";
|
|
1038
|
+
const workspaceManagementInstruction = "When you need to continue an earlier logical workspace or organize workspace state, use open_workspace(action=\"list\") to inspect candidates, then resume a selected workspaceId or ask the user before close_workspace cleanup.";
|
|
915
1039
|
const cardInstruction = config.skillsEnabled
|
|
916
|
-
?
|
|
917
|
-
:
|
|
1040
|
+
? `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 or capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`
|
|
1041
|
+
: `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}`;
|
|
918
1042
|
const instruction = workspaceReused
|
|
919
1043
|
? includeBootstrapContext
|
|
920
1044
|
? [
|
|
921
1045
|
`Workspace already exists as ${workspace.id} for this directory.`,
|
|
922
1046
|
"Reuse this workspaceId for subsequent tool calls.",
|
|
923
1047
|
"The complete project context is included because it has not yet been provided in this conversation or host context.",
|
|
1048
|
+
workspaceContextInstruction,
|
|
1049
|
+
workspaceManagementInstruction,
|
|
924
1050
|
].join("\n\n")
|
|
925
1051
|
: [
|
|
926
1052
|
`Workspace already open as ${workspace.id}.`,
|
|
927
1053
|
"Reuse this workspaceId for subsequent tool calls. This is the same directory previously opened in this conversation.",
|
|
928
1054
|
"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.",
|
|
1055
|
+
workspaceContextInstruction,
|
|
1056
|
+
workspaceManagementInstruction,
|
|
929
1057
|
].join("\n\n")
|
|
930
1058
|
: workspace.mode === "worktree"
|
|
931
1059
|
? "Use this workspaceId for subsequent tool calls. Follow the project instructions, nested instruction files, skills, agent profiles, and diagnostics returned for this isolated worktree."
|
|
@@ -999,6 +1127,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
999
1127
|
worktrees: knownWorktrees,
|
|
1000
1128
|
staleWorkspaces,
|
|
1001
1129
|
capabilityFingerprint,
|
|
1130
|
+
contextFingerprint,
|
|
1002
1131
|
capabilityCatalog,
|
|
1003
1132
|
agentsFiles: cardAgentsFiles,
|
|
1004
1133
|
availableAgentsFiles: cardAvailableAgentsFiles,
|
|
@@ -1018,6 +1147,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1018
1147
|
},
|
|
1019
1148
|
},
|
|
1020
1149
|
structuredContent: {
|
|
1150
|
+
action: "open",
|
|
1021
1151
|
workspaceId: workspace.id,
|
|
1022
1152
|
root: workspace.root,
|
|
1023
1153
|
mode: workspace.mode,
|
|
@@ -1026,6 +1156,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1026
1156
|
worktrees: knownWorktrees,
|
|
1027
1157
|
staleWorkspaces,
|
|
1028
1158
|
capabilityFingerprint,
|
|
1159
|
+
contextFingerprint,
|
|
1029
1160
|
capabilityCatalog,
|
|
1030
1161
|
...(includeBootstrapContext
|
|
1031
1162
|
? {
|
|
@@ -1120,9 +1251,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1120
1251
|
...(execution.card
|
|
1121
1252
|
? {
|
|
1122
1253
|
_meta: {
|
|
1123
|
-
tool:
|
|
1254
|
+
tool: toolNames.capability,
|
|
1124
1255
|
card: {
|
|
1125
1256
|
workspaceId,
|
|
1257
|
+
capabilityName: name,
|
|
1126
1258
|
summary: execution.card.summary ?? {},
|
|
1127
1259
|
files: execution.card.files,
|
|
1128
1260
|
payload: execution.card.payload ?? {},
|
|
@@ -1716,255 +1848,6 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1716
1848
|
});
|
|
1717
1849
|
});
|
|
1718
1850
|
}
|
|
1719
|
-
if (config.widgets === "changes") {
|
|
1720
|
-
registerAppTool(server, "show_changes", {
|
|
1721
|
-
title: "Show changes",
|
|
1722
|
-
description: "Show the changes made in this turn for an open workspace. Call this once after the final related file change and before your final response so the user can review the combined diff. Do not call it after each individual file change.",
|
|
1723
|
-
inputSchema: {
|
|
1724
|
-
workspaceId: z
|
|
1725
|
-
.string()
|
|
1726
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
1727
|
-
},
|
|
1728
|
-
outputSchema: resultOutputSchema(),
|
|
1729
|
-
...toolWidgetDescriptorMeta(config, "show_changes"),
|
|
1730
|
-
annotations: { readOnlyHint: true },
|
|
1731
|
-
}, async ({ workspaceId }, extra) => {
|
|
1732
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1733
|
-
return runToolWithHooks(hooks, {
|
|
1734
|
-
tool: "show_changes",
|
|
1735
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1736
|
-
operation: async () => {
|
|
1737
|
-
const startedAt = performance.now();
|
|
1738
|
-
const review = await reviewWorkspaceChanges(reviewCheckpoints, workspace);
|
|
1739
|
-
const content = [textBlock(review.result)];
|
|
1740
|
-
logToolCall(config, {
|
|
1741
|
-
tool: "show_changes",
|
|
1742
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1743
|
-
success: true,
|
|
1744
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1745
|
-
});
|
|
1746
|
-
return {
|
|
1747
|
-
content,
|
|
1748
|
-
_meta: {
|
|
1749
|
-
tool: "show_changes",
|
|
1750
|
-
card: {
|
|
1751
|
-
workspaceId,
|
|
1752
|
-
summary: review.summary,
|
|
1753
|
-
files: review.files,
|
|
1754
|
-
payload: {
|
|
1755
|
-
patch: review.patch,
|
|
1756
|
-
},
|
|
1757
|
-
},
|
|
1758
|
-
},
|
|
1759
|
-
structuredContent: {
|
|
1760
|
-
result: contentText(content),
|
|
1761
|
-
},
|
|
1762
|
-
};
|
|
1763
|
-
},
|
|
1764
|
-
});
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
if (config.toolMode === "full") {
|
|
1768
|
-
registerAppTool(server, toolNames.grep, {
|
|
1769
|
-
title: "Grep",
|
|
1770
|
-
description: "Search file contents inside an open workspace or the OS temp directory. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
|
|
1771
|
-
inputSchema: {
|
|
1772
|
-
workspaceId: z
|
|
1773
|
-
.string()
|
|
1774
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
1775
|
-
pattern: z.string().describe("Search pattern."),
|
|
1776
|
-
path: z
|
|
1777
|
-
.string()
|
|
1778
|
-
.optional()
|
|
1779
|
-
.describe("Optional path or glob scope relative to the workspace root, or an absolute path inside the OS temp directory."),
|
|
1780
|
-
include: z.string().optional().describe("Optional include glob."),
|
|
1781
|
-
},
|
|
1782
|
-
outputSchema: resultOutputSchema(),
|
|
1783
|
-
...toolWidgetDescriptorMeta(config, "search"),
|
|
1784
|
-
annotations: { readOnlyHint: true },
|
|
1785
|
-
}, async ({ workspaceId, ...input }, extra) => {
|
|
1786
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1787
|
-
return runToolWithHooks(hooks, {
|
|
1788
|
-
tool: toolNames.grep,
|
|
1789
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1790
|
-
payload: { pattern: input.pattern, path: input.path, include: input.include },
|
|
1791
|
-
isFailure: toolResultIsError,
|
|
1792
|
-
operation: async () => {
|
|
1793
|
-
const startedAt = performance.now();
|
|
1794
|
-
const response = await grepFilesTool(input, {
|
|
1795
|
-
cwd: workspace.root,
|
|
1796
|
-
root: workspace.root,
|
|
1797
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1798
|
-
});
|
|
1799
|
-
if (response.isError) {
|
|
1800
|
-
logFailedToolResponse(config, {
|
|
1801
|
-
tool: toolNames.grep,
|
|
1802
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1803
|
-
path: input.path,
|
|
1804
|
-
}, response.content, startedAt);
|
|
1805
|
-
return response;
|
|
1806
|
-
}
|
|
1807
|
-
const summary = {
|
|
1808
|
-
pattern: input.pattern,
|
|
1809
|
-
scope: input.path ?? ".",
|
|
1810
|
-
...textSummary(response.content),
|
|
1811
|
-
};
|
|
1812
|
-
logToolCall(config, {
|
|
1813
|
-
tool: toolNames.grep,
|
|
1814
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1815
|
-
path: input.path,
|
|
1816
|
-
success: true,
|
|
1817
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1818
|
-
});
|
|
1819
|
-
return {
|
|
1820
|
-
...response,
|
|
1821
|
-
_meta: {
|
|
1822
|
-
tool: toolNames.grep,
|
|
1823
|
-
card: {
|
|
1824
|
-
workspaceId,
|
|
1825
|
-
path: input.path,
|
|
1826
|
-
summary,
|
|
1827
|
-
payload: { content: response.content },
|
|
1828
|
-
},
|
|
1829
|
-
},
|
|
1830
|
-
structuredContent: {
|
|
1831
|
-
result: contentText(response.content),
|
|
1832
|
-
},
|
|
1833
|
-
};
|
|
1834
|
-
},
|
|
1835
|
-
});
|
|
1836
|
-
});
|
|
1837
|
-
registerAppTool(server, toolNames.glob, {
|
|
1838
|
-
title: "Glob",
|
|
1839
|
-
description: "Find files by glob pattern inside an open workspace or the OS temp directory. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
|
|
1840
|
-
inputSchema: {
|
|
1841
|
-
workspaceId: z
|
|
1842
|
-
.string()
|
|
1843
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
1844
|
-
pattern: z.string().describe("File glob pattern."),
|
|
1845
|
-
path: z
|
|
1846
|
-
.string()
|
|
1847
|
-
.optional()
|
|
1848
|
-
.describe("Optional path scope relative to the workspace root, or an absolute path inside the OS temp directory."),
|
|
1849
|
-
},
|
|
1850
|
-
outputSchema: resultOutputSchema(),
|
|
1851
|
-
...toolWidgetDescriptorMeta(config, "search"),
|
|
1852
|
-
annotations: { readOnlyHint: true },
|
|
1853
|
-
}, async ({ workspaceId, ...input }, extra) => {
|
|
1854
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1855
|
-
return runToolWithHooks(hooks, {
|
|
1856
|
-
tool: toolNames.glob,
|
|
1857
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1858
|
-
payload: { pattern: input.pattern, path: input.path },
|
|
1859
|
-
isFailure: toolResultIsError,
|
|
1860
|
-
operation: async () => {
|
|
1861
|
-
const startedAt = performance.now();
|
|
1862
|
-
const response = await findFilesTool(input, {
|
|
1863
|
-
cwd: workspace.root,
|
|
1864
|
-
root: workspace.root,
|
|
1865
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1866
|
-
});
|
|
1867
|
-
if (response.isError) {
|
|
1868
|
-
logFailedToolResponse(config, {
|
|
1869
|
-
tool: toolNames.glob,
|
|
1870
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1871
|
-
path: input.path,
|
|
1872
|
-
}, response.content, startedAt);
|
|
1873
|
-
return response;
|
|
1874
|
-
}
|
|
1875
|
-
const summary = {
|
|
1876
|
-
pattern: input.pattern,
|
|
1877
|
-
scope: input.path ?? ".",
|
|
1878
|
-
...textSummary(response.content),
|
|
1879
|
-
};
|
|
1880
|
-
logToolCall(config, {
|
|
1881
|
-
tool: toolNames.glob,
|
|
1882
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1883
|
-
path: input.path,
|
|
1884
|
-
success: true,
|
|
1885
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1886
|
-
});
|
|
1887
|
-
return {
|
|
1888
|
-
...response,
|
|
1889
|
-
_meta: {
|
|
1890
|
-
tool: toolNames.glob,
|
|
1891
|
-
card: {
|
|
1892
|
-
workspaceId,
|
|
1893
|
-
path: input.path,
|
|
1894
|
-
summary,
|
|
1895
|
-
payload: { content: response.content },
|
|
1896
|
-
},
|
|
1897
|
-
},
|
|
1898
|
-
structuredContent: {
|
|
1899
|
-
result: contentText(response.content),
|
|
1900
|
-
},
|
|
1901
|
-
};
|
|
1902
|
-
},
|
|
1903
|
-
});
|
|
1904
|
-
});
|
|
1905
|
-
registerAppTool(server, toolNames.ls, {
|
|
1906
|
-
title: "Ls",
|
|
1907
|
-
description: "List a directory inside an open workspace or the OS temp directory. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.",
|
|
1908
|
-
inputSchema: {
|
|
1909
|
-
workspaceId: z
|
|
1910
|
-
.string()
|
|
1911
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
1912
|
-
path: z
|
|
1913
|
-
.string()
|
|
1914
|
-
.describe("Directory path to list, relative to the workspace root or absolute inside the OS temp directory."),
|
|
1915
|
-
},
|
|
1916
|
-
outputSchema: resultOutputSchema(),
|
|
1917
|
-
...toolWidgetDescriptorMeta(config, "directory"),
|
|
1918
|
-
annotations: { readOnlyHint: true },
|
|
1919
|
-
}, async ({ workspaceId, ...input }, extra) => {
|
|
1920
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1921
|
-
return runToolWithHooks(hooks, {
|
|
1922
|
-
tool: toolNames.ls,
|
|
1923
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1924
|
-
payload: { path: input.path },
|
|
1925
|
-
isFailure: toolResultIsError,
|
|
1926
|
-
operation: async () => {
|
|
1927
|
-
const startedAt = performance.now();
|
|
1928
|
-
const response = await listDirectoryTool(input, {
|
|
1929
|
-
cwd: workspace.root,
|
|
1930
|
-
root: workspace.root,
|
|
1931
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1932
|
-
});
|
|
1933
|
-
if (response.isError) {
|
|
1934
|
-
logFailedToolResponse(config, {
|
|
1935
|
-
tool: toolNames.ls,
|
|
1936
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1937
|
-
path: input.path,
|
|
1938
|
-
}, response.content, startedAt);
|
|
1939
|
-
return response;
|
|
1940
|
-
}
|
|
1941
|
-
const summary = textSummary(response.content);
|
|
1942
|
-
logToolCall(config, {
|
|
1943
|
-
tool: toolNames.ls,
|
|
1944
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1945
|
-
path: input.path,
|
|
1946
|
-
success: true,
|
|
1947
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1948
|
-
});
|
|
1949
|
-
return {
|
|
1950
|
-
...response,
|
|
1951
|
-
_meta: {
|
|
1952
|
-
tool: toolNames.ls,
|
|
1953
|
-
card: {
|
|
1954
|
-
workspaceId,
|
|
1955
|
-
path: input.path,
|
|
1956
|
-
summary,
|
|
1957
|
-
payload: { content: response.content },
|
|
1958
|
-
},
|
|
1959
|
-
},
|
|
1960
|
-
structuredContent: {
|
|
1961
|
-
result: contentText(response.content),
|
|
1962
|
-
},
|
|
1963
|
-
};
|
|
1964
|
-
},
|
|
1965
|
-
});
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
1968
1851
|
if (config.toolMode !== "codex") {
|
|
1969
1852
|
registerAppTool(server, toolNames.shell, {
|
|
1970
1853
|
title: "Bash",
|
|
@@ -2146,15 +2029,6 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2146
2029
|
});
|
|
2147
2030
|
}
|
|
2148
2031
|
registerProcessTools(server, config, workspaces, processSessions, hooks);
|
|
2149
|
-
if (config.artifactsEnabled && isArtifactDownloadSupportedPlatform()) {
|
|
2150
|
-
registerArtifactTools(server, {
|
|
2151
|
-
config,
|
|
2152
|
-
workspaces,
|
|
2153
|
-
hooks,
|
|
2154
|
-
incomingArtifactAdapters,
|
|
2155
|
-
incomingArtifactRegistry,
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
2032
|
return server;
|
|
2159
2033
|
}
|
|
2160
2034
|
export function createServer(config = loadConfig(), options = {}) {
|