@akira-tl/forgerelay 0.8.8 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +10 -18
- package/capabilities/code-intelligence/GUIDE.md +7 -3
- package/capabilities/host-integration/GUIDE.md +2 -2
- package/capabilities/subagents/GUIDE.md +9 -0
- package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +6 -0
- package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
- package/dist/activity/history/bash-output-store.js +250 -0
- package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
- package/dist/activity/{query-service.js → history/query-service.js} +13 -7
- package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
- package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
- package/dist/activity/storage/history.js +12 -0
- package/dist/activity/storage/paths.js +56 -0
- package/dist/activity/storage/segmented-log.js +149 -0
- package/dist/cli/init.js +180 -0
- package/dist/cli/setup-support.js +181 -0
- package/dist/cli.js +23 -216
- package/dist/lsp/code-intelligence.js +18 -4
- package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
- package/dist/lsp/runtime/managed-language-servers.js +172 -0
- package/dist/lsp/runtime/manager.js +37 -2
- package/dist/lsp/runtime/process-launch.js +3 -0
- package/dist/lsp/test-support/server-fixture.js +14 -11
- package/dist/mcp/filesystem/filesystem-tools.js +245 -0
- package/dist/mcp/hooks/command-runner.js +79 -0
- package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
- package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
- package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
- package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
- package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
- package/dist/mcp/panel/app.js +143 -0
- package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
- package/dist/{process-platform.js → mcp/process/process-platform.js} +21 -9
- package/dist/{process-sessions.js → mcp/process/process-sessions.js} +3 -3
- package/dist/mcp/process/runtime.js +257 -0
- package/dist/mcp/process/tools.js +346 -0
- package/dist/mcp/server/core/activity-support.js +70 -0
- package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
- package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +9 -4
- package/dist/mcp/server/core/capability-support.js +90 -0
- package/dist/mcp/server/core/schemas.js +216 -0
- package/dist/mcp/server/core/tool-support.js +210 -0
- package/dist/mcp/server/core/version.js +9 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +455 -0
- package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +524 -0
- package/dist/mcp/server/transport/http-server.js +301 -0
- package/dist/mcp/server/transport/http-support.js +43 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +242 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +555 -0
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/{config.js → runtime/config/config.js} +4 -3
- package/dist/{user-config.js → runtime/config/user-config.js} +14 -63
- package/dist/{db → runtime/state/db}/client.js +1 -1
- package/dist/{db → runtime/state/db}/migrations.js +34 -3
- package/dist/runtime/state/lock/file-lock.js +130 -0
- package/dist/runtime/testing/server-fixture.js +190 -0
- package/dist/server.js +133 -4189
- package/dist/subagents/providers/adapters/claude.js +86 -52
- package/dist/subagents/providers/adapters/codex.js +103 -39
- package/dist/subagents/providers/adapters/opencode.js +114 -84
- package/dist/subagents/providers/adapters/pi.js +59 -8
- package/dist/subagents/providers/availability.js +15 -30
- package/dist/subagents/providers/runtime/external-command.js +56 -0
- package/dist/subagents/sessions/capability.js +1 -1
- package/dist/subagents/sessions/execution.js +1 -1
- package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
- package/dist/subagents/sessions/store.js +1 -1
- package/dist/ui/.vite/manifest.json +342 -410
- package/dist/ui/activity-panel-app.html +2 -4
- package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
- package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
- package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
- package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
- package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
- package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
- package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
- package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
- package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
- package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
- package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
- package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
- package/dist/ui/assets/css-C1tjBtVN.js +1 -0
- package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
- package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
- package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
- package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
- package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
- package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
- package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
- package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
- package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
- package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
- package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
- package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
- package/dist/ui/assets/html-By4dNnM4.js +1 -0
- package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
- package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
- package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
- package/dist/ui/assets/java-DYRe2lWG.js +1 -0
- package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
- package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
- package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
- package/dist/ui/assets/json-dbltgiiA.js +1 -0
- package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
- package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
- package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
- package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
- package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
- package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
- package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
- package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
- package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
- package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
- package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
- package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
- package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
- package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
- package/dist/ui/assets/r-CcezTbLq.js +1 -0
- package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
- package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
- package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
- package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
- package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
- package/dist/ui/assets/scss-BQb-cp01.js +1 -0
- package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
- package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
- package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
- package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
- package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
- package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
- package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
- package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
- package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
- package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
- package/dist/ui/assets/tsx-CajzROyy.js +1 -0
- package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
- package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
- package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
- package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
- package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
- package/dist/ui/assets/xml-BI86-WbH.js +1 -0
- package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
- package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
- package/dist/workspaces/bootstrap.js +72 -0
- package/dist/workspaces/context.js +337 -0
- package/dist/workspaces/conversation-test-support.js +86 -0
- package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
- package/dist/workspaces/inventory.js +131 -0
- package/dist/workspaces/paths.js +55 -0
- package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
- package/dist/workspaces/relay/result-support.js +111 -0
- package/dist/workspaces/relay/tests/test-support.js +203 -0
- package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
- package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +30 -3
- package/dist/workspaces/relay/types.js +1 -0
- package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
- package/dist/workspaces/resources/resource-monitor.js +377 -0
- package/dist/workspaces/resources/skills.js +283 -0
- package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
- package/dist/workspaces/sessions.js +460 -0
- package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
- package/dist/workspaces.js +77 -993
- package/docs/chatgpt-coding-workflow.md +7 -14
- package/docs/configuration.md +26 -34
- package/docs/gotchas.md +0 -30
- package/docs/roadmap.md +6 -10
- package/docs/security.md +1 -5
- package/docs/setup.md +0 -11
- package/package.json +6 -9
- package/scripts/ci/architecture.mjs +109 -0
- package/scripts/ci/verify.mjs +5 -0
- package/scripts/debug/accept/bootstrap.mjs +326 -0
- package/scripts/debug/accept/harness.mjs +76 -0
- package/scripts/debug/accept/support.mjs +432 -0
- package/scripts/debug/accept.mjs +43 -806
- package/scripts/debug/relay-accept/support.mjs +135 -0
- package/scripts/debug/relay-accept.mjs +26 -115
- package/scripts/debug/traffic/support.mjs +167 -0
- package/scripts/debug/traffic/traffic-audit.mjs +4 -161
- package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
- package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
- package/scripts/release/publish.mjs +12 -9
- package/scripts/release/release-gate.test.mjs +3 -1
- package/scripts/release/release-version.test.mjs +13 -1
- package/scripts/release-parity.mjs +1 -1
- package/scripts/release-proof.mjs +15 -0
- package/scripts/release-proof.test.mjs +27 -1
- package/scripts/release-version.mjs +16 -1
- package/scripts/wiki/sync.mjs +21 -10
- package/dist/activity/bash-output-store.js +0 -127
- package/dist/pi-tools.js +0 -110
- package/dist/skills.js +0 -127
- package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
- package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
- package/dist/ui/assets/c-IBzBpxAO.js +0 -1
- package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
- package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
- package/dist/ui/assets/css-DEfus9J5.js +0 -1
- package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
- package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
- package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
- package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
- package/dist/ui/assets/html-D-C6YZk4.js +0 -1
- package/dist/ui/assets/java-ppHvycj5.js +0 -1
- package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
- package/dist/ui/assets/json-Cw91r8h1.js +0 -1
- package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
- package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
- package/dist/ui/assets/r-DfFga4si.js +0 -1
- package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
- package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
- package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
- package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
- package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
- package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
- package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
- package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
- package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
- package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
- package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
- package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
- package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
- package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
- package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
- package/dist/ui/workspace-app.html +0 -18
- package/dist/ui/workspace-lifecycle-app.html +0 -18
- package/docs/agent-profile-schema.md +0 -114
- package/docs/artifact-exchange.md +0 -65
- package/docs/versioning.md +0 -205
- package/skills/subagent-delegation/SKILL.md +0 -132
- /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
- /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
- /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
- /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
- /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
- /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
- /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
- /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
- /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
- /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
- /package/dist/{oauth → mcp/oauth}/router.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
- /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
- /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
- /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
- /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
- /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
- /package/dist/{db → runtime/state/db}/schema.js +0 -0
- /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
- /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
- /package/dist/{git.js → workspaces/git/git.js} +0 -0
- /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
- /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
- /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
- /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
package/scripts/debug/accept.mjs
CHANGED
|
@@ -1,396 +1,56 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
existsSync,
|
|
8
|
-
mkdirSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
rmSync,
|
|
11
|
-
writeFileSync,
|
|
12
|
-
} from "node:fs";
|
|
13
|
-
import { homedir, tmpdir } from "node:os";
|
|
14
|
-
import { join, resolve } from "node:path";
|
|
15
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
16
|
-
import {
|
|
17
|
-
createDebugEnvironment,
|
|
18
|
-
debugBaseUrl,
|
|
19
|
-
debugMcpUrl,
|
|
20
|
-
debugRoot,
|
|
21
|
-
repoRoot,
|
|
22
|
-
} from "./runtime.mjs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { debugMcpUrl } from "./runtime.mjs";
|
|
23
6
|
import {
|
|
24
7
|
assertCodeIntelligenceShutdown as assertCodeIntelligenceAcceptanceShutdown,
|
|
25
8
|
exerciseCodeIntelligence as exerciseCodeIntelligenceAcceptance,
|
|
26
|
-
setupCodeIntelligenceProject as setupCodeIntelligenceAcceptanceProject,
|
|
27
9
|
} from "./code-intelligence-accept.mjs";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
rmSync(acceptanceRoot, { recursive: true, force: true });
|
|
47
|
-
mkdirSync(acceptanceRoot, { recursive: true });
|
|
48
|
-
setupGitProject(checkoutWorkspace);
|
|
49
|
-
setupGitProject(lifecycleDeleteWorkspace);
|
|
50
|
-
writeFileSync(join(lifecycleDeleteWorkspace, "keep.txt"), "keep checkout files\n");
|
|
51
|
-
writeFileSync(
|
|
52
|
-
join(lifecycleDeleteWorkspace, "AGENTS.md"),
|
|
53
|
-
"INSPECTION_ACCEPTANCE_BOOTSTRAP_SECRET\n",
|
|
54
|
-
);
|
|
55
|
-
setupCodeIntelligenceAcceptanceProject({
|
|
56
|
-
root: checkoutWorkspace,
|
|
57
|
-
fakeLanguageServer,
|
|
58
|
-
logPath: codeIntelligenceLog,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const { env } = createDebugEnvironment({
|
|
62
|
-
ownerToken,
|
|
10
|
+
import { createAcceptanceHarness } from "./accept/harness.mjs";
|
|
11
|
+
import { runBootstrapAcceptance } from "./accept/bootstrap.mjs";
|
|
12
|
+
import {
|
|
13
|
+
callTool,
|
|
14
|
+
curlRequest,
|
|
15
|
+
exerciseReleaseTagHooks,
|
|
16
|
+
exerciseSubagentHooks,
|
|
17
|
+
mcpHeaders,
|
|
18
|
+
pass,
|
|
19
|
+
readHookEntries,
|
|
20
|
+
setupGitProject,
|
|
21
|
+
stopServer,
|
|
22
|
+
toolText,
|
|
23
|
+
} from "./accept/support.mjs";
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
packageJson,
|
|
27
|
+
acceptanceRoot,
|
|
63
28
|
stateDir,
|
|
64
|
-
worktreeRoot,
|
|
65
29
|
hookLog,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
assert.equal(doctor.status, 0, doctor.stderr);
|
|
75
|
-
assert.match(doctor.stdout, /Public base URL: http:\/\/127\.0\.0\.1:7677/);
|
|
76
|
-
assert.match(doctor.stdout, /Tool mode: full/);
|
|
77
|
-
assert.match(doctor.stdout, /Widgets: changes/);
|
|
78
|
-
assert.match(doctor.stdout, /Trust proxy: off/);
|
|
79
|
-
pass("doctor resolved MCP shape", "public URL + tool/widgets/proxy state");
|
|
80
|
-
|
|
81
|
-
const server = spawn(process.execPath, ["--import", "tsx", "src/cli.ts", "serve"], {
|
|
82
|
-
cwd: repoRoot,
|
|
30
|
+
checkoutWorkspace,
|
|
31
|
+
lifecycleDeleteWorkspace,
|
|
32
|
+
codeIntelligenceLog,
|
|
33
|
+
gitProject,
|
|
34
|
+
releaseProject,
|
|
35
|
+
releaseRemote,
|
|
36
|
+
ownerToken,
|
|
37
|
+
tempAcceptanceRoot,
|
|
83
38
|
env,
|
|
84
|
-
|
|
85
|
-
});
|
|
39
|
+
server,
|
|
40
|
+
} = await createAcceptanceHarness();
|
|
86
41
|
let acceptanceCompleted = false;
|
|
87
42
|
|
|
88
43
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
assert.equal(protectedResource.status, 200);
|
|
95
|
-
assert.equal(protectedResource.json.resource, debugMcpUrl);
|
|
96
|
-
assert.equal(protectedResource.json.resource_name, "ForgeRelay");
|
|
97
|
-
pass("OAuth protected-resource discovery", protectedResource.json.resource);
|
|
98
|
-
|
|
99
|
-
const authorizationServer = jsonRequest(`${debugBaseUrl}/.well-known/oauth-authorization-server`);
|
|
100
|
-
assert.equal(authorizationServer.status, 200);
|
|
101
|
-
assert.equal(authorizationServer.json.authorization_endpoint, `${debugBaseUrl}/authorize`);
|
|
102
|
-
assert.equal(authorizationServer.json.token_endpoint, `${debugBaseUrl}/token`);
|
|
103
|
-
assert.equal(authorizationServer.json.registration_endpoint, `${debugBaseUrl}/register`);
|
|
104
|
-
pass("OAuth authorization-server discovery", authorizationServer.json.token_endpoint);
|
|
105
|
-
|
|
106
|
-
const unauthorized = curlRequest({
|
|
107
|
-
method: "POST",
|
|
108
|
-
url: debugMcpUrl,
|
|
109
|
-
headers: mcpHeaders(),
|
|
110
|
-
body: JSON.stringify(initializeRequest(1)),
|
|
111
|
-
});
|
|
112
|
-
assert.equal(unauthorized.status, 401);
|
|
113
|
-
assert.equal(JSON.parse(unauthorized.body).error, "invalid_token");
|
|
114
|
-
pass("unauthorized MCP is rejected", "HTTP 401 invalid_token");
|
|
115
|
-
|
|
116
|
-
const oauth = authorizeDebugClient(authorizationServer.json);
|
|
117
|
-
pass("OAuth owner-password flow", "registered client and issued access token");
|
|
118
|
-
|
|
119
|
-
const initialized = mcpRequest(oauth.accessToken, undefined, initializeRequest(1));
|
|
120
|
-
assert.equal(initialized.response.status, 200);
|
|
121
|
-
const sessionId = initialized.response.headers.get("mcp-session-id");
|
|
122
|
-
assert.ok(sessionId);
|
|
123
|
-
assert.equal(initialized.message.result.serverInfo.name, "forgerelay");
|
|
124
|
-
assert.equal(initialized.message.result.serverInfo.title, "ForgeRelay");
|
|
125
|
-
assert.equal(initialized.message.result.serverInfo.version, packageJson.version);
|
|
126
|
-
const serverInstructions = initialized.message.result.instructions ?? "";
|
|
127
|
-
assert.match(serverInstructions, /Shell commands may modify ordinary project files/);
|
|
128
|
-
assert.match(serverInstructions, /\/etc\/sudoers/);
|
|
129
|
-
assert.match(serverInstructions, /configuration files through shell only when the user's request explicitly calls for that configuration change/);
|
|
130
|
-
assert.match(serverInstructions, /Project-work order: open_workspace if needed → activity_panel\(workspaceId\) once → work tools/);
|
|
131
|
-
assert.ok(serverInstructions.length < 3_000, `server instructions should stay compact, got ${serverInstructions.length} characters`);
|
|
132
|
-
assert.doesNotMatch(serverInstructions, /fast-forwards the original target branch/);
|
|
133
|
-
assert.doesNotMatch(serverInstructions, /Do not create or modify files with bash/);
|
|
134
|
-
pass(
|
|
135
|
-
"MCP initialize",
|
|
136
|
-
JSON.stringify(initialized.message.result.serverInfo),
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
const initializedNotification = curlRequest({
|
|
140
|
-
method: "POST",
|
|
141
|
-
url: debugMcpUrl,
|
|
142
|
-
headers: mcpHeaders(oauth.accessToken, sessionId),
|
|
143
|
-
body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
|
|
144
|
-
});
|
|
145
|
-
assert.equal(initializedNotification.status, 202);
|
|
146
|
-
|
|
147
|
-
const tools = mcpRequest(oauth.accessToken, sessionId, {
|
|
148
|
-
jsonrpc: "2.0",
|
|
149
|
-
id: 2,
|
|
150
|
-
method: "tools/list",
|
|
151
|
-
params: {},
|
|
152
|
-
}).message.result.tools;
|
|
153
|
-
const toolNames = tools.map((tool) => tool.name);
|
|
154
|
-
assert.deepEqual(toolNames, [
|
|
155
|
-
"open_workspace",
|
|
156
|
-
"activity_panel",
|
|
157
|
-
"activity_snapshot",
|
|
158
|
-
"activity_index",
|
|
159
|
-
"activity_detail",
|
|
160
|
-
"activity_output",
|
|
161
|
-
"capability",
|
|
162
|
-
"close_workspace",
|
|
163
|
-
"read",
|
|
164
|
-
"write",
|
|
165
|
-
"edit",
|
|
166
|
-
"rename",
|
|
167
|
-
"delete",
|
|
168
|
-
"bash",
|
|
169
|
-
]);
|
|
170
|
-
const bashTool = tools.find((tool) => tool.name === "bash");
|
|
171
|
-
assert.match(bashTool?.description ?? "", /local user's authority/);
|
|
172
|
-
assert.doesNotMatch(bashTool?.description ?? "", /may modify ordinary project files/);
|
|
173
|
-
assert.doesNotMatch(bashTool?.description ?? "", /\/etc\/sudoers/);
|
|
174
|
-
assert.doesNotMatch(bashTool?.description ?? "", /external device or hardware mutations/);
|
|
175
|
-
assert.doesNotMatch(bashTool?.description ?? "", /Do not use bash to create, move, rename, or delete project files/);
|
|
176
|
-
assert.equal(bashTool?.inputSchema?.properties?.timeout, undefined);
|
|
177
|
-
assert.match(bashTool?.description ?? "", /action=process/);
|
|
178
|
-
assert.equal(bashTool?.inputSchema?.properties?.yieldTimeMs?.maximum, 300000);
|
|
179
|
-
assert.equal(bashTool?.inputSchema?.properties?.timeoutMs?.maximum, 86400000);
|
|
180
|
-
assert.match(bashTool?.inputSchema?.properties?.processId?.description ?? "", /action=process/);
|
|
181
|
-
assert.match(bashTool?.inputSchema?.properties?.interrupt?.description ?? "", /SIGINT/);
|
|
182
|
-
const openWorkspaceTool = tools.find((tool) => tool.name === "open_workspace");
|
|
183
|
-
assert.ok(openWorkspaceTool?.inputSchema?.properties?.workspaceId);
|
|
184
|
-
assert.ok(openWorkspaceTool?.inputSchema?.properties?.newWorkspace);
|
|
185
|
-
assert.ok(openWorkspaceTool?.outputSchema?.properties?.staleWorkspaces);
|
|
186
|
-
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityFingerprint);
|
|
187
|
-
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityCatalog);
|
|
188
|
-
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityGuides);
|
|
189
|
-
const capabilityTool = tools.find((tool) => tool.name === "capability");
|
|
190
|
-
assert.deepEqual(capabilityTool?._meta?.["openai/fileParams"], ["file"]);
|
|
191
|
-
const activityPanelTool = tools.find((tool) => tool.name === "activity_panel");
|
|
192
|
-
const activityTemplateUri = activityPanelTool?._meta?.ui?.resourceUri;
|
|
193
|
-
assert.match(
|
|
194
|
-
activityTemplateUri ?? "",
|
|
195
|
-
/^ui:\/\/forgerelay\/activity-panel-app-[0-9a-f]{12}\.html$/,
|
|
196
|
-
);
|
|
197
|
-
assert.deepEqual(activityPanelTool?._meta?.ui?.visibility, ["model", "app"]);
|
|
198
|
-
pass("MCP tools/list", `${toolNames.length} tools: ${toolNames.join(", ")}`);
|
|
199
|
-
|
|
200
|
-
const resources = mcpRequest(oauth.accessToken, sessionId, {
|
|
201
|
-
jsonrpc: "2.0",
|
|
202
|
-
id: 21,
|
|
203
|
-
method: "resources/list",
|
|
204
|
-
params: {},
|
|
205
|
-
}).message.result.resources;
|
|
206
|
-
const currentActivityResource = resources.find((resource) => resource.uri === activityTemplateUri);
|
|
207
|
-
assert.ok(currentActivityResource);
|
|
208
|
-
assert.equal(currentActivityResource._meta?.ui?.domain, debugBaseUrl);
|
|
209
|
-
assert.equal(
|
|
210
|
-
resources.some((resource) => /^ui:\/\/forgerelay\/workspace-lifecycle-app-/.test(resource.uri)),
|
|
211
|
-
false,
|
|
212
|
-
);
|
|
213
|
-
assert.ok(resources.some((resource) => resource.uri === "ui://forgerelay/workspace-app.html"));
|
|
214
|
-
|
|
215
|
-
const resourceTemplates = mcpRequest(oauth.accessToken, sessionId, {
|
|
216
|
-
jsonrpc: "2.0",
|
|
217
|
-
id: 22,
|
|
218
|
-
method: "resources/templates/list",
|
|
219
|
-
params: {},
|
|
220
|
-
}).message.result.resourceTemplates;
|
|
221
|
-
assert.ok(resourceTemplates.some(
|
|
222
|
-
(resourceTemplate) => resourceTemplate.uriTemplate === "ui://forgerelay/workspace-lifecycle-app-{revision}.html",
|
|
223
|
-
));
|
|
224
|
-
assert.ok(resourceTemplates.some(
|
|
225
|
-
(resourceTemplate) => resourceTemplate.uriTemplate === "ui://forgerelay/activity-panel-app-{revision}.html",
|
|
226
|
-
));
|
|
227
|
-
|
|
228
|
-
const readTemplate = (id, uri) => mcpRequest(oauth.accessToken, sessionId, {
|
|
229
|
-
jsonrpc: "2.0",
|
|
230
|
-
id,
|
|
231
|
-
method: "resources/read",
|
|
232
|
-
params: { uri },
|
|
233
|
-
}).message.result.contents[0];
|
|
234
|
-
|
|
235
|
-
const activityTemplate = readTemplate(24, activityTemplateUri);
|
|
236
|
-
assert.equal(activityTemplate.uri, activityTemplateUri);
|
|
237
|
-
assert.equal(activityTemplate.mimeType, "text/html;profile=mcp-app");
|
|
238
|
-
assert.match(activityTemplate.text ?? "", /activity-panel-app-[^\"]+\.js/);
|
|
239
|
-
assert.equal(activityTemplate._meta?.ui?.domain, debugBaseUrl);
|
|
240
|
-
assert.ok(activityTemplate._meta?.ui?.csp?.resourceDomains?.includes(debugBaseUrl));
|
|
241
|
-
const scriptUrl = activityTemplate.text?.match(/<script type="module" crossorigin src="([^"]+)"/)?.[1];
|
|
242
|
-
assert.ok(scriptUrl);
|
|
243
|
-
const scriptAsset = curlRequest({ method: "GET", url: scriptUrl });
|
|
244
|
-
assert.equal(scriptAsset.status, 200, scriptAsset.body);
|
|
245
|
-
|
|
246
|
-
const legacyTemplate = readTemplate(25, "ui://forgerelay/workspace-app.html");
|
|
247
|
-
assert.equal(legacyTemplate.uri, "ui://forgerelay/workspace-app.html");
|
|
248
|
-
assert.equal(legacyTemplate.mimeType, "text/html;profile=mcp-app");
|
|
249
|
-
pass("MCP app templates", `${activityTemplateUri} + historical compatibility templates`);
|
|
250
|
-
|
|
251
|
-
const workspaceConversationMeta = { "openai/session": "acceptance-workspace" };
|
|
252
|
-
const opened = callTool(oauth.accessToken, sessionId, 3, "open_workspace", {
|
|
253
|
-
path: checkoutWorkspace,
|
|
254
|
-
}, workspaceConversationMeta);
|
|
255
|
-
const workspaceId = opened.structuredContent.workspaceId;
|
|
256
|
-
assert.match(workspaceId, /^ws_/);
|
|
257
|
-
assert.equal(opened.structuredContent.action, "open");
|
|
258
|
-
assert.equal(opened.structuredContent.root, checkoutWorkspace);
|
|
259
|
-
assert.equal(opened.structuredContent.mode, "checkout");
|
|
260
|
-
assert.equal(typeof opened.structuredContent.contextFingerprint, "string");
|
|
261
|
-
assert.deepEqual(opened.structuredContent.capabilityFingerprint, {
|
|
262
|
-
version: packageJson.version,
|
|
263
|
-
toolMode: "full",
|
|
264
|
-
capabilities: [
|
|
265
|
-
"workspace.close",
|
|
266
|
-
"worktree.managed",
|
|
267
|
-
"filesystem.rename-move",
|
|
268
|
-
"filesystem.delete",
|
|
269
|
-
"process.lifecycle",
|
|
270
|
-
"hooks.lifecycle",
|
|
271
|
-
"capability-guides.read",
|
|
272
|
-
"code.intelligence",
|
|
273
|
-
"workspace.tasks",
|
|
274
|
-
"batch.execute",
|
|
275
|
-
...(process.platform === "linux" ? ["artifact.native-download"] : []),
|
|
276
|
-
"ui.mcp-app",
|
|
277
|
-
"review.changes",
|
|
278
|
-
],
|
|
279
|
-
});
|
|
280
|
-
const capabilityCatalog = opened.structuredContent.capabilityCatalog;
|
|
281
|
-
assert.deepEqual(capabilityCatalog.map((entry) => entry.name), [
|
|
282
|
-
"hooks.check",
|
|
283
|
-
"review.changes",
|
|
284
|
-
"code.intelligence",
|
|
285
|
-
"workspace.tasks",
|
|
286
|
-
"batch.execute",
|
|
287
|
-
...(process.platform === "linux" ? ["artifact.download"] : []),
|
|
288
|
-
]);
|
|
289
|
-
assert.equal(capabilityCatalog[0].available, true);
|
|
290
|
-
assert.equal(capabilityCatalog[0].guide.name, "lifecycle-hooks");
|
|
291
|
-
|
|
292
|
-
const checkoutTaskStatePath = join(stateDir, "workspaces", workspaceId, "tasks.json");
|
|
293
|
-
assert.ok(existsSync(checkoutTaskStatePath));
|
|
294
|
-
const checkoutTaskList = callTool(oauth.accessToken, sessionId, 130, "capability", {
|
|
295
|
-
workspaceId,
|
|
296
|
-
name: "workspace.tasks",
|
|
297
|
-
action: "run",
|
|
298
|
-
arguments: { operation: "list.create", name: "7677 release tasks" },
|
|
299
|
-
});
|
|
300
|
-
assert.equal(checkoutTaskList.isError, undefined);
|
|
301
|
-
const checkoutListId = checkoutTaskList.structuredContent.result.lists[0].id;
|
|
302
|
-
const checkoutTask = callTool(oauth.accessToken, sessionId, 131, "capability", {
|
|
303
|
-
workspaceId,
|
|
304
|
-
name: "workspace.tasks",
|
|
305
|
-
action: "run",
|
|
306
|
-
arguments: {
|
|
307
|
-
operation: "task.create",
|
|
308
|
-
listId: checkoutListId,
|
|
309
|
-
subject: "Verify v0.8.3 Task persistence",
|
|
310
|
-
content: "created through real MCP",
|
|
311
|
-
status: "in_progress",
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
assert.equal(checkoutTask.isError, undefined);
|
|
315
|
-
const checkoutTaskId = checkoutTask.structuredContent.result.lists[0].tasks[0].id;
|
|
316
|
-
const taskFingerprintBeforeExternal = checkoutTask.structuredContent.result.fingerprint;
|
|
317
|
-
const externalTaskState = JSON.parse(readFileSync(checkoutTaskStatePath, "utf8"));
|
|
318
|
-
externalTaskState.lists[0].tasks[0].content = "reloaded external task edit";
|
|
319
|
-
writeFileSync(checkoutTaskStatePath, `${JSON.stringify(externalTaskState, null, 2)}\n`);
|
|
320
|
-
const reloadedCheckoutTasks = callTool(oauth.accessToken, sessionId, 132, "capability", {
|
|
321
|
-
workspaceId,
|
|
322
|
-
name: "workspace.tasks",
|
|
323
|
-
action: "run",
|
|
324
|
-
arguments: {
|
|
325
|
-
operation: "get",
|
|
326
|
-
level: "detail",
|
|
327
|
-
listId: checkoutListId,
|
|
328
|
-
taskId: checkoutTaskId,
|
|
329
|
-
},
|
|
330
|
-
});
|
|
331
|
-
assert.equal(reloadedCheckoutTasks.isError, undefined);
|
|
332
|
-
assert.equal(reloadedCheckoutTasks.structuredContent.result.task.id, checkoutTaskId);
|
|
333
|
-
assert.equal(reloadedCheckoutTasks.structuredContent.result.task.content, "reloaded external task edit");
|
|
334
|
-
assert.notEqual(reloadedCheckoutTasks.structuredContent.result.fingerprint, taskFingerprintBeforeExternal);
|
|
335
|
-
|
|
336
|
-
const checkoutTaskSummary = callTool(oauth.accessToken, sessionId, 150, "capability", {
|
|
337
|
-
workspaceId,
|
|
338
|
-
name: "workspace.tasks",
|
|
339
|
-
action: "run",
|
|
340
|
-
arguments: { operation: "get" },
|
|
341
|
-
});
|
|
342
|
-
const summaryProjection = checkoutTaskSummary.structuredContent.result;
|
|
343
|
-
assert.equal(summaryProjection.level, "summary");
|
|
344
|
-
assert.equal(summaryProjection.lists[0].id, checkoutListId);
|
|
345
|
-
assert.equal(summaryProjection.lists[0].taskCount, 1);
|
|
346
|
-
assert.equal(summaryProjection.lists[0].unfinishedTaskCount, 1);
|
|
347
|
-
assert.equal(JSON.stringify(summaryProjection).includes(checkoutTaskId), false);
|
|
348
|
-
assert.equal(JSON.stringify(summaryProjection).includes("reloaded external task edit"), false);
|
|
349
|
-
|
|
350
|
-
const checkoutTaskHeaders = callTool(oauth.accessToken, sessionId, 151, "capability", {
|
|
351
|
-
workspaceId,
|
|
352
|
-
name: "workspace.tasks",
|
|
353
|
-
action: "run",
|
|
354
|
-
arguments: { operation: "get", level: "headers", listId: checkoutListId },
|
|
355
|
-
});
|
|
356
|
-
const headersProjection = checkoutTaskHeaders.structuredContent.result;
|
|
357
|
-
assert.equal(headersProjection.level, "headers");
|
|
358
|
-
assert.equal(headersProjection.lists.length, 1);
|
|
359
|
-
assert.equal(headersProjection.lists[0].tasks[0].id, checkoutTaskId);
|
|
360
|
-
assert.equal(headersProjection.lists[0].tasks[0].subject, "Verify v0.8.3 Task persistence");
|
|
361
|
-
assert.equal("content" in headersProjection.lists[0].tasks[0], false);
|
|
362
|
-
|
|
363
|
-
const checkoutTaskDetail = callTool(oauth.accessToken, sessionId, 152, "capability", {
|
|
364
|
-
workspaceId,
|
|
365
|
-
name: "workspace.tasks",
|
|
366
|
-
action: "run",
|
|
367
|
-
arguments: {
|
|
368
|
-
operation: "get",
|
|
369
|
-
level: "detail",
|
|
370
|
-
listId: checkoutListId,
|
|
371
|
-
taskId: checkoutTaskId,
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
const detailProjection = checkoutTaskDetail.structuredContent.result;
|
|
375
|
-
assert.equal(detailProjection.level, "detail");
|
|
376
|
-
assert.equal(detailProjection.task.id, checkoutTaskId);
|
|
377
|
-
assert.equal(detailProjection.task.content, "reloaded external task edit");
|
|
378
|
-
pass("workspace.tasks progressive disclosure", "summary -> headers -> one Task detail through real MCP");
|
|
379
|
-
|
|
380
|
-
for (let index = 0; index < 29; index += 1) {
|
|
381
|
-
const semanticWork = callTool(oauth.accessToken, sessionId, 160 + index, "read", {
|
|
382
|
-
workspaceId,
|
|
383
|
-
path: "README.md",
|
|
384
|
-
});
|
|
385
|
-
assert.doesNotMatch(toolText(semanticWork), /Reminder: this Workspace has unfinished active Tasks/);
|
|
386
|
-
}
|
|
387
|
-
const reminderWork = callTool(oauth.accessToken, sessionId, 189, "read", {
|
|
44
|
+
const {
|
|
45
|
+
oauth,
|
|
46
|
+
sessionId,
|
|
47
|
+
workspaceConversationMeta,
|
|
48
|
+
opened,
|
|
388
49
|
workspaceId,
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
pass("workspace.tasks reminder", "default 30 semantic work calls emitted one body-free reminder");
|
|
50
|
+
capabilityCatalog,
|
|
51
|
+
checkoutListId,
|
|
52
|
+
checkoutTaskId,
|
|
53
|
+
} = await runBootstrapAcceptance({ server, packageJson, ownerToken, checkoutWorkspace, stateDir });
|
|
394
54
|
|
|
395
55
|
const repeatedOpen = callTool(oauth.accessToken, sessionId, 84, "open_workspace", {
|
|
396
56
|
path: checkoutWorkspace,
|
|
@@ -1068,8 +728,8 @@ try {
|
|
|
1068
728
|
`${compositeWorkspaceId} close -> closed/non-routable -> same-id reopen -> delete; member Workspace preserved`,
|
|
1069
729
|
);
|
|
1070
730
|
|
|
1071
|
-
exerciseReleaseTagHooks(oauth.accessToken, sessionId);
|
|
1072
|
-
exerciseSubagentHooks(env, stateDir, workspaceId, checkoutWorkspace);
|
|
731
|
+
exerciseReleaseTagHooks(oauth.accessToken, sessionId, { acceptanceRoot, releaseProject, releaseRemote });
|
|
732
|
+
exerciseSubagentHooks(env, stateDir, workspaceId, checkoutWorkspace, acceptanceRoot);
|
|
1073
733
|
|
|
1074
734
|
const hookEntries = readHookEntries(hookLog);
|
|
1075
735
|
const hookEvents = hookEntries.map((entry) => entry.event);
|
|
@@ -1112,426 +772,3 @@ if (acceptanceCompleted) {
|
|
|
1112
772
|
console.log("\nForgeRelay 7677 acceptance passed.");
|
|
1113
773
|
console.log(`Artifacts: ${acceptanceRoot}`);
|
|
1114
774
|
}
|
|
1115
|
-
|
|
1116
|
-
function authorizeDebugClient(metadata) {
|
|
1117
|
-
const redirectUri = `${debugBaseUrl}/debug/callback`;
|
|
1118
|
-
const registration = jsonRequest(metadata.registration_endpoint, {
|
|
1119
|
-
method: "POST",
|
|
1120
|
-
body: JSON.stringify({
|
|
1121
|
-
client_name: "ForgeRelay 7677 acceptance",
|
|
1122
|
-
redirect_uris: [redirectUri],
|
|
1123
|
-
token_endpoint_auth_method: "none",
|
|
1124
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
1125
|
-
response_types: ["code"],
|
|
1126
|
-
}),
|
|
1127
|
-
headers: { "content-type": "application/json" },
|
|
1128
|
-
});
|
|
1129
|
-
assert.equal(registration.status, 201);
|
|
1130
|
-
|
|
1131
|
-
const verifier = randomBytes(32).toString("base64url");
|
|
1132
|
-
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
1133
|
-
const authorizationBody = new URLSearchParams({
|
|
1134
|
-
response_type: "code",
|
|
1135
|
-
client_id: registration.json.client_id,
|
|
1136
|
-
redirect_uri: redirectUri,
|
|
1137
|
-
code_challenge: challenge,
|
|
1138
|
-
code_challenge_method: "S256",
|
|
1139
|
-
scope: "devspace",
|
|
1140
|
-
resource: debugMcpUrl,
|
|
1141
|
-
state: "forgerelay-debug-acceptance",
|
|
1142
|
-
owner_token: ownerToken,
|
|
1143
|
-
}).toString();
|
|
1144
|
-
const authorization = curlRequest({
|
|
1145
|
-
method: "POST",
|
|
1146
|
-
url: metadata.authorization_endpoint,
|
|
1147
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
1148
|
-
body: authorizationBody,
|
|
1149
|
-
});
|
|
1150
|
-
assert.equal(authorization.status, 302);
|
|
1151
|
-
const redirect = new URL(authorization.headers.get("location"));
|
|
1152
|
-
assert.equal(redirect.origin + redirect.pathname, redirectUri);
|
|
1153
|
-
assert.equal(redirect.searchParams.get("state"), "forgerelay-debug-acceptance");
|
|
1154
|
-
const code = redirect.searchParams.get("code");
|
|
1155
|
-
assert.ok(code);
|
|
1156
|
-
|
|
1157
|
-
const token = jsonRequest(metadata.token_endpoint, {
|
|
1158
|
-
method: "POST",
|
|
1159
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
1160
|
-
body: new URLSearchParams({
|
|
1161
|
-
grant_type: "authorization_code",
|
|
1162
|
-
client_id: registration.json.client_id,
|
|
1163
|
-
code,
|
|
1164
|
-
redirect_uri: redirectUri,
|
|
1165
|
-
code_verifier: verifier,
|
|
1166
|
-
resource: debugMcpUrl,
|
|
1167
|
-
}).toString(),
|
|
1168
|
-
});
|
|
1169
|
-
assert.equal(token.status, 200);
|
|
1170
|
-
assert.equal(token.json.token_type, "bearer");
|
|
1171
|
-
assert.equal(token.json.scope, "devspace");
|
|
1172
|
-
assert.ok(token.json.access_token);
|
|
1173
|
-
return { accessToken: token.json.access_token };
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
function initializeRequest(id) {
|
|
1177
|
-
return {
|
|
1178
|
-
jsonrpc: "2.0",
|
|
1179
|
-
id,
|
|
1180
|
-
method: "initialize",
|
|
1181
|
-
params: {
|
|
1182
|
-
protocolVersion: "2025-06-18",
|
|
1183
|
-
capabilities: {
|
|
1184
|
-
extensions: {
|
|
1185
|
-
"io.modelcontextprotocol/ui": {
|
|
1186
|
-
mimeTypes: ["text/html;profile=mcp-app"],
|
|
1187
|
-
},
|
|
1188
|
-
},
|
|
1189
|
-
},
|
|
1190
|
-
clientInfo: { name: "forgerelay-debug-acceptance", version: "1.0.0" },
|
|
1191
|
-
},
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
function mcpRequest(accessToken, sessionId, request) {
|
|
1196
|
-
const response = curlRequest({
|
|
1197
|
-
method: "POST",
|
|
1198
|
-
url: debugMcpUrl,
|
|
1199
|
-
headers: mcpHeaders(accessToken, sessionId),
|
|
1200
|
-
body: JSON.stringify(request),
|
|
1201
|
-
});
|
|
1202
|
-
assert.equal(response.status, 200, response.body);
|
|
1203
|
-
return { response, message: parseMcpMessage(response.body, request.id) };
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
function callTool(accessToken, sessionId, id, name, args, meta) {
|
|
1207
|
-
const message = mcpRequest(accessToken, sessionId, {
|
|
1208
|
-
jsonrpc: "2.0",
|
|
1209
|
-
id,
|
|
1210
|
-
method: "tools/call",
|
|
1211
|
-
params: {
|
|
1212
|
-
name,
|
|
1213
|
-
arguments: args,
|
|
1214
|
-
...(meta ? { _meta: meta } : {}),
|
|
1215
|
-
},
|
|
1216
|
-
}).message;
|
|
1217
|
-
assert.equal(message.id, id);
|
|
1218
|
-
assert.ok(message.result, `tool ${name} did not return a result`);
|
|
1219
|
-
return message.result;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
function mcpHeaders(accessToken, sessionId) {
|
|
1223
|
-
return {
|
|
1224
|
-
...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}),
|
|
1225
|
-
...(sessionId ? { "mcp-session-id": sessionId } : {}),
|
|
1226
|
-
"content-type": "application/json",
|
|
1227
|
-
accept: "application/json, text/event-stream",
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
function parseMcpMessage(body, expectedId) {
|
|
1232
|
-
const dataLines = body
|
|
1233
|
-
.split(/\r?\n/)
|
|
1234
|
-
.filter((line) => line.startsWith("data:"))
|
|
1235
|
-
.map((line) => line.slice(5).trim())
|
|
1236
|
-
.filter(Boolean);
|
|
1237
|
-
const messages = dataLines.length > 0
|
|
1238
|
-
? dataLines.map((line) => JSON.parse(line))
|
|
1239
|
-
: [JSON.parse(body)];
|
|
1240
|
-
return messages.find((message) => message.id === expectedId) ?? messages[0];
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
function jsonRequest(url, options = {}) {
|
|
1244
|
-
const response = curlRequest({
|
|
1245
|
-
method: options.method ?? "GET",
|
|
1246
|
-
url,
|
|
1247
|
-
headers: options.headers,
|
|
1248
|
-
body: options.body,
|
|
1249
|
-
});
|
|
1250
|
-
return { ...response, json: JSON.parse(response.body) };
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
function curlRequest({ method = "GET", url, headers = {}, body }) {
|
|
1254
|
-
const marker = `__FORGERELAY_DEBUG_STATUS_${randomUUID()}__`;
|
|
1255
|
-
const args = [
|
|
1256
|
-
"--silent",
|
|
1257
|
-
"--show-error",
|
|
1258
|
-
"--max-time",
|
|
1259
|
-
"15",
|
|
1260
|
-
"--request",
|
|
1261
|
-
method,
|
|
1262
|
-
"--dump-header",
|
|
1263
|
-
"-",
|
|
1264
|
-
"--output",
|
|
1265
|
-
"-",
|
|
1266
|
-
"--write-out",
|
|
1267
|
-
`\n${marker}%{http_code}`,
|
|
1268
|
-
];
|
|
1269
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
1270
|
-
args.push("--header", `${name}: ${value}`);
|
|
1271
|
-
}
|
|
1272
|
-
if (body !== undefined) {
|
|
1273
|
-
args.push("--data-binary", "@-");
|
|
1274
|
-
}
|
|
1275
|
-
args.push(url);
|
|
1276
|
-
|
|
1277
|
-
const result = spawnSync("curl", args, {
|
|
1278
|
-
cwd: repoRoot,
|
|
1279
|
-
input: body,
|
|
1280
|
-
encoding: "utf8",
|
|
1281
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
1282
|
-
});
|
|
1283
|
-
if (result.status !== 0) {
|
|
1284
|
-
throw new Error(`curl ${method} ${url} failed: ${result.stderr.trim() || `exit ${result.status}`}`);
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
const statusMarker = `\n${marker}`;
|
|
1288
|
-
const markerIndex = result.stdout.lastIndexOf(statusMarker);
|
|
1289
|
-
assert.notEqual(markerIndex, -1, `curl response did not contain status marker for ${url}`);
|
|
1290
|
-
const rawResponse = result.stdout.slice(0, markerIndex);
|
|
1291
|
-
const status = Number(result.stdout.slice(markerIndex + statusMarker.length).trim());
|
|
1292
|
-
const separator = rawResponse.indexOf("\r\n\r\n") >= 0 ? "\r\n\r\n" : "\n\n";
|
|
1293
|
-
const headerEnd = rawResponse.indexOf(separator);
|
|
1294
|
-
assert.notEqual(headerEnd, -1, `curl response did not contain headers for ${url}`);
|
|
1295
|
-
const headerBlock = rawResponse.slice(0, headerEnd);
|
|
1296
|
-
const responseBody = rawResponse.slice(headerEnd + separator.length);
|
|
1297
|
-
const responseHeaders = new Map();
|
|
1298
|
-
for (const line of headerBlock.split(/\r?\n/).slice(1)) {
|
|
1299
|
-
const colon = line.indexOf(":");
|
|
1300
|
-
if (colon < 0) continue;
|
|
1301
|
-
responseHeaders.set(line.slice(0, colon).trim().toLowerCase(), line.slice(colon + 1).trim());
|
|
1302
|
-
}
|
|
1303
|
-
return { status, headers: responseHeaders, body: responseBody };
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
async function waitForHealth(child) {
|
|
1307
|
-
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
1308
|
-
if (child.exitCode !== null) {
|
|
1309
|
-
throw new Error(`debug server exited before health check: ${child.exitCode}`);
|
|
1310
|
-
}
|
|
1311
|
-
try {
|
|
1312
|
-
const response = jsonRequest(`${debugBaseUrl}/healthz`);
|
|
1313
|
-
if (response.status === 200) return response.json;
|
|
1314
|
-
} catch {
|
|
1315
|
-
// Server is still starting.
|
|
1316
|
-
}
|
|
1317
|
-
await delay(100);
|
|
1318
|
-
}
|
|
1319
|
-
throw new Error("debug server did not become healthy on 127.0.0.1:7677");
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
async function stopServer(child) {
|
|
1323
|
-
if (child.exitCode !== null) return;
|
|
1324
|
-
child.kill("SIGTERM");
|
|
1325
|
-
const exited = once(child, "exit");
|
|
1326
|
-
await Promise.race([
|
|
1327
|
-
exited,
|
|
1328
|
-
delay(3000).then(() => {
|
|
1329
|
-
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1330
|
-
}),
|
|
1331
|
-
]);
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
function setupGitProject(root) {
|
|
1335
|
-
mkdirSync(root, { recursive: true });
|
|
1336
|
-
writeFileSync(join(root, "README.md"), "debug git project\n", "utf8");
|
|
1337
|
-
runGit(root, ["init"]);
|
|
1338
|
-
runGit(root, ["config", "user.email", "forgerelay-debug@example.com"]);
|
|
1339
|
-
runGit(root, ["config", "user.name", "ForgeRelay Debug"]);
|
|
1340
|
-
runGit(root, ["add", "."]);
|
|
1341
|
-
runGit(root, ["commit", "-m", "Initial debug commit"]);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
function runGit(cwd, args) {
|
|
1345
|
-
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
1346
|
-
if (result.status !== 0) {
|
|
1347
|
-
throw new Error(`git ${args.join(" ")} failed: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
function gitOutput(cwd, args, options = {}) {
|
|
1352
|
-
const gitArgs = options.gitDir ? ["--git-dir", cwd, ...args] : args;
|
|
1353
|
-
const result = spawnSync("git", gitArgs, {
|
|
1354
|
-
...(options.gitDir ? {} : { cwd }),
|
|
1355
|
-
encoding: "utf8",
|
|
1356
|
-
});
|
|
1357
|
-
if (result.status !== 0) {
|
|
1358
|
-
throw new Error(`git ${gitArgs.join(" ")} failed: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
1359
|
-
}
|
|
1360
|
-
return result.stdout.trim();
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
function exerciseReleaseTagHooks(accessToken, sessionId) {
|
|
1364
|
-
setupGitProject(releaseProject);
|
|
1365
|
-
runGit(acceptanceRoot, ["init", "--bare", releaseRemote]);
|
|
1366
|
-
runGit(releaseProject, ["remote", "add", "origin", releaseRemote]);
|
|
1367
|
-
mkdirSync(join(releaseProject, ".forgerelay", "hooks"), { recursive: true });
|
|
1368
|
-
writeFileSync(
|
|
1369
|
-
join(releaseProject, ".forgerelay", "release-check.mjs"),
|
|
1370
|
-
[
|
|
1371
|
-
'import { writeFileSync } from "node:fs";',
|
|
1372
|
-
'const payload = process.env.FORGERELAY_HOOK_PAYLOAD ?? "{}";',
|
|
1373
|
-
'const parsed = JSON.parse(payload);',
|
|
1374
|
-
'writeFileSync("release-ci-ran.txt", payload);',
|
|
1375
|
-
'if (parsed.command === "git push origin v0.2.1") process.exit(17);',
|
|
1376
|
-
"",
|
|
1377
|
-
].join("\n"),
|
|
1378
|
-
);
|
|
1379
|
-
writeFileSync(
|
|
1380
|
-
join(releaseProject, ".forgerelay", "hooks", "release-tag-local-ci.json"),
|
|
1381
|
-
JSON.stringify({
|
|
1382
|
-
event: "BeforeTool",
|
|
1383
|
-
matcher: { tool: "bash", commandRegex: "git push origin v0\\.2\\.[01]" },
|
|
1384
|
-
command: "node .forgerelay/release-check.mjs",
|
|
1385
|
-
timeoutSeconds: 30,
|
|
1386
|
-
report: true,
|
|
1387
|
-
}, null, 2) + "\n",
|
|
1388
|
-
);
|
|
1389
|
-
runGit(releaseProject, ["add", ".forgerelay"]);
|
|
1390
|
-
runGit(releaseProject, ["commit", "-m", "Add release hook fixture"]);
|
|
1391
|
-
runGit(releaseProject, ["tag", "v0.2.0"]);
|
|
1392
|
-
runGit(releaseProject, ["tag", "v0.2.1"]);
|
|
1393
|
-
|
|
1394
|
-
const opened = callTool(accessToken, sessionId, 11, "open_workspace", {
|
|
1395
|
-
path: releaseProject,
|
|
1396
|
-
});
|
|
1397
|
-
const releaseWorkspaceId = opened.structuredContent.workspaceId;
|
|
1398
|
-
|
|
1399
|
-
const pushed = callTool(accessToken, sessionId, 12, "bash", {
|
|
1400
|
-
workspaceId: releaseWorkspaceId,
|
|
1401
|
-
command: "git status --short && git push origin v0.2.0 && echo release-pushed",
|
|
1402
|
-
});
|
|
1403
|
-
assert.equal(pushed.isError, undefined);
|
|
1404
|
-
assert.match(toolText(pushed), /release-tag-local-ci \(BeforeTool, project\) passed/);
|
|
1405
|
-
assert.ok(existsSync(join(releaseProject, "release-ci-ran.txt")));
|
|
1406
|
-
assert.deepEqual(JSON.parse(readFileSync(join(releaseProject, "release-ci-ran.txt"), "utf8")), {
|
|
1407
|
-
tool: "bash",
|
|
1408
|
-
action: "run",
|
|
1409
|
-
command: "git push origin v0.2.0",
|
|
1410
|
-
workingDirectory: ".",
|
|
1411
|
-
originalCommand: "git status --short && git push origin v0.2.0 && echo release-pushed",
|
|
1412
|
-
});
|
|
1413
|
-
assert.equal(
|
|
1414
|
-
gitOutput(releaseRemote, ["rev-parse", "refs/tags/v0.2.0"], { gitDir: true }),
|
|
1415
|
-
gitOutput(releaseProject, ["rev-parse", "v0.2.0"]),
|
|
1416
|
-
);
|
|
1417
|
-
|
|
1418
|
-
const blocked = callTool(accessToken, sessionId, 13, "bash", {
|
|
1419
|
-
workspaceId: releaseWorkspaceId,
|
|
1420
|
-
command: "git status --short && git push origin v0.2.1 && echo should-not-run",
|
|
1421
|
-
});
|
|
1422
|
-
assert.equal(blocked.isError, true);
|
|
1423
|
-
assert.match(toolText(blocked), /release-tag-local-ci.*failed/);
|
|
1424
|
-
const missingTag = spawnSync("git", ["--git-dir", releaseRemote, "show-ref", "--verify", "--quiet", "refs/tags/v0.2.1"]);
|
|
1425
|
-
assert.notEqual(missingTag.status, 0, "blocked release tag unexpectedly reached the remote");
|
|
1426
|
-
pass("release tag hook gate", "local Hook passed before v0.2.0 push and blocked v0.2.1 before remote mutation");
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
function exerciseSubagentHooks(runtimeEnv, debugStateDir, workspaceId, workspaceRoot) {
|
|
1430
|
-
const seedScript = [
|
|
1431
|
-
'import { SubagentSessionStore } from "./src/subagents/sessions/store.js";',
|
|
1432
|
-
'const store = new SubagentSessionStore(process.env.FORGERELAY_STATE_DIR);',
|
|
1433
|
-
'const record = store.create({',
|
|
1434
|
-
` workspaceId: ${JSON.stringify(workspaceId)},`,
|
|
1435
|
-
` workspaceRoot: ${JSON.stringify(workspaceRoot)},`,
|
|
1436
|
-
' profileName: "debug-missing-profile",',
|
|
1437
|
-
' provider: "codex",',
|
|
1438
|
-
' activeRun: { id: "run_debug_acceptance", startedAt: new Date().toISOString() },',
|
|
1439
|
-
'});',
|
|
1440
|
-
'store.close();',
|
|
1441
|
-
'process.stdout.write(record.id);',
|
|
1442
|
-
].join("\n");
|
|
1443
|
-
const seeded = spawnSync(process.execPath, ["--import", "tsx", "--input-type=module", "-e", seedScript], {
|
|
1444
|
-
cwd: repoRoot,
|
|
1445
|
-
env: { ...runtimeEnv, FORGERELAY_STATE_DIR: debugStateDir },
|
|
1446
|
-
encoding: "utf8",
|
|
1447
|
-
});
|
|
1448
|
-
if (seeded.status !== 0) {
|
|
1449
|
-
throw new Error(`unable to seed debug subagent: ${seeded.stderr.trim()}`);
|
|
1450
|
-
}
|
|
1451
|
-
const agentId = seeded.stdout.trim();
|
|
1452
|
-
assert.match(agentId, /^agt_/);
|
|
1453
|
-
|
|
1454
|
-
const promptFile = join(acceptanceRoot, "subagent-prompt.txt");
|
|
1455
|
-
writeFileSync(promptFile, "debug acceptance prompt that must not reach a provider\n", "utf8");
|
|
1456
|
-
const worker = spawnSync(
|
|
1457
|
-
process.execPath,
|
|
1458
|
-
["--import", "tsx", "src/cli.ts", "agents", "__worker", agentId, "--prompt-file", promptFile],
|
|
1459
|
-
{ cwd: repoRoot, env: runtimeEnv, encoding: "utf8", maxBuffer: 2 * 1024 * 1024 },
|
|
1460
|
-
);
|
|
1461
|
-
if (worker.status !== 0) {
|
|
1462
|
-
throw new Error(`debug subagent worker failed to execute: ${worker.stderr.trim()}`);
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
const inspectScript = [
|
|
1466
|
-
'import { SubagentSessionStore } from "./src/subagents/sessions/store.js";',
|
|
1467
|
-
'import { SubagentDeliveryMailbox } from "./src/subagents/sessions/delivery-mailbox.js";',
|
|
1468
|
-
'const store = new SubagentSessionStore(process.env.FORGERELAY_STATE_DIR);',
|
|
1469
|
-
`const record = store.get(${JSON.stringify(agentId)});`,
|
|
1470
|
-
`const deliveries = new SubagentDeliveryMailbox(process.env.FORGERELAY_STATE_DIR).claimSession(${JSON.stringify(workspaceId)}, ${JSON.stringify(agentId)});`,
|
|
1471
|
-
'store.close();',
|
|
1472
|
-
'process.stdout.write(JSON.stringify({ status: record?.status, latestRun: record?.latestRun, delivery: deliveries[0] }));',
|
|
1473
|
-
].join("\n");
|
|
1474
|
-
const inspected = spawnSync(process.execPath, ["--import", "tsx", "--input-type=module", "-e", inspectScript], {
|
|
1475
|
-
cwd: repoRoot,
|
|
1476
|
-
env: runtimeEnv,
|
|
1477
|
-
encoding: "utf8",
|
|
1478
|
-
});
|
|
1479
|
-
if (inspected.status !== 0) {
|
|
1480
|
-
throw new Error(`unable to inspect debug subagent: ${inspected.stderr.trim()}`);
|
|
1481
|
-
}
|
|
1482
|
-
const record = JSON.parse(inspected.stdout);
|
|
1483
|
-
assert.equal(record.status, "idle");
|
|
1484
|
-
assert.equal(record.latestRun?.status, "failed");
|
|
1485
|
-
assert.equal(record.delivery?.outcome, "failed");
|
|
1486
|
-
assert.match(record.delivery?.error ?? "", /Subagent profile not found/);
|
|
1487
|
-
pass("subagent hook path", `${agentId} stopped in deterministic failed Run without calling a provider`);
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
function toolText(result) {
|
|
1491
|
-
return (result.content ?? [])
|
|
1492
|
-
.filter((entry) => entry?.type === "text" && typeof entry.text === "string")
|
|
1493
|
-
.map((entry) => entry.text)
|
|
1494
|
-
.join("\n");
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
function readHookEntries(path) {
|
|
1498
|
-
return readFileSync(path, "utf8")
|
|
1499
|
-
.trim()
|
|
1500
|
-
.split(/\r?\n/)
|
|
1501
|
-
.filter(Boolean)
|
|
1502
|
-
.map((line) => JSON.parse(line));
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
async function assertDebugPortFree() {
|
|
1506
|
-
await new Promise((resolvePromise, rejectPromise) => {
|
|
1507
|
-
const socket = connect({ host: "127.0.0.1", port: 7677 });
|
|
1508
|
-
socket.setTimeout(500);
|
|
1509
|
-
socket.once("connect", () => {
|
|
1510
|
-
socket.destroy();
|
|
1511
|
-
rejectPromise(new Error("debug port 7677 is already in use; stop the existing debug server first"));
|
|
1512
|
-
});
|
|
1513
|
-
socket.once("error", (error) => {
|
|
1514
|
-
socket.destroy();
|
|
1515
|
-
if (error.code === "ECONNREFUSED") {
|
|
1516
|
-
resolvePromise();
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
|
-
rejectPromise(error);
|
|
1520
|
-
});
|
|
1521
|
-
socket.once("timeout", () => {
|
|
1522
|
-
socket.destroy();
|
|
1523
|
-
resolvePromise();
|
|
1524
|
-
});
|
|
1525
|
-
});
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
function assertCurlAvailable() {
|
|
1529
|
-
const result = spawnSync("curl", ["--version"], { encoding: "utf8" });
|
|
1530
|
-
if (result.status !== 0) {
|
|
1531
|
-
throw new Error("npm run debug:accept requires curl on PATH.");
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
function pass(label, detail) {
|
|
1536
|
-
console.log(`✓ ${label}: ${detail}`);
|
|
1537
|
-
}
|