@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.
Files changed (264) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +10 -18
  3. package/capabilities/code-intelligence/GUIDE.md +7 -3
  4. package/capabilities/host-integration/GUIDE.md +2 -2
  5. package/capabilities/subagents/GUIDE.md +9 -0
  6. package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +6 -0
  7. package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
  8. package/dist/activity/history/bash-output-store.js +250 -0
  9. package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
  10. package/dist/activity/{query-service.js → history/query-service.js} +13 -7
  11. package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
  12. package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
  13. package/dist/activity/storage/history.js +12 -0
  14. package/dist/activity/storage/paths.js +56 -0
  15. package/dist/activity/storage/segmented-log.js +149 -0
  16. package/dist/cli/init.js +180 -0
  17. package/dist/cli/setup-support.js +181 -0
  18. package/dist/cli.js +23 -216
  19. package/dist/lsp/code-intelligence.js +18 -4
  20. package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
  21. package/dist/lsp/runtime/managed-language-servers.js +172 -0
  22. package/dist/lsp/runtime/manager.js +37 -2
  23. package/dist/lsp/runtime/process-launch.js +3 -0
  24. package/dist/lsp/test-support/server-fixture.js +14 -11
  25. package/dist/mcp/filesystem/filesystem-tools.js +245 -0
  26. package/dist/mcp/hooks/command-runner.js +79 -0
  27. package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
  28. package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
  29. package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
  30. package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
  31. package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
  32. package/dist/mcp/panel/app.js +143 -0
  33. package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
  34. package/dist/{process-platform.js → mcp/process/process-platform.js} +21 -9
  35. package/dist/{process-sessions.js → mcp/process/process-sessions.js} +3 -3
  36. package/dist/mcp/process/runtime.js +257 -0
  37. package/dist/mcp/process/tools.js +346 -0
  38. package/dist/mcp/server/core/activity-support.js +70 -0
  39. package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
  40. package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +9 -4
  41. package/dist/mcp/server/core/capability-support.js +90 -0
  42. package/dist/mcp/server/core/schemas.js +216 -0
  43. package/dist/mcp/server/core/tool-support.js +210 -0
  44. package/dist/mcp/server/core/version.js +9 -0
  45. package/dist/mcp/server/operations/runtime/filesystem-tools.js +455 -0
  46. package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
  47. package/dist/mcp/server/operations/runtime/operation-runtime.js +524 -0
  48. package/dist/mcp/server/transport/http-server.js +301 -0
  49. package/dist/mcp/server/transport/http-support.js +43 -0
  50. package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +242 -0
  51. package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
  52. package/dist/mcp/server/workspace/runtime/workspace-open.js +555 -0
  53. package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
  54. package/dist/mcp/server-instructions.js +1 -1
  55. package/dist/{config.js → runtime/config/config.js} +4 -3
  56. package/dist/{user-config.js → runtime/config/user-config.js} +14 -63
  57. package/dist/{db → runtime/state/db}/client.js +1 -1
  58. package/dist/{db → runtime/state/db}/migrations.js +34 -3
  59. package/dist/runtime/state/lock/file-lock.js +130 -0
  60. package/dist/runtime/testing/server-fixture.js +190 -0
  61. package/dist/server.js +133 -4189
  62. package/dist/subagents/providers/adapters/claude.js +86 -52
  63. package/dist/subagents/providers/adapters/codex.js +103 -39
  64. package/dist/subagents/providers/adapters/opencode.js +114 -84
  65. package/dist/subagents/providers/adapters/pi.js +59 -8
  66. package/dist/subagents/providers/availability.js +15 -30
  67. package/dist/subagents/providers/runtime/external-command.js +56 -0
  68. package/dist/subagents/sessions/capability.js +1 -1
  69. package/dist/subagents/sessions/execution.js +1 -1
  70. package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
  71. package/dist/subagents/sessions/store.js +1 -1
  72. package/dist/ui/.vite/manifest.json +342 -410
  73. package/dist/ui/activity-panel-app.html +2 -4
  74. package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
  75. package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
  76. package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
  77. package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
  78. package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
  79. package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
  80. package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
  81. package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
  82. package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
  83. package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
  84. package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
  85. package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
  86. package/dist/ui/assets/css-C1tjBtVN.js +1 -0
  87. package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
  88. package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
  89. package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
  90. package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
  91. package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
  92. package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
  93. package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
  94. package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
  95. package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
  96. package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
  97. package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
  98. package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
  99. package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
  100. package/dist/ui/assets/html-By4dNnM4.js +1 -0
  101. package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
  102. package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
  103. package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
  104. package/dist/ui/assets/java-DYRe2lWG.js +1 -0
  105. package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
  106. package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
  107. package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
  108. package/dist/ui/assets/json-dbltgiiA.js +1 -0
  109. package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
  110. package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
  111. package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
  112. package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
  113. package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
  114. package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
  115. package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
  116. package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
  117. package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
  118. package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
  119. package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
  120. package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
  121. package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
  122. package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
  123. package/dist/ui/assets/r-CcezTbLq.js +1 -0
  124. package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
  125. package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
  126. package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
  127. package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
  128. package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
  129. package/dist/ui/assets/scss-BQb-cp01.js +1 -0
  130. package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
  131. package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
  132. package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
  133. package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
  134. package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
  135. package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
  136. package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
  137. package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
  138. package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
  139. package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
  140. package/dist/ui/assets/tsx-CajzROyy.js +1 -0
  141. package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
  142. package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
  143. package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
  144. package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
  145. package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
  146. package/dist/ui/assets/xml-BI86-WbH.js +1 -0
  147. package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
  148. package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
  149. package/dist/workspaces/bootstrap.js +72 -0
  150. package/dist/workspaces/context.js +337 -0
  151. package/dist/workspaces/conversation-test-support.js +86 -0
  152. package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
  153. package/dist/workspaces/inventory.js +131 -0
  154. package/dist/workspaces/paths.js +55 -0
  155. package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
  156. package/dist/workspaces/relay/result-support.js +111 -0
  157. package/dist/workspaces/relay/tests/test-support.js +203 -0
  158. package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
  159. package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +30 -3
  160. package/dist/workspaces/relay/types.js +1 -0
  161. package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
  162. package/dist/workspaces/resources/resource-monitor.js +377 -0
  163. package/dist/workspaces/resources/skills.js +283 -0
  164. package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
  165. package/dist/workspaces/sessions.js +460 -0
  166. package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
  167. package/dist/workspaces.js +77 -993
  168. package/docs/chatgpt-coding-workflow.md +7 -14
  169. package/docs/configuration.md +26 -34
  170. package/docs/gotchas.md +0 -30
  171. package/docs/roadmap.md +6 -10
  172. package/docs/security.md +1 -5
  173. package/docs/setup.md +0 -11
  174. package/package.json +6 -9
  175. package/scripts/ci/architecture.mjs +109 -0
  176. package/scripts/ci/verify.mjs +5 -0
  177. package/scripts/debug/accept/bootstrap.mjs +326 -0
  178. package/scripts/debug/accept/harness.mjs +76 -0
  179. package/scripts/debug/accept/support.mjs +432 -0
  180. package/scripts/debug/accept.mjs +43 -806
  181. package/scripts/debug/relay-accept/support.mjs +135 -0
  182. package/scripts/debug/relay-accept.mjs +26 -115
  183. package/scripts/debug/traffic/support.mjs +167 -0
  184. package/scripts/debug/traffic/traffic-audit.mjs +4 -161
  185. package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
  186. package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
  187. package/scripts/release/publish.mjs +12 -9
  188. package/scripts/release/release-gate.test.mjs +3 -1
  189. package/scripts/release/release-version.test.mjs +13 -1
  190. package/scripts/release-parity.mjs +1 -1
  191. package/scripts/release-proof.mjs +15 -0
  192. package/scripts/release-proof.test.mjs +27 -1
  193. package/scripts/release-version.mjs +16 -1
  194. package/scripts/wiki/sync.mjs +21 -10
  195. package/dist/activity/bash-output-store.js +0 -127
  196. package/dist/pi-tools.js +0 -110
  197. package/dist/skills.js +0 -127
  198. package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
  199. package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
  200. package/dist/ui/assets/c-IBzBpxAO.js +0 -1
  201. package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
  202. package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
  203. package/dist/ui/assets/css-DEfus9J5.js +0 -1
  204. package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
  205. package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
  206. package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
  207. package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
  208. package/dist/ui/assets/html-D-C6YZk4.js +0 -1
  209. package/dist/ui/assets/java-ppHvycj5.js +0 -1
  210. package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
  211. package/dist/ui/assets/json-Cw91r8h1.js +0 -1
  212. package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
  213. package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
  214. package/dist/ui/assets/r-DfFga4si.js +0 -1
  215. package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
  216. package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
  217. package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
  218. package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
  219. package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
  220. package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
  221. package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
  222. package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
  223. package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
  224. package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
  225. package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
  226. package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
  227. package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
  228. package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
  229. package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
  230. package/dist/ui/workspace-app.html +0 -18
  231. package/dist/ui/workspace-lifecycle-app.html +0 -18
  232. package/docs/agent-profile-schema.md +0 -114
  233. package/docs/artifact-exchange.md +0 -65
  234. package/docs/versioning.md +0 -205
  235. package/skills/subagent-delegation/SKILL.md +0 -132
  236. /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
  237. /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
  238. /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
  239. /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
  240. /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
  241. /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
  242. /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
  243. /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
  244. /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
  245. /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
  246. /package/dist/{oauth → mcp/oauth}/router.js +0 -0
  247. /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
  248. /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
  249. /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
  250. /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
  251. /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
  252. /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
  253. /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
  254. /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
  255. /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
  256. /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
  257. /package/dist/{db → runtime/state/db}/schema.js +0 -0
  258. /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
  259. /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
  260. /package/dist/{git.js → workspaces/git/git.js} +0 -0
  261. /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
  262. /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
  263. /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
  264. /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
