@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/docs/gotchas.md
CHANGED
|
@@ -234,8 +234,8 @@ Per-tool widgets default to:
|
|
|
234
234
|
FORGERELAY_WIDGETS=full
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
Use `FORGERELAY_WIDGETS=changes` for aggregate `
|
|
238
|
-
disable UI. Plain MCP clients may ignore MCP App widget metadata.
|
|
237
|
+
Use `FORGERELAY_WIDGETS=changes` for aggregate `review.changes` Capability results,
|
|
238
|
+
or `off` to disable UI. Plain MCP clients may ignore MCP App widget metadata.
|
|
239
239
|
|
|
240
240
|
If ChatGPT shows `Failed to fetch template`, first verify the server-side template
|
|
241
241
|
chain with:
|
|
@@ -245,7 +245,7 @@ npm run build
|
|
|
245
245
|
npm run debug:accept
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
The acceptance runner enables
|
|
248
|
+
The acceptance runner enables review-focused widgets and checks that the Gateway advertises a
|
|
249
249
|
content-hashed `ui://forgerelay/workspace-app-<hash>.html` resource, that
|
|
250
250
|
`resources/read` returns `text/html;profile=mcp-app`, and that the referenced
|
|
251
251
|
JavaScript asset is reachable. ForgeRelay also keeps the legacy
|
package/docs/roadmap.md
CHANGED
|
@@ -170,7 +170,18 @@ capability
|
|
|
170
170
|
- 删除已经完成迁移的 dedicated low-frequency tool aliases,确保新增 Capability 不再扩大常驻 tool count;
|
|
171
171
|
- 简化 fingerprint,使其用于版本/运行时能力摘要与 stale-Host 诊断,而不是重新枚举 tool implementation;
|
|
172
172
|
- 对 `open_workspace → catalog → capability describe/read/run`、managed worktree close、长进程 interaction、review/artifact capability、MCP App 与 stale-schema 情况做 7677 acceptance 和新 Host 会话验收;
|
|
173
|
-
- 0.3.5
|
|
173
|
+
- 0.3.5 完成 canonical MCP surface 与 fresh-Host 主验收后,0.3 的 progressive-disclosure 主体设计视为稳定;后续 0.3.x 只接收验收暴露出的兼容性或 lifecycle 补丁,0.4 仍回到原定 LSP code intelligence v1。
|
|
174
|
+
|
|
175
|
+
### 0.3.6 — Workspace bootstrap 与 inventory 补丁
|
|
176
|
+
|
|
177
|
+
0.3.6 处理 0.3.5 fresh-Host 验收后暴露的 Workspace 上下文与 logical-workspace 管理问题,不增加新的常驻 MCP tool,也不改变 0.4 的 LSP 主路线:
|
|
178
|
+
|
|
179
|
+
- `open_workspace` 的 bootstrap 去重从 logical `workspaceId` 身份提升为 conversation scope + canonical workspace target + context fingerprint;同一 conversation 切换到同一物理 checkout/worktree 的其他 logical workspace 时,只要 AGENTS、Skills、Capability guide/profile 等相关上下文没有变化,就不重复注入完整 bootstrap;
|
|
180
|
+
- `open_workspace` 提供 `context="auto" | "full" | "none"`。`auto` 默认只在当前 fingerprint 尚未交付或已变化时返回完整上下文,`full` 强制刷新,`none` 只取得 workspace handle/metadata 且不会把该 fingerprint 标记为已交付;
|
|
181
|
+
- context-delivery 状态与 conversation 当前绑定的 logical workspace 分离持久化,因此关闭或切换 logical handle 不会让 Agent 在同一 conversation 中忘记已经收到的项目上下文;delivery 状态继续服从有限生命周期清理,而不是永久缓存;
|
|
182
|
+
- `open_workspace(action="list")` 成为按需 logical-workspace inventory 入口,不增加 `list_workspaces`/`workspace.list` 第十个 Core tool;普通开发仍直接使用默认 `action="open"`,只有续接旧任务、选择 workspace 或整理状态时才读取 inventory;
|
|
183
|
+
- inventory 区分持久化 `status` 与派生 `state`:`status="active"` 表示尚未显式关闭,`state` 再区分 active、stale、invalid 与 closed;missing root 或外部删除的 managed worktree 可以保持可诊断的 active record,同时显示为 invalid;
|
|
184
|
+
- inventory 查看本身不刷新 workspace `lastUsedAt`,支持过滤与分页,并继续让现有 `close_workspace` 承担用户确认后的实际清理/finalize lifecycle。
|
|
174
185
|
|
|
175
186
|
必要安全语义始终留在 Core tool interface、Capability contract 或自动 Hook report 中;渐进式披露不能成为隐藏权限、隐式 autonomous workflow 或绕过 allowed roots/auth 的机制。`rename` 继续作为文件和目录 move/rename 的统一 primitive。
|
|
176
187
|
|
package/docs/security.md
CHANGED
|
@@ -179,8 +179,9 @@ the source checkout into a merge-conflict state.
|
|
|
179
179
|
|
|
180
180
|
## Native artifact download
|
|
181
181
|
|
|
182
|
-
When enabled, `
|
|
183
|
-
provided by the MCP host
|
|
182
|
+
When enabled, the registered `artifact.download` Capability accepts only the supported
|
|
183
|
+
native file value provided by the MCP host through the `capability` Gateway. It does
|
|
184
|
+
not accept arbitrary signed URLs, local paths,
|
|
184
185
|
embedded credentials, or base64 strings as substitutes.
|
|
185
186
|
|
|
186
187
|
Downloads are streamed, size-limited, created without overwrite, and published
|
package/package.json
CHANGED
package/scripts/debug/accept.mjs
CHANGED
|
@@ -37,14 +37,16 @@ assertCurlAvailable();
|
|
|
37
37
|
await assertDebugPortFree();
|
|
38
38
|
rmSync(acceptanceRoot, { recursive: true, force: true });
|
|
39
39
|
mkdirSync(acceptanceRoot, { recursive: true });
|
|
40
|
+
setupGitProject(checkoutWorkspace);
|
|
40
41
|
|
|
41
42
|
const { env } = createDebugEnvironment({
|
|
42
43
|
ownerToken,
|
|
43
44
|
stateDir,
|
|
44
45
|
worktreeRoot,
|
|
45
46
|
hookLog,
|
|
46
|
-
widgets: "
|
|
47
|
+
widgets: "changes",
|
|
47
48
|
});
|
|
49
|
+
env.FORGERELAY_ARTIFACTS = "1";
|
|
48
50
|
const doctor = spawnSync(process.execPath, ["--import", "tsx", "src/cli.ts", "doctor"], {
|
|
49
51
|
cwd: repoRoot,
|
|
50
52
|
env,
|
|
@@ -53,7 +55,7 @@ const doctor = spawnSync(process.execPath, ["--import", "tsx", "src/cli.ts", "do
|
|
|
53
55
|
assert.equal(doctor.status, 0, doctor.stderr);
|
|
54
56
|
assert.match(doctor.stdout, /Public base URL: http:\/\/127\.0\.0\.1:7677/);
|
|
55
57
|
assert.match(doctor.stdout, /Tool mode: full/);
|
|
56
|
-
assert.match(doctor.stdout, /Widgets:
|
|
58
|
+
assert.match(doctor.stdout, /Widgets: changes/);
|
|
57
59
|
assert.match(doctor.stdout, /Trust proxy: off/);
|
|
58
60
|
pass("doctor resolved MCP shape", "public URL + tool/widgets/proxy state");
|
|
59
61
|
|
|
@@ -129,11 +131,17 @@ try {
|
|
|
129
131
|
params: {},
|
|
130
132
|
}).message.result.tools;
|
|
131
133
|
const toolNames = tools.map((tool) => tool.name);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
assert.deepEqual(toolNames, [
|
|
135
|
+
"open_workspace",
|
|
136
|
+
"capability",
|
|
137
|
+
"close_workspace",
|
|
138
|
+
"read",
|
|
139
|
+
"write",
|
|
140
|
+
"edit",
|
|
141
|
+
"rename",
|
|
142
|
+
"delete",
|
|
143
|
+
"bash",
|
|
144
|
+
]);
|
|
137
145
|
const bashTool = tools.find((tool) => tool.name === "bash");
|
|
138
146
|
assert.match(bashTool?.description ?? "", /local user's authority/);
|
|
139
147
|
assert.doesNotMatch(bashTool?.description ?? "", /may modify ordinary project files/);
|
|
@@ -152,14 +160,16 @@ try {
|
|
|
152
160
|
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityFingerprint);
|
|
153
161
|
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityCatalog);
|
|
154
162
|
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityGuides);
|
|
155
|
-
const
|
|
163
|
+
const capabilityTool = tools.find((tool) => tool.name === "capability");
|
|
164
|
+
assert.deepEqual(capabilityTool?._meta?.["openai/fileParams"], ["file"]);
|
|
165
|
+
const templateUri = capabilityTool?._meta?.ui?.resourceUri;
|
|
156
166
|
assert.match(
|
|
157
167
|
templateUri ?? "",
|
|
158
168
|
/^ui:\/\/forgerelay\/workspace-app-[0-9a-f]{12}\.html$/,
|
|
159
169
|
JSON.stringify(bashTool ?? {}),
|
|
160
170
|
);
|
|
161
|
-
assert.deepEqual(
|
|
162
|
-
assert.equal(
|
|
171
|
+
assert.deepEqual(capabilityTool?._meta?.ui?.visibility, ["model", "app"]);
|
|
172
|
+
assert.equal(capabilityTool?._meta?.["openai/outputTemplate"], templateUri);
|
|
163
173
|
pass("MCP tools/list", `${toolNames.length} tools: ${toolNames.join(", ")}`);
|
|
164
174
|
|
|
165
175
|
const resources = mcpRequest(oauth.accessToken, sessionId, {
|
|
@@ -213,13 +223,16 @@ try {
|
|
|
213
223
|
assert.equal(historicalTemplate.text, template.text);
|
|
214
224
|
pass("MCP app template", `${templateUri} + legacy/history compatibility -> ${scriptUrl}`);
|
|
215
225
|
|
|
226
|
+
const workspaceConversationMeta = { "openai/session": "acceptance-workspace" };
|
|
216
227
|
const opened = callTool(oauth.accessToken, sessionId, 3, "open_workspace", {
|
|
217
228
|
path: checkoutWorkspace,
|
|
218
|
-
});
|
|
229
|
+
}, workspaceConversationMeta);
|
|
219
230
|
const workspaceId = opened.structuredContent.workspaceId;
|
|
220
231
|
assert.match(workspaceId, /^ws_/);
|
|
232
|
+
assert.equal(opened.structuredContent.action, "open");
|
|
221
233
|
assert.equal(opened.structuredContent.root, checkoutWorkspace);
|
|
222
234
|
assert.equal(opened.structuredContent.mode, "checkout");
|
|
235
|
+
assert.equal(typeof opened.structuredContent.contextFingerprint, "string");
|
|
223
236
|
assert.deepEqual(opened.structuredContent.capabilityFingerprint, {
|
|
224
237
|
version: packageJson.version,
|
|
225
238
|
toolMode: "full",
|
|
@@ -231,14 +244,69 @@ try {
|
|
|
231
244
|
"process.lifecycle",
|
|
232
245
|
"hooks.lifecycle",
|
|
233
246
|
"capability-guides.read",
|
|
234
|
-
"
|
|
247
|
+
...(process.platform === "linux" ? ["artifact.native-download"] : []),
|
|
235
248
|
"ui.mcp-app",
|
|
249
|
+
"review.changes",
|
|
236
250
|
],
|
|
237
251
|
});
|
|
238
252
|
const capabilityCatalog = opened.structuredContent.capabilityCatalog;
|
|
239
|
-
assert.deepEqual(capabilityCatalog.map((entry) => entry.name), [
|
|
253
|
+
assert.deepEqual(capabilityCatalog.map((entry) => entry.name), [
|
|
254
|
+
"hooks.check",
|
|
255
|
+
"review.changes",
|
|
256
|
+
...(process.platform === "linux" ? ["artifact.download"] : []),
|
|
257
|
+
]);
|
|
240
258
|
assert.equal(capabilityCatalog[0].available, true);
|
|
241
259
|
assert.equal(capabilityCatalog[0].guide.name, "lifecycle-hooks");
|
|
260
|
+
|
|
261
|
+
const freshLogical = callTool(oauth.accessToken, sessionId, 84, "open_workspace", {
|
|
262
|
+
path: checkoutWorkspace,
|
|
263
|
+
newWorkspace: true,
|
|
264
|
+
}, workspaceConversationMeta);
|
|
265
|
+
const freshLogicalId = freshLogical.structuredContent.workspaceId;
|
|
266
|
+
assert.notEqual(freshLogicalId, workspaceId);
|
|
267
|
+
assert.equal(freshLogical.structuredContent.action, "open");
|
|
268
|
+
assert.equal(
|
|
269
|
+
freshLogical.structuredContent.contextFingerprint,
|
|
270
|
+
opened.structuredContent.contextFingerprint,
|
|
271
|
+
);
|
|
272
|
+
assert.equal(freshLogical.structuredContent.agentsFiles, undefined);
|
|
273
|
+
assert.equal(freshLogical.structuredContent.capabilityGuides, undefined);
|
|
274
|
+
|
|
275
|
+
const workspaceInventory = callTool(oauth.accessToken, sessionId, 85, "open_workspace", {
|
|
276
|
+
action: "list",
|
|
277
|
+
root: checkoutWorkspace,
|
|
278
|
+
}, workspaceConversationMeta);
|
|
279
|
+
assert.equal(workspaceInventory.structuredContent.action, "list");
|
|
280
|
+
assert.equal(workspaceInventory.structuredContent.summary.matching, 2);
|
|
281
|
+
const inventoryEntries = workspaceInventory.structuredContent.workspaces;
|
|
282
|
+
assert.equal(inventoryEntries.length, 2);
|
|
283
|
+
assert.equal(
|
|
284
|
+
inventoryEntries.find((entry) => entry.workspaceId === freshLogicalId)?.current,
|
|
285
|
+
true,
|
|
286
|
+
);
|
|
287
|
+
assert.equal(
|
|
288
|
+
inventoryEntries.find((entry) => entry.workspaceId === workspaceId)?.current,
|
|
289
|
+
false,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const closedFreshLogical = callTool(oauth.accessToken, sessionId, 86, "close_workspace", {
|
|
293
|
+
workspaceId: freshLogicalId,
|
|
294
|
+
});
|
|
295
|
+
assert.equal(closedFreshLogical.isError, undefined);
|
|
296
|
+
const resumedOriginal = callTool(oauth.accessToken, sessionId, 87, "open_workspace", {
|
|
297
|
+
workspaceId,
|
|
298
|
+
}, workspaceConversationMeta);
|
|
299
|
+
assert.equal(resumedOriginal.structuredContent.workspaceId, workspaceId);
|
|
300
|
+
assert.equal(resumedOriginal.structuredContent.agentsFiles, undefined);
|
|
301
|
+
assert.equal(
|
|
302
|
+
resumedOriginal.structuredContent.contextFingerprint,
|
|
303
|
+
opened.structuredContent.contextFingerprint,
|
|
304
|
+
);
|
|
305
|
+
pass(
|
|
306
|
+
"workspace context + inventory",
|
|
307
|
+
`${workspaceId} -> ${freshLogicalId} -> list -> close -> resume without bootstrap replay`,
|
|
308
|
+
);
|
|
309
|
+
|
|
242
310
|
const directCapability = callTool(oauth.accessToken, sessionId, 79, "capability", {
|
|
243
311
|
workspaceId,
|
|
244
312
|
name: "hooks.check",
|
|
@@ -255,6 +323,18 @@ try {
|
|
|
255
323
|
assert.equal(describedCapability.isError, undefined);
|
|
256
324
|
assert.equal(describedCapability.structuredContent.capability.guide.name, "lifecycle-hooks");
|
|
257
325
|
assert.equal(describedCapability.structuredContent.capability.inputSchema.type, "object");
|
|
326
|
+
if (process.platform === "linux") {
|
|
327
|
+
const describedArtifact = callTool(oauth.accessToken, sessionId, 82, "capability", {
|
|
328
|
+
workspaceId,
|
|
329
|
+
name: "artifact.download",
|
|
330
|
+
action: "describe",
|
|
331
|
+
});
|
|
332
|
+
assert.equal(describedArtifact.isError, undefined);
|
|
333
|
+
assert.deepEqual(describedArtifact.structuredContent.capability.transport, {
|
|
334
|
+
nativeFileArgument: "file",
|
|
335
|
+
gatewayParameter: "file",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
258
338
|
const unknownCapability = callTool(oauth.accessToken, sessionId, 81, "capability", {
|
|
259
339
|
workspaceId,
|
|
260
340
|
name: "unknown.capability",
|
|
@@ -267,6 +347,7 @@ try {
|
|
|
267
347
|
assert.deepEqual(capabilityGuides.map((guide) => guide.name), [
|
|
268
348
|
"lifecycle-hooks",
|
|
269
349
|
"managed-worktrees",
|
|
350
|
+
"artifacts-review",
|
|
270
351
|
"host-integration",
|
|
271
352
|
"shell-processes",
|
|
272
353
|
]);
|
|
@@ -292,6 +373,19 @@ try {
|
|
|
292
373
|
assert.match(read.structuredContent.result, /forgerelay 7677 acceptance/);
|
|
293
374
|
pass("write + read", JSON.stringify(read.structuredContent));
|
|
294
375
|
|
|
376
|
+
const reviewed = callTool(oauth.accessToken, sessionId, 83, "capability", {
|
|
377
|
+
workspaceId,
|
|
378
|
+
name: "review.changes",
|
|
379
|
+
action: "run",
|
|
380
|
+
arguments: {},
|
|
381
|
+
});
|
|
382
|
+
assert.equal(reviewed.isError, undefined);
|
|
383
|
+
assert.match(reviewed.structuredContent.result.result, /Changed 1 file/);
|
|
384
|
+
assert.equal(reviewed._meta?.tool, "capability");
|
|
385
|
+
assert.equal(reviewed._meta?.card?.capabilityName, "review.changes");
|
|
386
|
+
assert.match(reviewed._meta?.card?.payload?.patch ?? "", /acceptance\.txt/);
|
|
387
|
+
pass("review.changes", "Capability Gateway produced the aggregate review card");
|
|
388
|
+
|
|
295
389
|
const shell = callTool(oauth.accessToken, sessionId, 6, "bash", {
|
|
296
390
|
workspaceId,
|
|
297
391
|
command: "printf debug-bash-ok",
|
|
@@ -549,12 +643,16 @@ function mcpRequest(accessToken, sessionId, request) {
|
|
|
549
643
|
return { response, message: parseMcpMessage(response.body, request.id) };
|
|
550
644
|
}
|
|
551
645
|
|
|
552
|
-
function callTool(accessToken, sessionId, id, name, args) {
|
|
646
|
+
function callTool(accessToken, sessionId, id, name, args, meta) {
|
|
553
647
|
const message = mcpRequest(accessToken, sessionId, {
|
|
554
648
|
jsonrpc: "2.0",
|
|
555
649
|
id,
|
|
556
650
|
method: "tools/call",
|
|
557
|
-
params: {
|
|
651
|
+
params: {
|
|
652
|
+
name,
|
|
653
|
+
arguments: args,
|
|
654
|
+
...(meta ? { _meta: meta } : {}),
|
|
655
|
+
},
|
|
558
656
|
}).message;
|
|
559
657
|
assert.equal(message.id, id);
|
|
560
658
|
assert.ok(message.result, `tool ${name} did not return a result`);
|