@akira-tl/forgerelay 0.8.7 → 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 +18 -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 +139 -4192
- 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 -409
- 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 +69 -991
- 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-CUAN6zyW.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-CgzrutLm.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-BrLbezbq.js +0 -1
- package/dist/ui/assets/scrollbar-CbhpdW05.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-Bhj96tsR.js +0 -1
- package/dist/ui/assets/workspace-app-CxwJuZyS.js +0 -111
- package/dist/ui/assets/workspace-app-D6UR0AFl.js +0 -5
- package/dist/ui/assets/workspace-app-ldjBmCJR.css +0 -1
- package/dist/ui/assets/workspace-lifecycle-app-Cqfhx9pV.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
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { loadCapabilityGuides } from "../mcp/server/core/capabilities.js";
|
|
4
|
+
import { createManagedWorktree, discardFreshManagedWorktree, } from "./git/git-worktrees.js";
|
|
5
|
+
import { AccessDeniedError, assertAllowedPath } from "../mcp/filesystem/roots.js";
|
|
6
|
+
import { loadSubagentProfiles } from "../subagents/profiles.js";
|
|
7
|
+
import { BOOTSTRAP_CONTEXT_COMPONENTS, bootstrapContextFingerprints, resolveBootstrapContextComponents, } from "./bootstrap.js";
|
|
8
|
+
import { canonicalPath, canonicalPersistedWorkspacePath } from "./paths.js";
|
|
9
|
+
const WORKSPACE_SESSION_IDLE_TTL_MS = 30 * 24 * 60 * 60 * 1_000;
|
|
10
|
+
const WORKSPACE_GC_INTERVAL_MS = 60 * 60 * 1_000;
|
|
11
|
+
/**
|
|
12
|
+
* Own persistent Workspace identity and restoration. The Registry coordinates
|
|
13
|
+
* product lifecycle; this service centralizes state folding, conversation
|
|
14
|
+
* bindings, idle-cache GC, and managed-worktree rehydration.
|
|
15
|
+
*/
|
|
16
|
+
export class WorkspaceSessionService {
|
|
17
|
+
config;
|
|
18
|
+
store;
|
|
19
|
+
workspaces;
|
|
20
|
+
context;
|
|
21
|
+
pendingOpens = new Map();
|
|
22
|
+
lastWorkspaceGcAt = 0;
|
|
23
|
+
constructor(config, store, workspaces, context) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.store = store;
|
|
26
|
+
this.workspaces = workspaces;
|
|
27
|
+
this.context = context;
|
|
28
|
+
}
|
|
29
|
+
async openOnce(operationKey, open) {
|
|
30
|
+
const pending = this.pendingOpens.get(operationKey);
|
|
31
|
+
if (pending) {
|
|
32
|
+
const context = await pending;
|
|
33
|
+
return { ...context, workspaceReused: true };
|
|
34
|
+
}
|
|
35
|
+
const operation = open();
|
|
36
|
+
this.pendingOpens.set(operationKey, operation);
|
|
37
|
+
try {
|
|
38
|
+
return await operation;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
if (this.pendingOpens.get(operationKey) === operation)
|
|
42
|
+
this.pendingOpens.delete(operationKey);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async workspaceTargetKeys(workspace) {
|
|
46
|
+
if (workspace.mode === "checkout") {
|
|
47
|
+
return [JSON.stringify(["checkout", await canonicalPath(workspace.root), null])];
|
|
48
|
+
}
|
|
49
|
+
const keys = [JSON.stringify(["worktree-path", await canonicalPath(workspace.root)])];
|
|
50
|
+
if (workspace.sourceRoot && workspace.worktree?.targetBranch) {
|
|
51
|
+
keys.push(JSON.stringify([
|
|
52
|
+
"worktree",
|
|
53
|
+
await canonicalPath(workspace.sourceRoot),
|
|
54
|
+
workspace.worktree.targetBranch,
|
|
55
|
+
]));
|
|
56
|
+
}
|
|
57
|
+
return keys;
|
|
58
|
+
}
|
|
59
|
+
async boundConversationContext(conversationScopeId, targetKey, mode, matches, bootstrapContext) {
|
|
60
|
+
if (!conversationScopeId || !this.store)
|
|
61
|
+
return undefined;
|
|
62
|
+
const binding = this.store.getConversationBinding(conversationScopeId, targetKey);
|
|
63
|
+
if (!binding)
|
|
64
|
+
return undefined;
|
|
65
|
+
const session = this.store.getSession(binding.workspaceSessionId);
|
|
66
|
+
if (!session || session.status !== "active" || session.mode !== mode) {
|
|
67
|
+
this.store.deleteConversationBinding(conversationScopeId, targetKey);
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const root = await this.validSessionRoot(session);
|
|
71
|
+
if (!root) {
|
|
72
|
+
this.store.deleteConversationBinding(conversationScopeId, targetKey);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (!await matches(session, root)) {
|
|
76
|
+
this.store.deleteConversationBinding(conversationScopeId, targetKey);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const context = await this.reusedWorkspaceContext(this.getWorkspace(session.id));
|
|
80
|
+
return this.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
|
|
81
|
+
}
|
|
82
|
+
withConversationContext(context, conversationScopeId, targetKey, bootstrapContext) {
|
|
83
|
+
if (!conversationScopeId || !this.store) {
|
|
84
|
+
const bootstrapContextComponents = resolveBootstrapContextComponents(bootstrapContext, context.bootstrapComponentFingerprints, []);
|
|
85
|
+
return {
|
|
86
|
+
...context,
|
|
87
|
+
bootstrapContextComponents,
|
|
88
|
+
includeBootstrapContext: bootstrapContextComponents.length > 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const delivery = this.store.getContextDelivery(conversationScopeId, targetKey);
|
|
92
|
+
const bootstrapContextComponents = resolveBootstrapContextComponents(bootstrapContext, context.bootstrapComponentFingerprints, delivery ? [delivery] : [], context.contextFingerprint);
|
|
93
|
+
const includeBootstrapContext = bootstrapContextComponents.length > 0;
|
|
94
|
+
this.store.setConversationBinding({
|
|
95
|
+
conversationScopeId,
|
|
96
|
+
targetKey,
|
|
97
|
+
workspaceSessionId: context.workspace.id,
|
|
98
|
+
});
|
|
99
|
+
if (bootstrapContext !== "none" &&
|
|
100
|
+
(includeBootstrapContext ||
|
|
101
|
+
(delivery?.contextFingerprint === context.contextFingerprint && !delivery.componentFingerprints))) {
|
|
102
|
+
this.store.setContextDelivery({
|
|
103
|
+
conversationScopeId,
|
|
104
|
+
targetKey,
|
|
105
|
+
contextFingerprint: context.contextFingerprint,
|
|
106
|
+
componentFingerprints: context.bootstrapComponentFingerprints,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return { ...context, bootstrapContextComponents, includeBootstrapContext };
|
|
110
|
+
}
|
|
111
|
+
pruneIdleWorkspaceSessions(protectedWorkspaceIds, force = false) {
|
|
112
|
+
if (!this.store)
|
|
113
|
+
return;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
if (!force && now - this.lastWorkspaceGcAt < WORKSPACE_GC_INTERVAL_MS)
|
|
116
|
+
return;
|
|
117
|
+
this.lastWorkspaceGcAt = now;
|
|
118
|
+
const activeSessions = this.store.listSessions({ status: "active" });
|
|
119
|
+
const sessionsById = new Map(activeSessions.map((session) => [session.id, session]));
|
|
120
|
+
const isIdle = (session) => {
|
|
121
|
+
const lastUsedAt = Date.parse(session.lastUsedAt);
|
|
122
|
+
return Number.isFinite(lastUsedAt) && now - lastUsedAt >= WORKSPACE_SESSION_IDLE_TTL_MS;
|
|
123
|
+
};
|
|
124
|
+
for (const binding of this.store.listConversationBindings()) {
|
|
125
|
+
if (!sessionsById.has(binding.workspaceSessionId)) {
|
|
126
|
+
this.store.deleteConversationBinding(binding.conversationScopeId, binding.targetKey);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const delivery of this.store.listContextDeliveries()) {
|
|
130
|
+
const deliveredAt = Date.parse(delivery.deliveredAt);
|
|
131
|
+
if (Number.isFinite(deliveredAt) && now - deliveredAt >= WORKSPACE_SESSION_IDLE_TTL_MS) {
|
|
132
|
+
this.store.deleteContextDelivery(delivery.conversationScopeId, delivery.targetKey);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const boundWorkspaceIds = new Set(this.store.listConversationBindings().map((binding) => binding.workspaceSessionId));
|
|
136
|
+
const worktreeAnchors = new Map();
|
|
137
|
+
for (const session of activeSessions) {
|
|
138
|
+
if (session.mode !== "worktree")
|
|
139
|
+
continue;
|
|
140
|
+
const key = resolve(session.root);
|
|
141
|
+
const current = worktreeAnchors.get(key);
|
|
142
|
+
if (!current || current.lastUsedAt < session.lastUsedAt)
|
|
143
|
+
worktreeAnchors.set(key, session);
|
|
144
|
+
}
|
|
145
|
+
for (const session of activeSessions) {
|
|
146
|
+
if (!isIdle(session))
|
|
147
|
+
continue;
|
|
148
|
+
if (protectedWorkspaceIds.has(session.id) || boundWorkspaceIds.has(session.id))
|
|
149
|
+
continue;
|
|
150
|
+
if (session.mode === "worktree" && worktreeAnchors.get(resolve(session.root))?.id === session.id)
|
|
151
|
+
continue;
|
|
152
|
+
this.workspaces.delete(session.id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
foldLegacyWorkspaceSessions() {
|
|
156
|
+
if (!this.store)
|
|
157
|
+
return;
|
|
158
|
+
const groups = new Map();
|
|
159
|
+
for (const session of this.store.listSessions()) {
|
|
160
|
+
const targetPath = canonicalPersistedWorkspacePath(session.root);
|
|
161
|
+
const key = JSON.stringify([session.mode, targetPath]);
|
|
162
|
+
const group = groups.get(key) ?? [];
|
|
163
|
+
group.push(session);
|
|
164
|
+
groups.set(key, group);
|
|
165
|
+
}
|
|
166
|
+
for (const sessions of groups.values()) {
|
|
167
|
+
if (sessions.length < 2)
|
|
168
|
+
continue;
|
|
169
|
+
const ordered = [...sessions].sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id));
|
|
170
|
+
const canonical = ordered[0];
|
|
171
|
+
if (!canonical)
|
|
172
|
+
continue;
|
|
173
|
+
const createdAt = ordered.reduce((earliest, session) => session.createdAt < earliest ? session.createdAt : earliest, canonical.createdAt);
|
|
174
|
+
const lastUsedAt = ordered.reduce((latest, session) => session.lastUsedAt > latest ? session.lastUsedAt : latest, canonical.lastUsedAt);
|
|
175
|
+
const status = ordered.some((session) => session.status === "active") ? "active" : canonical.status;
|
|
176
|
+
this.store.foldSessions({
|
|
177
|
+
canonicalId: canonical.id,
|
|
178
|
+
aliasIds: ordered.slice(1).map((session) => session.id),
|
|
179
|
+
createdAt,
|
|
180
|
+
lastUsedAt,
|
|
181
|
+
status,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async findReusableWorkspaceByDirectory(directoryKey, mode, reopenClosedCheckout = false) {
|
|
186
|
+
const sessions = this.store ? this.store.listSessions({ mode }) : this.activeSessions(mode);
|
|
187
|
+
for (const session of sessions) {
|
|
188
|
+
const reusable = session.status === "active" ||
|
|
189
|
+
(reopenClosedCheckout && mode === "checkout" && session.status === "closed");
|
|
190
|
+
if (!reusable)
|
|
191
|
+
continue;
|
|
192
|
+
const root = await this.validSessionRoot(session);
|
|
193
|
+
if (!root || await canonicalPath(root) !== directoryKey)
|
|
194
|
+
continue;
|
|
195
|
+
if (session.status === "closed") {
|
|
196
|
+
this.store?.setSessionStatus(session.id, "active");
|
|
197
|
+
const reopened = this.store?.getSession(session.id);
|
|
198
|
+
if (!reopened)
|
|
199
|
+
continue;
|
|
200
|
+
return this.workspaceFromSession(reopened, false);
|
|
201
|
+
}
|
|
202
|
+
return this.workspaceFromSession(session, false);
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
async findReusableWorktreeContextBySource(sourceKey, targetBranch) {
|
|
207
|
+
const sessions = this.store ? this.store.listSessions({ mode: "worktree" }) : this.activeSessions("worktree");
|
|
208
|
+
const closedMatches = [];
|
|
209
|
+
for (const session of sessions) {
|
|
210
|
+
if (!session.sourceRoot || session.targetBranch !== targetBranch)
|
|
211
|
+
continue;
|
|
212
|
+
if (await canonicalPath(session.sourceRoot) !== sourceKey)
|
|
213
|
+
continue;
|
|
214
|
+
if (session.status === "closed") {
|
|
215
|
+
if (session.managed)
|
|
216
|
+
closedMatches.push(session);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (session.status !== "active")
|
|
220
|
+
continue;
|
|
221
|
+
const root = await this.validSessionRoot(session);
|
|
222
|
+
if (!root)
|
|
223
|
+
continue;
|
|
224
|
+
return this.reusedWorkspaceContext(this.workspaceFromSession(session, false));
|
|
225
|
+
}
|
|
226
|
+
if (closedMatches.length === 1)
|
|
227
|
+
return this.reopenClosedManagedWorktreeContext(closedMatches[0]);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
activeSessions(mode) {
|
|
231
|
+
if (this.store)
|
|
232
|
+
return this.store.listSessions({ status: "active", mode });
|
|
233
|
+
return [...this.workspaces.values()]
|
|
234
|
+
.filter((workspace) => workspace.mode === mode)
|
|
235
|
+
.map((workspace) => ({
|
|
236
|
+
id: workspace.id,
|
|
237
|
+
root: workspace.root,
|
|
238
|
+
status: "active",
|
|
239
|
+
mode: workspace.mode,
|
|
240
|
+
sourceRoot: workspace.sourceRoot,
|
|
241
|
+
baseRef: workspace.worktree?.baseRef,
|
|
242
|
+
baseSha: workspace.worktree?.baseSha,
|
|
243
|
+
branch: workspace.worktree?.branch,
|
|
244
|
+
targetBranch: workspace.worktree?.targetBranch,
|
|
245
|
+
managed: workspace.worktree?.managed ?? false,
|
|
246
|
+
createdAt: "",
|
|
247
|
+
lastUsedAt: "",
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
async validSessionRoot(session) {
|
|
251
|
+
try {
|
|
252
|
+
const root = this.context.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);
|
|
253
|
+
const rootStats = await stat(root);
|
|
254
|
+
return rootStats.isDirectory() ? root : undefined;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (error instanceof AccessDeniedError ||
|
|
258
|
+
(isErrnoException(error) && (error.code === "ENOENT" || error.code === "ENOTDIR")))
|
|
259
|
+
return undefined;
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
tryManagedWorktreePath(path) {
|
|
264
|
+
try {
|
|
265
|
+
return assertAllowedPath(path, [this.config.worktreeRoot]);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (error instanceof AccessDeniedError)
|
|
269
|
+
return undefined;
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async reusedWorkspaceContext(workspace) {
|
|
274
|
+
Object.assign(workspace, this.context.loadSkillsForWorkspace(workspace.root));
|
|
275
|
+
workspace.capabilityGuides = loadCapabilityGuides(this.config);
|
|
276
|
+
workspace.agentProfiles = await loadSubagentProfiles(this.config, workspace.root);
|
|
277
|
+
workspace.scannedInstructionDirs.clear();
|
|
278
|
+
workspace.knownInstructionPathsByDir.clear();
|
|
279
|
+
workspace.loadedInstructionRealPaths.clear();
|
|
280
|
+
workspace.loadedInstructionPaths.clear();
|
|
281
|
+
const agentsFiles = await this.context.loadInitialAgentsFiles(workspace);
|
|
282
|
+
const availableAgentsFiles = await this.context.findAvailableAgentsFiles(workspace, agentsFiles);
|
|
283
|
+
const { contextFingerprint, componentFingerprints: bootstrapComponentFingerprints, } = bootstrapContextFingerprints(workspace, agentsFiles, availableAgentsFiles);
|
|
284
|
+
return {
|
|
285
|
+
workspace,
|
|
286
|
+
agentsFiles,
|
|
287
|
+
availableAgentsFiles,
|
|
288
|
+
contextFingerprint,
|
|
289
|
+
bootstrapComponentFingerprints,
|
|
290
|
+
bootstrapContextComponents: [...BOOTSTRAP_CONTEXT_COMPONENTS],
|
|
291
|
+
hookReports: [],
|
|
292
|
+
workspaceReused: true,
|
|
293
|
+
includeBootstrapContext: true,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async workspaceForOpen(workspaceId) {
|
|
297
|
+
const session = this.store?.getSession(workspaceId);
|
|
298
|
+
if (session?.status === "closed" && session.mode === "checkout") {
|
|
299
|
+
this.store?.setSessionStatus(session.id, "active");
|
|
300
|
+
const reopened = this.store?.getSession(session.id);
|
|
301
|
+
if (!reopened)
|
|
302
|
+
throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`);
|
|
303
|
+
return this.workspaceFromSession(reopened, true);
|
|
304
|
+
}
|
|
305
|
+
return this.getWorkspace(workspaceId);
|
|
306
|
+
}
|
|
307
|
+
async reopenClosedManagedWorktreeContext(session) {
|
|
308
|
+
const operationKey = JSON.stringify(["worktree-reopen", session.id]);
|
|
309
|
+
return this.openOnce(operationKey, async () => {
|
|
310
|
+
const current = this.store?.getSession(session.id);
|
|
311
|
+
if (!current)
|
|
312
|
+
throw new Error(`Unknown workspaceId: ${session.id}. Call open_workspace first.`);
|
|
313
|
+
if (current.status === "active")
|
|
314
|
+
return this.reusedWorkspaceContext(this.getWorkspace(current.id));
|
|
315
|
+
if (current.status !== "closed" || current.mode !== "worktree") {
|
|
316
|
+
throw new Error(`Workspace ${current.id} is not a closed managed-worktree Workspace.`);
|
|
317
|
+
}
|
|
318
|
+
return this.reopenClosedManagedWorktreeContextUnlocked(current);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async reopenClosedManagedWorktreeContextUnlocked(session) {
|
|
322
|
+
if (!this.store) {
|
|
323
|
+
throw new Error(`Workspace ${session.id} cannot be reopened without persistent Workspace state.`);
|
|
324
|
+
}
|
|
325
|
+
if (!session.managed || !session.sourceRoot || !session.targetBranch) {
|
|
326
|
+
throw new Error(`Workspace ${session.id} does not have enough managed-worktree metadata to recreate its execution backing.`);
|
|
327
|
+
}
|
|
328
|
+
const worktree = await createManagedWorktree({
|
|
329
|
+
sourcePath: session.sourceRoot,
|
|
330
|
+
baseRef: session.targetBranch,
|
|
331
|
+
config: this.config,
|
|
332
|
+
});
|
|
333
|
+
const candidateSession = {
|
|
334
|
+
...session,
|
|
335
|
+
root: worktree.path,
|
|
336
|
+
status: "active",
|
|
337
|
+
sourceRoot: worktree.sourceRoot,
|
|
338
|
+
baseRef: worktree.baseRef,
|
|
339
|
+
baseSha: worktree.baseSha,
|
|
340
|
+
branch: worktree.branch,
|
|
341
|
+
targetBranch: worktree.targetBranch,
|
|
342
|
+
managed: true,
|
|
343
|
+
};
|
|
344
|
+
const workspace = this.workspaceFromSession(candidateSession, false);
|
|
345
|
+
try {
|
|
346
|
+
const context = await this.reusedWorkspaceContext(workspace);
|
|
347
|
+
this.store.replaceWorktreeBacking({
|
|
348
|
+
id: session.id,
|
|
349
|
+
root: worktree.path,
|
|
350
|
+
sourceRoot: worktree.sourceRoot,
|
|
351
|
+
baseRef: worktree.baseRef,
|
|
352
|
+
baseSha: worktree.baseSha,
|
|
353
|
+
branch: worktree.branch,
|
|
354
|
+
targetBranch: worktree.targetBranch,
|
|
355
|
+
});
|
|
356
|
+
return context;
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
this.workspaces.delete(session.id);
|
|
360
|
+
try {
|
|
361
|
+
await discardFreshManagedWorktree({ worktree, config: this.config });
|
|
362
|
+
}
|
|
363
|
+
catch (cleanupError) {
|
|
364
|
+
const original = error instanceof Error ? error.message : String(error);
|
|
365
|
+
const cleanup = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
366
|
+
throw new Error(`${original} Reopen rollback also failed: ${cleanup}`);
|
|
367
|
+
}
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
getWorkspaceSession(workspaceId) {
|
|
372
|
+
const session = this.store?.getSession(workspaceId);
|
|
373
|
+
if (session)
|
|
374
|
+
return session;
|
|
375
|
+
const workspace = this.workspaces.get(workspaceId);
|
|
376
|
+
if (!workspace)
|
|
377
|
+
throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`);
|
|
378
|
+
return {
|
|
379
|
+
id: workspace.id,
|
|
380
|
+
root: workspace.root,
|
|
381
|
+
status: "active",
|
|
382
|
+
mode: workspace.mode,
|
|
383
|
+
sourceRoot: workspace.sourceRoot,
|
|
384
|
+
baseRef: workspace.worktree?.baseRef,
|
|
385
|
+
baseSha: workspace.worktree?.baseSha,
|
|
386
|
+
branch: workspace.worktree?.branch,
|
|
387
|
+
targetBranch: workspace.worktree?.targetBranch,
|
|
388
|
+
managed: workspace.worktree?.managed ?? false,
|
|
389
|
+
createdAt: "",
|
|
390
|
+
lastUsedAt: "",
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
deleteConversationBindingsForWorkspace(workspaceId) {
|
|
394
|
+
if (!this.store)
|
|
395
|
+
return;
|
|
396
|
+
for (const binding of this.store.listConversationBindings()) {
|
|
397
|
+
if (binding.workspaceSessionId === workspaceId) {
|
|
398
|
+
this.store.deleteConversationBinding(binding.conversationScopeId, binding.targetKey);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
getWorkspace(workspaceId) {
|
|
403
|
+
const workspace = this.workspaces.get(workspaceId);
|
|
404
|
+
if (workspace) {
|
|
405
|
+
this.store?.touchSession(workspaceId);
|
|
406
|
+
return workspace;
|
|
407
|
+
}
|
|
408
|
+
const session = this.store?.getSession(workspaceId);
|
|
409
|
+
if (!session || session.status !== "active") {
|
|
410
|
+
throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`);
|
|
411
|
+
}
|
|
412
|
+
return this.workspaceFromSession(session, true);
|
|
413
|
+
}
|
|
414
|
+
workspaceFromSession(session, touch) {
|
|
415
|
+
const existing = this.workspaces.get(session.id);
|
|
416
|
+
if (existing) {
|
|
417
|
+
if (touch)
|
|
418
|
+
this.store?.touchSession(session.id);
|
|
419
|
+
return existing;
|
|
420
|
+
}
|
|
421
|
+
const root = this.context.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);
|
|
422
|
+
const restoredWorkspace = {
|
|
423
|
+
id: session.id,
|
|
424
|
+
root,
|
|
425
|
+
mode: session.mode,
|
|
426
|
+
sourceRoot: session.sourceRoot,
|
|
427
|
+
worktree: session.mode === "worktree"
|
|
428
|
+
? {
|
|
429
|
+
path: root,
|
|
430
|
+
baseRef: session.baseRef ?? "HEAD",
|
|
431
|
+
baseSha: session.baseSha ?? "",
|
|
432
|
+
branch: session.branch,
|
|
433
|
+
targetBranch: session.targetBranch,
|
|
434
|
+
dirtySource: false,
|
|
435
|
+
detached: !session.branch,
|
|
436
|
+
managed: session.managed,
|
|
437
|
+
}
|
|
438
|
+
: undefined,
|
|
439
|
+
...this.context.loadSkillsForWorkspace(root),
|
|
440
|
+
capabilityGuides: loadCapabilityGuides(this.config),
|
|
441
|
+
agentProfiles: [],
|
|
442
|
+
activatedSkillDirs: new Set(),
|
|
443
|
+
activatedCapabilityGuideDirs: new Set(),
|
|
444
|
+
scannedInstructionDirs: new Set(),
|
|
445
|
+
knownInstructionPathsByDir: new Map(),
|
|
446
|
+
loadedInstructionRealPaths: new Set(),
|
|
447
|
+
loadedInstructionPaths: new Set(),
|
|
448
|
+
};
|
|
449
|
+
if (touch)
|
|
450
|
+
this.store?.touchSession(session.id);
|
|
451
|
+
this.workspaces.set(restoredWorkspace.id, restoredWorkspace);
|
|
452
|
+
return restoredWorkspace;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function isErrnoException(error) {
|
|
456
|
+
return error instanceof Error && "code" in error;
|
|
457
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { and, desc, eq } from "drizzle-orm";
|
|
2
|
-
import { openDatabase } from "
|
|
3
|
-
import { workspaceContextDeliveries, workspaceConversationBindings, workspaceSessionAliases, workspaceSessions, } from "
|
|
2
|
+
import { openDatabase } from "../../runtime/state/db/client.js";
|
|
3
|
+
import { workspaceContextDeliveries, workspaceConversationBindings, workspaceSessionAliases, workspaceSessions, } from "../../runtime/state/db/schema.js";
|
|
4
4
|
const DEFAULT_TOUCH_FLUSH_INTERVAL_MS = 5 * 60 * 1_000;
|
|
5
5
|
export class SqliteWorkspaceStore {
|
|
6
6
|
database;
|