@akira-tl/forgerelay 0.8.8 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +10 -18
- package/capabilities/host-integration/GUIDE.md +2 -2
- package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
- package/dist/activity/history/bash-output-store.js +250 -0
- package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
- package/dist/activity/{query-service.js → history/query-service.js} +13 -7
- package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
- package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
- package/dist/activity/storage/history.js +12 -0
- package/dist/activity/storage/paths.js +56 -0
- package/dist/activity/storage/segmented-log.js +149 -0
- package/dist/cli/setup-support.js +116 -0
- package/dist/cli.js +18 -126
- package/dist/lsp/code-intelligence.js +1 -1
- package/dist/lsp/test-support/server-fixture.js +9 -9
- package/dist/mcp/filesystem/filesystem-tools.js +245 -0
- package/dist/mcp/hooks/command-runner.js +79 -0
- package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
- package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
- package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
- package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
- package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
- package/dist/mcp/panel/app.js +143 -0
- package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
- package/dist/mcp/process/runtime.js +257 -0
- package/dist/mcp/process/tools.js +346 -0
- package/dist/mcp/server/core/activity-support.js +70 -0
- package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
- package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +2 -2
- package/dist/mcp/server/core/capability-support.js +90 -0
- package/dist/mcp/server/core/schemas.js +216 -0
- package/dist/mcp/server/core/tool-support.js +207 -0
- package/dist/mcp/server/core/version.js +9 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +400 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +523 -0
- package/dist/mcp/server/transport/http-server.js +301 -0
- package/dist/mcp/server/transport/http-support.js +43 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +222 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +552 -0
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
- package/dist/{config.js → runtime/config/config.js} +3 -3
- package/dist/{user-config.js → runtime/config/user-config.js} +14 -54
- package/dist/{db → runtime/state/db}/client.js +1 -1
- package/dist/{db → runtime/state/db}/migrations.js +34 -3
- package/dist/runtime/state/lock/file-lock.js +130 -0
- package/dist/runtime/testing/server-fixture.js +190 -0
- package/dist/server.js +116 -4202
- package/dist/subagents/providers/adapters/claude.js +86 -52
- package/dist/subagents/providers/adapters/codex.js +103 -39
- package/dist/subagents/providers/adapters/opencode.js +114 -84
- package/dist/subagents/providers/adapters/pi.js +59 -8
- package/dist/subagents/providers/availability.js +15 -30
- package/dist/subagents/providers/runtime/external-command.js +56 -0
- package/dist/subagents/sessions/capability.js +1 -1
- package/dist/subagents/sessions/execution.js +1 -1
- package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
- package/dist/subagents/sessions/store.js +1 -1
- package/dist/ui/.vite/manifest.json +342 -410
- package/dist/ui/activity-panel-app.html +2 -4
- package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
- package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
- package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
- package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
- package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
- package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
- package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
- package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
- package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
- package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
- package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
- package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
- package/dist/ui/assets/css-C1tjBtVN.js +1 -0
- package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
- package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
- package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
- package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
- package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
- package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
- package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
- package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
- package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
- package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
- package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
- package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
- package/dist/ui/assets/html-By4dNnM4.js +1 -0
- package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
- package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
- package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
- package/dist/ui/assets/java-DYRe2lWG.js +1 -0
- package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
- package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
- package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
- package/dist/ui/assets/json-dbltgiiA.js +1 -0
- package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
- package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
- package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
- package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
- package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
- package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
- package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
- package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
- package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
- package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
- package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
- package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
- package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
- package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
- package/dist/ui/assets/r-CcezTbLq.js +1 -0
- package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
- package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
- package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
- package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
- package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
- package/dist/ui/assets/scss-BQb-cp01.js +1 -0
- package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
- package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
- package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
- package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
- package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
- package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
- package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
- package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
- package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
- package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
- package/dist/ui/assets/tsx-CajzROyy.js +1 -0
- package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
- package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
- package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
- package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
- package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
- package/dist/ui/assets/xml-BI86-WbH.js +1 -0
- package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
- package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
- package/dist/workspaces/bootstrap.js +72 -0
- package/dist/workspaces/context.js +302 -0
- package/dist/workspaces/conversation-test-support.js +86 -0
- package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
- package/dist/workspaces/inventory.js +131 -0
- package/dist/workspaces/paths.js +55 -0
- package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
- package/dist/workspaces/relay/result-support.js +111 -0
- package/dist/workspaces/relay/tests/test-support.js +203 -0
- package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
- package/dist/workspaces/relay/types.js +1 -0
- package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
- package/dist/workspaces/resources/skills.js +285 -0
- package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
- package/dist/workspaces/sessions.js +457 -0
- package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
- package/dist/workspaces.js +68 -995
- package/docs/chatgpt-coding-workflow.md +3 -11
- package/docs/configuration.md +12 -25
- package/docs/gotchas.md +0 -30
- package/docs/roadmap.md +6 -10
- package/docs/security.md +1 -5
- package/docs/setup.md +0 -11
- package/package.json +6 -8
- package/scripts/ci/architecture.mjs +109 -0
- package/scripts/ci/verify.mjs +5 -0
- package/scripts/debug/accept/bootstrap.mjs +326 -0
- package/scripts/debug/accept/harness.mjs +76 -0
- package/scripts/debug/accept/support.mjs +432 -0
- package/scripts/debug/accept.mjs +43 -806
- package/scripts/debug/relay-accept/support.mjs +135 -0
- package/scripts/debug/relay-accept.mjs +26 -115
- package/scripts/debug/traffic/support.mjs +167 -0
- package/scripts/debug/traffic/traffic-audit.mjs +4 -161
- package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
- package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
- package/scripts/wiki/sync.mjs +21 -10
- package/dist/activity/bash-output-store.js +0 -127
- package/dist/pi-tools.js +0 -110
- package/dist/skills.js +0 -127
- package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
- package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
- package/dist/ui/assets/c-IBzBpxAO.js +0 -1
- package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
- package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
- package/dist/ui/assets/css-DEfus9J5.js +0 -1
- package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
- package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
- package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
- package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
- package/dist/ui/assets/html-D-C6YZk4.js +0 -1
- package/dist/ui/assets/java-ppHvycj5.js +0 -1
- package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
- package/dist/ui/assets/json-Cw91r8h1.js +0 -1
- package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
- package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
- package/dist/ui/assets/r-DfFga4si.js +0 -1
- package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
- package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
- package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
- package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
- package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
- package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
- package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
- package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
- package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
- package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
- package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
- package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
- package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
- package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
- package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
- package/dist/ui/workspace-app.html +0 -18
- package/dist/ui/workspace-lifecycle-app.html +0 -18
- package/docs/agent-profile-schema.md +0 -114
- package/docs/artifact-exchange.md +0 -65
- package/docs/versioning.md +0 -205
- /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
- /package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +0 -0
- /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
- /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
- /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
- /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
- /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
- /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
- /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
- /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
- /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
- /package/dist/{oauth → mcp/oauth}/router.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
- /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
- /package/dist/{process-platform.js → mcp/process/process-platform.js} +0 -0
- /package/dist/{process-sessions.js → mcp/process/process-sessions.js} +0 -0
- /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
- /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
- /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
- /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
- /package/dist/{db → runtime/state/db}/schema.js +0 -0
- /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
- /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
- /package/dist/{git.js → workspaces/git/git.js} +0 -0
- /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
- /package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +0 -0
- /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
- /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
- /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { isRemoteMcpUnauthorized, refreshRemoteAuthentication, } from "./remote-auth.js";
|
|
6
|
-
import { RemoteMcpConnectionPool } from "./remote-mcp-connection-pool.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const ROUTE_LOCK_STALE_MS = 30_000;
|
|
12
|
-
const ROUTE_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
5
|
+
import { isRemoteMcpUnauthorized, refreshRemoteAuthentication, } from "./auth/remote-auth.js";
|
|
6
|
+
import { RemoteMcpConnectionPool } from "./transport/remote-mcp-connection-pool.js";
|
|
7
|
+
import { withFileLock } from "../../runtime/state/lock/file-lock.js";
|
|
8
|
+
import { withRemoteServiceEndpoint } from "./transport/remote-transport.js";
|
|
9
|
+
import { loadForgeRelayFiles, writeForgeRelayRemote, } from "../../runtime/config/user-config.js";
|
|
10
|
+
import { assertRemoteToolSucceeded, stringField, copyStringField, copyBooleanField, copyNumberField, safeTaskSummary, toolResultText, remapToolResultWorkspaceId, replaceExactWorkspaceId, sanitizedRemoteError, errorMessage } from "./result-support.js";
|
|
13
11
|
export class RemoteWorkspaceRelay {
|
|
14
12
|
routes = new Map();
|
|
15
13
|
turnRoutes = new Map();
|
|
@@ -109,7 +107,7 @@ export class RemoteWorkspaceRelay {
|
|
|
109
107
|
throw new Error("Remote open_workspace response did not include a valid workspace mode.");
|
|
110
108
|
}
|
|
111
109
|
const sourceRoot = typeof structured?.sourceRoot === "string" ? structured.sourceRoot : undefined;
|
|
112
|
-
const route = this.findOrCreateRoute({
|
|
110
|
+
const route = await this.findOrCreateRoute({
|
|
113
111
|
remoteInstanceId: resolved.remote.instanceId,
|
|
114
112
|
remoteWorkspaceId,
|
|
115
113
|
root,
|
|
@@ -167,7 +165,7 @@ export class RemoteWorkspaceRelay {
|
|
|
167
165
|
? structured.mode
|
|
168
166
|
: route.mode;
|
|
169
167
|
const sourceRoot = typeof structured?.sourceRoot === "string" ? structured.sourceRoot : route.sourceRoot;
|
|
170
|
-
this.findOrCreateRoute({
|
|
168
|
+
await this.findOrCreateRoute({
|
|
171
169
|
remoteInstanceId: route.remoteInstanceId,
|
|
172
170
|
remoteWorkspaceId: route.remoteWorkspaceId,
|
|
173
171
|
root,
|
|
@@ -194,6 +192,9 @@ export class RemoteWorkspaceRelay {
|
|
|
194
192
|
async bash(gatewayWorkspaceId, input, conversationScopeId) {
|
|
195
193
|
return this.callWorkspaceTool(gatewayWorkspaceId, "bash", input, conversationScopeId);
|
|
196
194
|
}
|
|
195
|
+
async workspaceInstruction(gatewayWorkspaceId, path, conversationScopeId) {
|
|
196
|
+
return this.callWorkspaceTool(gatewayWorkspaceId, "workspace_instruction", { path }, conversationScopeId);
|
|
197
|
+
}
|
|
197
198
|
async execCommand(gatewayWorkspaceId, input, conversationScopeId) {
|
|
198
199
|
return this.callWorkspaceTool(gatewayWorkspaceId, "exec_command", input, conversationScopeId);
|
|
199
200
|
}
|
|
@@ -296,7 +297,7 @@ export class RemoteWorkspaceRelay {
|
|
|
296
297
|
const remoteStructured = result.structuredContent;
|
|
297
298
|
if (action === "delete") {
|
|
298
299
|
this.routes.delete(gatewayWorkspaceId);
|
|
299
|
-
this.deletePersistedRoute(gatewayWorkspaceId);
|
|
300
|
+
await this.deletePersistedRoute(gatewayWorkspaceId);
|
|
300
301
|
}
|
|
301
302
|
for (const [turnId, routedWorkspaceId] of this.turnRoutes) {
|
|
302
303
|
if (routedWorkspaceId === gatewayWorkspaceId)
|
|
@@ -356,35 +357,33 @@ export class RemoteWorkspaceRelay {
|
|
|
356
357
|
this.routes.set(workspaceId, route);
|
|
357
358
|
}
|
|
358
359
|
}
|
|
359
|
-
findOrCreateRoute(input) {
|
|
360
|
-
let selected;
|
|
360
|
+
async findOrCreateRoute(input) {
|
|
361
361
|
mkdirSync(this.routeStateDir, { recursive: true });
|
|
362
|
-
this.
|
|
362
|
+
const selected = await withFileLock(`${this.routeStatePath}.lock`, () => {
|
|
363
363
|
const routes = this.readRoutesFromDisk();
|
|
364
364
|
const existing = [...routes.values()]
|
|
365
365
|
.filter((route) => route.remoteInstanceId === input.remoteInstanceId &&
|
|
366
366
|
route.remoteWorkspaceId === input.remoteWorkspaceId)
|
|
367
367
|
.sort((left, right) => left.gatewayWorkspaceId.localeCompare(right.gatewayWorkspaceId))[0];
|
|
368
|
-
|
|
368
|
+
const route = {
|
|
369
369
|
gatewayWorkspaceId: existing?.gatewayWorkspaceId ?? this.allocateGatewayWorkspaceId(routes),
|
|
370
370
|
...input,
|
|
371
371
|
};
|
|
372
|
-
routes.set(
|
|
372
|
+
routes.set(route.gatewayWorkspaceId, route);
|
|
373
373
|
this.writeRoutesToDisk(routes);
|
|
374
|
+
return route;
|
|
374
375
|
});
|
|
375
|
-
if (!selected)
|
|
376
|
-
throw new Error("Failed to persist relayed Workspace route.");
|
|
377
376
|
this.routes.set(selected.gatewayWorkspaceId, selected);
|
|
378
377
|
return selected;
|
|
379
378
|
}
|
|
380
|
-
deletePersistedRoute(workspaceId) {
|
|
381
|
-
this.updatePersistedRoutes((routes) => {
|
|
379
|
+
async deletePersistedRoute(workspaceId) {
|
|
380
|
+
await this.updatePersistedRoutes((routes) => {
|
|
382
381
|
routes.delete(workspaceId);
|
|
383
382
|
});
|
|
384
383
|
}
|
|
385
|
-
updatePersistedRoutes(update) {
|
|
384
|
+
async updatePersistedRoutes(update) {
|
|
386
385
|
mkdirSync(this.routeStateDir, { recursive: true });
|
|
387
|
-
this.
|
|
386
|
+
await withFileLock(`${this.routeStatePath}.lock`, () => {
|
|
388
387
|
const routes = this.readRoutesFromDisk();
|
|
389
388
|
update(routes);
|
|
390
389
|
this.writeRoutesToDisk(routes);
|
|
@@ -418,43 +417,6 @@ export class RemoteWorkspaceRelay {
|
|
|
418
417
|
rmSync(tempPath, { force: true });
|
|
419
418
|
}
|
|
420
419
|
}
|
|
421
|
-
withRouteFileLock(operation) {
|
|
422
|
-
const lockPath = `${this.routeStatePath}.lock`;
|
|
423
|
-
const deadline = Date.now() + ROUTE_LOCK_TIMEOUT_MS;
|
|
424
|
-
for (;;) {
|
|
425
|
-
try {
|
|
426
|
-
const fd = openSync(lockPath, "wx", 0o600);
|
|
427
|
-
closeSync(fd);
|
|
428
|
-
break;
|
|
429
|
-
}
|
|
430
|
-
catch (error) {
|
|
431
|
-
const code = error.code;
|
|
432
|
-
if (code !== "EEXIST")
|
|
433
|
-
throw error;
|
|
434
|
-
try {
|
|
435
|
-
if (Date.now() - statSync(lockPath).mtimeMs > ROUTE_LOCK_STALE_MS) {
|
|
436
|
-
rmSync(lockPath, { force: true });
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
catch (statError) {
|
|
441
|
-
if (statError.code === "ENOENT")
|
|
442
|
-
continue;
|
|
443
|
-
throw statError;
|
|
444
|
-
}
|
|
445
|
-
if (Date.now() >= deadline) {
|
|
446
|
-
throw new Error(`Timed out waiting for relayed workspace route lock: ${lockPath}`);
|
|
447
|
-
}
|
|
448
|
-
Atomics.wait(ROUTE_LOCK_SLEEP, 0, 0, ROUTE_LOCK_RETRY_MS);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
try {
|
|
452
|
-
return operation();
|
|
453
|
-
}
|
|
454
|
-
finally {
|
|
455
|
-
rmSync(lockPath, { force: true });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
420
|
remoteByAlias(aliasInput) {
|
|
459
421
|
const alias = aliasInput.trim();
|
|
460
422
|
if (!alias)
|
|
@@ -522,7 +484,7 @@ export class RemoteWorkspaceRelay {
|
|
|
522
484
|
}
|
|
523
485
|
async refreshRemote(alias, remote, endpoint) {
|
|
524
486
|
const refreshed = await refreshRemoteAuthentication(remote, endpoint);
|
|
525
|
-
writeForgeRelayRemote(alias, refreshed, this.authEnv);
|
|
487
|
+
await writeForgeRelayRemote(alias, refreshed, this.authEnv);
|
|
526
488
|
return refreshed;
|
|
527
489
|
}
|
|
528
490
|
allocateGatewayWorkspaceId(routes = this.routes) {
|
|
@@ -533,114 +495,3 @@ export class RemoteWorkspaceRelay {
|
|
|
533
495
|
return workspaceId;
|
|
534
496
|
}
|
|
535
497
|
}
|
|
536
|
-
function assertRemoteToolSucceeded(alias, tool, result) {
|
|
537
|
-
if (result.isError !== true)
|
|
538
|
-
return;
|
|
539
|
-
throw new Error(`Remote ForgeRelay ${alias} ${tool} failed: ${toolResultText(result)}`);
|
|
540
|
-
}
|
|
541
|
-
function stringField(structured, field, label) {
|
|
542
|
-
const value = structured?.[field];
|
|
543
|
-
if (typeof value !== "string" || !value) {
|
|
544
|
-
throw new Error(`${label} did not include ${field}.`);
|
|
545
|
-
}
|
|
546
|
-
return value;
|
|
547
|
-
}
|
|
548
|
-
function copyStringField(source, target, field) {
|
|
549
|
-
const value = source[field];
|
|
550
|
-
if (typeof value === "string")
|
|
551
|
-
target[field] = value;
|
|
552
|
-
}
|
|
553
|
-
function copyBooleanField(source, target, field) {
|
|
554
|
-
const value = source[field];
|
|
555
|
-
if (typeof value === "boolean")
|
|
556
|
-
target[field] = value;
|
|
557
|
-
}
|
|
558
|
-
function copyNumberField(source, target, field) {
|
|
559
|
-
const value = source[field];
|
|
560
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0)
|
|
561
|
-
target[field] = value;
|
|
562
|
-
}
|
|
563
|
-
function safeTaskSummary(value) {
|
|
564
|
-
if (!value || typeof value !== "object")
|
|
565
|
-
return undefined;
|
|
566
|
-
const summary = value;
|
|
567
|
-
if (summary.level !== "summary" ||
|
|
568
|
-
summary.version !== 1 ||
|
|
569
|
-
typeof summary.revision !== "number" ||
|
|
570
|
-
!Number.isInteger(summary.revision) ||
|
|
571
|
-
summary.revision < 0 ||
|
|
572
|
-
!Array.isArray(summary.lists)) {
|
|
573
|
-
return undefined;
|
|
574
|
-
}
|
|
575
|
-
const lists = [];
|
|
576
|
-
for (const value of summary.lists) {
|
|
577
|
-
if (!value || typeof value !== "object")
|
|
578
|
-
return undefined;
|
|
579
|
-
const list = value;
|
|
580
|
-
if (typeof list.id !== "string" ||
|
|
581
|
-
typeof list.name !== "string" ||
|
|
582
|
-
(list.state !== "active" && list.state !== "archived") ||
|
|
583
|
-
typeof list.revision !== "number" || !Number.isInteger(list.revision) || list.revision <= 0 ||
|
|
584
|
-
typeof list.taskCount !== "number" || !Number.isInteger(list.taskCount) || list.taskCount < 0 ||
|
|
585
|
-
typeof list.unfinishedTaskCount !== "number" || !Number.isInteger(list.unfinishedTaskCount) || list.unfinishedTaskCount < 0) {
|
|
586
|
-
return undefined;
|
|
587
|
-
}
|
|
588
|
-
lists.push({
|
|
589
|
-
id: list.id,
|
|
590
|
-
name: list.name,
|
|
591
|
-
state: list.state,
|
|
592
|
-
revision: list.revision,
|
|
593
|
-
taskCount: list.taskCount,
|
|
594
|
-
unfinishedTaskCount: list.unfinishedTaskCount,
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
return {
|
|
598
|
-
level: "summary",
|
|
599
|
-
version: 1,
|
|
600
|
-
revision: summary.revision,
|
|
601
|
-
lists,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
function toolResultText(result) {
|
|
605
|
-
return (result.content ?? [])
|
|
606
|
-
.filter((entry) => entry.type === "text")
|
|
607
|
-
.map((entry) => entry.text)
|
|
608
|
-
.join("\n") || "remote tool returned an error";
|
|
609
|
-
}
|
|
610
|
-
function remapToolResultWorkspaceId(result, remoteWorkspaceId, gatewayWorkspaceId) {
|
|
611
|
-
return {
|
|
612
|
-
...result,
|
|
613
|
-
content: (result.content ?? []).map((entry) => entry.type === "text"
|
|
614
|
-
? { ...entry, text: entry.text.split(remoteWorkspaceId).join(gatewayWorkspaceId) }
|
|
615
|
-
: entry),
|
|
616
|
-
...(result._meta
|
|
617
|
-
? { _meta: replaceExactWorkspaceId(result._meta, remoteWorkspaceId, gatewayWorkspaceId) }
|
|
618
|
-
: {}),
|
|
619
|
-
...(result.structuredContent
|
|
620
|
-
? {
|
|
621
|
-
structuredContent: replaceExactWorkspaceId(result.structuredContent, remoteWorkspaceId, gatewayWorkspaceId),
|
|
622
|
-
}
|
|
623
|
-
: {}),
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
function replaceExactWorkspaceId(value, from, to) {
|
|
627
|
-
if (typeof value === "string")
|
|
628
|
-
return value.split(from).join(to);
|
|
629
|
-
if (Array.isArray(value))
|
|
630
|
-
return value.map((entry) => replaceExactWorkspaceId(entry, from, to));
|
|
631
|
-
if (!value || typeof value !== "object")
|
|
632
|
-
return value;
|
|
633
|
-
return Object.fromEntries(Object.entries(value)
|
|
634
|
-
.map(([key, entry]) => [key, replaceExactWorkspaceId(entry, from, to)]));
|
|
635
|
-
}
|
|
636
|
-
function sanitizedRemoteError(error, remoteWorkspaceId, gatewayWorkspaceId) {
|
|
637
|
-
let message = errorMessage(error);
|
|
638
|
-
if (remoteWorkspaceId && gatewayWorkspaceId) {
|
|
639
|
-
message = message.split(remoteWorkspaceId).join(gatewayWorkspaceId);
|
|
640
|
-
}
|
|
641
|
-
message = message.replace(/(^|[^A-Za-z0-9_])ws_[0-9a-f]{10}(?=$|[^A-Za-z0-9_])/g, "$1[remote-workspace]");
|
|
642
|
-
return new Error(message, { cause: error });
|
|
643
|
-
}
|
|
644
|
-
function errorMessage(error) {
|
|
645
|
-
return error instanceof Error ? error.message : String(error);
|
|
646
|
-
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync, } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { markAdvertisedFileSourceActivated, resolveAdvertisedFileReadPath, } from "../../mcp/filesystem/advertised-files.js";
|
|
6
|
+
import { expandHomePath, isPathInsideRoot } from "../../mcp/filesystem/roots.js";
|
|
7
|
+
const SUBAGENT_DELEGATION_NAME = "subagent-delegation";
|
|
8
|
+
const FRONTMATTER_DELIMITER = "---";
|
|
9
|
+
export function effectiveSkillPaths(config, cwd) {
|
|
10
|
+
const defaultPathCandidates = [
|
|
11
|
+
join(homedir(), ".agents", "skills"),
|
|
12
|
+
resolve(cwd, ".agents", "skills"),
|
|
13
|
+
config.configSkillsDir,
|
|
14
|
+
join(config.agentDir, "skills"),
|
|
15
|
+
];
|
|
16
|
+
const defaultPaths = defaultPathCandidates.filter((path) => path !== undefined && existsSync(path));
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
return [...defaultPaths, ...config.skillPaths]
|
|
19
|
+
.map((path) => resolveSkillPath(path, cwd))
|
|
20
|
+
.filter((path) => {
|
|
21
|
+
if (seen.has(path))
|
|
22
|
+
return false;
|
|
23
|
+
seen.add(path);
|
|
24
|
+
return true;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function resolveSkillPath(path, cwd) {
|
|
28
|
+
return resolve(cwd, expandHomePath(path));
|
|
29
|
+
}
|
|
30
|
+
export function loadWorkspaceSkills(config, cwd) {
|
|
31
|
+
if (!config.skillsEnabled)
|
|
32
|
+
return { skills: [], diagnostics: [] };
|
|
33
|
+
const skills = [];
|
|
34
|
+
const diagnostics = [];
|
|
35
|
+
const winners = new Map();
|
|
36
|
+
for (const sourcePath of effectiveSkillPaths(config, cwd)) {
|
|
37
|
+
const candidates = discoverSkills(sourcePath, diagnostics);
|
|
38
|
+
for (const skill of candidates) {
|
|
39
|
+
const existing = winners.get(skill.name);
|
|
40
|
+
if (existing) {
|
|
41
|
+
diagnostics.push({
|
|
42
|
+
type: "collision",
|
|
43
|
+
message: `Skill '${skill.name}' from ${skill.filePath} was ignored because ${existing.skill.filePath} was loaded first.`,
|
|
44
|
+
collision: {
|
|
45
|
+
resourceType: "skill",
|
|
46
|
+
name: skill.name,
|
|
47
|
+
winnerPath: existing.skill.filePath,
|
|
48
|
+
loserPath: skill.filePath,
|
|
49
|
+
winnerSource: existing.source,
|
|
50
|
+
loserSource: sourcePath,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
winners.set(skill.name, { skill, source: sourcePath });
|
|
56
|
+
skills.push(skill);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (config.subagents)
|
|
60
|
+
return { skills, diagnostics };
|
|
61
|
+
return {
|
|
62
|
+
skills: skills.filter((skill) => skill.name !== SUBAGENT_DELEGATION_NAME),
|
|
63
|
+
diagnostics: diagnostics.filter((diagnostic) => {
|
|
64
|
+
const collision = diagnostic.collision;
|
|
65
|
+
return !(collision?.resourceType === "skill" && collision.name === SUBAGENT_DELEGATION_NAME);
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function discoverSkills(sourcePath, diagnostics) {
|
|
70
|
+
if (!existsSync(sourcePath))
|
|
71
|
+
return [];
|
|
72
|
+
let stats;
|
|
73
|
+
try {
|
|
74
|
+
stats = statSync(sourcePath);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
type: "warning",
|
|
79
|
+
message: `Unable to inspect skill path ${sourcePath}: ${errorMessage(error)}`,
|
|
80
|
+
path: sourcePath,
|
|
81
|
+
});
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
if (stats.isFile()) {
|
|
85
|
+
const skill = loadSkillFile(sourcePath, diagnostics);
|
|
86
|
+
return skill ? [skill] : [];
|
|
87
|
+
}
|
|
88
|
+
if (!stats.isDirectory())
|
|
89
|
+
return [];
|
|
90
|
+
const entry = join(sourcePath, "SKILL.md");
|
|
91
|
+
if (existsSync(entry)) {
|
|
92
|
+
const skill = loadSkillFile(entry, diagnostics);
|
|
93
|
+
return skill ? [skill] : [];
|
|
94
|
+
}
|
|
95
|
+
const skills = [];
|
|
96
|
+
for (const entryName of sortedDirectoryEntries(sourcePath, diagnostics)) {
|
|
97
|
+
const child = join(sourcePath, entryName);
|
|
98
|
+
let childStats;
|
|
99
|
+
try {
|
|
100
|
+
childStats = statSync(child);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
diagnostics.push({
|
|
104
|
+
type: "warning",
|
|
105
|
+
message: `Unable to inspect skill candidate ${child}: ${errorMessage(error)}`,
|
|
106
|
+
path: child,
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (childStats.isDirectory()) {
|
|
111
|
+
skills.push(...discoverSkills(child, diagnostics));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (childStats.isFile() && extname(entryName).toLowerCase() === ".md") {
|
|
115
|
+
const skill = loadSkillFile(child, diagnostics);
|
|
116
|
+
if (skill)
|
|
117
|
+
skills.push(skill);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return skills;
|
|
121
|
+
}
|
|
122
|
+
function sortedDirectoryEntries(path, diagnostics) {
|
|
123
|
+
try {
|
|
124
|
+
return readdirSync(path).sort((left, right) => left.localeCompare(right));
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
diagnostics.push({
|
|
128
|
+
type: "warning",
|
|
129
|
+
message: `Unable to read skill directory ${path}: ${errorMessage(error)}`,
|
|
130
|
+
path,
|
|
131
|
+
});
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function loadSkillFile(filePath, diagnostics) {
|
|
136
|
+
let content;
|
|
137
|
+
try {
|
|
138
|
+
content = readFileSync(filePath, "utf8");
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
diagnostics.push({
|
|
142
|
+
type: "warning",
|
|
143
|
+
message: `Unable to read skill file ${filePath}: ${errorMessage(error)}`,
|
|
144
|
+
path: filePath,
|
|
145
|
+
});
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
let frontmatter = {};
|
|
149
|
+
try {
|
|
150
|
+
frontmatter = parseSkillFrontmatter(content);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
diagnostics.push({
|
|
154
|
+
type: "error",
|
|
155
|
+
message: `Invalid skill frontmatter in ${filePath}: ${errorMessage(error)}`,
|
|
156
|
+
path: filePath,
|
|
157
|
+
});
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const baseDir = dirname(filePath);
|
|
161
|
+
const name = normalizedSkillName(frontmatter.name, filePath);
|
|
162
|
+
if (!name) {
|
|
163
|
+
diagnostics.push({
|
|
164
|
+
type: "error",
|
|
165
|
+
message: `Skill ${filePath} has an invalid or empty name.`,
|
|
166
|
+
path: filePath,
|
|
167
|
+
});
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const description = typeof frontmatter.description === "string"
|
|
171
|
+
? frontmatter.description.trim()
|
|
172
|
+
: "";
|
|
173
|
+
return {
|
|
174
|
+
name,
|
|
175
|
+
description,
|
|
176
|
+
filePath: resolve(filePath),
|
|
177
|
+
baseDir: resolve(baseDir),
|
|
178
|
+
disableModelInvocation: frontmatter["disable-model-invocation"] === true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function parseSkillFrontmatter(content) {
|
|
182
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
183
|
+
if (lines[0]?.trim() !== FRONTMATTER_DELIMITER)
|
|
184
|
+
return {};
|
|
185
|
+
const end = lines.findIndex((line, index) => index > 0 && line.trim() === FRONTMATTER_DELIMITER);
|
|
186
|
+
if (end < 0)
|
|
187
|
+
throw new Error("opening '---' is missing a closing delimiter");
|
|
188
|
+
const parsed = parseYaml(lines.slice(1, end).join("\n"));
|
|
189
|
+
if (parsed === null || parsed === undefined)
|
|
190
|
+
return {};
|
|
191
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
192
|
+
throw new Error("frontmatter must be a YAML mapping");
|
|
193
|
+
}
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
function normalizedSkillName(value, filePath) {
|
|
197
|
+
const explicit = typeof value === "string" ? value.trim() : "";
|
|
198
|
+
if (explicit)
|
|
199
|
+
return explicit;
|
|
200
|
+
if (basename(filePath).toLowerCase() === "skill.md")
|
|
201
|
+
return basename(dirname(filePath)).trim();
|
|
202
|
+
return basename(filePath, extname(filePath)).trim();
|
|
203
|
+
}
|
|
204
|
+
function errorMessage(error) {
|
|
205
|
+
return error instanceof Error ? error.message : String(error);
|
|
206
|
+
}
|
|
207
|
+
export function resolveSkillReadPath(skills, activatedSkillDirs, inputPath) {
|
|
208
|
+
const virtualRead = resolveVirtualSkillReadPath(skills, activatedSkillDirs, inputPath);
|
|
209
|
+
if (virtualRead)
|
|
210
|
+
return virtualRead;
|
|
211
|
+
const resolution = resolveAdvertisedFileReadPath(skills, activatedSkillDirs, inputPath);
|
|
212
|
+
if (!resolution)
|
|
213
|
+
return undefined;
|
|
214
|
+
return {
|
|
215
|
+
absolutePath: resolution.absolutePath,
|
|
216
|
+
skill: resolution.source,
|
|
217
|
+
isSkillFile: resolution.isEntryFile,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function resolveVirtualSkillReadPath(skills, activatedSkillDirs, inputPath) {
|
|
221
|
+
const prefix = "skills://";
|
|
222
|
+
if (!inputPath.startsWith(prefix))
|
|
223
|
+
return undefined;
|
|
224
|
+
const requested = inputPath.slice(prefix.length);
|
|
225
|
+
const slashIndex = requested.indexOf("/");
|
|
226
|
+
const encodedName = slashIndex === -1 ? requested : requested.slice(0, slashIndex);
|
|
227
|
+
if (!encodedName)
|
|
228
|
+
throw new Error(`Invalid skill URI: ${inputPath}`);
|
|
229
|
+
let name;
|
|
230
|
+
try {
|
|
231
|
+
name = decodeURIComponent(encodedName);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
throw new Error(`Invalid skill URI: ${inputPath}`);
|
|
235
|
+
}
|
|
236
|
+
const skill = skills.find((candidate) => candidate.name === name);
|
|
237
|
+
if (!skill)
|
|
238
|
+
throw new Error(`Unknown advertised skill: ${name}`);
|
|
239
|
+
if (slashIndex === -1 || slashIndex === requested.length - 1) {
|
|
240
|
+
return {
|
|
241
|
+
absolutePath: resolve(skill.filePath),
|
|
242
|
+
skill,
|
|
243
|
+
isSkillFile: true,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const relativePart = requested.slice(slashIndex + 1);
|
|
247
|
+
const segments = relativePart.split("/").map((segment) => {
|
|
248
|
+
let decoded;
|
|
249
|
+
try {
|
|
250
|
+
decoded = decodeURIComponent(segment);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
throw new Error(`Invalid skill URI: ${inputPath}`);
|
|
254
|
+
}
|
|
255
|
+
if (!decoded || decoded === "." || decoded === ".." || decoded.includes("/") || decoded.includes("\\")) {
|
|
256
|
+
throw new Error(`Invalid skill URI: ${inputPath}`);
|
|
257
|
+
}
|
|
258
|
+
return decoded;
|
|
259
|
+
});
|
|
260
|
+
const baseDir = resolve(skill.baseDir);
|
|
261
|
+
if (!activatedSkillDirs.has(baseDir))
|
|
262
|
+
return undefined;
|
|
263
|
+
const absolutePath = resolve(baseDir, ...segments);
|
|
264
|
+
if (!isPathInsideRoot(absolutePath, baseDir)) {
|
|
265
|
+
throw new Error(`Skill resource is outside its skill directory: ${inputPath}`);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
absolutePath,
|
|
269
|
+
skill,
|
|
270
|
+
isSkillFile: false,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
export function markSkillActivated(activatedSkillDirs, skill) {
|
|
274
|
+
markAdvertisedFileSourceActivated(activatedSkillDirs, skill);
|
|
275
|
+
}
|
|
276
|
+
export function formatPathForPrompt(path) {
|
|
277
|
+
const home = resolve(homedir());
|
|
278
|
+
const resolvedPath = resolve(path);
|
|
279
|
+
if (resolvedPath === home)
|
|
280
|
+
return "~";
|
|
281
|
+
if (resolvedPath.startsWith(`${home}${sep}`)) {
|
|
282
|
+
return `~/${resolvedPath.slice(home.length + 1).split(sep).join("/")}`;
|
|
283
|
+
}
|
|
284
|
+
return resolvedPath.split(sep).join("/");
|
|
285
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { git, getGitEligibility, safeWorkspaceRefSegment } from "
|
|
5
|
-
const REVIEW_REF_PREFIX = "refs/
|
|
4
|
+
import { git, getGitEligibility, safeWorkspaceRefSegment } from "../git/git.js";
|
|
5
|
+
const REVIEW_REF_PREFIX = "refs/forgerelay/review";
|
|
6
6
|
const DEFAULT_MAX_REVIEW_WORKSPACE_STATES = 128;
|
|
7
7
|
export function createReviewCheckpointManager(options = {}) {
|
|
8
8
|
const maxWorkspaceStates = options.maxWorkspaceStates ?? DEFAULT_MAX_REVIEW_WORKSPACE_STATES;
|