@@ -1,13 +1,14 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
- import { realpathSync } from "node:fs";
3
- import { mkdir, opendir, readFile, realpath, stat } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { basename, dirname, join, relative, resolve, sep } from "node:path";
6
- import { loadCapabilityGuides, markCapabilityGuideActivated, resolveCapabilityGuideReadPath, } from "./capabilities.js";
7
- import { HookRunner } from "./hooks.js";
8
- import { closeManagedWorktree, createManagedWorktree, discardFreshManagedWorktree, resolveManagedWorktreeBase, } from "./git-worktrees.js";
9
- import { AccessDeniedError, assertAllowedPath, isPathInsideRoot, resolveAllowedPath, } from "./roots.js";
10
- import { loadWorkspaceSkills, markSkillActivated, resolveSkillReadPath, } from "./skills.js";
1
+ import { randomBytes } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import { loadCapabilityGuides, } from "./mcp/server/core/capabilities.js";
4
+ import { WorkspaceContextService, formatAgentsPath } from "./workspaces/context.js";
5
+ import { BOOTSTRAP_CONTEXT_COMPONENTS, bootstrapContextFingerprints, resolveBootstrapContextComponents, } from "./workspaces/bootstrap.js";
6
+ import { canonicalPath, ensureCheckoutWorkspaceRoot } from "./workspaces/paths.js";
7
+ import { WorkspaceInventoryService } from "./workspaces/inventory.js";
8
+ import { WorkspaceSessionService } from "./workspaces/sessions.js";
9
+ import { HookRunner } from "./mcp/hooks/hooks.js";
10
+ import { closeManagedWorktree, createManagedWorktree, resolveManagedWorktreeBase, } from "./workspaces/git/git-worktrees.js";
11
+ import { assertAllowedPath, } from "./mcp/filesystem/roots.js";
11
12
  import { loadSubagentProfiles, } from "./subagents/profiles.js";
12
13
  const WORKSPACE_STALE_REMINDER_MS = 2 * 24 * 60 * 60 * 1_000;
13
14
  const WORKSPACE_SESSION_IDLE_TTL_MS = 30 * 24 * 60 * 60 * 1_000;
@@ -17,21 +18,26 @@ export class WorkspaceRegistry {
17
18
  config;
18
19
  store;
19
20
  workspaces = new Map();
20
- pendingOpens = new Map();
21
21
  hooks;
22
- lastWorkspaceGcAt = 0;
22
+ context;
23
+ sessions;
24
+ inventory;
23
25
  constructor(config, store) {
24
26
  this.config = config;
25
27
  this.store = store;
26
28
  this.hooks = new HookRunner(config.hooks, config.logging);
27
- this.foldLegacyWorkspaceSessions();
28
- this.pruneIdleWorkspaceSessions(new Set(), true);
29
+ this.context = new WorkspaceContextService(config);
30
+ this.sessions = new WorkspaceSessionService(config, store, this.workspaces, this.context);
31
+ this.inventory = new WorkspaceInventoryService(config, store, this.workspaces, this.sessions);
32
+ this.sessions.foldLegacyWorkspaceSessions();
33
+ this.sessions.pruneIdleWorkspaceSessions(new Set(), true);
29
34
  }
30
35
  get cachedWorkspaceCount() {
31
36
  return this.workspaces.size;
32
37
  }
33
38
  async openWorkspace(input, openOptions = {}) {
34
- this.pruneIdleWorkspaceSessions(openOptions.protectedWorkspaceIds ?? new Set());
39
+ this.sessions.pruneIdleWorkspaceSessions(openOptions.protectedWorkspaceIds ?? new Set());
40
+ this.context.pruneWorkspaceResources(this.workspaces.keys());
35
41
  const workspaceInput = typeof input === "string" ? { path: input } : input;
36
42
  const bootstrapContext = workspaceInput.context ?? "auto";
37
43
  if (workspaceInput.workspaceId) {
@@ -46,148 +52,17 @@ export class WorkspaceRegistry {
46
52
  }
47
53
  return this.openReusableCheckout(workspaceInput.path, openOptions.conversationScopeId, bootstrapContext);
48
54
  }
49
- async inspectWorkspace(workspaceId) {
50
- let session = this.store?.getSession(workspaceId);
51
- if (!session) {
52
- const workspace = this.workspaces.get(workspaceId);
53
- if (workspace) {
54
- session = {
55
- id: workspace.id,
56
- root: workspace.root,
57
- status: "active",
58
- mode: workspace.mode,
59
- sourceRoot: workspace.sourceRoot,
60
- baseRef: workspace.worktree?.baseRef,
61
- baseSha: workspace.worktree?.baseSha,
62
- branch: workspace.worktree?.branch,
63
- targetBranch: workspace.worktree?.targetBranch,
64
- managed: workspace.worktree?.managed ?? false,
65
- createdAt: "",
66
- lastUsedAt: "",
67
- };
68
- }
69
- }
70
- if (!session)
71
- throw new Error(`Unknown workspaceId: ${workspaceId}.`);
72
- const entry = await this.inventoryEntryForSession(session, Date.now(), false);
73
- const { current: _current, ...inspection } = entry;
74
- return {
75
- kind: "workspace",
76
- location: "local",
77
- ...inspection,
78
- };
55
+ inspectWorkspace(workspaceId) {
56
+ return this.inventory.inspectWorkspace(workspaceId);
79
57
  }
80
- async listWorkspaces(input = {}, openOptions = {}) {
81
- this.pruneIdleWorkspaceSessions(openOptions.protectedWorkspaceIds ?? new Set());
82
- const now = Date.now();
83
- const sessions = this.store
84
- ? this.store.listSessions()
85
- : [...this.workspaces.values()].map((workspace) => ({
86
- id: workspace.id,
87
- root: workspace.root,
88
- status: "active",
89
- mode: workspace.mode,
90
- sourceRoot: workspace.sourceRoot,
91
- baseRef: workspace.worktree?.baseRef,
92
- baseSha: workspace.worktree?.baseSha,
93
- branch: workspace.worktree?.branch,
94
- targetBranch: workspace.worktree?.targetBranch,
95
- managed: workspace.worktree?.managed ?? false,
96
- createdAt: "",
97
- lastUsedAt: "",
98
- }));
99
- const currentWorkspaceIds = new Set(openOptions.conversationScopeId && this.store
100
- ? this.store
101
- .listConversationBindings()
102
- .filter((binding) => binding.conversationScopeId === openOptions.conversationScopeId)
103
- .map((binding) => binding.workspaceSessionId)
104
- : []);
105
- const workspaceIdFilter = input.workspaceId
106
- ? this.store?.getSession(input.workspaceId)?.id ?? input.workspaceId
107
- : undefined;
108
- const rootKey = input.root
109
- ? await canonicalPath(assertAllowedPath(input.root, [...this.config.allowedRoots, this.config.worktreeRoot]))
110
- : undefined;
111
- const entries = await Promise.all(sessions.map((session) => this.inventoryEntryForSession(session, now, currentWorkspaceIds.has(session.id))));
112
- const filtered = [];
113
- for (let index = 0; index < sessions.length; index += 1) {
114
- const session = sessions[index];
115
- const entry = entries[index];
116
- if (!session || !entry)
117
- continue;
118
- if (workspaceIdFilter && entry.workspaceId !== workspaceIdFilter)
119
- continue;
120
- if (input.status && entry.status !== input.status)
121
- continue;
122
- if (input.state && entry.state !== input.state)
123
- continue;
124
- if (input.mode && entry.mode !== input.mode)
125
- continue;
126
- if (input.staleOnly && entry.state !== "stale")
127
- continue;
128
- if (rootKey) {
129
- const sessionRootKey = await canonicalPath(session.root);
130
- const sourceRootKey = session.sourceRoot ? await canonicalPath(session.sourceRoot) : undefined;
131
- if (sessionRootKey !== rootKey && sourceRootKey !== rootKey)
132
- continue;
133
- }
134
- filtered.push(entry);
135
- }
136
- const summary = filtered.reduce((counts, entry) => {
137
- counts[entry.state] += 1;
138
- return counts;
139
- }, { active: 0, stale: 0, invalid: 0, closed: 0 });
140
- const offset = Math.max(0, input.offset ?? 0);
141
- const limit = Math.min(100, Math.max(1, input.limit ?? 50));
142
- return {
143
- workspaces: filtered.slice(offset, offset + limit),
144
- summary: {
145
- total: entries.length,
146
- matching: filtered.length,
147
- ...summary,
148
- },
149
- page: {
150
- offset,
151
- limit,
152
- hasMore: offset + limit < filtered.length,
153
- },
154
- };
155
- }
156
- async inventoryEntryForSession(session, now, current) {
157
- const rootValid = await this.validSessionRoot(session) !== undefined;
158
- const lastUsedAt = Date.parse(session.lastUsedAt);
159
- const idleMs = Number.isFinite(lastUsedAt) ? Math.max(0, now - lastUsedAt) : 0;
160
- const state = session.status !== "active"
161
- ? "closed"
162
- : !rootValid
163
- ? "invalid"
164
- : idleMs >= WORKSPACE_STALE_REMINDER_MS
165
- ? "stale"
166
- : "active";
167
- const projectRoot = session.sourceRoot ?? session.root;
168
- return {
169
- label: `${basename(resolve(projectRoot)) || "workspace"}/${session.id}`,
170
- workspaceId: session.id,
171
- root: session.root,
172
- status: session.status,
173
- state,
174
- mode: session.mode,
175
- sourceRoot: session.sourceRoot,
176
- branch: session.branch,
177
- targetBranch: session.targetBranch,
178
- managed: session.managed,
179
- createdAt: session.createdAt,
180
- lastUsedAt: session.lastUsedAt,
181
- idleMs,
182
- rootValid,
183
- current,
184
- };
58
+ listWorkspaces(input = {}, openOptions = {}) {
59
+ return this.inventory.listWorkspaces(input, openOptions);
185
60
  }
186
61
  async resumeWorkspace(workspaceId, conversationScopeId, bootstrapContext = "auto") {
187
62
  const session = this.store?.getSession(workspaceId);
188
63
  const context = session?.status === "closed" && session.mode === "worktree"
189
- ? await this.reopenClosedManagedWorktreeContext(session)
190
- : await this.reusedWorkspaceContext(await this.workspaceForOpen(workspaceId));
64
+ ? await this.sessions.reopenClosedManagedWorktreeContext(session)
65
+ : await this.sessions.reusedWorkspaceContext(await this.sessions.workspaceForOpen(workspaceId));
191
66
  const workspace = context.workspace;
192
67
  if (!conversationScopeId || !this.store) {
193
68
  const bootstrapContextComponents = resolveBootstrapContextComponents(bootstrapContext, context.bootstrapComponentFingerprints, []);
@@ -197,7 +72,7 @@ export class WorkspaceRegistry {
197
72
  includeBootstrapContext: bootstrapContextComponents.length > 0,
198
73
  };
199
74
  }
200
- const targetKeys = await this.workspaceTargetKeys(workspace);
75
+ const targetKeys = await this.sessions.workspaceTargetKeys(workspace);
201
76
  const deliveries = targetKeys
202
77
  .map((targetKey) => this.store?.getContextDelivery(conversationScopeId, targetKey))
203
78
  .filter((delivery) => delivery !== undefined);
@@ -232,7 +107,7 @@ export class WorkspaceRegistry {
232
107
  const lastUsedAt = Date.parse(session.lastUsedAt);
233
108
  if (!Number.isFinite(lastUsedAt) || now - lastUsedAt < WORKSPACE_STALE_REMINDER_MS)
234
109
  continue;
235
- const root = await this.validSessionRoot(session);
110
+ const root = await this.sessions.validSessionRoot(session);
236
111
  if (!root || await canonicalPath(root) !== workspaceRootKey)
237
112
  continue;
238
113
  stale.push({
@@ -255,9 +130,10 @@ export class WorkspaceRegistry {
255
130
  throw new Error(`Workspace ${canonicalWorkspaceId} is backed by a managed worktree. Use close_workspace with its managed-worktree finalize lifecycle.`);
256
131
  }
257
132
  if (this.store) {
258
- this.deleteConversationBindingsForWorkspace(canonicalWorkspaceId);
133
+ this.sessions.deleteConversationBindingsForWorkspace(canonicalWorkspaceId);
259
134
  this.store.setSessionStatus(canonicalWorkspaceId, "closed");
260
135
  }
136
+ this.context.forgetWorkspaceResources(canonicalWorkspaceId);
261
137
  this.workspaces.delete(canonicalWorkspaceId);
262
138
  }
263
139
  deleteWorkspace(workspaceId) {
@@ -265,13 +141,14 @@ export class WorkspaceRegistry {
265
141
  if (session.mode === "worktree" && session.status === "active") {
266
142
  throw new Error(`Workspace ${session.id} is an active managed-worktree Workspace. Finalize it safely before deleting its persistent identity.`);
267
143
  }
268
- this.deleteConversationBindingsForWorkspace(session.id);
144
+ this.sessions.deleteConversationBindingsForWorkspace(session.id);
269
145
  this.store?.deleteSession(session.id);
146
+ this.context.forgetWorkspaceResources(session.id);
270
147
  this.workspaces.delete(session.id);
271
148
  }
272
149
  workspaceIdsForPhysicalWorkspace(workspace) {
273
150
  const root = resolve(workspace.root);
274
- return this.activeSessions(workspace.mode)
151
+ return this.sessions.activeSessions(workspace.mode)
275
152
  .filter((session) => resolve(session.root) === root)
276
153
  .map((session) => session.id);
277
154
  }
@@ -284,12 +161,12 @@ export class WorkspaceRegistry {
284
161
  ? await canonicalPath(workspace.root)
285
162
  : undefined;
286
163
  const resultsByRoot = new Map();
287
- for (const session of this.activeSessions("worktree")) {
164
+ for (const session of this.sessions.activeSessions("worktree")) {
288
165
  if (!session.sourceRoot)
289
166
  continue;
290
167
  if (await canonicalPath(session.sourceRoot) !== sourceKey)
291
168
  continue;
292
- const root = await this.validSessionRoot(session);
169
+ const root = await this.sessions.validSessionRoot(session);
293
170
  if (!root)
294
171
  continue;
295
172
  const rootKey = await canonicalPath(root);
@@ -342,7 +219,7 @@ export class WorkspaceRegistry {
342
219
  targetBranch: managedWorktree.targetBranch,
343
220
  },
344
221
  });
345
- const aliasedWorkspaceIds = this.activeSessions("worktree")
222
+ const aliasedWorkspaceIds = this.sessions.activeSessions("worktree")
346
223
  .filter((session) => resolve(session.root) === resolve(workspace.root))
347
224
  .map((session) => session.id);
348
225
  const result = await closeManagedWorktree({
@@ -367,8 +244,9 @@ export class WorkspaceRegistry {
367
244
  },
368
245
  }));
369
246
  for (const aliasedWorkspaceId of aliasedWorkspaceIds) {
370
- this.deleteConversationBindingsForWorkspace(aliasedWorkspaceId);
247
+ this.sessions.deleteConversationBindingsForWorkspace(aliasedWorkspaceId);
371
248
  this.store?.setSessionStatus(aliasedWorkspaceId, "closed");
249
+ this.context.forgetWorkspaceResources(aliasedWorkspaceId);
372
250
  this.workspaces.delete(aliasedWorkspaceId);
373
251
  }
374
252
  return { ...result, hookReports };
@@ -377,37 +255,37 @@ export class WorkspaceRegistry {
377
255
  const allowedPath = assertAllowedPath(path, this.config.allowedRoots);
378
256
  const projectKey = await canonicalPath(allowedPath);
379
257
  const targetKey = JSON.stringify(["checkout", projectKey, null]);
380
- const boundContext = await this.boundConversationContext(conversationScopeId, targetKey, "checkout", async (session, root) => session.mode === "checkout" && await canonicalPath(root) === projectKey, bootstrapContext);
258
+ const boundContext = await this.sessions.boundConversationContext(conversationScopeId, targetKey, "checkout", async (session, root) => session.mode === "checkout" && await canonicalPath(root) === projectKey, bootstrapContext);
381
259
  if (boundContext)
382
260
  return boundContext;
383
- const context = await this.openOnce(targetKey, async () => {
384
- const reusableWorkspace = await this.findReusableWorkspaceByDirectory(projectKey, "checkout", true);
261
+ const context = await this.sessions.openOnce(targetKey, async () => {
262
+ const reusableWorkspace = await this.sessions.findReusableWorkspaceByDirectory(projectKey, "checkout", true);
385
263
  if (!reusableWorkspace)
386
264
  return this.openCheckoutWorkspace(path);
387
- return this.reusedWorkspaceContext(reusableWorkspace);
265
+ return this.sessions.reusedWorkspaceContext(reusableWorkspace);
388
266
  });
389
- return this.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
267
+ return this.sessions.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
390
268
  }
391
269
  async openReusableWorktree(input, conversationScopeId) {
392
270
  const path = input.path;
393
271
  if (!path)
394
272
  throw new Error("Worktree mode requires path.");
395
273
  const bootstrapContext = input.context ?? "auto";
396
- const managedPath = this.tryManagedWorktreePath(path);
274
+ const managedPath = this.sessions.tryManagedWorktreePath(path);
397
275
  if (managedPath) {
398
276
  const worktreeKey = await canonicalPath(managedPath);
399
277
  const targetKey = JSON.stringify(["worktree-path", worktreeKey]);
400
- const boundContext = await this.boundConversationContext(conversationScopeId, targetKey, "worktree", async (session, root) => session.mode === "worktree" && await canonicalPath(root) === worktreeKey, bootstrapContext);
278
+ const boundContext = await this.sessions.boundConversationContext(conversationScopeId, targetKey, "worktree", async (session, root) => session.mode === "worktree" && await canonicalPath(root) === worktreeKey, bootstrapContext);
401
279
  if (boundContext)
402
280
  return boundContext;
403
- const context = await this.openOnce(targetKey, async () => {
404
- const reusableWorkspace = await this.findReusableWorkspaceByDirectory(worktreeKey, "worktree");
281
+ const context = await this.sessions.openOnce(targetKey, async () => {
282
+ const reusableWorkspace = await this.sessions.findReusableWorkspaceByDirectory(worktreeKey, "worktree");
405
283
  if (!reusableWorkspace) {
406
284
  throw new Error(`Managed worktree is not registered as an active ForgeRelay workspace: ${managedPath}. Open the source project in worktree mode to create or recover a managed worktree first.`);
407
285
  }
408
- return this.reusedWorkspaceContext(reusableWorkspace);
286
+ return this.sessions.reusedWorkspaceContext(reusableWorkspace);
409
287
  });
410
- return this.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
288
+ return this.sessions.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
411
289
  }
412
290
  const resolvedBase = await resolveManagedWorktreeBase({
413
291
  sourcePath: path,
@@ -418,533 +296,43 @@ export class WorkspaceRegistry {
418
296
  const targetKey = JSON.stringify(["worktree", sourceKey, resolvedBase.targetBranch]);
419
297
  if (input.newWorktree) {
420
298
  const context = await this.openWorktreeWorkspace(path, input.baseRef);
421
- return this.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
299
+ return this.sessions.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
422
300
  }
423
- const boundContext = await this.boundConversationContext(conversationScopeId, targetKey, "worktree", async (session) => session.mode === "worktree" &&
301
+ const boundContext = await this.sessions.boundConversationContext(conversationScopeId, targetKey, "worktree", async (session) => session.mode === "worktree" &&
424
302
  session.sourceRoot !== undefined &&
425
303
  await canonicalPath(session.sourceRoot) === sourceKey &&
426
304
  session.targetBranch === resolvedBase.targetBranch, bootstrapContext);
427
305
  if (boundContext)
428
306
  return boundContext;
429
- const context = await this.openOnce(targetKey, async () => {
430
- const reusableContext = await this.findReusableWorktreeContextBySource(sourceKey, resolvedBase.targetBranch);
307
+ const context = await this.sessions.openOnce(targetKey, async () => {
308
+ const reusableContext = await this.sessions.findReusableWorktreeContextBySource(sourceKey, resolvedBase.targetBranch);
431
309
  return reusableContext ?? this.openWorktreeWorkspace(path, input.baseRef);
432
310
  });
433
- return this.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
434
- }
435
- async openOnce(operationKey, open) {
436
- const pending = this.pendingOpens.get(operationKey);
437
- if (pending) {
438
- const context = await pending;
439
- return { ...context, workspaceReused: true };
440
- }
441
- const operation = open();
442
- this.pendingOpens.set(operationKey, operation);
443
- try {
444
- return await operation;
445
- }
446
- finally {
447
- if (this.pendingOpens.get(operationKey) === operation) {
448
- this.pendingOpens.delete(operationKey);
449
- }
450
- }
451
- }
452
- async workspaceTargetKeys(workspace) {
453
- if (workspace.mode === "checkout") {
454
- return [JSON.stringify(["checkout", await canonicalPath(workspace.root), null])];
455
- }
456
- const keys = [JSON.stringify(["worktree-path", await canonicalPath(workspace.root)])];
457
- if (workspace.sourceRoot && workspace.worktree?.targetBranch) {
458
- keys.push(JSON.stringify([
459
- "worktree",
460
- await canonicalPath(workspace.sourceRoot),
461
- workspace.worktree.targetBranch,
462
- ]));
463
- }
464
- return keys;
465
- }
466
- async boundConversationContext(conversationScopeId, targetKey, mode, matches, bootstrapContext) {
467
- if (!conversationScopeId || !this.store)
468
- return undefined;
469
- const binding = this.store.getConversationBinding(conversationScopeId, targetKey);
470
- if (!binding)
471
- return undefined;
472
- const session = this.store.getSession(binding.workspaceSessionId);
473
- if (!session || session.status !== "active" || session.mode !== mode) {
474
- this.store.deleteConversationBinding(conversationScopeId, targetKey);
475
- return undefined;
476
- }
477
- const root = await this.validSessionRoot(session);
478
- if (!root) {
479
- this.store.deleteConversationBinding(conversationScopeId, targetKey);
480
- return undefined;
481
- }
482
- if (!await matches(session, root)) {
483
- this.store.deleteConversationBinding(conversationScopeId, targetKey);
484
- return undefined;
485
- }
486
- const context = await this.reusedWorkspaceContext(this.getWorkspace(session.id));
487
- return this.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
488
- }
489
- withConversationContext(context, conversationScopeId, targetKey, bootstrapContext) {
490
- if (!conversationScopeId || !this.store) {
491
- const bootstrapContextComponents = resolveBootstrapContextComponents(bootstrapContext, context.bootstrapComponentFingerprints, []);
492
- return {
493
- ...context,
494
- bootstrapContextComponents,
495
- includeBootstrapContext: bootstrapContextComponents.length > 0,
496
- };
497
- }
498
- const delivery = this.store.getContextDelivery(conversationScopeId, targetKey);
499
- const bootstrapContextComponents = resolveBootstrapContextComponents(bootstrapContext, context.bootstrapComponentFingerprints, delivery ? [delivery] : [], context.contextFingerprint);
500
- const includeBootstrapContext = bootstrapContextComponents.length > 0;
501
- this.store.setConversationBinding({
502
- conversationScopeId,
503
- targetKey,
504
- workspaceSessionId: context.workspace.id,
505
- });
506
- if (bootstrapContext !== "none" &&
507
- (includeBootstrapContext ||
508
- (delivery?.contextFingerprint === context.contextFingerprint && !delivery.componentFingerprints))) {
509
- this.store.setContextDelivery({
510
- conversationScopeId,
511
- targetKey,
512
- contextFingerprint: context.contextFingerprint,
513
- componentFingerprints: context.bootstrapComponentFingerprints,
514
- });
515
- }
516
- return { ...context, bootstrapContextComponents, includeBootstrapContext };
517
- }
518
- pruneIdleWorkspaceSessions(protectedWorkspaceIds, force = false) {
519
- if (!this.store)
520
- return;
521
- const now = Date.now();
522
- if (!force && now - this.lastWorkspaceGcAt < WORKSPACE_GC_INTERVAL_MS)
523
- return;
524
- this.lastWorkspaceGcAt = now;
525
- const activeSessions = this.store.listSessions({ status: "active" });
526
- const sessionsById = new Map(activeSessions.map((session) => [session.id, session]));
527
- const isIdle = (session) => {
528
- const lastUsedAt = Date.parse(session.lastUsedAt);
529
- return Number.isFinite(lastUsedAt) && now - lastUsedAt >= WORKSPACE_SESSION_IDLE_TTL_MS;
530
- };
531
- for (const binding of this.store.listConversationBindings()) {
532
- const session = sessionsById.get(binding.workspaceSessionId);
533
- if (!session) {
534
- this.store.deleteConversationBinding(binding.conversationScopeId, binding.targetKey);
535
- }
536
- }
537
- for (const delivery of this.store.listContextDeliveries()) {
538
- const deliveredAt = Date.parse(delivery.deliveredAt);
539
- if (Number.isFinite(deliveredAt) &&
540
- now - deliveredAt >= WORKSPACE_SESSION_IDLE_TTL_MS) {
541
- this.store.deleteContextDelivery(delivery.conversationScopeId, delivery.targetKey);
542
- }
543
- }
544
- const boundWorkspaceIds = new Set(this.store.listConversationBindings().map((binding) => binding.workspaceSessionId));
545
- const worktreeAnchors = new Map();
546
- for (const session of activeSessions) {
547
- if (session.mode !== "worktree")
548
- continue;
549
- const key = resolve(session.root);
550
- const current = worktreeAnchors.get(key);
551
- if (!current || current.lastUsedAt < session.lastUsedAt) {
552
- worktreeAnchors.set(key, session);
553
- }
554
- }
555
- for (const session of activeSessions) {
556
- if (!isIdle(session))
557
- continue;
558
- if (protectedWorkspaceIds.has(session.id) || boundWorkspaceIds.has(session.id))
559
- continue;
560
- if (session.mode === "worktree" &&
561
- worktreeAnchors.get(resolve(session.root))?.id === session.id) {
562
- continue;
563
- }
564
- this.workspaces.delete(session.id);
565
- }
566
- }
567
- foldLegacyWorkspaceSessions() {
568
- if (!this.store)
569
- return;
570
- const groups = new Map();
571
- for (const session of this.store.listSessions()) {
572
- const targetPath = canonicalPersistedWorkspacePath(session.root);
573
- const key = JSON.stringify([session.mode, targetPath]);
574
- const group = groups.get(key) ?? [];
575
- group.push(session);
576
- groups.set(key, group);
577
- }
578
- for (const sessions of groups.values()) {
579
- if (sessions.length < 2)
580
- continue;
581
- const ordered = [...sessions].sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id));
582
- const canonical = ordered[0];
583
- if (!canonical)
584
- continue;
585
- const createdAt = ordered.reduce((earliest, session) => session.createdAt < earliest ? session.createdAt : earliest, canonical.createdAt);
586
- const lastUsedAt = ordered.reduce((latest, session) => session.lastUsedAt > latest ? session.lastUsedAt : latest, canonical.lastUsedAt);
587
- const status = ordered.some((session) => session.status === "active")
588
- ? "active"
589
- : canonical.status;
590
- this.store.foldSessions({
591
- canonicalId: canonical.id,
592
- aliasIds: ordered.slice(1).map((session) => session.id),
593
- createdAt,
594
- lastUsedAt,
595
- status,
596
- });
597
- }
598
- }
599
- async findReusableWorkspaceByDirectory(directoryKey, mode, reopenClosedCheckout = false) {
600
- const sessions = this.store
601
- ? this.store.listSessions({ mode })
602
- : this.activeSessions(mode);
603
- for (const session of sessions) {
604
- const reusable = session.status === "active" ||
605
- (reopenClosedCheckout && mode === "checkout" && session.status === "closed");
606
- if (!reusable)
607
- continue;
608
- const root = await this.validSessionRoot(session);
609
- if (!root)
610
- continue;
611
- if (await canonicalPath(root) !== directoryKey)
612
- continue;
613
- if (session.status === "closed") {
614
- this.store?.setSessionStatus(session.id, "active");
615
- const reopened = this.store?.getSession(session.id);
616
- if (!reopened)
617
- continue;
618
- return this.workspaceFromSession(reopened, false);
619
- }
620
- return this.workspaceFromSession(session, false);
621
- }
622
- return undefined;
623
- }
624
- async findReusableWorktreeContextBySource(sourceKey, targetBranch) {
625
- const sessions = this.store
626
- ? this.store.listSessions({ mode: "worktree" })
627
- : this.activeSessions("worktree");
628
- const closedMatches = [];
629
- for (const session of sessions) {
630
- if (!session.sourceRoot || session.targetBranch !== targetBranch)
631
- continue;
632
- if (await canonicalPath(session.sourceRoot) !== sourceKey)
633
- continue;
634
- if (session.status === "closed") {
635
- if (session.managed)
636
- closedMatches.push(session);
637
- continue;
638
- }
639
- if (session.status !== "active")
640
- continue;
641
- const root = await this.validSessionRoot(session);
642
- if (!root)
643
- continue;
644
- return this.reusedWorkspaceContext(this.workspaceFromSession(session, false));
645
- }
646
- if (closedMatches.length === 1) {
647
- return this.reopenClosedManagedWorktreeContext(closedMatches[0]);
648
- }
649
- return undefined;
650
- }
651
- activeSessions(mode) {
652
- if (this.store) {
653
- return this.store.listSessions({ status: "active", mode });
654
- }
655
- return [...this.workspaces.values()]
656
- .filter((workspace) => workspace.mode === mode)
657
- .map((workspace) => ({
658
- id: workspace.id,
659
- root: workspace.root,
660
- status: "active",
661
- mode: workspace.mode,
662
- sourceRoot: workspace.sourceRoot,
663
- baseRef: workspace.worktree?.baseRef,
664
- baseSha: workspace.worktree?.baseSha,
665
- branch: workspace.worktree?.branch,
666
- targetBranch: workspace.worktree?.targetBranch,
667
- managed: workspace.worktree?.managed ?? false,
668
- createdAt: "",
669
- lastUsedAt: "",
670
- }));
671
- }
672
- async validSessionRoot(session) {
673
- try {
674
- const root = this.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);
675
- const rootStats = await stat(root);
676
- return rootStats.isDirectory() ? root : undefined;
677
- }
678
- catch (error) {
679
- if (error instanceof AccessDeniedError ||
680
- (isErrnoException(error) && (error.code === "ENOENT" || error.code === "ENOTDIR"))) {
681
- return undefined;
682
- }
683
- throw error;
684
- }
685
- }
686
- tryManagedWorktreePath(path) {
687
- try {
688
- return assertAllowedPath(path, [this.config.worktreeRoot]);
689
- }
690
- catch (error) {
691
- if (error instanceof AccessDeniedError)
692
- return undefined;
693
- throw error;
694
- }
695
- }
696
- async reusedWorkspaceContext(workspace) {
697
- Object.assign(workspace, this.loadSkillsForWorkspace(workspace.root));
698
- workspace.capabilityGuides = loadCapabilityGuides(this.config);
699
- workspace.agentProfiles = await loadSubagentProfiles(this.config, workspace.root);
700
- workspace.scannedInstructionDirs.clear();
701
- workspace.knownInstructionPathsByDir.clear();
702
- workspace.loadedInstructionRealPaths.clear();
703
- workspace.loadedInstructionPaths.clear();
704
- const agentsFiles = await this.loadInitialAgentsFiles(workspace);
705
- const availableAgentsFiles = await this.findAvailableAgentsFiles(workspace, agentsFiles);
706
- const { contextFingerprint, componentFingerprints: bootstrapComponentFingerprints, } = bootstrapContextFingerprints(workspace, agentsFiles, availableAgentsFiles);
707
- return {
708
- workspace,
709
- agentsFiles,
710
- availableAgentsFiles,
711
- contextFingerprint,
712
- bootstrapComponentFingerprints,
713
- bootstrapContextComponents: [...BOOTSTRAP_CONTEXT_COMPONENTS],
714
- hookReports: [],
715
- workspaceReused: true,
716
- includeBootstrapContext: true,
717
- };
718
- }
719
- async workspaceForOpen(workspaceId) {
720
- const session = this.store?.getSession(workspaceId);
721
- if (session?.status === "closed" && session.mode === "checkout") {
722
- this.store?.setSessionStatus(session.id, "active");
723
- const reopened = this.store?.getSession(session.id);
724
- if (!reopened) {
725
- throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`);
726
- }
727
- return this.workspaceFromSession(reopened, true);
728
- }
729
- return this.getWorkspace(workspaceId);
730
- }
731
- async reopenClosedManagedWorktreeContext(session) {
732
- const operationKey = JSON.stringify(["worktree-reopen", session.id]);
733
- return this.openOnce(operationKey, async () => {
734
- const current = this.store?.getSession(session.id);
735
- if (!current) {
736
- throw new Error(`Unknown workspaceId: ${session.id}. Call open_workspace first.`);
737
- }
738
- if (current.status === "active") {
739
- return this.reusedWorkspaceContext(this.getWorkspace(current.id));
740
- }
741
- if (current.status !== "closed" || current.mode !== "worktree") {
742
- throw new Error(`Workspace ${current.id} is not a closed managed-worktree Workspace.`);
743
- }
744
- return this.reopenClosedManagedWorktreeContextUnlocked(current);
745
- });
746
- }
747
- async reopenClosedManagedWorktreeContextUnlocked(session) {
748
- if (!this.store) {
749
- throw new Error(`Workspace ${session.id} cannot be reopened without persistent Workspace state.`);
750
- }
751
- if (!session.managed || !session.sourceRoot || !session.targetBranch) {
752
- throw new Error(`Workspace ${session.id} does not have enough managed-worktree metadata to recreate its execution backing.`);
753
- }
754
- const worktree = await createManagedWorktree({
755
- sourcePath: session.sourceRoot,
756
- baseRef: session.targetBranch,
757
- config: this.config,
758
- });
759
- const candidateSession = {
760
- ...session,
761
- root: worktree.path,
762
- status: "active",
763
- sourceRoot: worktree.sourceRoot,
764
- baseRef: worktree.baseRef,
765
- baseSha: worktree.baseSha,
766
- branch: worktree.branch,
767
- targetBranch: worktree.targetBranch,
768
- managed: true,
769
- };
770
- const workspace = this.workspaceFromSession(candidateSession, false);
771
- try {
772
- const context = await this.reusedWorkspaceContext(workspace);
773
- this.store.replaceWorktreeBacking({
774
- id: session.id,
775
- root: worktree.path,
776
- sourceRoot: worktree.sourceRoot,
777
- baseRef: worktree.baseRef,
778
- baseSha: worktree.baseSha,
779
- branch: worktree.branch,
780
- targetBranch: worktree.targetBranch,
781
- });
782
- return context;
783
- }
784
- catch (error) {
785
- this.workspaces.delete(session.id);
786
- try {
787
- await discardFreshManagedWorktree({ worktree, config: this.config });
788
- }
789
- catch (cleanupError) {
790
- const original = error instanceof Error ? error.message : String(error);
791
- const cleanup = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
792
- throw new Error(`${original} Reopen rollback also failed: ${cleanup}`);
793
- }
794
- throw error;
795
- }
311
+ return this.sessions.withConversationContext(context, conversationScopeId, targetKey, bootstrapContext);
796
312
  }
797
313
  getWorkspaceSession(workspaceId) {
798
- const session = this.store?.getSession(workspaceId);
799
- if (session)
800
- return session;
801
- const workspace = this.workspaces.get(workspaceId);
802
- if (!workspace) {
803
- throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`);
804
- }
805
- return {
806
- id: workspace.id,
807
- root: workspace.root,
808
- status: "active",
809
- mode: workspace.mode,
810
- sourceRoot: workspace.sourceRoot,
811
- baseRef: workspace.worktree?.baseRef,
812
- baseSha: workspace.worktree?.baseSha,
813
- branch: workspace.worktree?.branch,
814
- targetBranch: workspace.worktree?.targetBranch,
815
- managed: workspace.worktree?.managed ?? false,
816
- createdAt: "",
817
- lastUsedAt: "",
818
- };
819
- }
820
- deleteConversationBindingsForWorkspace(workspaceId) {
821
- if (!this.store)
822
- return;
823
- for (const binding of this.store.listConversationBindings()) {
824
- if (binding.workspaceSessionId === workspaceId) {
825
- this.store.deleteConversationBinding(binding.conversationScopeId, binding.targetKey);
826
- }
827
- }
314
+ return this.sessions.getWorkspaceSession(workspaceId);
828
315
  }
829
316
  getWorkspace(workspaceId) {
830
- const workspace = this.workspaces.get(workspaceId);
831
- if (workspace) {
832
- this.store?.touchSession(workspaceId);
833
- return workspace;
834
- }
835
- const session = this.store?.getSession(workspaceId);
836
- if (!session || session.status !== "active") {
837
- throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`);
838
- }
839
- return this.workspaceFromSession(session, true);
840
- }
841
- workspaceFromSession(session, touch) {
842
- const existing = this.workspaces.get(session.id);
843
- if (existing) {
844
- if (touch)
845
- this.store?.touchSession(session.id);
846
- return existing;
847
- }
848
- const root = this.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);
849
- const restoredWorkspace = {
850
- id: session.id,
851
- root,
852
- mode: session.mode,
853
- sourceRoot: session.sourceRoot,
854
- worktree: session.mode === "worktree"
855
- ? {
856
- path: root,
857
- baseRef: session.baseRef ?? "HEAD",
858
- baseSha: session.baseSha ?? "",
859
- branch: session.branch,
860
- targetBranch: session.targetBranch,
861
- dirtySource: false,
862
- detached: !session.branch,
863
- managed: session.managed,
864
- }
865
- : undefined,
866
- ...this.loadSkillsForWorkspace(root),
867
- capabilityGuides: loadCapabilityGuides(this.config),
868
- agentProfiles: [],
869
- activatedSkillDirs: new Set(),
870
- activatedCapabilityGuideDirs: new Set(),
871
- scannedInstructionDirs: new Set(),
872
- knownInstructionPathsByDir: new Map(),
873
- loadedInstructionRealPaths: new Set(),
874
- loadedInstructionPaths: new Set(),
875
- };
876
- if (touch)
877
- this.store?.touchSession(session.id);
878
- this.workspaces.set(restoredWorkspace.id, restoredWorkspace);
879
- return restoredWorkspace;
317
+ return this.sessions.getWorkspace(workspaceId);
880
318
  }
881
319
  fileToolRoots(workspace) {
882
- return [workspace.root, tmpdir()];
320
+ return this.context.fileToolRoots(workspace);
883
321
  }
884
322
  resolvePath(workspace, inputPath) {
885
- const absolutePath = resolveAllowedPath(inputPath, workspace.root, [workspace.root]);
886
- if (!isPathInsideRoot(absolutePath, workspace.root)) {
887
- throw new Error(`Path is outside workspace root: ${inputPath}`);
888
- }
889
- return absolutePath;
323
+ return this.context.resolvePath(workspace, inputPath);
890
324
  }
891
325
  resolveReadPath(workspace, inputPath) {
892
- if (inputPath.startsWith("skills://")) {
893
- const skillRead = resolveSkillReadPath(workspace.skills, workspace.activatedSkillDirs, inputPath);
894
- if (!skillRead) {
895
- throw new Error(`Skill resource is not readable before its skill is loaded: ${inputPath}. Read skills://<name> first.`);
896
- }
897
- return {
898
- absolutePath: skillRead.absolutePath,
899
- readRoots: [workspace.root, skillRead.skill.baseDir],
900
- skillRead,
901
- };
902
- }
903
- try {
904
- return {
905
- absolutePath: this.resolvePath(workspace, inputPath),
906
- readRoots: [workspace.root],
907
- };
908
- }
909
- catch (workspaceError) {
910
- const skillRead = resolveSkillReadPath(workspace.skills, workspace.activatedSkillDirs, inputPath);
911
- if (skillRead) {
912
- return {
913
- absolutePath: skillRead.absolutePath,
914
- readRoots: [workspace.root, skillRead.skill.baseDir],
915
- skillRead,
916
- };
917
- }
918
- const capabilityGuideRead = resolveCapabilityGuideReadPath(workspace.capabilityGuides, workspace.activatedCapabilityGuideDirs, inputPath);
919
- if (capabilityGuideRead) {
920
- return {
921
- absolutePath: capabilityGuideRead.absolutePath,
922
- readRoots: [workspace.root, capabilityGuideRead.guide.baseDir],
923
- capabilityGuideRead,
924
- };
925
- }
926
- try {
927
- return {
928
- absolutePath: resolveAllowedPath(inputPath, workspace.root, [tmpdir()]),
929
- readRoots: this.fileToolRoots(workspace),
930
- };
931
- }
932
- catch {
933
- throw workspaceError;
934
- }
935
- }
326
+ return this.context.resolveReadPath(workspace, inputPath);
936
327
  }
937
328
  markReadPathLoaded(workspace, readPath) {
938
- if (readPath.skillRead?.isSkillFile) {
939
- markSkillActivated(workspace.activatedSkillDirs, readPath.skillRead.skill);
940
- }
941
- if (readPath.capabilityGuideRead?.isGuideFile) {
942
- markCapabilityGuideActivated(workspace.activatedCapabilityGuideDirs, readPath.capabilityGuideRead.guide);
943
- }
329
+ this.context.markReadPathLoaded(workspace, readPath);
330
+ }
331
+ readAdvertisedInstruction(workspace, inputPath) {
332
+ return this.context.readAdvertisedInstruction(workspace, inputPath);
944
333
  }
945
334
  resolveWorkingDirectory(workspace, workingDirectory) {
946
- const directory = workingDirectory ? this.resolvePath(workspace, workingDirectory) : workspace.root;
947
- return assertAllowedPath(directory, [workspace.root]);
335
+ return this.context.resolveWorkingDirectory(workspace, workingDirectory);
948
336
  }
949
337
  async openCheckoutWorkspace(path) {
950
338
  const root = assertAllowedPath(path, this.config.allowedRoots);
@@ -974,7 +362,7 @@ export class WorkspaceRegistry {
974
362
  mode: input.mode,
975
363
  sourceRoot: input.sourceRoot,
976
364
  worktree: input.worktree,
977
- ...this.loadSkillsForWorkspace(input.root),
365
+ ...this.context.loadSkillsForWorkspace(input.root),
978
366
  capabilityGuides: loadCapabilityGuides(this.config),
979
367
  agentProfiles: await loadSubagentProfiles(this.config, input.root),
980
368
  activatedSkillDirs: new Set(),
@@ -1008,8 +396,9 @@ export class WorkspaceRegistry {
1008
396
  targetBranch: workspace.worktree?.targetBranch,
1009
397
  },
1010
398
  });
1011
- const agentsFiles = await this.loadInitialAgentsFiles(workspace);
1012
- const availableAgentsFiles = await this.findAvailableAgentsFiles(workspace, agentsFiles);
399
+ const agentsFiles = await this.context.loadInitialAgentsFiles(workspace);
400
+ const availableAgentsFiles = await this.context.findAvailableAgentsFiles(workspace, agentsFiles);
401
+ this.context.trackWorkspaceResources(workspace, agentsFiles, availableAgentsFiles);
1013
402
  const { contextFingerprint, componentFingerprints: bootstrapComponentFingerprints, } = bootstrapContextFingerprints(workspace, agentsFiles, availableAgentsFiles);
1014
403
  return {
1015
404
  workspace,
@@ -1023,320 +412,15 @@ export class WorkspaceRegistry {
1023
412
  includeBootstrapContext: true,
1024
413
  };
1025
414
  }
1026
- loadSkillsForWorkspace(root) {
1027
- const result = loadWorkspaceSkills(this.config, root);
1028
- return {
1029
- skills: result.skills,
1030
- skillDiagnostics: result.diagnostics,
1031
- };
1032
- }
1033
- assertWorkspaceRootAllowed(root, mode, sourceRoot) {
1034
- if (mode === "worktree") {
1035
- if (!sourceRoot) {
1036
- throw new Error(`Stored worktree workspace is missing sourceRoot: ${root}`);
1037
- }
1038
- assertAllowedPath(sourceRoot, this.config.allowedRoots);
1039
- return assertAllowedPath(root, [this.config.worktreeRoot]);
1040
- }
1041
- return assertAllowedPath(root, this.config.allowedRoots);
1042
- }
1043
- async loadInitialAgentsFiles(workspace) {
1044
- const systemInstructionsPath = resolve(this.config.systemInstructionsPath);
1045
- const loadedFiles = [];
1046
- const systemInstructions = await readSystemInstructions(systemInstructionsPath);
1047
- const systemInstructionsRealPath = await tryRealpath(systemInstructionsPath);
1048
- if (systemInstructions !== undefined) {
1049
- loadedFiles.push({
1050
- path: systemInstructionsPath,
1051
- content: systemInstructions,
1052
- });
1053
- workspace.loadedInstructionPaths.add(systemInstructionsPath);
1054
- if (systemInstructionsRealPath) {
1055
- workspace.loadedInstructionRealPaths.add(systemInstructionsRealPath);
1056
- }
1057
- }
1058
- await this.discoverInstructionTree(workspace, workspace.root, INITIAL_INSTRUCTION_DISCOVERY_DEPTH);
1059
- loadedFiles.push(...await this.loadKnownInstructionsInDirectory(workspace, workspace.root));
1060
- return loadedFiles;
1061
- }
1062
- async findAvailableAgentsFiles(workspace, loadedFiles) {
1063
- const loadedPaths = new Set(loadedFiles.map((file) => resolve(file.path)));
1064
- const discovered = [];
1065
- for (const paths of workspace.knownInstructionPathsByDir.values()) {
1066
- for (const path of paths) {
1067
- if (loadedPaths.has(path))
1068
- continue;
1069
- const realPath = await tryRealpath(path);
1070
- if (realPath && workspace.loadedInstructionRealPaths.has(realPath))
1071
- continue;
1072
- discovered.push({ path });
1073
- }
1074
- }
1075
- return discovered.sort((a, b) => a.path.localeCompare(b.path));
1076
- }
1077
- async discoverPathInstructions(workspace, inputPath) {
1078
- const absolutePath = resolve(inputPath);
1079
- if (!isPathInsideRoot(absolutePath, workspace.root))
1080
- return [];
1081
- const targetDirectory = dirname(absolutePath);
1082
- const relationship = relative(workspace.root, targetDirectory);
1083
- if (relationship === ".." ||
1084
- relationship.startsWith(`..${sep}`) ||
1085
- resolve(targetDirectory) === resolve(this.config.agentDir) ||
1086
- isPathInsideRoot(targetDirectory, resolve(this.config.agentDir))) {
1087
- return [];
1088
- }
1089
- const directories = [resolve(workspace.root)];
1090
- if (relationship) {
1091
- let current = resolve(workspace.root);
1092
- for (const segment of relationship.split(sep).filter(Boolean)) {
1093
- if (SKIPPED_CONTEXT_DIRS.has(segment))
1094
- break;
1095
- current = join(current, segment);
1096
- directories.push(current);
1097
- }
1098
- }
1099
- const loaded = [];
1100
- for (const directory of directories) {
1101
- await this.discoverInstructionTree(workspace, directory, 0);
1102
- loaded.push(...await this.loadKnownInstructionsInDirectory(workspace, directory));
1103
- }
1104
- return loaded;
1105
- }
1106
- async discoverInstructionTree(workspace, directory, remainingDepth) {
1107
- const resolvedDirectory = resolve(directory);
1108
- if (workspace.scannedInstructionDirs.has(resolvedDirectory))
1109
- return;
1110
- workspace.scannedInstructionDirs.add(resolvedDirectory);
1111
- if (resolvedDirectory !== resolve(workspace.root) &&
1112
- isPathInsideRoot(resolvedDirectory, resolve(this.config.agentDir))) {
1113
- return;
1114
- }
1115
- let entries;
1116
- try {
1117
- entries = await opendir(resolvedDirectory);
1118
- }
1119
- catch {
1120
- return;
1121
- }
1122
- const instructionPaths = [];
1123
- const childDirectories = [];
1124
- for await (const entry of entries) {
1125
- const path = join(resolvedDirectory, entry.name);
1126
- if (entry.isFile() && CONTEXT_FILE_NAMES.has(entry.name)) {
1127
- instructionPaths.push(path);
1128
- continue;
1129
- }
1130
- if (remainingDepth > 0 &&
1131
- entry.isDirectory() &&
1132
- !SKIPPED_CONTEXT_DIRS.has(entry.name)) {
1133
- childDirectories.push(path);
1134
- }
1135
- }
1136
- workspace.knownInstructionPathsByDir.set(resolvedDirectory, instructionPaths.sort((left, right) => left.localeCompare(right)));
1137
- if (remainingDepth <= 0)
1138
- return;
1139
- for (const childDirectory of childDirectories) {
1140
- await this.discoverInstructionTree(workspace, childDirectory, remainingDepth - 1);
1141
- }
1142
- }
1143
- async loadKnownInstructionsInDirectory(workspace, directory) {
1144
- const resolvedDirectory = resolve(directory);
1145
- const paths = workspace.knownInstructionPathsByDir.get(resolvedDirectory) ?? [];
1146
- const loaded = [];
1147
- const resolvedRoot = (await tryRealpath(workspace.root)) ?? resolve(workspace.root);
1148
- const realDirectory = (await tryRealpath(resolvedDirectory)) ?? resolvedDirectory;
1149
- for (const path of paths) {
1150
- const realPath = await tryRealpath(path);
1151
- if (!realPath)
1152
- continue;
1153
- if (!isPathInsideRoot(realPath, resolvedRoot))
1154
- continue;
1155
- if (dirname(realPath) !== realDirectory)
1156
- continue;
1157
- if (workspace.loadedInstructionRealPaths.has(realPath))
1158
- continue;
1159
- let content;
1160
- try {
1161
- content = await readFile(realPath, "utf8");
1162
- }
1163
- catch (error) {
1164
- if (isErrnoException(error) && (error.code === "ENOENT" || error.code === "ENOTDIR"))
1165
- continue;
1166
- throw error;
1167
- }
1168
- workspace.loadedInstructionRealPaths.add(realPath);
1169
- workspace.loadedInstructionPaths.add(path);
1170
- loaded.push({ path, content });
1171
- }
1172
- return loaded;
1173
- }
1174
- }
1175
- const BOOTSTRAP_CONTEXT_COMPONENTS = [
1176
- "agentsFiles",
1177
- "availableAgentsFiles",
1178
- "skills",
1179
- "skillDiagnostics",
1180
- "capabilityGuides",
1181
- "agentProfiles",
1182
- ];
1183
- function resolveBootstrapContextComponents(mode, currentFingerprints, deliveries, contextFingerprint) {
1184
- if (mode === "none")
1185
- return [];
1186
- if (mode === "full")
1187
- return [...BOOTSTRAP_CONTEXT_COMPONENTS];
1188
- if (deliveries.length === 0)
1189
- return [...BOOTSTRAP_CONTEXT_COMPONENTS];
1190
- if (contextFingerprint &&
1191
- deliveries.some((delivery) => !delivery.componentFingerprints && delivery.contextFingerprint === contextFingerprint)) {
1192
- return [];
1193
- }
1194
- return BOOTSTRAP_CONTEXT_COMPONENTS.filter((component) => !deliveries.some((delivery) => delivery.componentFingerprints?.[component] === currentFingerprints[component]));
1195
- }
1196
- function bootstrapContextFingerprints(workspace, agentsFiles, availableAgentsFiles) {
1197
- const payload = {
1198
- agentsFiles: agentsFiles
1199
- .map((file) => ({ path: resolve(file.path), content: file.content }))
1200
- .sort((left, right) => left.path.localeCompare(right.path)),
1201
- availableAgentsFiles: availableAgentsFiles
1202
- .map((file) => resolve(file.path))
1203
- .sort((left, right) => left.localeCompare(right)),
1204
- skills: workspace.skills
1205
- .map((skill) => ({
1206
- name: skill.name,
1207
- description: skill.description,
1208
- filePath: resolve(skill.filePath),
1209
- disableModelInvocation: skill.disableModelInvocation ?? false,
1210
- }))
1211
- .sort((left, right) => left.name.localeCompare(right.name) || left.filePath.localeCompare(right.filePath)),
1212
- skillDiagnostics: workspace.skillDiagnostics,
1213
- capabilityGuides: workspace.capabilityGuides
1214
- .map((guide) => ({
1215
- name: guide.name,
1216
- description: guide.description,
1217
- whenToRead: guide.whenToRead,
1218
- filePath: resolve(guide.filePath),
1219
- }))
1220
- .sort((left, right) => left.name.localeCompare(right.name)),
1221
- agentProfiles: workspace.agentProfiles
1222
- .map((profile) => ({
1223
- name: profile.name,
1224
- description: profile.description,
1225
- provider: profile.provider,
1226
- model: profile.model,
1227
- thinking: profile.thinking,
1228
- filePath: resolve(profile.filePath),
1229
- }))
1230
- .sort((left, right) => left.name.localeCompare(right.name)),
1231
- };
1232
- const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
1233
- return {
1234
- contextFingerprint: hash(payload),
1235
- componentFingerprints: {
1236
- agentsFiles: hash(payload.agentsFiles),
1237
- availableAgentsFiles: hash(payload.availableAgentsFiles),
1238
- skills: hash(payload.skills),
1239
- skillDiagnostics: hash(payload.skillDiagnostics),
1240
- capabilityGuides: hash(payload.capabilityGuides),
1241
- agentProfiles: hash(payload.agentProfiles),
1242
- },
1243
- };
1244
- }
1245
- function canonicalPersistedWorkspacePath(path) {
1246
- const missingSegments = [];
1247
- let candidate = path;
1248
- while (true) {
1249
- try {
1250
- return resolve(realpathSync(candidate), ...missingSegments.slice().reverse());
1251
- }
1252
- catch (error) {
1253
- if (!isErrnoException(error) || (error.code !== "ENOENT" && error.code !== "ENOTDIR")) {
1254
- return resolve(path);
1255
- }
1256
- const parent = dirname(candidate);
1257
- if (parent === candidate)
1258
- return resolve(path);
1259
- missingSegments.push(basename(candidate));
1260
- candidate = parent;
1261
- }
1262
- }
1263
- }
1264
- async function canonicalPath(path) {
1265
- const missingSegments = [];
1266
- let candidate = path;
1267
- while (true) {
1268
- try {
1269
- return resolve(await realpath(candidate), ...missingSegments.slice().reverse());
1270
- }
1271
- catch (error) {
1272
- if (!isErrnoException(error) || (error.code !== "ENOENT" && error.code !== "ENOTDIR")) {
1273
- throw error;
1274
- }
1275
- const parent = dirname(candidate);
1276
- if (parent === candidate)
1277
- return path;
1278
- missingSegments.push(basename(candidate));
1279
- candidate = parent;
1280
- }
1281
- }
1282
- }
1283
- export async function ensureCheckoutWorkspaceRoot(path, ops = { stat, mkdir }) {
1284
- try {
1285
- return await ops.stat(path);
1286
- }
1287
- catch (error) {
1288
- if (!isErrnoException(error) || error.code !== "ENOENT") {
1289
- throw error;
1290
- }
415
+ discoverPathInstructions(workspace, inputPath) {
416
+ return this.context.discoverPathInstructions(workspace, inputPath);
1291
417
  }
1292
- await ops.mkdir(path, { recursive: true });
1293
- return await ops.stat(path);
1294
- }
1295
- const CONTEXT_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
1296
- const SKIPPED_CONTEXT_DIRS = new Set([
1297
- ".git",
1298
- ".hg",
1299
- ".svn",
1300
- ".forgerelay",
1301
- ".devspace",
1302
- "node_modules",
1303
- "dist",
1304
- "build",
1305
- ".next",
1306
- ".turbo",
1307
- ".cache",
1308
- ]);
1309
- export function formatAgentsPath(path, workspaceRoot) {
1310
- if (!workspaceRoot)
1311
- return path.split(sep).join("/");
1312
- const relationship = relative(workspaceRoot, path);
1313
- if (relationship === "" ||
1314
- relationship.startsWith("..") ||
1315
- relationship === ".." ||
1316
- relationship.includes(`..${sep}`)) {
1317
- return path.split(sep).join("/");
1318
- }
1319
- return relationship.split(sep).join("/");
1320
- }
1321
- async function readSystemInstructions(path) {
1322
- try {
1323
- return await readFile(path, "utf8");
1324
- }
1325
- catch (error) {
1326
- if (isErrnoException(error) && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
1327
- return undefined;
1328
- }
1329
- throw error;
1330
- }
1331
- }
1332
- async function tryRealpath(path) {
1333
- try {
1334
- return await realpath(path);
418
+ claimResourceUpdates(workspaceId, conversationScopeId) {
419
+ return this.context.claimResourceUpdates(workspaceId, conversationScopeId);
1335
420
  }
1336
- catch {
1337
- return undefined;
421
+ acknowledgeResourceUpdates(workspaceId, conversationScopeId) {
422
+ this.context.acknowledgeResourceUpdates(workspaceId, conversationScopeId);
1338
423
  }
1339
424
  }
1340
- function isErrnoException(error) {
1341
- return error instanceof Error && "code" in error;
1342
- }
425
+ export { formatAgentsPath };
426
+ export { ensureCheckoutWorkspaceRoot } from "./workspaces/paths.js";