@agimon-ai/doompi-git 0.0.1-alpha.4 → 0.0.1-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +2 -0
  2. package/dist/api-contracts.cjs +1 -0
  3. package/dist/api-contracts.d.cts +2 -0
  4. package/dist/api-contracts.d.mts +2 -0
  5. package/dist/api-contracts.mjs +1 -0
  6. package/dist/constants/git.cjs +2 -0
  7. package/dist/constants/git.cjs.map +1 -0
  8. package/dist/constants/git.mjs +2 -0
  9. package/dist/constants/git.mjs.map +1 -0
  10. package/dist/controllers/gitCommand.cjs +2 -0
  11. package/dist/controllers/gitCommand.cjs.map +1 -0
  12. package/dist/controllers/gitCommand.mjs +2 -0
  13. package/dist/controllers/gitCommand.mjs.map +1 -0
  14. package/dist/controllers/gitSession.cjs +2 -0
  15. package/dist/controllers/gitSession.cjs.map +1 -0
  16. package/dist/controllers/gitSession.mjs +2 -0
  17. package/dist/controllers/gitSession.mjs.map +1 -0
  18. package/dist/controllers/hubApi.cjs +2 -0
  19. package/dist/controllers/hubApi.cjs.map +1 -0
  20. package/dist/controllers/hubApi.mjs +2 -0
  21. package/dist/controllers/hubApi.mjs.map +1 -0
  22. package/dist/controllers/worktreesChannel.cjs +2 -0
  23. package/dist/controllers/worktreesChannel.cjs.map +1 -0
  24. package/dist/controllers/worktreesChannel.mjs +2 -0
  25. package/dist/controllers/worktreesChannel.mjs.map +1 -0
  26. package/dist/extensions/pi.cjs +2 -1
  27. package/dist/extensions/pi.cjs.map +1 -0
  28. package/dist/extensions/pi.d.cts +6 -2
  29. package/dist/extensions/pi.d.cts.map +1 -0
  30. package/dist/extensions/pi.d.mts +6 -2
  31. package/dist/extensions/pi.d.mts.map +1 -0
  32. package/dist/extensions/pi.mjs +2 -1
  33. package/dist/extensions/pi.mjs.map +1 -0
  34. package/dist/extensions/server.cjs +2 -0
  35. package/dist/extensions/server.cjs.map +1 -0
  36. package/dist/extensions/server.d.cts +5 -0
  37. package/dist/extensions/server.d.cts.map +1 -0
  38. package/dist/extensions/server.d.mts +5 -0
  39. package/dist/extensions/server.d.mts.map +1 -0
  40. package/dist/extensions/server.mjs +2 -0
  41. package/dist/extensions/server.mjs.map +1 -0
  42. package/dist/index.cjs +1 -1
  43. package/dist/index.d.cts +3 -4
  44. package/dist/index.d.mts +3 -4
  45. package/dist/index.mjs +1 -1
  46. package/dist/schemas/apiContracts.cjs +2 -0
  47. package/dist/schemas/apiContracts.cjs.map +1 -0
  48. package/dist/schemas/apiContracts.d.cts +53 -0
  49. package/dist/schemas/apiContracts.d.cts.map +1 -0
  50. package/dist/schemas/apiContracts.d.mts +53 -0
  51. package/dist/schemas/apiContracts.d.mts.map +1 -0
  52. package/dist/schemas/apiContracts.mjs +2 -0
  53. package/dist/schemas/apiContracts.mjs.map +1 -0
  54. package/dist/{adapters/filesystem/atomicJson.cjs → services/atomicJson/index.cjs} +1 -1
  55. package/dist/services/atomicJson/index.cjs.map +1 -0
  56. package/dist/{adapters/filesystem/atomicJson.mjs → services/atomicJson/index.mjs} +1 -1
  57. package/dist/services/atomicJson/index.mjs.map +1 -0
  58. package/dist/services/compositionMirror/index.cjs +2 -0
  59. package/dist/services/compositionMirror/index.cjs.map +1 -0
  60. package/dist/services/compositionMirror/index.mjs +2 -0
  61. package/dist/services/compositionMirror/index.mjs.map +1 -0
  62. package/dist/services/errors/index.cjs +2 -0
  63. package/dist/services/errors/index.cjs.map +1 -0
  64. package/dist/services/errors/index.mjs +2 -0
  65. package/dist/services/errors/index.mjs.map +1 -0
  66. package/dist/services/{extensionService.cjs → extensionService/index.cjs} +1 -1
  67. package/dist/services/extensionService/index.cjs.map +1 -0
  68. package/dist/services/extensionService/index.d.cts +8 -0
  69. package/dist/services/extensionService/index.d.cts.map +1 -0
  70. package/dist/services/extensionService/index.d.mts +8 -0
  71. package/dist/services/extensionService/index.d.mts.map +1 -0
  72. package/dist/services/{extensionService.mjs → extensionService/index.mjs} +1 -1
  73. package/dist/services/extensionService/index.mjs.map +1 -0
  74. package/dist/{adapters/worktree/gitCli.cjs → services/gitCli/index.cjs} +2 -2
  75. package/dist/services/gitCli/index.cjs.map +1 -0
  76. package/dist/{adapters/worktree/gitCli.mjs → services/gitCli/index.mjs} +2 -2
  77. package/dist/services/gitCli/index.mjs.map +1 -0
  78. package/dist/services/gitDependencies/index.cjs +2 -0
  79. package/dist/services/gitDependencies/index.cjs.map +1 -0
  80. package/dist/services/gitDependencies/index.d.cts +6 -0
  81. package/dist/services/gitDependencies/index.d.cts.map +1 -0
  82. package/dist/services/gitDependencies/index.d.mts +6 -0
  83. package/dist/services/gitDependencies/index.d.mts.map +1 -0
  84. package/dist/services/gitDependencies/index.mjs +2 -0
  85. package/dist/services/gitDependencies/index.mjs.map +1 -0
  86. package/dist/services/paths/index.cjs +2 -0
  87. package/dist/services/paths/index.cjs.map +1 -0
  88. package/dist/services/paths/index.mjs +2 -0
  89. package/dist/services/paths/index.mjs.map +1 -0
  90. package/dist/{adapters/worktree/repositoryIdentity.cjs → services/repositoryIdentity/index.cjs} +1 -1
  91. package/dist/services/repositoryIdentity/index.cjs.map +1 -0
  92. package/dist/{adapters/worktree/repositoryIdentity.mjs → services/repositoryIdentity/index.mjs} +1 -1
  93. package/dist/services/repositoryIdentity/index.mjs.map +1 -0
  94. package/dist/services/runWorktree/index.cjs +15 -0
  95. package/dist/services/runWorktree/index.cjs.map +1 -0
  96. package/dist/services/runWorktree/index.mjs +15 -0
  97. package/dist/services/runWorktree/index.mjs.map +1 -0
  98. package/dist/services/worktreeEvents/index.cjs +2 -0
  99. package/dist/services/worktreeEvents/index.cjs.map +1 -0
  100. package/dist/services/worktreeEvents/index.d.cts +15 -0
  101. package/dist/services/worktreeEvents/index.d.cts.map +1 -0
  102. package/dist/services/worktreeEvents/index.d.mts +15 -0
  103. package/dist/services/worktreeEvents/index.d.mts.map +1 -0
  104. package/dist/services/worktreeEvents/index.mjs +2 -0
  105. package/dist/services/worktreeEvents/index.mjs.map +1 -0
  106. package/dist/services/{worktreeNaming.cjs → worktreeNaming/index.cjs} +2 -2
  107. package/dist/services/{worktreeNaming.cjs.map → worktreeNaming/index.cjs.map} +1 -1
  108. package/dist/services/{worktreeNaming.d.mts → worktreeNaming/index.d.cts} +10 -3
  109. package/dist/services/worktreeNaming/index.d.cts.map +1 -0
  110. package/dist/services/{worktreeNaming.d.cts → worktreeNaming/index.d.mts} +10 -3
  111. package/dist/services/worktreeNaming/index.d.mts.map +1 -0
  112. package/dist/services/{worktreeNaming.mjs → worktreeNaming/index.mjs} +2 -2
  113. package/dist/services/{worktreeNaming.mjs.map → worktreeNaming/index.mjs.map} +1 -1
  114. package/dist/services/worktreeOperations/index.cjs +2 -0
  115. package/dist/services/worktreeOperations/index.cjs.map +1 -0
  116. package/dist/{adapters/worktree/worktreeOperations.d.cts → services/worktreeOperations/index.d.cts} +14 -8
  117. package/dist/services/worktreeOperations/index.d.cts.map +1 -0
  118. package/dist/{adapters/worktree/worktreeOperations.d.mts → services/worktreeOperations/index.d.mts} +14 -8
  119. package/dist/services/worktreeOperations/index.d.mts.map +1 -0
  120. package/dist/services/worktreeOperations/index.mjs +2 -0
  121. package/dist/services/worktreeOperations/index.mjs.map +1 -0
  122. package/dist/services/worktreeRegistry/index.cjs +2 -0
  123. package/dist/services/worktreeRegistry/index.cjs.map +1 -0
  124. package/dist/services/worktreeRegistry/index.mjs +2 -0
  125. package/dist/services/worktreeRegistry/index.mjs.map +1 -0
  126. package/dist/tools/runWorktree.cjs +2 -0
  127. package/dist/tools/runWorktree.cjs.map +1 -0
  128. package/dist/tools/runWorktree.mjs +2 -0
  129. package/dist/tools/runWorktree.mjs.map +1 -0
  130. package/dist/types/extension.d.cts +1 -1
  131. package/dist/types/extension.d.mts +1 -1
  132. package/package.json +27 -21
  133. package/src/extensions/web.ts +33 -0
  134. package/src/prompts/doompi-use-git/SKILL.md +5 -2
  135. package/src/types/worktreeRegistry.ts +9 -0
  136. package/src/web/components/RunWorktreeToolMessage.tsx +21 -13
  137. package/src/web/components/WorktreesActivitySection.tsx +29 -13
  138. package/src/web/components/WorktreesPanel.tsx +98 -48
  139. package/src/web/components/toolRenderers.ts +3 -2
  140. package/src/web/stores/worktreesActivityStore.ts +3 -2
  141. package/src/web/tsconfig.json +1 -1
  142. package/dist/adapters/channel/worktreeChannel.cjs +0 -2
  143. package/dist/adapters/channel/worktreeChannel.cjs.map +0 -1
  144. package/dist/adapters/channel/worktreeChannel.d.cts +0 -40
  145. package/dist/adapters/channel/worktreeChannel.d.cts.map +0 -1
  146. package/dist/adapters/channel/worktreeChannel.d.mts +0 -40
  147. package/dist/adapters/channel/worktreeChannel.d.mts.map +0 -1
  148. package/dist/adapters/channel/worktreeChannel.mjs +0 -2
  149. package/dist/adapters/channel/worktreeChannel.mjs.map +0 -1
  150. package/dist/adapters/filesystem/atomicJson.cjs.map +0 -1
  151. package/dist/adapters/filesystem/atomicJson.mjs.map +0 -1
  152. package/dist/adapters/filesystem/paths.cjs +0 -2
  153. package/dist/adapters/filesystem/paths.cjs.map +0 -1
  154. package/dist/adapters/filesystem/paths.mjs +0 -2
  155. package/dist/adapters/filesystem/paths.mjs.map +0 -1
  156. package/dist/adapters/hub/hubClient.cjs +0 -2
  157. package/dist/adapters/hub/hubClient.cjs.map +0 -1
  158. package/dist/adapters/hub/hubClient.d.cts +0 -1
  159. package/dist/adapters/hub/hubClient.d.mts +0 -1
  160. package/dist/adapters/hub/hubClient.mjs +0 -2
  161. package/dist/adapters/hub/hubClient.mjs.map +0 -1
  162. package/dist/adapters/pi/extension.cjs +0 -2
  163. package/dist/adapters/pi/extension.cjs.map +0 -1
  164. package/dist/adapters/pi/extension.d.cts +0 -9
  165. package/dist/adapters/pi/extension.d.cts.map +0 -1
  166. package/dist/adapters/pi/extension.d.mts +0 -9
  167. package/dist/adapters/pi/extension.d.mts.map +0 -1
  168. package/dist/adapters/pi/extension.mjs +0 -2
  169. package/dist/adapters/pi/extension.mjs.map +0 -1
  170. package/dist/adapters/pi/extensions/runWorktreeTool.cjs +0 -15
  171. package/dist/adapters/pi/extensions/runWorktreeTool.cjs.map +0 -1
  172. package/dist/adapters/pi/extensions/runWorktreeTool.mjs +0 -15
  173. package/dist/adapters/pi/extensions/runWorktreeTool.mjs.map +0 -1
  174. package/dist/adapters/web/worktreesChannel.cjs +0 -2
  175. package/dist/adapters/web/worktreesChannel.cjs.map +0 -1
  176. package/dist/adapters/web/worktreesChannel.d.cts +0 -21
  177. package/dist/adapters/web/worktreesChannel.d.cts.map +0 -1
  178. package/dist/adapters/web/worktreesChannel.d.mts +0 -21
  179. package/dist/adapters/web/worktreesChannel.d.mts.map +0 -1
  180. package/dist/adapters/web/worktreesChannel.mjs +0 -2
  181. package/dist/adapters/web/worktreesChannel.mjs.map +0 -1
  182. package/dist/adapters/worktree/dependencyInstall.cjs +0 -2
  183. package/dist/adapters/worktree/dependencyInstall.cjs.map +0 -1
  184. package/dist/adapters/worktree/dependencyInstall.mjs +0 -2
  185. package/dist/adapters/worktree/dependencyInstall.mjs.map +0 -1
  186. package/dist/adapters/worktree/gitCli.cjs.map +0 -1
  187. package/dist/adapters/worktree/gitCli.mjs.map +0 -1
  188. package/dist/adapters/worktree/repositoryIdentity.cjs.map +0 -1
  189. package/dist/adapters/worktree/repositoryIdentity.mjs.map +0 -1
  190. package/dist/adapters/worktree/worktreeOperations.cjs +0 -2
  191. package/dist/adapters/worktree/worktreeOperations.cjs.map +0 -1
  192. package/dist/adapters/worktree/worktreeOperations.d.cts.map +0 -1
  193. package/dist/adapters/worktree/worktreeOperations.d.mts.map +0 -1
  194. package/dist/adapters/worktree/worktreeOperations.mjs +0 -2
  195. package/dist/adapters/worktree/worktreeOperations.mjs.map +0 -1
  196. package/dist/adapters/worktree/worktreeRegistry.cjs +0 -2
  197. package/dist/adapters/worktree/worktreeRegistry.cjs.map +0 -1
  198. package/dist/adapters/worktree/worktreeRegistry.mjs +0 -2
  199. package/dist/adapters/worktree/worktreeRegistry.mjs.map +0 -1
  200. package/dist/commands/doomGitCommand.cjs +0 -2
  201. package/dist/commands/doomGitCommand.cjs.map +0 -1
  202. package/dist/commands/doomGitCommand.mjs +0 -2
  203. package/dist/commands/doomGitCommand.mjs.map +0 -1
  204. package/dist/container/index.cjs +0 -2
  205. package/dist/container/index.cjs.map +0 -1
  206. package/dist/container/index.d.cts +0 -6
  207. package/dist/container/index.d.cts.map +0 -1
  208. package/dist/container/index.d.mts +0 -6
  209. package/dist/container/index.d.mts.map +0 -1
  210. package/dist/container/index.mjs +0 -2
  211. package/dist/container/index.mjs.map +0 -1
  212. package/dist/hubApi.cjs +0 -2
  213. package/dist/hubApi.cjs.map +0 -1
  214. package/dist/hubApi.d.cts +0 -6
  215. package/dist/hubApi.d.cts.map +0 -1
  216. package/dist/hubApi.d.mts +0 -6
  217. package/dist/hubApi.d.mts.map +0 -1
  218. package/dist/hubApi.mjs +0 -2
  219. package/dist/hubApi.mjs.map +0 -1
  220. package/dist/services/extensionService.cjs.map +0 -1
  221. package/dist/services/extensionService.d.cts +0 -8
  222. package/dist/services/extensionService.d.cts.map +0 -1
  223. package/dist/services/extensionService.d.mts +0 -8
  224. package/dist/services/extensionService.d.mts.map +0 -1
  225. package/dist/services/extensionService.mjs.map +0 -1
  226. package/dist/services/support/errors.cjs +0 -2
  227. package/dist/services/support/errors.cjs.map +0 -1
  228. package/dist/services/support/errors.mjs +0 -2
  229. package/dist/services/support/errors.mjs.map +0 -1
  230. package/dist/services/worktreeNaming.d.cts.map +0 -1
  231. package/dist/services/worktreeNaming.d.mts.map +0 -1
  232. package/dist/webHub.cjs +0 -1
  233. package/dist/webHub.d.cts +0 -2
  234. package/dist/webHub.d.mts +0 -2
  235. package/dist/webHub.mjs +0 -1
  236. package/src/exports/webClient.ts +0 -1
  237. package/src/web/index.ts +0 -30
@@ -1 +0,0 @@
1
- {"version":3,"file":"hubClient.mjs","names":[],"sources":["../../../src/adapters/hub/hubClient.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { hubAdvertisementPath, parseHubAdvertisement } from '@agimon-ai/doompi-web-contracts';\nimport type { HubAdvertisement } from '@agimon-ai/doompi-web-contracts';\n\nconst HEALTH_ROUTE = '/api/health';\nconst SESSIONS_ROUTE = '/api/sessions';\nconst PROBE_TIMEOUT_MS = 1000;\n/**\n * Creating a session is not a quick call.\n *\n * The hub validates, then runs `ensureSessionSynced`, then spawns. A cockpit\n * that has never synced the composition builds it first, which is minutes on a\n * cold cache, and a fresh worktree also installs its dependencies. A 30s\n * timeout was measured aborting a create that the hub then completed anyway,\n * leaving the caller told it failed while a session started behind it.\n */\nconst CREATE_TIMEOUT_MS = 600_000;\n/** Stopping is just a signal and a record removal, so it stays short. */\nconst STOP_TIMEOUT_MS = 30_000;\nconst HUB_ROLE = 'hub';\n\n/** Raised when the cockpit that owns session lifecycle is not reachable. */\nexport class HubUnavailableError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'HubUnavailableError';\n }\n}\n\n/**\n * Whether the hub still has a record for a session.\n *\n * The session server writes its record into the shared registry directory and\n * removes it on exit, so the file's presence is the liveness signal. A pid is\n * not available here: the hub spawns the process, so this package never learns\n * one, and a pid it invented would be a field that always lies.\n */\nexport function sessionIsLive(registryDir: string, sessionId: string): boolean {\n return fs.existsSync(`${registryDir}/sessions/${sessionId}.json`);\n}\n\n/**\n * Asks the hub to stop a session it started.\n *\n * Stopping is the hub's job for the same reason starting is. Signalling the\n * process directly would leave the hub's own record of it behind and race the\n * cleanup it does on the way down.\n *\n * A session the hub does not know about is already stopped, which is the\n * outcome the caller wanted, so a 404 is success.\n */\nexport async function stopWorktreeSession(registryDir: string, sessionId: string): Promise<void> {\n const advertisement = readHubAdvertisement(registryDir);\n if (advertisement === undefined) return;\n if (!(await hubAnswers(advertisement.url))) return;\n try {\n const response = await fetch(`${advertisement.url}${SESSIONS_ROUTE}/${encodeURIComponent(sessionId)}`, {\n method: 'DELETE',\n signal: AbortSignal.timeout(STOP_TIMEOUT_MS),\n });\n if (!response.ok && response.status !== 404) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n throw new HubUnavailableError(\n body.error ?? `The cockpit could not stop the session (${String(response.status)}).`,\n );\n }\n } catch (error) {\n if (error instanceof HubUnavailableError) throw error;\n throw new HubUnavailableError(`Could not reach the cockpit: ${(error as Error).message}`);\n }\n}\n\nexport interface CreateWorktreeSessionInput {\n /** The worktree directory; must already exist. */\n cwd: string;\n /** Rail display name for the new session. */\n name: string;\n /** The session asking for this one, so the rail can nest them. */\n parentSessionId: string;\n registryDir: string;\n}\n\n/** Reads the hub's published address, if it published one. */\nexport function readHubAdvertisement(registryDir: string): HubAdvertisement | undefined {\n let raw: string;\n try {\n raw = fs.readFileSync(hubAdvertisementPath(registryDir), 'utf8');\n } catch {\n return undefined;\n }\n return parseHubAdvertisement(raw);\n}\n\n/**\n * Confirms a live cockpit hub answers at the address, rather than a leftover.\n *\n * The advertisement file outlives a crash and a port gets reused, so finding it\n * proves nothing. Only a hub that answers /api/health and calls itself a hub is\n * worth sending a session-create to; anything else is something else's port.\n */\nasync function hubAnswers(url: string): Promise<boolean> {\n try {\n const response = await fetch(`${url}${HEALTH_ROUTE}`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });\n if (!response.ok) return false;\n const body = (await response.json()) as { ok?: boolean; role?: string };\n return body.ok === true && body.role === HUB_ROLE;\n } catch {\n return false;\n }\n}\n\n/**\n * Asks the cockpit hub to start a session in a worktree.\n *\n * The hub owns session lifecycle: it validates the directory, syncs the\n * composition, spawns the server and records the lineage. This package only\n * asks. Creating the process here instead would put a second spawn path beside\n * the hub's, with its own idea of what a session needs, and a session server is\n * bound to one session anyway so it has no business starting another.\n *\n * The panel and the agent both come through here, so there is one route to keep\n * correct rather than two that drift.\n */\nexport async function createWorktreeSession(input: CreateWorktreeSessionInput): Promise<string> {\n const advertisement = readHubAdvertisement(input.registryDir);\n if (advertisement === undefined) {\n throw new HubUnavailableError(\n 'No cockpit is running. Worktree sessions are created by the cockpit, so start it and try again.',\n );\n }\n if (!(await hubAnswers(advertisement.url))) {\n throw new HubUnavailableError(\n `The cockpit that advertised ${advertisement.url} is no longer answering. Start it and try again.`,\n );\n }\n\n let response: Response;\n try {\n response = await fetch(`${advertisement.url}${SESSIONS_ROUTE}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n cwd: input.cwd,\n name: input.name,\n parentSessionId: input.parentSessionId,\n provenance: 'worktree',\n }),\n signal: AbortSignal.timeout(CREATE_TIMEOUT_MS),\n });\n } catch (error) {\n throw new HubUnavailableError(`Could not reach the cockpit: ${(error as Error).message}`);\n }\n\n const body = (await response.json().catch(() => ({}))) as { sessionId?: string; error?: string };\n if (!response.ok) {\n throw new HubUnavailableError(body.error ?? `The cockpit refused the session (${String(response.status)}).`);\n }\n if (typeof body.sessionId !== 'string' || body.sessionId === '') {\n throw new HubUnavailableError('The cockpit accepted the session but did not name it.');\n }\n return body.sessionId;\n}\n"],"mappings":"yHAIA,MACM,EAAiB,gBAiBvB,IAAa,EAAb,cAAyC,KAAM,CAC7C,YAAY,EAAiB,CAC3B,MAAM,CAAO,EACb,KAAK,KAAO,qBACd,CACF,EAUA,SAAgB,EAAc,EAAqB,EAA4B,CAC7E,OAAO,EAAG,WAAW,GAAG,EAAY,YAAY,EAAU,MAAM,CAClE,CAYA,eAAsB,EAAoB,EAAqB,EAAkC,CAC/F,IAAM,EAAgB,EAAqB,CAAW,EAClD,OAAkB,IAAA,IAChB,MAAM,EAAW,EAAc,GAAG,EACxC,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,GAAG,EAAc,MAAM,EAAe,GAAG,mBAAmB,CAAS,IAAK,CACrG,OAAQ,SACR,OAAQ,YAAY,QAAQ,GAAe,CAC7C,CAAC,EACD,GAAI,CAAC,EAAS,IAAM,EAAS,SAAW,IAEtC,MAAM,IAAI,GACR,MAFkB,EAAS,KAAK,CAAC,CAAC,WAAa,CAAC,EAAE,EAElD,CAAK,OAAS,2CAA2C,OAAO,EAAS,MAAM,EAAE,GACnF,CAEJ,OAAS,EAAO,CAEd,MADI,aAAiB,EAA2B,EAC1C,IAAI,EAAoB,gCAAiC,EAAgB,SAAS,CAC1F,CACF,CAaA,SAAgB,EAAqB,EAAmD,CACtF,IAAI,EACJ,GAAI,CACF,EAAM,EAAG,aAAa,EAAqB,CAAW,EAAG,MAAM,CACjE,MAAQ,CACN,MACF,CACA,OAAO,EAAsB,CAAG,CAClC,CASA,eAAe,EAAW,EAA+B,CACvD,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,GAAG,eAAsB,CAAE,OAAQ,YAAY,QAAQ,GAAgB,CAAE,CAAC,EACvG,GAAI,CAAC,EAAS,GAAI,MAAO,GACzB,IAAM,EAAQ,MAAM,EAAS,KAAK,EAClC,OAAO,EAAK,KAAO,IAAQ,EAAK,OAAS,KAC3C,MAAQ,CACN,MAAO,EACT,CACF,CAcA,eAAsB,EAAsB,EAAoD,CAC9F,IAAM,EAAgB,EAAqB,EAAM,WAAW,EAC5D,GAAI,IAAkB,IAAA,GACpB,MAAM,IAAI,EACR,iGACF,EAEF,GAAI,CAAE,MAAM,EAAW,EAAc,GAAG,EACtC,MAAM,IAAI,EACR,+BAA+B,EAAc,IAAI,iDACnD,EAGF,IAAI,EACJ,GAAI,CACF,EAAW,MAAM,MAAM,GAAG,EAAc,MAAM,IAAkB,CAC9D,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CACnB,IAAK,EAAM,IACX,KAAM,EAAM,KACZ,gBAAiB,EAAM,gBACvB,WAAY,UACd,CAAC,EACD,OAAQ,YAAY,QAAQ,GAAiB,CAC/C,CAAC,CACH,OAAS,EAAO,CACd,MAAM,IAAI,EAAoB,gCAAiC,EAAgB,SAAS,CAC1F,CAEA,IAAM,EAAQ,MAAM,EAAS,KAAK,CAAC,CAAC,WAAa,CAAC,EAAE,EACpD,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EAAoB,EAAK,OAAS,oCAAoC,OAAO,EAAS,MAAM,EAAE,GAAG,EAE7G,GAAI,OAAO,EAAK,WAAc,UAAY,EAAK,YAAc,GAC3D,MAAM,IAAI,EAAoB,uDAAuD,EAEvF,OAAO,EAAK,SACd"}
@@ -1,2 +0,0 @@
1
- const e=require("../../commands/doomGitCommand.cjs"),t=require("./extensions/runWorktreeTool.cjs"),n=require("../../container/index.cjs");let r=require("@agimon-ai/doompi-extension-contracts/cordis-host"),i=require("@agimon-ai/doompi-extension-contracts/help");const a=`@agimon-ai/doompi-git`;function o(r,o,s=n.createGitContainer()){r.inject([i.DOOM_HELP_SERVICE],e=>{let t=(0,i.requireDoomHelpService)(e).register({source:a,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-use-git`,description:`Use @agimon-ai/doompi-git: Git worktree sessions for DoomPi: spawn an isolated worktree with its own session and manage it from the parent`}]});return()=>t.dispose()}),e.registerGitCommand(o,s.service),t.registerRunWorktreeTool(o,s.operations)}function s(e,t){o(e,t.pi,t.dependencies)}async function c(e,t={}){let i=n.createGitContainer(t),o=await(0,r.connectDoomCordisHost)(e,a),c=o.root.plugin(s,{pi:e,dependencies:i});try{await c}catch(e){try{await c.dispose()}finally{await o.dispose()}throw e}let l;e.on(`session_shutdown`,()=>l??=(async()=>{try{await c.dispose()}finally{await o.dispose()}})())}exports.activateGitExtension=c,exports.default=c,exports.installGitRuntime=o;
2
- //# sourceMappingURL=extension.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.cjs","names":["createGitContainer","DOOM_HELP_SERVICE","requireDoomHelpService","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["// @scaffold-generated\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerGitCommand } from '../../commands/doomGitCommand.ts';\nimport { registerRunWorktreeTool } from './extensions/runWorktreeTool.ts';\nimport { createGitContainer } from '../../container';\nimport type { GitExtensionDependencies } from '../../types/extension';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-git';\n\n/**\n * Installs the package runtime inside its host-owned Cordis plugin fiber.\n *\n * Everything registered inside the effect is owned by the cordis fiber, so one\n * dispose on session shutdown unwinds it in reverse order. Yield a disposer for\n * each registration rather than tracking cleanup by hand; Pi can reload an\n * extension in-process, and a leaked listener duplicates work against stale\n * session state.\n */\nexport function installGitRuntime(\n cordis: Context,\n pi: ExtensionAPI,\n dependencies: GitExtensionDependencies = createGitContainer(),\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-git',\n description:\n 'Use @agimon-ai/doompi-git: Git worktree sessions for DoomPi: spawn an isolated worktree with its own session and manage it from the parent',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n registerGitCommand(pi, dependencies.service);\n registerRunWorktreeTool(pi, dependencies.operations);\n}\n\ninterface GitPluginConfig {\n readonly pi: ExtensionAPI;\n readonly dependencies: GitExtensionDependencies;\n}\n\nfunction gitPlugin(cordis: Context, config: GitPluginConfig): void {\n installGitRuntime(cordis, config.pi, config.dependencies);\n}\n\n/** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */\nexport async function activateGitExtension(\n pi: ExtensionAPI,\n // Partial so a caller, and every test, can stand in one collaborator without\n // having to construct the rest of the graph it does not care about.\n overrides: Partial<GitExtensionDependencies> = {},\n): Promise<void> {\n const dependencies = createGitContainer(overrides);\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(gitPlugin, { pi, dependencies });\n\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default activateGitExtension;\n"],"mappings":"qQAUA,MAAM,EAAiB,wBAWvB,SAAgB,EACd,EACA,EACA,EAAyCA,EAAAA,mBAAmB,EACtD,CACN,EAAO,OAAO,CAACC,EAAAA,iBAAiB,EAAI,GAAgB,CAClD,IAAM,GAAA,EAAeC,EAAAA,uBAAAA,CAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,KACA,OAAQ,CACN,CACE,KAAM,iBACN,YACE,4IACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,EAAA,mBAAmB,EAAI,EAAa,OAAO,EAC3C,EAAA,wBAAwB,EAAI,EAAa,UAAU,CACrD,CAOA,SAAS,EAAU,EAAiB,EAA+B,CACjE,EAAkB,EAAQ,EAAO,GAAI,EAAO,YAAY,CAC1D,CAGA,eAAsB,EACpB,EAGA,EAA+C,CAAC,EACjC,CACf,IAAM,EAAeF,EAAAA,mBAAmB,CAAS,EAC3C,EAAa,MAAA,EAAMG,EAAAA,sBAAAA,CAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAW,CAAE,KAAI,cAAa,CAAC,EAEpE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CAEA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
@@ -1,9 +0,0 @@
1
- import { GitExtensionDependencies } from "../../types/extension.cjs";
2
- import "@deepseek-ai/cordis";
3
- import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
- //#region src/adapters/pi/extension.d.ts
5
- /** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */
6
- declare function activateGitExtension(pi: ExtensionAPI, overrides?: Partial<GitExtensionDependencies>): Promise<void>;
7
- //#endregion
8
- export { activateGitExtension, activateGitExtension as default };
9
- //# sourceMappingURL=extension.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;iBAuDsB,qBACpB,IAAI,cAGJ,YAAW,QAAQ,4BAClB"}
@@ -1,9 +0,0 @@
1
- import { GitExtensionDependencies } from "../../types/extension.mjs";
2
- import "@deepseek-ai/cordis";
3
- import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
- //#region src/adapters/pi/extension.d.ts
5
- /** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */
6
- declare function activateGitExtension(pi: ExtensionAPI, overrides?: Partial<GitExtensionDependencies>): Promise<void>;
7
- //#endregion
8
- export { activateGitExtension, activateGitExtension as default };
9
- //# sourceMappingURL=extension.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;iBAuDsB,qBACpB,IAAI,cAGJ,YAAW,QAAQ,4BAClB"}
@@ -1,2 +0,0 @@
1
- import{registerGitCommand as e}from"../../commands/doomGitCommand.mjs";import{registerRunWorktreeTool as t}from"./extensions/runWorktreeTool.mjs";import{createGitContainer as n}from"../../container/index.mjs";import{connectDoomCordisHost as r}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as i,requireDoomHelpService as a}from"@agimon-ai/doompi-extension-contracts/help";const o=`@agimon-ai/doompi-git`;function s(r,s,c=n()){r.inject([i],e=>{let t=a(e).register({source:o,moduleUrl:import.meta.url,skills:[{name:`doompi-use-git`,description:`Use @agimon-ai/doompi-git: Git worktree sessions for DoomPi: spawn an isolated worktree with its own session and manage it from the parent`}]});return()=>t.dispose()}),e(s,c.service),t(s,c.operations)}function c(e,t){s(e,t.pi,t.dependencies)}async function l(e,t={}){let i=n(t),a=await r(e,o),s=a.root.plugin(c,{pi:e,dependencies:i});try{await s}catch(e){try{await s.dispose()}finally{await a.dispose()}throw e}let l;e.on(`session_shutdown`,()=>l??=(async()=>{try{await s.dispose()}finally{await a.dispose()}})())}export{l as activateGitExtension,l as default,s as installGitRuntime};
2
- //# sourceMappingURL=extension.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["// @scaffold-generated\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerGitCommand } from '../../commands/doomGitCommand.ts';\nimport { registerRunWorktreeTool } from './extensions/runWorktreeTool.ts';\nimport { createGitContainer } from '../../container';\nimport type { GitExtensionDependencies } from '../../types/extension';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-git';\n\n/**\n * Installs the package runtime inside its host-owned Cordis plugin fiber.\n *\n * Everything registered inside the effect is owned by the cordis fiber, so one\n * dispose on session shutdown unwinds it in reverse order. Yield a disposer for\n * each registration rather than tracking cleanup by hand; Pi can reload an\n * extension in-process, and a leaked listener duplicates work against stale\n * session state.\n */\nexport function installGitRuntime(\n cordis: Context,\n pi: ExtensionAPI,\n dependencies: GitExtensionDependencies = createGitContainer(),\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-git',\n description:\n 'Use @agimon-ai/doompi-git: Git worktree sessions for DoomPi: spawn an isolated worktree with its own session and manage it from the parent',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n registerGitCommand(pi, dependencies.service);\n registerRunWorktreeTool(pi, dependencies.operations);\n}\n\ninterface GitPluginConfig {\n readonly pi: ExtensionAPI;\n readonly dependencies: GitExtensionDependencies;\n}\n\nfunction gitPlugin(cordis: Context, config: GitPluginConfig): void {\n installGitRuntime(cordis, config.pi, config.dependencies);\n}\n\n/** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */\nexport async function activateGitExtension(\n pi: ExtensionAPI,\n // Partial so a caller, and every test, can stand in one collaborator without\n // having to construct the rest of the graph it does not care about.\n overrides: Partial<GitExtensionDependencies> = {},\n): Promise<void> {\n const dependencies = createGitContainer(overrides);\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(gitPlugin, { pi, dependencies });\n\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default activateGitExtension;\n"],"mappings":"sZAUA,MAAM,EAAiB,wBAWvB,SAAgB,EACd,EACA,EACA,EAAyC,EAAmB,EACtD,CACN,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAe,EAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,iBACN,YACE,4IACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,EAAmB,EAAI,EAAa,OAAO,EAC3C,EAAwB,EAAI,EAAa,UAAU,CACrD,CAOA,SAAS,EAAU,EAAiB,EAA+B,CACjE,EAAkB,EAAQ,EAAO,GAAI,EAAO,YAAY,CAC1D,CAGA,eAAsB,EACpB,EAGA,EAA+C,CAAC,EACjC,CACf,IAAM,EAAe,EAAmB,CAAS,EAC3C,EAAa,MAAM,EAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAW,CAAE,KAAI,cAAa,CAAC,EAEpE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CAEA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
@@ -1,15 +0,0 @@
1
- const e=require("../../../services/support/errors.cjs"),t=require("../../../schemas/runWorktreeTool.cjs"),n=`run_worktree`,r=new Set(Object.values(t.WORKTREE_ACTIONS));function i(n){if(typeof n!=`object`||!n||Array.isArray(n))throw e.invalidRequest(`run_worktree parameters must be an object.`,`Call it with one documented action shape.`);let i=n,a=i.action;if(typeof a!=`string`||!r.has(a))throw e.invalidRequest(`Unknown action ${typeof a==`string`?`'${a}'`:`value`}.`,`Use one of: ${[...r].join(`, `)}.`);let o=t.WORKTREE_ACTION_FIELDS[a],s=Object.keys(i).filter(e=>!o.includes(e));if(s.length>0)throw e.invalidRequest(`action='${a}' does not accept: ${s.join(`, `)}.`,`Fields for '${a}': ${o.join(`, `)}.`);for(let t of[`branch`,`id`])if(o.includes(t)&&t in i){let n=i[t];if(typeof n!=`string`||n.trim()===``)throw e.invalidRequest(`action='${a}' requires a nonblank '${t}'.`,`Pass a ${t}.`)}if(a===t.WORKTREE_ACTIONS.spawn_worktree&&typeof i.branch!=`string`)throw e.invalidRequest(`action='spawn_worktree' requires 'branch'.`,`Name the branch to create.`);if((a===t.WORKTREE_ACTIONS.close_worktree||a===t.WORKTREE_ACTIONS.status||a===t.WORKTREE_ACTIONS.merge)&&typeof i.id!=`string`)throw e.invalidRequest(`action='${a}' requires 'id'.`,`Call list to see the current ids.`);return i}function a(e){return`${e.id} ${e.branch} ${e.status} ${e.path}`}function o(...e){return[{type:`text`,text:e.join(`
2
- `)}]}async function s(e,n,r){let i={cwd:r.cwd,sessionId:r.sessionManager.getSessionId()};switch(e.action){case t.WORKTREE_ACTIONS.spawn_worktree:{let t=await n.spawn(i,{branch:e.branch,...e.baseRef===void 0?{}:{baseRef:e.baseRef},...e.name===void 0?{}:{name:e.name}});return{content:o(`Worktree ${t.id} created on ${t.branch} from ${t.baseRef}.`,`Path: ${t.path}`,`Session ${t.sessionId} is starting; it appears nested under this one in the rail.`),details:{action:e.action,records:[t]}}}case t.WORKTREE_ACTIONS.close_worktree:{let t=await n.close(i,e.id,e.force??!1);return{content:o(`Worktree ${t.id} (${t.branch}) closed and removed.`),details:{action:e.action,records:[t]}}}case t.WORKTREE_ACTIONS.list:{let t=await n.list(i);return{content:t.length===0?o(`No worktrees for this repository.`):o(`${String(t.length)} worktree(s):`,...t.map(a)),details:{action:e.action,records:t}}}case t.WORKTREE_ACTIONS.status:{let{record:t,dirtyFiles:r}=await n.status(i,e.id);return{content:o(a(t),`Session: ${t.sessionId}`,r.length===0?`Clean.`:`${String(r.length)} uncommitted file(s): ${r.slice(0,20).join(`, `)}`),details:{action:e.action,records:[t]}}}case t.WORKTREE_ACTIONS.merge:{let t=await n.merge(i,e.id,...e.message===void 0?[]:[e.message]);return{content:o(`Merged ${t.branch} into this checkout.`),details:{action:e.action,records:[t]}}}case t.WORKTREE_ACTIONS.send:return await n.send(i,e.id,e.message),{content:o(`Message sent to worktree ${e.id}.`),details:{action:e.action}};case t.WORKTREE_ACTIONS.messages:{let t=await n.messages(i,e.id);return{content:t.length===0?o(`No messages.`):o(`${String(t.length)} message(s):`,...t.map(e=>`[${e.sentAt}] ${e.from}: ${e.text}`)),details:{action:e.action}}}case t.WORKTREE_ACTIONS.prune:{let t=e.dryRun??!1,r=await n.prune(i,t);return{content:o(t?`Prune plan (nothing was changed):`:`Pruned:`,`Remove: ${r.remove.length===0?`none`:r.remove.map(e=>e.id).join(`, `)}`,`Forget: ${r.forget.length===0?`none`:r.forget.map(e=>e.id).join(`, `)}`,`Kept, uncommitted work: ${r.keptDirty.length===0?`none`:r.keptDirty.map(e=>e.id).join(`, `)}`,`Untracked directories, left alone: ${r.untracked.length===0?`none`:r.untracked.join(`, `)}`),details:{action:e.action}}}}}function c(e,r){let a={name:n,label:`Worktree`,description:`Create and manage git worktrees, each with its own DoomPi session nested under this one.
3
-
4
- Actions:
5
- - spawn_worktree: create a worktree on a new branch and start a session in it. Requires 'branch'. Optional 'baseRef' (defaults to the current branch), 'name' for the rail, 'task' as the first message.
6
- - close_worktree: stop a worktree's session and delete its directory. Requires 'id'. Refuses a dirty tree unless 'force' is true.
7
- - list: every worktree for this repository, with status.
8
- - status: one worktree and its uncommitted files. Requires 'id'.
9
- - merge: merge a worktree's branch into this checkout. Requires 'id'. Refuses when this checkout is dirty.
10
- - prune: remove orphaned worktrees. Pass 'dryRun' to see the plan without destroying anything.
11
- - send: send a message to the other side of a worktree. Requires 'id' and 'message'.
12
- - messages: read messages waiting for you from a worktree. Requires 'id'.
13
-
14
- Worktrees live outside the repository, under ~/.pi/.doom/git/worktrees.`,parameters:t.RunWorktreeParams,prepareArguments:i,execute:async(e,t,n,a,o)=>s(i(t),r,o)};e.registerTool(a)}exports.RUN_WORKTREE_TOOL_NAME=n,exports.registerRunWorktreeTool=c,exports.validateParams=i;
15
- //# sourceMappingURL=runWorktreeTool.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runWorktreeTool.cjs","names":["WORKTREE_ACTIONS","invalidRequest","WORKTREE_ACTION_FIELDS","RunWorktreeParams"],"sources":["../../../../src/adapters/pi/extensions/runWorktreeTool.ts"],"sourcesContent":["import type { AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport {\n RunWorktreeParams,\n WORKTREE_ACTION_FIELDS,\n WORKTREE_ACTIONS,\n type WorktreeAction,\n type RunWorktreeToolParams,\n} from '../../../schemas/runWorktreeTool.ts';\nimport { invalidRequest } from '../../../services/support/errors.ts';\nimport type { WorktreeOperations } from '../../worktree/worktreeOperations.ts';\nimport type { WorktreeRecord } from '../../../types/worktreeRegistry.ts';\n\nexport const RUN_WORKTREE_TOOL_NAME = 'run_worktree';\n\nconst IMPLEMENTED_ACTIONS: ReadonlySet<string> = new Set(Object.values(WORKTREE_ACTIONS));\n\nconst DESCRIPTION = `Create and manage git worktrees, each with its own DoomPi session nested under this one.\n\nActions:\n- spawn_worktree: create a worktree on a new branch and start a session in it. Requires 'branch'. Optional 'baseRef' (defaults to the current branch), 'name' for the rail, 'task' as the first message.\n- close_worktree: stop a worktree's session and delete its directory. Requires 'id'. Refuses a dirty tree unless 'force' is true.\n- list: every worktree for this repository, with status.\n- status: one worktree and its uncommitted files. Requires 'id'.\n- merge: merge a worktree's branch into this checkout. Requires 'id'. Refuses when this checkout is dirty.\n- prune: remove orphaned worktrees. Pass 'dryRun' to see the plan without destroying anything.\n- send: send a message to the other side of a worktree. Requires 'id' and 'message'.\n- messages: read messages waiting for you from a worktree. Requires 'id'.\n\nWorktrees live outside the repository, under ~/.pi/.doom/git/worktrees.`;\n\nexport interface RunWorktreeToolDetails {\n action: WorktreeAction;\n records?: WorktreeRecord[];\n}\n\n/**\n * Rejects a call that is not exactly one documented action shape.\n *\n * Unknown fields are refused rather than ignored. A caller that passes 'branch'\n * to close_worktree has a different operation in mind than the one that would\n * run, and silently dropping the field would carry it out anyway.\n */\nexport function validateParams(input: unknown): RunWorktreeToolParams {\n if (typeof input !== 'object' || input === null || Array.isArray(input)) {\n throw invalidRequest('run_worktree parameters must be an object.', 'Call it with one documented action shape.');\n }\n const record = input as Record<string, unknown>;\n const action = record.action;\n if (typeof action !== 'string' || !IMPLEMENTED_ACTIONS.has(action)) {\n throw invalidRequest(\n `Unknown action ${typeof action === 'string' ? `'${action}'` : 'value'}.`,\n `Use one of: ${[...IMPLEMENTED_ACTIONS].join(', ')}.`,\n );\n }\n const allowed = WORKTREE_ACTION_FIELDS[action as WorktreeAction];\n const unexpected = Object.keys(record).filter((key) => !allowed.includes(key));\n if (unexpected.length > 0) {\n throw invalidRequest(\n `action='${action}' does not accept: ${unexpected.join(', ')}.`,\n `Fields for '${action}': ${allowed.join(', ')}.`,\n );\n }\n for (const field of ['branch', 'id'] as const) {\n if (allowed.includes(field) && field in record) {\n const value = record[field];\n if (typeof value !== 'string' || value.trim() === '') {\n throw invalidRequest(`action='${action}' requires a nonblank '${field}'.`, `Pass a ${field}.`);\n }\n }\n }\n if (action === WORKTREE_ACTIONS.spawn_worktree && typeof record.branch !== 'string') {\n throw invalidRequest(\"action='spawn_worktree' requires 'branch'.\", 'Name the branch to create.');\n }\n if (\n (action === WORKTREE_ACTIONS.close_worktree ||\n action === WORKTREE_ACTIONS.status ||\n action === WORKTREE_ACTIONS.merge) &&\n typeof record.id !== 'string'\n ) {\n throw invalidRequest(`action='${action}' requires 'id'.`, 'Call list to see the current ids.');\n }\n return record as RunWorktreeToolParams;\n}\n\nfunction describe(record: WorktreeRecord): string {\n return `${record.id} ${record.branch} ${record.status} ${record.path}`;\n}\n\n/** A tool result's content, which Pi expects as content blocks rather than a string. */\nfunction text(...lines: readonly string[]): [{ type: 'text'; text: string }] {\n return [{ type: 'text', text: lines.join('\\n') }];\n}\n\nasync function run(\n params: RunWorktreeToolParams,\n operations: WorktreeOperations,\n ctx: ExtensionContext,\n): Promise<AgentToolResult<RunWorktreeToolDetails>> {\n const context = { cwd: ctx.cwd, sessionId: ctx.sessionManager.getSessionId() };\n switch (params.action) {\n case WORKTREE_ACTIONS.spawn_worktree: {\n const record = await operations.spawn(context, {\n branch: params.branch,\n ...(params.baseRef === undefined ? {} : { baseRef: params.baseRef }),\n ...(params.name === undefined ? {} : { name: params.name }),\n });\n return {\n content: text(\n `Worktree ${record.id} created on ${record.branch} from ${record.baseRef}.`,\n `Path: ${record.path}`,\n `Session ${record.sessionId} is starting; it appears nested under this one in the rail.`,\n ),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.close_worktree: {\n const record = await operations.close(context, params.id, params.force ?? false);\n return {\n content: text(`Worktree ${record.id} (${record.branch}) closed and removed.`),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.list: {\n const records = await operations.list(context);\n return {\n content:\n records.length === 0\n ? text('No worktrees for this repository.')\n : text(`${String(records.length)} worktree(s):`, ...records.map(describe)),\n details: { action: params.action, records },\n };\n }\n case WORKTREE_ACTIONS.status: {\n const { record, dirtyFiles } = await operations.status(context, params.id);\n return {\n content: text(\n describe(record),\n `Session: ${record.sessionId}`,\n dirtyFiles.length === 0\n ? 'Clean.'\n : `${String(dirtyFiles.length)} uncommitted file(s): ${dirtyFiles.slice(0, 20).join(', ')}`,\n ),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.merge: {\n const record = await operations.merge(\n context,\n params.id,\n ...(params.message === undefined ? [] : ([params.message] as const)),\n );\n return {\n content: text(`Merged ${record.branch} into this checkout.`),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.send: {\n await operations.send(context, params.id, params.message);\n return { content: text(`Message sent to worktree ${params.id}.`), details: { action: params.action } };\n }\n case WORKTREE_ACTIONS.messages: {\n const waiting = await operations.messages(context, params.id);\n return {\n content:\n waiting.length === 0\n ? text('No messages.')\n : text(\n `${String(waiting.length)} message(s):`,\n ...waiting.map((entry) => `[${entry.sentAt}] ${entry.from}: ${entry.text}`),\n ),\n details: { action: params.action },\n };\n }\n case WORKTREE_ACTIONS.prune: {\n const dryRun = params.dryRun ?? false;\n const plan = await operations.prune(context, dryRun);\n const lines = [\n dryRun ? 'Prune plan (nothing was changed):' : 'Pruned:',\n `Remove: ${plan.remove.length === 0 ? 'none' : plan.remove.map((record) => record.id).join(', ')}`,\n `Forget: ${plan.forget.length === 0 ? 'none' : plan.forget.map((record) => record.id).join(', ')}`,\n `Kept, uncommitted work: ${plan.keptDirty.length === 0 ? 'none' : plan.keptDirty.map((record) => record.id).join(', ')}`,\n `Untracked directories, left alone: ${plan.untracked.length === 0 ? 'none' : plan.untracked.join(', ')}`,\n ];\n return { content: text(...lines), details: { action: params.action } };\n }\n }\n}\n\n/** Registers `run_worktree` on a Pi host, once. */\nexport function registerRunWorktreeTool(pi: ExtensionAPI, operations: WorktreeOperations): void {\n const tool: ToolDefinition<typeof RunWorktreeParams, RunWorktreeToolDetails> = {\n name: RUN_WORKTREE_TOOL_NAME,\n label: 'Worktree',\n description: DESCRIPTION,\n parameters: RunWorktreeParams,\n prepareArguments: validateParams,\n execute: async (_id, rawParams, _signal, _onUpdate, ctx) => run(validateParams(rawParams), operations, ctx),\n };\n pi.registerTool(tool);\n}\n"],"mappings":"0GAaa,EAAyB,eAEhC,EAA2C,IAAI,IAAI,OAAO,OAAOA,EAAAA,gBAAgB,CAAC,EA4BxF,SAAgB,EAAe,EAAuC,CACpE,GAAI,OAAO,GAAU,WAAY,GAAkB,MAAM,QAAQ,CAAK,EACpE,MAAMC,EAAAA,eAAe,6CAA8C,2CAA2C,EAEhH,IAAM,EAAS,EACT,EAAS,EAAO,OACtB,GAAI,OAAO,GAAW,UAAY,CAAC,EAAoB,IAAI,CAAM,EAC/D,MAAMA,EAAAA,eACJ,kBAAkB,OAAO,GAAW,SAAW,IAAI,EAAO,GAAK,QAAQ,GACvE,eAAe,CAAC,GAAG,CAAmB,CAAC,CAAC,KAAK,IAAI,EAAE,EACrD,EAEF,IAAM,EAAUC,EAAAA,uBAAuB,GACjC,EAAa,OAAO,KAAK,CAAM,CAAC,CAAC,OAAQ,GAAQ,CAAC,EAAQ,SAAS,CAAG,CAAC,EAC7E,GAAI,EAAW,OAAS,EACtB,MAAMD,EAAAA,eACJ,WAAW,EAAO,qBAAqB,EAAW,KAAK,IAAI,EAAE,GAC7D,eAAe,EAAO,KAAK,EAAQ,KAAK,IAAI,EAAE,EAChD,EAEF,IAAK,IAAM,IAAS,CAAC,SAAU,IAAI,EACjC,GAAI,EAAQ,SAAS,CAAK,GAAK,KAAS,EAAQ,CAC9C,IAAM,EAAQ,EAAO,GACrB,GAAI,OAAO,GAAU,UAAY,EAAM,KAAK,IAAM,GAChD,MAAMA,EAAAA,eAAe,WAAW,EAAO,yBAAyB,EAAM,IAAK,UAAU,EAAM,EAAE,CAEjG,CAEF,GAAI,IAAWD,EAAAA,iBAAiB,gBAAkB,OAAO,EAAO,QAAW,SACzE,MAAMC,EAAAA,eAAe,6CAA8C,4BAA4B,EAEjG,IACG,IAAWD,EAAAA,iBAAiB,gBAC3B,IAAWA,EAAAA,iBAAiB,QAC5B,IAAWA,EAAAA,iBAAiB,QAC9B,OAAO,EAAO,IAAO,SAErB,MAAMC,EAAAA,eAAe,WAAW,EAAO,kBAAmB,mCAAmC,EAE/F,OAAO,CACT,CAEA,SAAS,EAAS,EAAgC,CAChD,MAAO,GAAG,EAAO,GAAG,IAAI,EAAO,OAAO,IAAI,EAAO,OAAO,IAAI,EAAO,MACrE,CAGA,SAAS,EAAK,GAAG,EAA4D,CAC3E,MAAO,CAAC,CAAE,KAAM,OAAQ,KAAM,EAAM,KAAK;CAAI,CAAE,CAAC,CAClD,CAEA,eAAe,EACb,EACA,EACA,EACkD,CAClD,IAAM,EAAU,CAAE,IAAK,EAAI,IAAK,UAAW,EAAI,eAAe,aAAa,CAAE,EAC7E,OAAQ,EAAO,OAAf,CACE,KAAKD,EAAAA,iBAAiB,eAAgB,CACpC,IAAM,EAAS,MAAM,EAAW,MAAM,EAAS,CAC7C,OAAQ,EAAO,OACf,GAAI,EAAO,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAO,OAAQ,EAClE,GAAI,EAAO,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAO,IAAK,CAC3D,CAAC,EACD,MAAO,CACL,QAAS,EACP,YAAY,EAAO,GAAG,cAAc,EAAO,OAAO,QAAQ,EAAO,QAAQ,GACzE,SAAS,EAAO,OAChB,WAAW,EAAO,UAAU,4DAC9B,EACA,QAAS,CAAE,OAAQ,EAAO,OAAQ,QAAS,CAAC,CAAM,CAAE,CACtD,CACF,CACA,KAAKA,EAAAA,iBAAiB,eAAgB,CACpC,IAAM,EAAS,MAAM,EAAW,MAAM,EAAS,EAAO,GAAI,EAAO,OAAS,EAAK,EAC/E,MAAO,CACL,QAAS,EAAK,YAAY,EAAO,GAAG,IAAI,EAAO,OAAO,sBAAsB,EAC5E,QAAS,CAAE,OAAQ,EAAO,OAAQ,QAAS,CAAC,CAAM,CAAE,CACtD,CACF,CACA,KAAKA,EAAAA,iBAAiB,KAAM,CAC1B,IAAM,EAAU,MAAM,EAAW,KAAK,CAAO,EAC7C,MAAO,CACL,QACE,EAAQ,SAAW,EACf,EAAK,mCAAmC,EACxC,EAAK,GAAG,OAAO,EAAQ,MAAM,EAAE,eAAgB,GAAG,EAAQ,IAAI,CAAQ,CAAC,EAC7E,QAAS,CAAE,OAAQ,EAAO,OAAQ,SAAQ,CAC5C,CACF,CACA,KAAKA,EAAAA,iBAAiB,OAAQ,CAC5B,GAAM,CAAE,SAAQ,cAAe,MAAM,EAAW,OAAO,EAAS,EAAO,EAAE,EACzE,MAAO,CACL,QAAS,EACP,EAAS,CAAM,EACf,YAAY,EAAO,YACnB,EAAW,SAAW,EAClB,SACA,GAAG,OAAO,EAAW,MAAM,EAAE,wBAAwB,EAAW,MAAM,EAAG,EAAE,CAAC,CAAC,KAAK,IAAI,GAC5F,EACA,QAAS,CAAE,OAAQ,EAAO,OAAQ,QAAS,CAAC,CAAM,CAAE,CACtD,CACF,CACA,KAAKA,EAAAA,iBAAiB,MAAO,CAC3B,IAAM,EAAS,MAAM,EAAW,MAC9B,EACA,EAAO,GACP,GAAI,EAAO,UAAY,IAAA,GAAY,CAAC,EAAK,CAAC,EAAO,OAAO,CAC1D,EACA,MAAO,CACL,QAAS,EAAK,UAAU,EAAO,OAAO,qBAAqB,EAC3D,QAAS,CAAE,OAAQ,EAAO,OAAQ,QAAS,CAAC,CAAM,CAAE,CACtD,CACF,CACA,KAAKA,EAAAA,iBAAiB,KAEpB,OADA,MAAM,EAAW,KAAK,EAAS,EAAO,GAAI,EAAO,OAAO,EACjD,CAAE,QAAS,EAAK,4BAA4B,EAAO,GAAG,EAAE,EAAG,QAAS,CAAE,OAAQ,EAAO,MAAO,CAAE,EAEvG,KAAKA,EAAAA,iBAAiB,SAAU,CAC9B,IAAM,EAAU,MAAM,EAAW,SAAS,EAAS,EAAO,EAAE,EAC5D,MAAO,CACL,QACE,EAAQ,SAAW,EACf,EAAK,cAAc,EACnB,EACE,GAAG,OAAO,EAAQ,MAAM,EAAE,cAC1B,GAAG,EAAQ,IAAK,GAAU,IAAI,EAAM,OAAO,IAAI,EAAM,KAAK,IAAI,EAAM,MAAM,CAC5E,EACN,QAAS,CAAE,OAAQ,EAAO,MAAO,CACnC,CACF,CACA,KAAKA,EAAAA,iBAAiB,MAAO,CAC3B,IAAM,EAAS,EAAO,QAAU,GAC1B,EAAO,MAAM,EAAW,MAAM,EAAS,CAAM,EAQnD,MAAO,CAAE,QAAS,EANhB,EAAS,oCAAsC,UAC/C,WAAW,EAAK,OAAO,SAAW,EAAI,OAAS,EAAK,OAAO,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,KAAK,IAAI,IAC/F,WAAW,EAAK,OAAO,SAAW,EAAI,OAAS,EAAK,OAAO,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,KAAK,IAAI,IAC/F,2BAA2B,EAAK,UAAU,SAAW,EAAI,OAAS,EAAK,UAAU,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,KAAK,IAAI,IACrH,sCAAsC,EAAK,UAAU,SAAW,EAAI,OAAS,EAAK,UAAU,KAAK,IAAI,GAExE,EAAG,QAAS,CAAE,OAAQ,EAAO,MAAO,CAAE,CACvE,CACF,CACF,CAGA,SAAgB,EAAwB,EAAkB,EAAsC,CAC9F,IAAM,EAAyE,CAC7E,KAAM,EACN,MAAO,WACP,YAAa;;;;;;;;;;;;yEACb,WAAYG,EAAAA,kBACZ,iBAAkB,EAClB,QAAS,MAAO,EAAK,EAAW,EAAS,EAAW,IAAQ,EAAI,EAAe,CAAS,EAAG,EAAY,CAAG,CAC5G,EACA,EAAG,aAAa,CAAI,CACtB"}
@@ -1,15 +0,0 @@
1
- import{invalidRequest as e}from"../../../services/support/errors.mjs";import{RunWorktreeParams as t,WORKTREE_ACTIONS as n,WORKTREE_ACTION_FIELDS as r}from"../../../schemas/runWorktreeTool.mjs";const i=`run_worktree`,a=new Set(Object.values(n));function o(t){if(typeof t!=`object`||!t||Array.isArray(t))throw e(`run_worktree parameters must be an object.`,`Call it with one documented action shape.`);let i=t,o=i.action;if(typeof o!=`string`||!a.has(o))throw e(`Unknown action ${typeof o==`string`?`'${o}'`:`value`}.`,`Use one of: ${[...a].join(`, `)}.`);let s=r[o],c=Object.keys(i).filter(e=>!s.includes(e));if(c.length>0)throw e(`action='${o}' does not accept: ${c.join(`, `)}.`,`Fields for '${o}': ${s.join(`, `)}.`);for(let t of[`branch`,`id`])if(s.includes(t)&&t in i){let n=i[t];if(typeof n!=`string`||n.trim()===``)throw e(`action='${o}' requires a nonblank '${t}'.`,`Pass a ${t}.`)}if(o===n.spawn_worktree&&typeof i.branch!=`string`)throw e(`action='spawn_worktree' requires 'branch'.`,`Name the branch to create.`);if((o===n.close_worktree||o===n.status||o===n.merge)&&typeof i.id!=`string`)throw e(`action='${o}' requires 'id'.`,`Call list to see the current ids.`);return i}function s(e){return`${e.id} ${e.branch} ${e.status} ${e.path}`}function c(...e){return[{type:`text`,text:e.join(`
2
- `)}]}async function l(e,t,r){let i={cwd:r.cwd,sessionId:r.sessionManager.getSessionId()};switch(e.action){case n.spawn_worktree:{let n=await t.spawn(i,{branch:e.branch,...e.baseRef===void 0?{}:{baseRef:e.baseRef},...e.name===void 0?{}:{name:e.name}});return{content:c(`Worktree ${n.id} created on ${n.branch} from ${n.baseRef}.`,`Path: ${n.path}`,`Session ${n.sessionId} is starting; it appears nested under this one in the rail.`),details:{action:e.action,records:[n]}}}case n.close_worktree:{let n=await t.close(i,e.id,e.force??!1);return{content:c(`Worktree ${n.id} (${n.branch}) closed and removed.`),details:{action:e.action,records:[n]}}}case n.list:{let n=await t.list(i);return{content:n.length===0?c(`No worktrees for this repository.`):c(`${String(n.length)} worktree(s):`,...n.map(s)),details:{action:e.action,records:n}}}case n.status:{let{record:n,dirtyFiles:r}=await t.status(i,e.id);return{content:c(s(n),`Session: ${n.sessionId}`,r.length===0?`Clean.`:`${String(r.length)} uncommitted file(s): ${r.slice(0,20).join(`, `)}`),details:{action:e.action,records:[n]}}}case n.merge:{let n=await t.merge(i,e.id,...e.message===void 0?[]:[e.message]);return{content:c(`Merged ${n.branch} into this checkout.`),details:{action:e.action,records:[n]}}}case n.send:return await t.send(i,e.id,e.message),{content:c(`Message sent to worktree ${e.id}.`),details:{action:e.action}};case n.messages:{let n=await t.messages(i,e.id);return{content:n.length===0?c(`No messages.`):c(`${String(n.length)} message(s):`,...n.map(e=>`[${e.sentAt}] ${e.from}: ${e.text}`)),details:{action:e.action}}}case n.prune:{let n=e.dryRun??!1,r=await t.prune(i,n);return{content:c(n?`Prune plan (nothing was changed):`:`Pruned:`,`Remove: ${r.remove.length===0?`none`:r.remove.map(e=>e.id).join(`, `)}`,`Forget: ${r.forget.length===0?`none`:r.forget.map(e=>e.id).join(`, `)}`,`Kept, uncommitted work: ${r.keptDirty.length===0?`none`:r.keptDirty.map(e=>e.id).join(`, `)}`,`Untracked directories, left alone: ${r.untracked.length===0?`none`:r.untracked.join(`, `)}`),details:{action:e.action}}}}}function u(e,n){let r={name:i,label:`Worktree`,description:`Create and manage git worktrees, each with its own DoomPi session nested under this one.
3
-
4
- Actions:
5
- - spawn_worktree: create a worktree on a new branch and start a session in it. Requires 'branch'. Optional 'baseRef' (defaults to the current branch), 'name' for the rail, 'task' as the first message.
6
- - close_worktree: stop a worktree's session and delete its directory. Requires 'id'. Refuses a dirty tree unless 'force' is true.
7
- - list: every worktree for this repository, with status.
8
- - status: one worktree and its uncommitted files. Requires 'id'.
9
- - merge: merge a worktree's branch into this checkout. Requires 'id'. Refuses when this checkout is dirty.
10
- - prune: remove orphaned worktrees. Pass 'dryRun' to see the plan without destroying anything.
11
- - send: send a message to the other side of a worktree. Requires 'id' and 'message'.
12
- - messages: read messages waiting for you from a worktree. Requires 'id'.
13
-
14
- Worktrees live outside the repository, under ~/.pi/.doom/git/worktrees.`,parameters:t,prepareArguments:o,execute:async(e,t,r,i,a)=>l(o(t),n,a)};e.registerTool(r)}export{i as RUN_WORKTREE_TOOL_NAME,u as registerRunWorktreeTool,o as validateParams};
15
- //# sourceMappingURL=runWorktreeTool.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runWorktreeTool.mjs","names":[],"sources":["../../../../src/adapters/pi/extensions/runWorktreeTool.ts"],"sourcesContent":["import type { AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport {\n RunWorktreeParams,\n WORKTREE_ACTION_FIELDS,\n WORKTREE_ACTIONS,\n type WorktreeAction,\n type RunWorktreeToolParams,\n} from '../../../schemas/runWorktreeTool.ts';\nimport { invalidRequest } from '../../../services/support/errors.ts';\nimport type { WorktreeOperations } from '../../worktree/worktreeOperations.ts';\nimport type { WorktreeRecord } from '../../../types/worktreeRegistry.ts';\n\nexport const RUN_WORKTREE_TOOL_NAME = 'run_worktree';\n\nconst IMPLEMENTED_ACTIONS: ReadonlySet<string> = new Set(Object.values(WORKTREE_ACTIONS));\n\nconst DESCRIPTION = `Create and manage git worktrees, each with its own DoomPi session nested under this one.\n\nActions:\n- spawn_worktree: create a worktree on a new branch and start a session in it. Requires 'branch'. Optional 'baseRef' (defaults to the current branch), 'name' for the rail, 'task' as the first message.\n- close_worktree: stop a worktree's session and delete its directory. Requires 'id'. Refuses a dirty tree unless 'force' is true.\n- list: every worktree for this repository, with status.\n- status: one worktree and its uncommitted files. Requires 'id'.\n- merge: merge a worktree's branch into this checkout. Requires 'id'. Refuses when this checkout is dirty.\n- prune: remove orphaned worktrees. Pass 'dryRun' to see the plan without destroying anything.\n- send: send a message to the other side of a worktree. Requires 'id' and 'message'.\n- messages: read messages waiting for you from a worktree. Requires 'id'.\n\nWorktrees live outside the repository, under ~/.pi/.doom/git/worktrees.`;\n\nexport interface RunWorktreeToolDetails {\n action: WorktreeAction;\n records?: WorktreeRecord[];\n}\n\n/**\n * Rejects a call that is not exactly one documented action shape.\n *\n * Unknown fields are refused rather than ignored. A caller that passes 'branch'\n * to close_worktree has a different operation in mind than the one that would\n * run, and silently dropping the field would carry it out anyway.\n */\nexport function validateParams(input: unknown): RunWorktreeToolParams {\n if (typeof input !== 'object' || input === null || Array.isArray(input)) {\n throw invalidRequest('run_worktree parameters must be an object.', 'Call it with one documented action shape.');\n }\n const record = input as Record<string, unknown>;\n const action = record.action;\n if (typeof action !== 'string' || !IMPLEMENTED_ACTIONS.has(action)) {\n throw invalidRequest(\n `Unknown action ${typeof action === 'string' ? `'${action}'` : 'value'}.`,\n `Use one of: ${[...IMPLEMENTED_ACTIONS].join(', ')}.`,\n );\n }\n const allowed = WORKTREE_ACTION_FIELDS[action as WorktreeAction];\n const unexpected = Object.keys(record).filter((key) => !allowed.includes(key));\n if (unexpected.length > 0) {\n throw invalidRequest(\n `action='${action}' does not accept: ${unexpected.join(', ')}.`,\n `Fields for '${action}': ${allowed.join(', ')}.`,\n );\n }\n for (const field of ['branch', 'id'] as const) {\n if (allowed.includes(field) && field in record) {\n const value = record[field];\n if (typeof value !== 'string' || value.trim() === '') {\n throw invalidRequest(`action='${action}' requires a nonblank '${field}'.`, `Pass a ${field}.`);\n }\n }\n }\n if (action === WORKTREE_ACTIONS.spawn_worktree && typeof record.branch !== 'string') {\n throw invalidRequest(\"action='spawn_worktree' requires 'branch'.\", 'Name the branch to create.');\n }\n if (\n (action === WORKTREE_ACTIONS.close_worktree ||\n action === WORKTREE_ACTIONS.status ||\n action === WORKTREE_ACTIONS.merge) &&\n typeof record.id !== 'string'\n ) {\n throw invalidRequest(`action='${action}' requires 'id'.`, 'Call list to see the current ids.');\n }\n return record as RunWorktreeToolParams;\n}\n\nfunction describe(record: WorktreeRecord): string {\n return `${record.id} ${record.branch} ${record.status} ${record.path}`;\n}\n\n/** A tool result's content, which Pi expects as content blocks rather than a string. */\nfunction text(...lines: readonly string[]): [{ type: 'text'; text: string }] {\n return [{ type: 'text', text: lines.join('\\n') }];\n}\n\nasync function run(\n params: RunWorktreeToolParams,\n operations: WorktreeOperations,\n ctx: ExtensionContext,\n): Promise<AgentToolResult<RunWorktreeToolDetails>> {\n const context = { cwd: ctx.cwd, sessionId: ctx.sessionManager.getSessionId() };\n switch (params.action) {\n case WORKTREE_ACTIONS.spawn_worktree: {\n const record = await operations.spawn(context, {\n branch: params.branch,\n ...(params.baseRef === undefined ? {} : { baseRef: params.baseRef }),\n ...(params.name === undefined ? {} : { name: params.name }),\n });\n return {\n content: text(\n `Worktree ${record.id} created on ${record.branch} from ${record.baseRef}.`,\n `Path: ${record.path}`,\n `Session ${record.sessionId} is starting; it appears nested under this one in the rail.`,\n ),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.close_worktree: {\n const record = await operations.close(context, params.id, params.force ?? false);\n return {\n content: text(`Worktree ${record.id} (${record.branch}) closed and removed.`),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.list: {\n const records = await operations.list(context);\n return {\n content:\n records.length === 0\n ? text('No worktrees for this repository.')\n : text(`${String(records.length)} worktree(s):`, ...records.map(describe)),\n details: { action: params.action, records },\n };\n }\n case WORKTREE_ACTIONS.status: {\n const { record, dirtyFiles } = await operations.status(context, params.id);\n return {\n content: text(\n describe(record),\n `Session: ${record.sessionId}`,\n dirtyFiles.length === 0\n ? 'Clean.'\n : `${String(dirtyFiles.length)} uncommitted file(s): ${dirtyFiles.slice(0, 20).join(', ')}`,\n ),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.merge: {\n const record = await operations.merge(\n context,\n params.id,\n ...(params.message === undefined ? [] : ([params.message] as const)),\n );\n return {\n content: text(`Merged ${record.branch} into this checkout.`),\n details: { action: params.action, records: [record] },\n };\n }\n case WORKTREE_ACTIONS.send: {\n await operations.send(context, params.id, params.message);\n return { content: text(`Message sent to worktree ${params.id}.`), details: { action: params.action } };\n }\n case WORKTREE_ACTIONS.messages: {\n const waiting = await operations.messages(context, params.id);\n return {\n content:\n waiting.length === 0\n ? text('No messages.')\n : text(\n `${String(waiting.length)} message(s):`,\n ...waiting.map((entry) => `[${entry.sentAt}] ${entry.from}: ${entry.text}`),\n ),\n details: { action: params.action },\n };\n }\n case WORKTREE_ACTIONS.prune: {\n const dryRun = params.dryRun ?? false;\n const plan = await operations.prune(context, dryRun);\n const lines = [\n dryRun ? 'Prune plan (nothing was changed):' : 'Pruned:',\n `Remove: ${plan.remove.length === 0 ? 'none' : plan.remove.map((record) => record.id).join(', ')}`,\n `Forget: ${plan.forget.length === 0 ? 'none' : plan.forget.map((record) => record.id).join(', ')}`,\n `Kept, uncommitted work: ${plan.keptDirty.length === 0 ? 'none' : plan.keptDirty.map((record) => record.id).join(', ')}`,\n `Untracked directories, left alone: ${plan.untracked.length === 0 ? 'none' : plan.untracked.join(', ')}`,\n ];\n return { content: text(...lines), details: { action: params.action } };\n }\n }\n}\n\n/** Registers `run_worktree` on a Pi host, once. */\nexport function registerRunWorktreeTool(pi: ExtensionAPI, operations: WorktreeOperations): void {\n const tool: ToolDefinition<typeof RunWorktreeParams, RunWorktreeToolDetails> = {\n name: RUN_WORKTREE_TOOL_NAME,\n label: 'Worktree',\n description: DESCRIPTION,\n parameters: RunWorktreeParams,\n prepareArguments: validateParams,\n execute: async (_id, rawParams, _signal, _onUpdate, ctx) => run(validateParams(rawParams), operations, ctx),\n };\n pi.registerTool(tool);\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=require("../worktree/gitCli.cjs"),t=require("../hub/hubClient.cjs"),n=require("../filesystem/paths.cjs"),r=require("../worktree/worktreeRegistry.cjs"),i=require("../../services/support/errors.cjs"),a=require("../worktree/worktreeOperations.cjs"),o=require("../../types/webWorktrees.cjs");function s(e){return{worktrees:e.worktrees,...e.pending===void 0?{}:{pending:e.pending},...e.error===void 0?{}:{error:e.error}}}function c(e,t){try{return r.createWorktreeRegistry(n.registryFile(e.cwd,t.homeDir)).list().flatMap(n=>{let r=n.parentSessionId===e.sessionId,i=!r&&!t.isSessionLive(t.registryDir,n.parentSessionId);return!r&&!i?[]:[{id:n.id,branch:n.branch,path:n.path,sessionId:n.status===`orphaned`?null:n.sessionId,orphaned:n.status===`orphaned`,unowned:i}]})}catch{return[]}}function l(e,t){return e.length===t.length&&e.every((e,n)=>{let r=t[n];return r!==void 0&&e.id===r.id&&e.branch===r.branch&&e.sessionId===r.sessionId&&e.orphaned===r.orphaned&&e.unowned===r.unowned})}function u(e){if(typeof e!=`object`||!e)return;let t=e;if(t.action===`create`)return typeof t.branch!=`string`||t.branch.trim()===``?void 0:{action:`create`,branch:t.branch.trim(),...typeof t.baseRef==`string`&&t.baseRef.trim()!==``?{baseRef:t.baseRef.trim()}:{}};if(t.action===`close`)return typeof t.id!=`string`||t.id===``?void 0:{action:`close`,id:t.id,...t.force===!0?{force:!0}:{}}}function d(e){return e instanceof i.DoomGitExpectedError?e.message:`The worktree operation failed.`}function f(r={}){let i=r.intervalMs??4e3,f;return{frameType:o.GIT_WORKTREES_TYPE,start(o){let p=r.operations??a.createWorktreeOperations({git:e.createWorktreeGit()}),m={registryDir:r.registryDir??n.hubRegistryDir(),homeDir:r.homeDir,isSessionLive:r.isSessionLive??t.sessionIsLive},h=new Map,g=new Map,_=new Map,v=new Set,y=(e,t)=>{let n=h.get(e.sessionId),r={worktrees:c(e,m),pending:n?.pending,error:n?.error};(t||n===void 0||!l(n.worktrees,r.worktrees)||n.pending!==r.pending||n.error!==r.error)&&(h.set(e.sessionId,r),o.publish(e.sessionId,s(r)))},b=(e,t,n)=>{let r=h.get(e.sessionId);h.set(e.sessionId,{worktrees:r?.worktrees??c(e,m),pending:t,error:n}),y(e,!0)},x=async(e,t)=>{let n={cwd:e.cwd,sessionId:e.sessionId};if(t.action===`create`){b(e,`creating ${t.branch}\u2026`,void 0),await p.spawn(n,{branch:t.branch,...t.baseRef===void 0?{}:{baseRef:t.baseRef}});return}b(e,`closing ${t.id}\u2026`,void 0),await p.close(n,t.id,t.force??!1)};return f=(e,t)=>{let n=u(t);n!==void 0&&(v.has(e.sessionId)||(v.add(e.sessionId),x(e,n).then(()=>{b(e,void 0,void 0)}).catch(t=>{o.onNotice(`worktree command failed for ${e.sessionId} (${d(t)})`),b(e,void 0,d(t))}).finally(()=>{v.delete(e.sessionId)})))},{payloadFor(e){let t=h.get(e.sessionId);return t===void 0?void 0:s(t)},sessionAdded(e){g.set(e.sessionId,e),y(e,!1);let t=setInterval(()=>{y(e,!1)},i);t.unref?.(),_.set(e.sessionId,t)},sessionRemoved(e){let t=_.get(e);t!==void 0&&clearInterval(t),_.delete(e),h.delete(e),g.delete(e),v.delete(e)},close(){for(let e of _.values())clearInterval(e);_.clear(),h.clear(),g.clear(),v.clear()}}},receive(e,t){f?.(e,t)}}}const p=[f()];exports.createWorktreesChannel=f,exports.webHubChannels=p;
2
- //# sourceMappingURL=worktreesChannel.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worktreesChannel.cjs","names":["createWorktreeRegistry","registryFile","DoomGitExpectedError","GIT_WORKTREES_TYPE","createWorktreeOperations","createWorktreeGit","hubRegistryDir","sessionIsLive"],"sources":["../../../src/adapters/web/worktreesChannel.ts"],"sourcesContent":["/**\n * The worktrees data channel, and the commands the dock sends back through it.\n *\n * DESIGN PATTERNS:\n * - Hub-scoped: it reads this machine's registry files, which a browser cannot.\n * - Scoped to the session that owns the worktree, not to the repository. Two\n * sessions in one checkout are two pieces of work; showing each the other's\n * worktrees offered a close button for something the reader never made.\n * A worktree whose parent session is gone is the exception: it is shown to\n * everyone in the repository, marked unowned, or nobody could ever close it.\n * - Poll, do not watch. The registry is a single small JSON file written by\n * this package alone, and a watcher on a path that may not exist yet costs\n * more than re-reading a few hundred bytes.\n * - Commands run the same `worktreeOperations` the tool runs, so the panel and\n * the agent cannot drift into two ideas of what creating a worktree means.\n * - Publish only what the dock renders. Paths and provenance stay in the\n * registry; the page gets a view.\n *\n * AVOID:\n * - Throwing out of a poll or a command. A registry that cannot be read is an\n * empty list, and a failed command is an error string on the next frame:\n * the dock degrades to what it can tell the reader, never to a dead channel.\n */\nimport type { HubChannelSource, HubSessionScope, WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport { GIT_WORKTREES_TYPE, type GitWorktreesCommand, type WorktreeView } from '../../types/webWorktrees.ts';\nimport { DoomGitExpectedError } from '../../services/support/errors.ts';\nimport { hubRegistryDir, registryFile } from '../filesystem/paths.ts';\nimport { sessionIsLive } from '../hub/hubClient.ts';\nimport { createWorktreeGit } from '../worktree/gitCli.ts';\nimport { createWorktreeOperations, type WorktreeOperations } from '../worktree/worktreeOperations.ts';\nimport { createWorktreeRegistry } from '../worktree/worktreeRegistry.ts';\n\n/** How often a session's registry is re-read. Worktrees change by hand, not by the second. */\nconst POLL_MS = 4000;\n\ninterface SessionState {\n worktrees: WorktreeView[];\n pending: string | undefined;\n error: string | undefined;\n}\n\nfunction payloadOf(state: SessionState): Record<string, unknown> {\n return {\n worktrees: state.worktrees,\n ...(state.pending === undefined ? {} : { pending: state.pending }),\n ...(state.error === undefined ? {} : { error: state.error }),\n };\n}\n\n/** What the channel needs from its surroundings, injected so tests stay off this machine. */\nexport interface WorktreesChannelOptions {\n intervalMs?: number;\n operations?: WorktreeOperations;\n homeDir?: string;\n registryDir?: string;\n isSessionLive?: (registryDir: string, sessionId: string) => boolean;\n}\n\ninterface ViewDeps {\n registryDir: string;\n homeDir: string | undefined;\n isSessionLive: (registryDir: string, sessionId: string) => boolean;\n}\n\n/**\n * The worktrees one session may see: its own, plus any whose parent session\n * has gone. Liveness is the registry record's presence, the same signal the\n * rail uses to decide a session exists at all.\n */\nfunction viewsFor(scope: HubSessionScope, deps: ViewDeps): WorktreeView[] {\n try {\n return createWorktreeRegistry(registryFile(scope.cwd, deps.homeDir))\n .list()\n .flatMap((record) => {\n const owned = record.parentSessionId === scope.sessionId;\n const unowned = !owned && !deps.isSessionLive(deps.registryDir, record.parentSessionId);\n if (!owned && !unowned) return [];\n return [\n {\n id: record.id,\n branch: record.branch,\n path: record.path,\n sessionId: record.status === 'orphaned' ? null : record.sessionId,\n orphaned: record.status === 'orphaned',\n unowned,\n },\n ];\n });\n } catch {\n return [];\n }\n}\n\nfunction sameList(left: readonly WorktreeView[], right: readonly WorktreeView[]): boolean {\n if (left.length !== right.length) return false;\n return left.every((entry, index) => {\n const other = right[index];\n return (\n other !== undefined &&\n entry.id === other.id &&\n entry.branch === other.branch &&\n entry.sessionId === other.sessionId &&\n entry.orphaned === other.orphaned &&\n entry.unowned === other.unowned\n );\n });\n}\n\n/** A command the page sent, or undefined when the frame was not one. */\nfunction parseCommand(payload: unknown): GitWorktreesCommand | undefined {\n if (typeof payload !== 'object' || payload === null) return undefined;\n const value = payload as Record<string, unknown>;\n if (value.action === 'create') {\n if (typeof value.branch !== 'string' || value.branch.trim() === '') return undefined;\n return {\n action: 'create',\n branch: value.branch.trim(),\n ...(typeof value.baseRef === 'string' && value.baseRef.trim() !== '' ? { baseRef: value.baseRef.trim() } : {}),\n };\n }\n if (value.action === 'close') {\n if (typeof value.id !== 'string' || value.id === '') return undefined;\n return { action: 'close', id: value.id, ...(value.force === true ? { force: true } : {}) };\n }\n return undefined;\n}\n\n/**\n * What the reader is told when a command fails.\n *\n * An expected failure explains itself. Anything else is summarised, never\n * echoed: a git error can carry a remote URL with a token in it.\n */\nfunction failureText(error: unknown): string {\n return error instanceof DoomGitExpectedError ? error.message : 'The worktree operation failed.';\n}\n\n/**\n * One poller per managed session, publishing only when the list actually\n * changed so an idle cockpit stays quiet.\n */\nexport function createWorktreesChannel(options: WorktreesChannelOptions = {}): WebHubChannel {\n const intervalMs = options.intervalMs ?? POLL_MS;\n // Assigned by start, because receive is reachable only while the channel is\n // running and the command needs the state the poller keeps.\n let receiveCommand: ((scope: HubSessionScope, payload: unknown) => void) | undefined;\n\n return {\n frameType: GIT_WORKTREES_TYPE,\n start(host) {\n const worktrees = options.operations ?? createWorktreeOperations({ git: createWorktreeGit() });\n const views: ViewDeps = {\n registryDir: options.registryDir ?? hubRegistryDir(),\n homeDir: options.homeDir,\n isSessionLive: options.isSessionLive ?? sessionIsLive,\n };\n const latest = new Map<string, SessionState>();\n const scopes = new Map<string, HubSessionScope>();\n const timers = new Map<string, ReturnType<typeof setInterval>>();\n const busy = new Set<string>();\n\n /** Republishes a session, skipping a frame that would say nothing new. */\n const publish = (scope: HubSessionScope, force: boolean): void => {\n const previous = latest.get(scope.sessionId);\n const next: SessionState = {\n worktrees: viewsFor(scope, views),\n pending: previous?.pending,\n error: previous?.error,\n };\n if (\n !force &&\n previous !== undefined &&\n sameList(previous.worktrees, next.worktrees) &&\n previous.pending === next.pending &&\n previous.error === next.error\n ) {\n return;\n }\n latest.set(scope.sessionId, next);\n host.publish(scope.sessionId, payloadOf(next));\n };\n\n /** Sets the operation label and clears the last failure, then publishes. */\n const mark = (scope: HubSessionScope, pending: string | undefined, error: string | undefined): void => {\n const previous = latest.get(scope.sessionId);\n latest.set(scope.sessionId, {\n worktrees: previous?.worktrees ?? viewsFor(scope, views),\n pending,\n error,\n });\n publish(scope, true);\n };\n\n const run = async (scope: HubSessionScope, command: GitWorktreesCommand): Promise<void> => {\n const context = { cwd: scope.cwd, sessionId: scope.sessionId };\n if (command.action === 'create') {\n mark(scope, `creating ${command.branch}\\u2026`, undefined);\n await worktrees.spawn(context, {\n branch: command.branch,\n ...(command.baseRef === undefined ? {} : { baseRef: command.baseRef }),\n });\n return;\n }\n mark(scope, `closing ${command.id}\\u2026`, undefined);\n await worktrees.close(context, command.id, command.force ?? false);\n };\n\n const source: HubChannelSource = {\n payloadFor(scope) {\n const state = latest.get(scope.sessionId);\n return state === undefined ? undefined : payloadOf(state);\n },\n sessionAdded(scope) {\n scopes.set(scope.sessionId, scope);\n publish(scope, false);\n const timer = setInterval(() => {\n publish(scope, false);\n }, intervalMs);\n timer.unref?.();\n timers.set(scope.sessionId, timer);\n },\n sessionRemoved(sessionId) {\n const timer = timers.get(sessionId);\n if (timer !== undefined) clearInterval(timer);\n timers.delete(sessionId);\n latest.delete(sessionId);\n scopes.delete(sessionId);\n busy.delete(sessionId);\n },\n close() {\n for (const timer of timers.values()) clearInterval(timer);\n timers.clear();\n latest.clear();\n scopes.clear();\n busy.clear();\n },\n };\n receiveCommand = (scope, payload) => {\n const command = parseCommand(payload);\n if (command === undefined) return;\n // One operation per session. A create runs for minutes, and a second\n // click must not start a second worktree behind the first.\n if (busy.has(scope.sessionId)) return;\n busy.add(scope.sessionId);\n void run(scope, command)\n .then(() => {\n mark(scope, undefined, undefined);\n })\n .catch((error: unknown) => {\n host.onNotice(`worktree command failed for ${scope.sessionId} (${failureText(error)})`);\n mark(scope, undefined, failureText(error));\n })\n .finally(() => {\n busy.delete(scope.sessionId);\n });\n };\n\n return source;\n },\n receive(scope, payload) {\n receiveCommand?.(scope, payload);\n },\n };\n}\n\n/** The named export the generated hub channel registry imports. */\nexport const webHubChannels: readonly WebHubChannel[] = [createWorktreesChannel()];\n"],"mappings":"wSAyCA,SAAS,EAAU,EAA8C,CAC/D,MAAO,CACL,UAAW,EAAM,UACjB,GAAI,EAAM,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAM,OAAQ,EAChE,GAAI,EAAM,QAAU,IAAA,GAAY,CAAC,EAAI,CAAE,MAAO,EAAM,KAAM,CAC5D,CACF,CAsBA,SAAS,EAAS,EAAwB,EAAgC,CACxE,GAAI,CACF,OAAOA,EAAAA,uBAAuBC,EAAAA,aAAa,EAAM,IAAK,EAAK,OAAO,CAAC,CAAC,CACjE,KAAK,CAAC,CACN,QAAS,GAAW,CACnB,IAAM,EAAQ,EAAO,kBAAoB,EAAM,UACzC,EAAU,CAAC,GAAS,CAAC,EAAK,cAAc,EAAK,YAAa,EAAO,eAAe,EAEtF,MADI,CAAC,GAAS,CAAC,EAAgB,CAAC,EACzB,CACL,CACE,GAAI,EAAO,GACX,OAAQ,EAAO,OACf,KAAM,EAAO,KACb,UAAW,EAAO,SAAW,WAAa,KAAO,EAAO,UACxD,SAAU,EAAO,SAAW,WAC5B,SACF,CACF,CACF,CAAC,CACL,MAAQ,CACN,MAAO,CAAC,CACV,CACF,CAEA,SAAS,EAAS,EAA+B,EAAyC,CAExF,OADI,EAAK,SAAW,EAAM,QACnB,EAAK,OAAO,EAAO,IAAU,CAClC,IAAM,EAAQ,EAAM,GACpB,OACE,IAAU,IAAA,IACV,EAAM,KAAO,EAAM,IACnB,EAAM,SAAW,EAAM,QACvB,EAAM,YAAc,EAAM,WAC1B,EAAM,WAAa,EAAM,UACzB,EAAM,UAAY,EAAM,OAE5B,CAAC,CACH,CAGA,SAAS,EAAa,EAAmD,CACvE,GAAI,OAAO,GAAY,WAAY,EAAkB,OACrD,IAAM,EAAQ,EACd,GAAI,EAAM,SAAW,SAEnB,OADI,OAAO,EAAM,QAAW,UAAY,EAAM,OAAO,KAAK,IAAM,GAAI,OAC7D,CACL,OAAQ,SACR,OAAQ,EAAM,OAAO,KAAK,EAC1B,GAAI,OAAO,EAAM,SAAY,UAAY,EAAM,QAAQ,KAAK,IAAM,GAAK,CAAE,QAAS,EAAM,QAAQ,KAAK,CAAE,EAAI,CAAC,CAC9G,EAEF,GAAI,EAAM,SAAW,QAEnB,OADI,OAAO,EAAM,IAAO,UAAY,EAAM,KAAO,GAAI,OAC9C,CAAE,OAAQ,QAAS,GAAI,EAAM,GAAI,GAAI,EAAM,QAAU,GAAO,CAAE,MAAO,EAAK,EAAI,CAAC,CAAG,CAG7F,CAQA,SAAS,EAAY,EAAwB,CAC3C,OAAO,aAAiBC,EAAAA,qBAAuB,EAAM,QAAU,gCACjE,CAMA,SAAgB,EAAuB,EAAmC,CAAC,EAAkB,CAC3F,IAAM,EAAa,EAAQ,YAAc,IAGrC,EAEJ,MAAO,CACL,UAAWC,EAAAA,mBACX,MAAM,EAAM,CACV,IAAM,EAAY,EAAQ,YAAcC,EAAAA,yBAAyB,CAAE,IAAKC,EAAAA,kBAAkB,CAAE,CAAC,EACvF,EAAkB,CACtB,YAAa,EAAQ,aAAeC,EAAAA,eAAe,EACnD,QAAS,EAAQ,QACjB,cAAe,EAAQ,eAAiBC,EAAAA,aAC1C,EACM,EAAS,IAAI,IACb,EAAS,IAAI,IACb,EAAS,IAAI,IACb,EAAO,IAAI,IAGX,GAAW,EAAwB,IAAyB,CAChE,IAAM,EAAW,EAAO,IAAI,EAAM,SAAS,EACrC,EAAqB,CACzB,UAAW,EAAS,EAAO,CAAK,EAChC,QAAS,GAAU,QACnB,MAAO,GAAU,KACnB,GAEG,GACD,IAAa,IAAA,IACb,GAAS,EAAS,UAAW,EAAK,SAAS,GAC3C,EAAS,UAAY,EAAK,SAC1B,EAAS,QAAU,EAAK,SAI1B,EAAO,IAAI,EAAM,UAAW,CAAI,EAChC,EAAK,QAAQ,EAAM,UAAW,EAAU,CAAI,CAAC,EAC/C,EAGM,GAAQ,EAAwB,EAA6B,IAAoC,CACrG,IAAM,EAAW,EAAO,IAAI,EAAM,SAAS,EAC3C,EAAO,IAAI,EAAM,UAAW,CAC1B,UAAW,GAAU,WAAa,EAAS,EAAO,CAAK,EACvD,UACA,OACF,CAAC,EACD,EAAQ,EAAO,EAAI,CACrB,EAEM,EAAM,MAAO,EAAwB,IAAgD,CACzF,IAAM,EAAU,CAAE,IAAK,EAAM,IAAK,UAAW,EAAM,SAAU,EAC7D,GAAI,EAAQ,SAAW,SAAU,CAC/B,EAAK,EAAO,YAAY,EAAQ,OAAO,QAAS,IAAA,EAAS,EACzD,MAAM,EAAU,MAAM,EAAS,CAC7B,OAAQ,EAAQ,OAChB,GAAI,EAAQ,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAQ,OAAQ,CACtE,CAAC,EACD,MACF,CACA,EAAK,EAAO,WAAW,EAAQ,GAAG,QAAS,IAAA,EAAS,EACpD,MAAM,EAAU,MAAM,EAAS,EAAQ,GAAI,EAAQ,OAAS,EAAK,CACnE,EAoDA,MApBA,IAAkB,EAAO,IAAY,CACnC,IAAM,EAAU,EAAa,CAAO,EAChC,IAAY,IAAA,KAGZ,EAAK,IAAI,EAAM,SAAS,IAC5B,EAAK,IAAI,EAAM,SAAS,EACxB,EAAS,EAAO,CAAO,CAAC,CACrB,SAAW,CACV,EAAK,EAAO,IAAA,GAAW,IAAA,EAAS,CAClC,CAAC,CAAC,CACD,MAAO,GAAmB,CACzB,EAAK,SAAS,+BAA+B,EAAM,UAAU,IAAI,EAAY,CAAK,EAAE,EAAE,EACtF,EAAK,EAAO,IAAA,GAAW,EAAY,CAAK,CAAC,CAC3C,CAAC,CAAC,CACD,YAAc,CACb,EAAK,OAAO,EAAM,SAAS,CAC7B,CAAC,GACL,EAEO,CAjDL,WAAW,EAAO,CAChB,IAAM,EAAQ,EAAO,IAAI,EAAM,SAAS,EACxC,OAAO,IAAU,IAAA,GAAY,IAAA,GAAY,EAAU,CAAK,CAC1D,EACA,aAAa,EAAO,CAClB,EAAO,IAAI,EAAM,UAAW,CAAK,EACjC,EAAQ,EAAO,EAAK,EACpB,IAAM,EAAQ,gBAAkB,CAC9B,EAAQ,EAAO,EAAK,CACtB,EAAG,CAAU,EACb,EAAM,QAAQ,EACd,EAAO,IAAI,EAAM,UAAW,CAAK,CACnC,EACA,eAAe,EAAW,CACxB,IAAM,EAAQ,EAAO,IAAI,CAAS,EAC9B,IAAU,IAAA,IAAW,cAAc,CAAK,EAC5C,EAAO,OAAO,CAAS,EACvB,EAAO,OAAO,CAAS,EACvB,EAAO,OAAO,CAAS,EACvB,EAAK,OAAO,CAAS,CACvB,EACA,OAAQ,CACN,IAAK,IAAM,KAAS,EAAO,OAAO,EAAG,cAAc,CAAK,EACxD,EAAO,MAAM,EACb,EAAO,MAAM,EACb,EAAO,MAAM,EACb,EAAK,MAAM,CACb,CAsBK,CACT,EACA,QAAQ,EAAO,EAAS,CACtB,IAAiB,EAAO,CAAO,CACjC,CACF,CACF,CAGA,MAAa,EAA2C,CAAC,EAAuB,CAAC"}
@@ -1,21 +0,0 @@
1
- import { WorktreeOperations } from "../worktree/worktreeOperations.cjs";
2
- import { WebHubChannel } from "@agimon-ai/doompi-web-contracts";
3
- //#region src/adapters/web/worktreesChannel.d.ts
4
- /** What the channel needs from its surroundings, injected so tests stay off this machine. */
5
- interface WorktreesChannelOptions {
6
- intervalMs?: number;
7
- operations?: WorktreeOperations;
8
- homeDir?: string;
9
- registryDir?: string;
10
- isSessionLive?: (registryDir: string, sessionId: string) => boolean;
11
- }
12
- /**
13
- * One poller per managed session, publishing only when the list actually
14
- * changed so an idle cockpit stays quiet.
15
- */
16
- declare function createWorktreesChannel(options?: WorktreesChannelOptions): WebHubChannel;
17
- /** The named export the generated hub channel registry imports. */
18
- declare const webHubChannels: readonly WebHubChannel[];
19
- //#endregion
20
- export { WorktreesChannelOptions, createWorktreesChannel, webHubChannels };
21
- //# sourceMappingURL=worktreesChannel.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worktreesChannel.d.cts","names":[],"sources":["../../../src/adapters/web/worktreesChannel.ts"],"mappings":";;;;UAkDiB;EACf;EACA,aAAa;EACb;EACA;EACA,iBAAiB,qBAAqB;;;;;;iBAsFxB,uBAAuB,UAAS,0BAA+B;;cA6HlE,yBAAyB"}
@@ -1,21 +0,0 @@
1
- import { WorktreeOperations } from "../worktree/worktreeOperations.mjs";
2
- import { WebHubChannel } from "@agimon-ai/doompi-web-contracts";
3
- //#region src/adapters/web/worktreesChannel.d.ts
4
- /** What the channel needs from its surroundings, injected so tests stay off this machine. */
5
- interface WorktreesChannelOptions {
6
- intervalMs?: number;
7
- operations?: WorktreeOperations;
8
- homeDir?: string;
9
- registryDir?: string;
10
- isSessionLive?: (registryDir: string, sessionId: string) => boolean;
11
- }
12
- /**
13
- * One poller per managed session, publishing only when the list actually
14
- * changed so an idle cockpit stays quiet.
15
- */
16
- declare function createWorktreesChannel(options?: WorktreesChannelOptions): WebHubChannel;
17
- /** The named export the generated hub channel registry imports. */
18
- declare const webHubChannels: readonly WebHubChannel[];
19
- //#endregion
20
- export { WorktreesChannelOptions, createWorktreesChannel, webHubChannels };
21
- //# sourceMappingURL=worktreesChannel.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worktreesChannel.d.mts","names":[],"sources":["../../../src/adapters/web/worktreesChannel.ts"],"mappings":";;;;UAkDiB;EACf;EACA,aAAa;EACb;EACA;EACA,iBAAiB,qBAAqB;;;;;;iBAsFxB,uBAAuB,UAAS,0BAA+B;;cA6HlE,yBAAyB"}
@@ -1,2 +0,0 @@
1
- import{createWorktreeGit as e}from"../worktree/gitCli.mjs";import{sessionIsLive as t}from"../hub/hubClient.mjs";import{hubRegistryDir as n,registryFile as r}from"../filesystem/paths.mjs";import{createWorktreeRegistry as i}from"../worktree/worktreeRegistry.mjs";import{DoomGitExpectedError as a}from"../../services/support/errors.mjs";import{createWorktreeOperations as o}from"../worktree/worktreeOperations.mjs";import{GIT_WORKTREES_TYPE as s}from"../../types/webWorktrees.mjs";function c(e){return{worktrees:e.worktrees,...e.pending===void 0?{}:{pending:e.pending},...e.error===void 0?{}:{error:e.error}}}function l(e,t){try{return i(r(e.cwd,t.homeDir)).list().flatMap(n=>{let r=n.parentSessionId===e.sessionId,i=!r&&!t.isSessionLive(t.registryDir,n.parentSessionId);return!r&&!i?[]:[{id:n.id,branch:n.branch,path:n.path,sessionId:n.status===`orphaned`?null:n.sessionId,orphaned:n.status===`orphaned`,unowned:i}]})}catch{return[]}}function u(e,t){return e.length===t.length&&e.every((e,n)=>{let r=t[n];return r!==void 0&&e.id===r.id&&e.branch===r.branch&&e.sessionId===r.sessionId&&e.orphaned===r.orphaned&&e.unowned===r.unowned})}function d(e){if(typeof e!=`object`||!e)return;let t=e;if(t.action===`create`)return typeof t.branch!=`string`||t.branch.trim()===``?void 0:{action:`create`,branch:t.branch.trim(),...typeof t.baseRef==`string`&&t.baseRef.trim()!==``?{baseRef:t.baseRef.trim()}:{}};if(t.action===`close`)return typeof t.id!=`string`||t.id===``?void 0:{action:`close`,id:t.id,...t.force===!0?{force:!0}:{}}}function f(e){return e instanceof a?e.message:`The worktree operation failed.`}function p(r={}){let i=r.intervalMs??4e3,a;return{frameType:s,start(s){let p=r.operations??o({git:e()}),m={registryDir:r.registryDir??n(),homeDir:r.homeDir,isSessionLive:r.isSessionLive??t},h=new Map,g=new Map,_=new Map,v=new Set,y=(e,t)=>{let n=h.get(e.sessionId),r={worktrees:l(e,m),pending:n?.pending,error:n?.error};(t||n===void 0||!u(n.worktrees,r.worktrees)||n.pending!==r.pending||n.error!==r.error)&&(h.set(e.sessionId,r),s.publish(e.sessionId,c(r)))},b=(e,t,n)=>{let r=h.get(e.sessionId);h.set(e.sessionId,{worktrees:r?.worktrees??l(e,m),pending:t,error:n}),y(e,!0)},x=async(e,t)=>{let n={cwd:e.cwd,sessionId:e.sessionId};if(t.action===`create`){b(e,`creating ${t.branch}\u2026`,void 0),await p.spawn(n,{branch:t.branch,...t.baseRef===void 0?{}:{baseRef:t.baseRef}});return}b(e,`closing ${t.id}\u2026`,void 0),await p.close(n,t.id,t.force??!1)};return a=(e,t)=>{let n=d(t);n!==void 0&&(v.has(e.sessionId)||(v.add(e.sessionId),x(e,n).then(()=>{b(e,void 0,void 0)}).catch(t=>{s.onNotice(`worktree command failed for ${e.sessionId} (${f(t)})`),b(e,void 0,f(t))}).finally(()=>{v.delete(e.sessionId)})))},{payloadFor(e){let t=h.get(e.sessionId);return t===void 0?void 0:c(t)},sessionAdded(e){g.set(e.sessionId,e),y(e,!1);let t=setInterval(()=>{y(e,!1)},i);t.unref?.(),_.set(e.sessionId,t)},sessionRemoved(e){let t=_.get(e);t!==void 0&&clearInterval(t),_.delete(e),h.delete(e),g.delete(e),v.delete(e)},close(){for(let e of _.values())clearInterval(e);_.clear(),h.clear(),g.clear(),v.clear()}}},receive(e,t){a?.(e,t)}}}const m=[p()];export{p as createWorktreesChannel,m as webHubChannels};
2
- //# sourceMappingURL=worktreesChannel.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worktreesChannel.mjs","names":[],"sources":["../../../src/adapters/web/worktreesChannel.ts"],"sourcesContent":["/**\n * The worktrees data channel, and the commands the dock sends back through it.\n *\n * DESIGN PATTERNS:\n * - Hub-scoped: it reads this machine's registry files, which a browser cannot.\n * - Scoped to the session that owns the worktree, not to the repository. Two\n * sessions in one checkout are two pieces of work; showing each the other's\n * worktrees offered a close button for something the reader never made.\n * A worktree whose parent session is gone is the exception: it is shown to\n * everyone in the repository, marked unowned, or nobody could ever close it.\n * - Poll, do not watch. The registry is a single small JSON file written by\n * this package alone, and a watcher on a path that may not exist yet costs\n * more than re-reading a few hundred bytes.\n * - Commands run the same `worktreeOperations` the tool runs, so the panel and\n * the agent cannot drift into two ideas of what creating a worktree means.\n * - Publish only what the dock renders. Paths and provenance stay in the\n * registry; the page gets a view.\n *\n * AVOID:\n * - Throwing out of a poll or a command. A registry that cannot be read is an\n * empty list, and a failed command is an error string on the next frame:\n * the dock degrades to what it can tell the reader, never to a dead channel.\n */\nimport type { HubChannelSource, HubSessionScope, WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport { GIT_WORKTREES_TYPE, type GitWorktreesCommand, type WorktreeView } from '../../types/webWorktrees.ts';\nimport { DoomGitExpectedError } from '../../services/support/errors.ts';\nimport { hubRegistryDir, registryFile } from '../filesystem/paths.ts';\nimport { sessionIsLive } from '../hub/hubClient.ts';\nimport { createWorktreeGit } from '../worktree/gitCli.ts';\nimport { createWorktreeOperations, type WorktreeOperations } from '../worktree/worktreeOperations.ts';\nimport { createWorktreeRegistry } from '../worktree/worktreeRegistry.ts';\n\n/** How often a session's registry is re-read. Worktrees change by hand, not by the second. */\nconst POLL_MS = 4000;\n\ninterface SessionState {\n worktrees: WorktreeView[];\n pending: string | undefined;\n error: string | undefined;\n}\n\nfunction payloadOf(state: SessionState): Record<string, unknown> {\n return {\n worktrees: state.worktrees,\n ...(state.pending === undefined ? {} : { pending: state.pending }),\n ...(state.error === undefined ? {} : { error: state.error }),\n };\n}\n\n/** What the channel needs from its surroundings, injected so tests stay off this machine. */\nexport interface WorktreesChannelOptions {\n intervalMs?: number;\n operations?: WorktreeOperations;\n homeDir?: string;\n registryDir?: string;\n isSessionLive?: (registryDir: string, sessionId: string) => boolean;\n}\n\ninterface ViewDeps {\n registryDir: string;\n homeDir: string | undefined;\n isSessionLive: (registryDir: string, sessionId: string) => boolean;\n}\n\n/**\n * The worktrees one session may see: its own, plus any whose parent session\n * has gone. Liveness is the registry record's presence, the same signal the\n * rail uses to decide a session exists at all.\n */\nfunction viewsFor(scope: HubSessionScope, deps: ViewDeps): WorktreeView[] {\n try {\n return createWorktreeRegistry(registryFile(scope.cwd, deps.homeDir))\n .list()\n .flatMap((record) => {\n const owned = record.parentSessionId === scope.sessionId;\n const unowned = !owned && !deps.isSessionLive(deps.registryDir, record.parentSessionId);\n if (!owned && !unowned) return [];\n return [\n {\n id: record.id,\n branch: record.branch,\n path: record.path,\n sessionId: record.status === 'orphaned' ? null : record.sessionId,\n orphaned: record.status === 'orphaned',\n unowned,\n },\n ];\n });\n } catch {\n return [];\n }\n}\n\nfunction sameList(left: readonly WorktreeView[], right: readonly WorktreeView[]): boolean {\n if (left.length !== right.length) return false;\n return left.every((entry, index) => {\n const other = right[index];\n return (\n other !== undefined &&\n entry.id === other.id &&\n entry.branch === other.branch &&\n entry.sessionId === other.sessionId &&\n entry.orphaned === other.orphaned &&\n entry.unowned === other.unowned\n );\n });\n}\n\n/** A command the page sent, or undefined when the frame was not one. */\nfunction parseCommand(payload: unknown): GitWorktreesCommand | undefined {\n if (typeof payload !== 'object' || payload === null) return undefined;\n const value = payload as Record<string, unknown>;\n if (value.action === 'create') {\n if (typeof value.branch !== 'string' || value.branch.trim() === '') return undefined;\n return {\n action: 'create',\n branch: value.branch.trim(),\n ...(typeof value.baseRef === 'string' && value.baseRef.trim() !== '' ? { baseRef: value.baseRef.trim() } : {}),\n };\n }\n if (value.action === 'close') {\n if (typeof value.id !== 'string' || value.id === '') return undefined;\n return { action: 'close', id: value.id, ...(value.force === true ? { force: true } : {}) };\n }\n return undefined;\n}\n\n/**\n * What the reader is told when a command fails.\n *\n * An expected failure explains itself. Anything else is summarised, never\n * echoed: a git error can carry a remote URL with a token in it.\n */\nfunction failureText(error: unknown): string {\n return error instanceof DoomGitExpectedError ? error.message : 'The worktree operation failed.';\n}\n\n/**\n * One poller per managed session, publishing only when the list actually\n * changed so an idle cockpit stays quiet.\n */\nexport function createWorktreesChannel(options: WorktreesChannelOptions = {}): WebHubChannel {\n const intervalMs = options.intervalMs ?? POLL_MS;\n // Assigned by start, because receive is reachable only while the channel is\n // running and the command needs the state the poller keeps.\n let receiveCommand: ((scope: HubSessionScope, payload: unknown) => void) | undefined;\n\n return {\n frameType: GIT_WORKTREES_TYPE,\n start(host) {\n const worktrees = options.operations ?? createWorktreeOperations({ git: createWorktreeGit() });\n const views: ViewDeps = {\n registryDir: options.registryDir ?? hubRegistryDir(),\n homeDir: options.homeDir,\n isSessionLive: options.isSessionLive ?? sessionIsLive,\n };\n const latest = new Map<string, SessionState>();\n const scopes = new Map<string, HubSessionScope>();\n const timers = new Map<string, ReturnType<typeof setInterval>>();\n const busy = new Set<string>();\n\n /** Republishes a session, skipping a frame that would say nothing new. */\n const publish = (scope: HubSessionScope, force: boolean): void => {\n const previous = latest.get(scope.sessionId);\n const next: SessionState = {\n worktrees: viewsFor(scope, views),\n pending: previous?.pending,\n error: previous?.error,\n };\n if (\n !force &&\n previous !== undefined &&\n sameList(previous.worktrees, next.worktrees) &&\n previous.pending === next.pending &&\n previous.error === next.error\n ) {\n return;\n }\n latest.set(scope.sessionId, next);\n host.publish(scope.sessionId, payloadOf(next));\n };\n\n /** Sets the operation label and clears the last failure, then publishes. */\n const mark = (scope: HubSessionScope, pending: string | undefined, error: string | undefined): void => {\n const previous = latest.get(scope.sessionId);\n latest.set(scope.sessionId, {\n worktrees: previous?.worktrees ?? viewsFor(scope, views),\n pending,\n error,\n });\n publish(scope, true);\n };\n\n const run = async (scope: HubSessionScope, command: GitWorktreesCommand): Promise<void> => {\n const context = { cwd: scope.cwd, sessionId: scope.sessionId };\n if (command.action === 'create') {\n mark(scope, `creating ${command.branch}\\u2026`, undefined);\n await worktrees.spawn(context, {\n branch: command.branch,\n ...(command.baseRef === undefined ? {} : { baseRef: command.baseRef }),\n });\n return;\n }\n mark(scope, `closing ${command.id}\\u2026`, undefined);\n await worktrees.close(context, command.id, command.force ?? false);\n };\n\n const source: HubChannelSource = {\n payloadFor(scope) {\n const state = latest.get(scope.sessionId);\n return state === undefined ? undefined : payloadOf(state);\n },\n sessionAdded(scope) {\n scopes.set(scope.sessionId, scope);\n publish(scope, false);\n const timer = setInterval(() => {\n publish(scope, false);\n }, intervalMs);\n timer.unref?.();\n timers.set(scope.sessionId, timer);\n },\n sessionRemoved(sessionId) {\n const timer = timers.get(sessionId);\n if (timer !== undefined) clearInterval(timer);\n timers.delete(sessionId);\n latest.delete(sessionId);\n scopes.delete(sessionId);\n busy.delete(sessionId);\n },\n close() {\n for (const timer of timers.values()) clearInterval(timer);\n timers.clear();\n latest.clear();\n scopes.clear();\n busy.clear();\n },\n };\n receiveCommand = (scope, payload) => {\n const command = parseCommand(payload);\n if (command === undefined) return;\n // One operation per session. A create runs for minutes, and a second\n // click must not start a second worktree behind the first.\n if (busy.has(scope.sessionId)) return;\n busy.add(scope.sessionId);\n void run(scope, command)\n .then(() => {\n mark(scope, undefined, undefined);\n })\n .catch((error: unknown) => {\n host.onNotice(`worktree command failed for ${scope.sessionId} (${failureText(error)})`);\n mark(scope, undefined, failureText(error));\n })\n .finally(() => {\n busy.delete(scope.sessionId);\n });\n };\n\n return source;\n },\n receive(scope, payload) {\n receiveCommand?.(scope, payload);\n },\n };\n}\n\n/** The named export the generated hub channel registry imports. */\nexport const webHubChannels: readonly WebHubChannel[] = [createWorktreesChannel()];\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:child_process"),n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);const i=[{file:`pnpm-lock.yaml`,command:`pnpm`,args:[`install`,`--prefer-offline`]},{file:`yarn.lock`,command:`yarn`,args:[`install`]},{file:`package-lock.json`,command:`npm`,args:[`install`]},{file:`bun.lockb`,command:`bun`,args:[`install`]}];function a(e){return i.find(t=>n.default.existsSync(r.default.join(e,t.file)))}async function o(e,i=Date.now){if(!n.default.existsSync(r.default.join(e,`package.json`)))return{kind:`skipped`,reason:`no-manifest`};let o=a(e);if(o===void 0)return{kind:`skipped`,reason:`no-lockfile`};let s=i();return await new Promise(n=>{(0,t.execFile)(o.command,[...o.args],{cwd:e,timeout:9e5,env:{...process.env,LC_ALL:`C`}},(e,t,r)=>{if(e===null){n({kind:`installed`,command:o.command,durationMs:i()-s});return}n({kind:`failed`,command:o.command,code:typeof e.code==`number`?e.code:null,stderrBytes:Buffer.byteLength(String(r))})})})}exports.installDependencies=o,exports.installerFor=a;
2
- //# sourceMappingURL=dependencyInstall.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dependencyInstall.cjs","names":["fs","path"],"sources":["../../../src/adapters/worktree/dependencyInstall.ts"],"sourcesContent":["/**\n * Installing a fresh worktree's dependencies.\n *\n * DESIGN PATTERNS:\n * - The package manager is chosen by the lockfile the repository already\n * committed, never guessed and never configured. A worktree shares the\n * parent's history, so the lockfile is always the right answer.\n * - A repository with no manifest installs nothing and reports that plainly.\n * Not every checkout is a Node project.\n * - Failure is reported, not thrown. A worktree whose install failed is still\n * a usable worktree; refusing to create it would be a worse trade than\n * letting the caller decide.\n *\n * AVOID:\n * - Carrying stdout or stderr into the result. Installers print absolute paths\n * and registry URLs; the caller gets a code and a byte length.\n */\nimport { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * A cold install pulls the whole store. The repository's own measured install\n * is well under a minute when the store is warm, but a first run on a new\n * machine is not, and killing it half way leaves a broken node_modules.\n */\nconst INSTALL_TIMEOUT_MS = 900_000;\n\nconst LOCKFILES: ReadonlyArray<{ file: string; command: string; args: readonly string[] }> = [\n { file: 'pnpm-lock.yaml', command: 'pnpm', args: ['install', '--prefer-offline'] },\n { file: 'yarn.lock', command: 'yarn', args: ['install'] },\n { file: 'package-lock.json', command: 'npm', args: ['install'] },\n { file: 'bun.lockb', command: 'bun', args: ['install'] },\n];\n\nexport type InstallOutcome =\n | { kind: 'skipped'; reason: 'no-manifest' | 'no-lockfile' }\n | { kind: 'installed'; command: string; durationMs: number }\n | { kind: 'failed'; command: string; code: number | null; stderrBytes: number };\n\n/** Chooses the installer this repository already uses, by its committed lockfile. */\nexport function installerFor(root: string): { command: string; args: readonly string[] } | undefined {\n return LOCKFILES.find((entry) => fs.existsSync(path.join(root, entry.file)));\n}\n\n/**\n * Installs into a freshly created worktree.\n *\n * The session that opens in the worktree resolves the repository's own\n * composition, and every workspace package path in it points at a directory\n * that only exists once dependencies are linked. Without this the session\n * falls back to the global bundle and silently loses the repository's packages.\n */\nexport async function installDependencies(root: string, now: () => number = Date.now): Promise<InstallOutcome> {\n if (!fs.existsSync(path.join(root, 'package.json'))) return { kind: 'skipped', reason: 'no-manifest' };\n const installer = installerFor(root);\n if (installer === undefined) return { kind: 'skipped', reason: 'no-lockfile' };\n\n const startedAt = now();\n return await new Promise<InstallOutcome>((resolve) => {\n execFile(\n installer.command,\n [...installer.args],\n { cwd: root, timeout: INSTALL_TIMEOUT_MS, env: { ...process.env, LC_ALL: 'C' } },\n (error, _stdout, stderr) => {\n if (error === null) {\n resolve({ kind: 'installed', command: installer.command, durationMs: now() - startedAt });\n return;\n }\n resolve({\n kind: 'failed',\n command: installer.command,\n code: typeof error.code === 'number' ? error.code : null,\n stderrBytes: Buffer.byteLength(String(stderr)),\n });\n },\n );\n });\n}\n"],"mappings":"8KA0BA,MAEM,EAAuF,CAC3F,CAAE,KAAM,iBAAkB,QAAS,OAAQ,KAAM,CAAC,UAAW,kBAAkB,CAAE,EACjF,CAAE,KAAM,YAAa,QAAS,OAAQ,KAAM,CAAC,SAAS,CAAE,EACxD,CAAE,KAAM,oBAAqB,QAAS,MAAO,KAAM,CAAC,SAAS,CAAE,EAC/D,CAAE,KAAM,YAAa,QAAS,MAAO,KAAM,CAAC,SAAS,CAAE,CACzD,EAQA,SAAgB,EAAa,EAAwE,CACnG,OAAO,EAAU,KAAM,GAAUA,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAK,EAAM,EAAM,IAAI,CAAC,CAAC,CAC7E,CAUA,eAAsB,EAAoB,EAAc,EAAoB,KAAK,IAA8B,CAC7G,GAAI,CAACD,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAK,EAAM,cAAc,CAAC,EAAG,MAAO,CAAE,KAAM,UAAW,OAAQ,aAAc,EACrG,IAAM,EAAY,EAAa,CAAI,EACnC,GAAI,IAAc,IAAA,GAAW,MAAO,CAAE,KAAM,UAAW,OAAQ,aAAc,EAE7E,IAAM,EAAY,EAAI,EACtB,OAAO,MAAM,IAAI,QAAyB,GAAY,EACpD,EAAA,EAAA,SAAA,CACE,EAAU,QACV,CAAC,GAAG,EAAU,IAAI,EAClB,CAAE,IAAK,EAAM,QAAS,IAAoB,IAAK,CAAE,GAAG,QAAQ,IAAK,OAAQ,GAAI,CAAE,GAC9E,EAAO,EAAS,IAAW,CAC1B,GAAI,IAAU,KAAM,CAClB,EAAQ,CAAE,KAAM,YAAa,QAAS,EAAU,QAAS,WAAY,EAAI,EAAI,CAAU,CAAC,EACxF,MACF,CACA,EAAQ,CACN,KAAM,SACN,QAAS,EAAU,QACnB,KAAM,OAAO,EAAM,MAAS,SAAW,EAAM,KAAO,KACpD,YAAa,OAAO,WAAW,OAAO,CAAM,CAAC,CAC/C,CAAC,CACH,CACF,CACF,CAAC,CACH"}
@@ -1,2 +0,0 @@
1
- import{execFile as e}from"node:child_process";import t from"node:fs";import n from"node:path";const r=[{file:`pnpm-lock.yaml`,command:`pnpm`,args:[`install`,`--prefer-offline`]},{file:`yarn.lock`,command:`yarn`,args:[`install`]},{file:`package-lock.json`,command:`npm`,args:[`install`]},{file:`bun.lockb`,command:`bun`,args:[`install`]}];function i(e){return r.find(r=>t.existsSync(n.join(e,r.file)))}async function a(r,a=Date.now){if(!t.existsSync(n.join(r,`package.json`)))return{kind:`skipped`,reason:`no-manifest`};let o=i(r);if(o===void 0)return{kind:`skipped`,reason:`no-lockfile`};let s=a();return await new Promise(t=>{e(o.command,[...o.args],{cwd:r,timeout:9e5,env:{...process.env,LC_ALL:`C`}},(e,n,r)=>{if(e===null){t({kind:`installed`,command:o.command,durationMs:a()-s});return}t({kind:`failed`,command:o.command,code:typeof e.code==`number`?e.code:null,stderrBytes:Buffer.byteLength(String(r))})})})}export{a as installDependencies,i as installerFor};
2
- //# sourceMappingURL=dependencyInstall.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dependencyInstall.mjs","names":[],"sources":["../../../src/adapters/worktree/dependencyInstall.ts"],"sourcesContent":["/**\n * Installing a fresh worktree's dependencies.\n *\n * DESIGN PATTERNS:\n * - The package manager is chosen by the lockfile the repository already\n * committed, never guessed and never configured. A worktree shares the\n * parent's history, so the lockfile is always the right answer.\n * - A repository with no manifest installs nothing and reports that plainly.\n * Not every checkout is a Node project.\n * - Failure is reported, not thrown. A worktree whose install failed is still\n * a usable worktree; refusing to create it would be a worse trade than\n * letting the caller decide.\n *\n * AVOID:\n * - Carrying stdout or stderr into the result. Installers print absolute paths\n * and registry URLs; the caller gets a code and a byte length.\n */\nimport { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * A cold install pulls the whole store. The repository's own measured install\n * is well under a minute when the store is warm, but a first run on a new\n * machine is not, and killing it half way leaves a broken node_modules.\n */\nconst INSTALL_TIMEOUT_MS = 900_000;\n\nconst LOCKFILES: ReadonlyArray<{ file: string; command: string; args: readonly string[] }> = [\n { file: 'pnpm-lock.yaml', command: 'pnpm', args: ['install', '--prefer-offline'] },\n { file: 'yarn.lock', command: 'yarn', args: ['install'] },\n { file: 'package-lock.json', command: 'npm', args: ['install'] },\n { file: 'bun.lockb', command: 'bun', args: ['install'] },\n];\n\nexport type InstallOutcome =\n | { kind: 'skipped'; reason: 'no-manifest' | 'no-lockfile' }\n | { kind: 'installed'; command: string; durationMs: number }\n | { kind: 'failed'; command: string; code: number | null; stderrBytes: number };\n\n/** Chooses the installer this repository already uses, by its committed lockfile. */\nexport function installerFor(root: string): { command: string; args: readonly string[] } | undefined {\n return LOCKFILES.find((entry) => fs.existsSync(path.join(root, entry.file)));\n}\n\n/**\n * Installs into a freshly created worktree.\n *\n * The session that opens in the worktree resolves the repository's own\n * composition, and every workspace package path in it points at a directory\n * that only exists once dependencies are linked. Without this the session\n * falls back to the global bundle and silently loses the repository's packages.\n */\nexport async function installDependencies(root: string, now: () => number = Date.now): Promise<InstallOutcome> {\n if (!fs.existsSync(path.join(root, 'package.json'))) return { kind: 'skipped', reason: 'no-manifest' };\n const installer = installerFor(root);\n if (installer === undefined) return { kind: 'skipped', reason: 'no-lockfile' };\n\n const startedAt = now();\n return await new Promise<InstallOutcome>((resolve) => {\n execFile(\n installer.command,\n [...installer.args],\n { cwd: root, timeout: INSTALL_TIMEOUT_MS, env: { ...process.env, LC_ALL: 'C' } },\n (error, _stdout, stderr) => {\n if (error === null) {\n resolve({ kind: 'installed', command: installer.command, durationMs: now() - startedAt });\n return;\n }\n resolve({\n kind: 'failed',\n command: installer.command,\n code: typeof error.code === 'number' ? error.code : null,\n stderrBytes: Buffer.byteLength(String(stderr)),\n });\n },\n );\n });\n}\n"],"mappings":"8FA0BA,MAEM,EAAuF,CAC3F,CAAE,KAAM,iBAAkB,QAAS,OAAQ,KAAM,CAAC,UAAW,kBAAkB,CAAE,EACjF,CAAE,KAAM,YAAa,QAAS,OAAQ,KAAM,CAAC,SAAS,CAAE,EACxD,CAAE,KAAM,oBAAqB,QAAS,MAAO,KAAM,CAAC,SAAS,CAAE,EAC/D,CAAE,KAAM,YAAa,QAAS,MAAO,KAAM,CAAC,SAAS,CAAE,CACzD,EAQA,SAAgB,EAAa,EAAwE,CACnG,OAAO,EAAU,KAAM,GAAU,EAAG,WAAW,EAAK,KAAK,EAAM,EAAM,IAAI,CAAC,CAAC,CAC7E,CAUA,eAAsB,EAAoB,EAAc,EAAoB,KAAK,IAA8B,CAC7G,GAAI,CAAC,EAAG,WAAW,EAAK,KAAK,EAAM,cAAc,CAAC,EAAG,MAAO,CAAE,KAAM,UAAW,OAAQ,aAAc,EACrG,IAAM,EAAY,EAAa,CAAI,EACnC,GAAI,IAAc,IAAA,GAAW,MAAO,CAAE,KAAM,UAAW,OAAQ,aAAc,EAE7E,IAAM,EAAY,EAAI,EACtB,OAAO,MAAM,IAAI,QAAyB,GAAY,CACpD,EACE,EAAU,QACV,CAAC,GAAG,EAAU,IAAI,EAClB,CAAE,IAAK,EAAM,QAAS,IAAoB,IAAK,CAAE,GAAG,QAAQ,IAAK,OAAQ,GAAI,CAAE,GAC9E,EAAO,EAAS,IAAW,CAC1B,GAAI,IAAU,KAAM,CAClB,EAAQ,CAAE,KAAM,YAAa,QAAS,EAAU,QAAS,WAAY,EAAI,EAAI,CAAU,CAAC,EACxF,MACF,CACA,EAAQ,CACN,KAAM,SACN,QAAS,EAAU,QACnB,KAAM,OAAO,EAAM,MAAS,SAAW,EAAM,KAAO,KACpD,YAAa,OAAO,WAAW,OAAO,CAAM,CAAC,CAC/C,CAAC,CACH,CACF,CACF,CAAC,CACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"gitCli.cjs","names":["fs"],"sources":["../../../src/adapters/worktree/gitCli.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport type { WorktreeGit } from '../../types/worktreeRegistry.ts';\n\n/**\n * Git, as this package needs it.\n *\n * The timeouts are not uniform because the operations are not comparable. A\n * read answers in milliseconds; `worktree add` copies an entire checkout and\n * `worktree remove` deletes one, both bounded by the filesystem rather than by\n * git, and both minutes rather than seconds on a large repository or on\n * Windows. A single timeout would either fail honest work or hang on a wedged\n * one.\n */\nconst READ_TIMEOUT_MS = 30_000;\nconst ADD_TIMEOUT_MS = 300_000;\nconst REMOVE_TIMEOUT_MS = 300_000;\nconst PRUNE_TIMEOUT_MS = 15_000;\nconst MAX_OUTPUT_BYTES = 1_000_000;\n\ninterface GitResult {\n code: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * Runs git and reports the outcome without ever throwing on a non-zero exit.\n *\n * LC_ALL is pinned because two callers below decide what to do by matching\n * git's own wording, and a translated message would silently turn a recognised\n * condition into an unrecognised failure.\n */\nfunction git(cwd: string, args: readonly string[], timeout: number): Promise<GitResult> {\n return new Promise((resolve) => {\n execFile(\n 'git',\n [...args],\n { cwd, timeout, maxBuffer: MAX_OUTPUT_BYTES, env: { ...process.env, LC_ALL: 'C' } },\n (error, stdout, stderr) => {\n const code = error && typeof error.code === 'number' ? error.code : error ? 1 : 0;\n resolve({ code, stdout: String(stdout), stderr: String(stderr) });\n },\n );\n });\n}\n\n/**\n * The message a failed git call is allowed to surface.\n *\n * Raw stderr never travels: it can carry a remote URL with a token in it, and\n * this text ends up in a tool result the model reads and may repeat.\n */\nfunction failure(operation: string, result: GitResult): Error {\n return new Error(\n `git ${operation} failed (exit ${String(result.code)}, ${String(result.stderr.length)} bytes of stderr)`,\n );\n}\n\n/** Git's own words for \"that worktree is not there any more\". */\nfunction missingWorktree(stderr: string): boolean {\n const normalized = stderr.toLowerCase();\n return normalized.includes('is not a working tree') || normalized.includes('cannot remove working tree');\n}\n\nexport function createWorktreeGit(): WorktreeGit {\n const prune = async (repositoryRoot: string): Promise<void> => {\n await git(repositoryRoot, ['worktree', 'prune'], PRUNE_TIMEOUT_MS);\n };\n\n return {\n async addWorktree({ repositoryRoot, path, branch, baseRef }) {\n const result = await git(repositoryRoot, ['worktree', 'add', '-b', branch, path, baseRef], ADD_TIMEOUT_MS);\n if (result.code !== 0) throw failure('worktree add', result);\n },\n\n async removeWorktree({ repositoryRoot, path, force }) {\n const args = ['worktree', 'remove', ...(force ? ['--force'] : []), path];\n const result = await git(repositoryRoot, args, REMOVE_TIMEOUT_MS);\n if (result.code === 0) return;\n // A directory deleted behind git's back leaves an administrative entry\n // that makes a later `worktree add` at the same path refuse. Treating\n // \"already gone\" as success and pruning is what keeps that path reusable,\n // and it makes remove idempotent for a caller retrying after a crash.\n if (missingWorktree(result.stderr) && !fs.existsSync(path)) {\n await prune(repositoryRoot);\n return;\n }\n throw failure('worktree remove', result);\n },\n\n pruneWorktrees: prune,\n\n async listWorktreePaths(repositoryRoot) {\n const result = await git(repositoryRoot, ['worktree', 'list', '--porcelain'], READ_TIMEOUT_MS);\n if (result.code !== 0) return [];\n return result.stdout\n .split('\\n')\n .filter((line) => line.startsWith('worktree '))\n .map((line) => line.slice('worktree '.length).trim())\n .filter((line) => line !== '');\n },\n\n async dirtyFiles(path) {\n const result = await git(path, ['status', '--porcelain'], READ_TIMEOUT_MS);\n if (result.code !== 0) return [];\n return result.stdout\n .split('\\n')\n .map((line) => line.slice(3).trim())\n .filter((line) => line !== '');\n },\n\n async repositoryRoot(cwd) {\n const result = await git(cwd, ['rev-parse', '--show-toplevel'], READ_TIMEOUT_MS);\n if (result.code !== 0) return undefined;\n const root = result.stdout.trim();\n return root === '' ? undefined : root;\n },\n\n async currentBranch(cwd) {\n const result = await git(cwd, ['rev-parse', '--abbrev-ref', 'HEAD'], READ_TIMEOUT_MS);\n if (result.code !== 0) return undefined;\n const branch = result.stdout.trim();\n return branch === '' ? undefined : branch;\n },\n\n async mergeBranch({ repositoryRoot, branch, message }) {\n // --no-ff so the worktree's work stays identifiable as a unit after the\n // fact. A fast-forward would erase the fact that it was ever separate,\n // which is the one thing worth keeping about a worktree.\n const args = ['merge', '--no-ff', ...(message === undefined ? [] : ['-m', message]), branch];\n const result = await git(repositoryRoot, args, READ_TIMEOUT_MS);\n if (result.code === 0) return;\n // A conflicted merge leaves the parent mid-merge, which is a state the\n // person has to resolve; aborting for them would throw away the conflict\n // markers they need to see.\n throw failure('merge', result);\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"gitCli.mjs","names":[],"sources":["../../../src/adapters/worktree/gitCli.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport type { WorktreeGit } from '../../types/worktreeRegistry.ts';\n\n/**\n * Git, as this package needs it.\n *\n * The timeouts are not uniform because the operations are not comparable. A\n * read answers in milliseconds; `worktree add` copies an entire checkout and\n * `worktree remove` deletes one, both bounded by the filesystem rather than by\n * git, and both minutes rather than seconds on a large repository or on\n * Windows. A single timeout would either fail honest work or hang on a wedged\n * one.\n */\nconst READ_TIMEOUT_MS = 30_000;\nconst ADD_TIMEOUT_MS = 300_000;\nconst REMOVE_TIMEOUT_MS = 300_000;\nconst PRUNE_TIMEOUT_MS = 15_000;\nconst MAX_OUTPUT_BYTES = 1_000_000;\n\ninterface GitResult {\n code: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * Runs git and reports the outcome without ever throwing on a non-zero exit.\n *\n * LC_ALL is pinned because two callers below decide what to do by matching\n * git's own wording, and a translated message would silently turn a recognised\n * condition into an unrecognised failure.\n */\nfunction git(cwd: string, args: readonly string[], timeout: number): Promise<GitResult> {\n return new Promise((resolve) => {\n execFile(\n 'git',\n [...args],\n { cwd, timeout, maxBuffer: MAX_OUTPUT_BYTES, env: { ...process.env, LC_ALL: 'C' } },\n (error, stdout, stderr) => {\n const code = error && typeof error.code === 'number' ? error.code : error ? 1 : 0;\n resolve({ code, stdout: String(stdout), stderr: String(stderr) });\n },\n );\n });\n}\n\n/**\n * The message a failed git call is allowed to surface.\n *\n * Raw stderr never travels: it can carry a remote URL with a token in it, and\n * this text ends up in a tool result the model reads and may repeat.\n */\nfunction failure(operation: string, result: GitResult): Error {\n return new Error(\n `git ${operation} failed (exit ${String(result.code)}, ${String(result.stderr.length)} bytes of stderr)`,\n );\n}\n\n/** Git's own words for \"that worktree is not there any more\". */\nfunction missingWorktree(stderr: string): boolean {\n const normalized = stderr.toLowerCase();\n return normalized.includes('is not a working tree') || normalized.includes('cannot remove working tree');\n}\n\nexport function createWorktreeGit(): WorktreeGit {\n const prune = async (repositoryRoot: string): Promise<void> => {\n await git(repositoryRoot, ['worktree', 'prune'], PRUNE_TIMEOUT_MS);\n };\n\n return {\n async addWorktree({ repositoryRoot, path, branch, baseRef }) {\n const result = await git(repositoryRoot, ['worktree', 'add', '-b', branch, path, baseRef], ADD_TIMEOUT_MS);\n if (result.code !== 0) throw failure('worktree add', result);\n },\n\n async removeWorktree({ repositoryRoot, path, force }) {\n const args = ['worktree', 'remove', ...(force ? ['--force'] : []), path];\n const result = await git(repositoryRoot, args, REMOVE_TIMEOUT_MS);\n if (result.code === 0) return;\n // A directory deleted behind git's back leaves an administrative entry\n // that makes a later `worktree add` at the same path refuse. Treating\n // \"already gone\" as success and pruning is what keeps that path reusable,\n // and it makes remove idempotent for a caller retrying after a crash.\n if (missingWorktree(result.stderr) && !fs.existsSync(path)) {\n await prune(repositoryRoot);\n return;\n }\n throw failure('worktree remove', result);\n },\n\n pruneWorktrees: prune,\n\n async listWorktreePaths(repositoryRoot) {\n const result = await git(repositoryRoot, ['worktree', 'list', '--porcelain'], READ_TIMEOUT_MS);\n if (result.code !== 0) return [];\n return result.stdout\n .split('\\n')\n .filter((line) => line.startsWith('worktree '))\n .map((line) => line.slice('worktree '.length).trim())\n .filter((line) => line !== '');\n },\n\n async dirtyFiles(path) {\n const result = await git(path, ['status', '--porcelain'], READ_TIMEOUT_MS);\n if (result.code !== 0) return [];\n return result.stdout\n .split('\\n')\n .map((line) => line.slice(3).trim())\n .filter((line) => line !== '');\n },\n\n async repositoryRoot(cwd) {\n const result = await git(cwd, ['rev-parse', '--show-toplevel'], READ_TIMEOUT_MS);\n if (result.code !== 0) return undefined;\n const root = result.stdout.trim();\n return root === '' ? undefined : root;\n },\n\n async currentBranch(cwd) {\n const result = await git(cwd, ['rev-parse', '--abbrev-ref', 'HEAD'], READ_TIMEOUT_MS);\n if (result.code !== 0) return undefined;\n const branch = result.stdout.trim();\n return branch === '' ? undefined : branch;\n },\n\n async mergeBranch({ repositoryRoot, branch, message }) {\n // --no-ff so the worktree's work stays identifiable as a unit after the\n // fact. A fast-forward would erase the fact that it was ever separate,\n // which is the one thing worth keeping about a worktree.\n const args = ['merge', '--no-ff', ...(message === undefined ? [] : ['-m', message]), branch];\n const result = await git(repositoryRoot, args, READ_TIMEOUT_MS);\n if (result.code === 0) return;\n // A conflicted merge leaves the parent mid-merge, which is a state the\n // person has to resolve; aborting for them would throw away the conflict\n // markers they need to see.\n throw failure('merge', result);\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"repositoryIdentity.cjs","names":["path","fs","createHash"],"sources":["../../../src/adapters/worktree/repositoryIdentity.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nconst ID_LENGTH = 12;\nconst MAX_LABEL_LENGTH = 32;\n\n/**\n * The git directory shared by a repository and all of its worktrees.\n *\n * A normal checkout has a `.git` directory. A linked worktree has a `.git`\n * FILE pointing at `<common>/worktrees/<name>`, whose `commondir` points back\n * at the shared directory. Following that chain is what makes every worktree of\n * one repository agree on the repository's identity, which matters here because\n * the registry is per repository and must be found from inside a worktree too.\n */\nexport function gitCommonDirectory(root: string): string | undefined {\n const marker = path.join(root, '.git');\n let stat: fs.Stats;\n try {\n stat = fs.statSync(marker);\n } catch {\n return undefined;\n }\n if (stat.isDirectory()) return marker;\n let pointer: string;\n try {\n pointer = fs.readFileSync(marker, 'utf8').trim();\n } catch {\n return undefined;\n }\n if (!pointer.startsWith('gitdir:')) return undefined;\n const gitDir = path.resolve(root, pointer.slice('gitdir:'.length).trim());\n try {\n const common = fs.readFileSync(path.join(gitDir, 'commondir'), 'utf8').trim();\n return path.resolve(gitDir, common);\n } catch {\n // A submodule's .git file has no commondir; its gitdir is already common.\n return gitDir;\n }\n}\n\n/** A directory name that is recognisable to a person and safe on a filesystem. */\nexport function repositoryLabel(root: string): string {\n const label = path\n .basename(root)\n .toLowerCase()\n .replace(/[^a-z0-9]+/gu, '-')\n .replace(/^-+|-+$/gu, '')\n .slice(0, MAX_LABEL_LENGTH);\n return label === '' ? 'repository' : label;\n}\n\n/**\n * A stable id for the repository a directory belongs to.\n *\n * Keyed on the common git directory rather than the given path, so the main\n * checkout and every worktree of it resolve to one id and therefore to one\n * registry. Falling back to the path keeps a non-repository directory usable\n * instead of failing, though nothing here should reach that case.\n */\nexport function repositoryId(root: string): string {\n const common = gitCommonDirectory(root);\n const token = common === undefined ? `root:${path.resolve(root)}` : `git:${common}`;\n return createHash('sha256').update(token).digest('hex').slice(0, ID_LENGTH);\n}\n\n/** A short, collision-resistant suffix for one worktree directory. */\nexport function shortId(seed: string): string {\n return createHash('sha256').update(seed).digest('hex').slice(0, 8);\n}\n"],"mappings":"uKAgBA,SAAgB,EAAmB,EAAkC,CACnE,IAAM,EAASA,EAAAA,QAAK,KAAK,EAAM,MAAM,EACjC,EACJ,GAAI,CACF,EAAOC,EAAAA,QAAG,SAAS,CAAM,CAC3B,MAAQ,CACN,MACF,CACA,GAAI,EAAK,YAAY,EAAG,OAAO,EAC/B,IAAI,EACJ,GAAI,CACF,EAAUA,EAAAA,QAAG,aAAa,EAAQ,MAAM,CAAC,CAAC,KAAK,CACjD,MAAQ,CACN,MACF,CACA,GAAI,CAAC,EAAQ,WAAW,SAAS,EAAG,OACpC,IAAM,EAASD,EAAAA,QAAK,QAAQ,EAAM,EAAQ,MAAM,CAAgB,CAAC,CAAC,KAAK,CAAC,EACxE,GAAI,CACF,IAAM,EAASC,EAAAA,QAAG,aAAaD,EAAAA,QAAK,KAAK,EAAQ,WAAW,EAAG,MAAM,CAAC,CAAC,KAAK,EAC5E,OAAOA,EAAAA,QAAK,QAAQ,EAAQ,CAAM,CACpC,MAAQ,CAEN,OAAO,CACT,CACF,CAGA,SAAgB,EAAgB,EAAsB,CACpD,IAAM,EAAQA,EAAAA,QACX,SAAS,CAAI,CAAC,CACd,YAAY,CAAC,CACb,QAAQ,eAAgB,GAAG,CAAC,CAC5B,QAAQ,YAAa,EAAE,CAAC,CACxB,MAAM,EAAG,EAAgB,EAC5B,OAAO,IAAU,GAAK,aAAe,CACvC,CAUA,SAAgB,EAAa,EAAsB,CACjD,IAAM,EAAS,EAAmB,CAAI,EAChC,EAAQ,IAAW,IAAA,GAAY,QAAQA,EAAAA,QAAK,QAAQ,CAAI,IAAM,OAAO,IAC3E,OAAA,EAAOE,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAS,CAC5E,CAGA,SAAgB,EAAQ,EAAsB,CAC5C,OAAA,EAAOA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,CAAC,CACnE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"repositoryIdentity.mjs","names":[],"sources":["../../../src/adapters/worktree/repositoryIdentity.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nconst ID_LENGTH = 12;\nconst MAX_LABEL_LENGTH = 32;\n\n/**\n * The git directory shared by a repository and all of its worktrees.\n *\n * A normal checkout has a `.git` directory. A linked worktree has a `.git`\n * FILE pointing at `<common>/worktrees/<name>`, whose `commondir` points back\n * at the shared directory. Following that chain is what makes every worktree of\n * one repository agree on the repository's identity, which matters here because\n * the registry is per repository and must be found from inside a worktree too.\n */\nexport function gitCommonDirectory(root: string): string | undefined {\n const marker = path.join(root, '.git');\n let stat: fs.Stats;\n try {\n stat = fs.statSync(marker);\n } catch {\n return undefined;\n }\n if (stat.isDirectory()) return marker;\n let pointer: string;\n try {\n pointer = fs.readFileSync(marker, 'utf8').trim();\n } catch {\n return undefined;\n }\n if (!pointer.startsWith('gitdir:')) return undefined;\n const gitDir = path.resolve(root, pointer.slice('gitdir:'.length).trim());\n try {\n const common = fs.readFileSync(path.join(gitDir, 'commondir'), 'utf8').trim();\n return path.resolve(gitDir, common);\n } catch {\n // A submodule's .git file has no commondir; its gitdir is already common.\n return gitDir;\n }\n}\n\n/** A directory name that is recognisable to a person and safe on a filesystem. */\nexport function repositoryLabel(root: string): string {\n const label = path\n .basename(root)\n .toLowerCase()\n .replace(/[^a-z0-9]+/gu, '-')\n .replace(/^-+|-+$/gu, '')\n .slice(0, MAX_LABEL_LENGTH);\n return label === '' ? 'repository' : label;\n}\n\n/**\n * A stable id for the repository a directory belongs to.\n *\n * Keyed on the common git directory rather than the given path, so the main\n * checkout and every worktree of it resolve to one id and therefore to one\n * registry. Falling back to the path keeps a non-repository directory usable\n * instead of failing, though nothing here should reach that case.\n */\nexport function repositoryId(root: string): string {\n const common = gitCommonDirectory(root);\n const token = common === undefined ? `root:${path.resolve(root)}` : `git:${common}`;\n return createHash('sha256').update(token).digest('hex').slice(0, ID_LENGTH);\n}\n\n/** A short, collision-resistant suffix for one worktree directory. */\nexport function shortId(seed: string): string {\n return createHash('sha256').update(seed).digest('hex').slice(0, 8);\n}\n"],"mappings":"yFAgBA,SAAgB,EAAmB,EAAkC,CACnE,IAAM,EAAS,EAAK,KAAK,EAAM,MAAM,EACjC,EACJ,GAAI,CACF,EAAO,EAAG,SAAS,CAAM,CAC3B,MAAQ,CACN,MACF,CACA,GAAI,EAAK,YAAY,EAAG,OAAO,EAC/B,IAAI,EACJ,GAAI,CACF,EAAU,EAAG,aAAa,EAAQ,MAAM,CAAC,CAAC,KAAK,CACjD,MAAQ,CACN,MACF,CACA,GAAI,CAAC,EAAQ,WAAW,SAAS,EAAG,OACpC,IAAM,EAAS,EAAK,QAAQ,EAAM,EAAQ,MAAM,CAAgB,CAAC,CAAC,KAAK,CAAC,EACxE,GAAI,CACF,IAAM,EAAS,EAAG,aAAa,EAAK,KAAK,EAAQ,WAAW,EAAG,MAAM,CAAC,CAAC,KAAK,EAC5E,OAAO,EAAK,QAAQ,EAAQ,CAAM,CACpC,MAAQ,CAEN,OAAO,CACT,CACF,CAGA,SAAgB,EAAgB,EAAsB,CACpD,IAAM,EAAQ,EACX,SAAS,CAAI,CAAC,CACd,YAAY,CAAC,CACb,QAAQ,eAAgB,GAAG,CAAC,CAC5B,QAAQ,YAAa,EAAE,CAAC,CACxB,MAAM,EAAG,EAAgB,EAC5B,OAAO,IAAU,GAAK,aAAe,CACvC,CAUA,SAAgB,EAAa,EAAsB,CACjD,IAAM,EAAS,EAAmB,CAAI,EAChC,EAAQ,IAAW,IAAA,GAAY,QAAQ,EAAK,QAAQ,CAAI,IAAM,OAAO,IAC3E,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAS,CAC5E,CAGA,SAAgB,EAAQ,EAAsB,CAC5C,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,CAAC,CACnE"}
@@ -1,2 +0,0 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./dependencyInstall.cjs"),n=require("../hub/hubClient.cjs"),r=require("./repositoryIdentity.cjs"),i=require("../filesystem/paths.cjs"),a=require("../channel/worktreeChannel.cjs"),o=require("./worktreeRegistry.cjs"),s=require("../../services/worktreeNaming.cjs"),c=require("../../services/support/errors.cjs");let l=require("node:fs");l=e.__toESM(l,1);let u=require("node:crypto");function d(e){let{git:d}=e,f=e.createSession??n.createWorktreeSession,p=e.install??t.installDependencies,m=e.stopSession??n.stopWorktreeSession,h=e.isSessionLive??n.sessionIsLive,g=e.registryDir??i.hubRegistryDir(),_=e.now??(()=>new Date),v={alive:e=>h(g,e.sessionId),exists:e=>l.default.existsSync(e)},y=async t=>{let n=await d.repositoryRoot(t.cwd);if(n===void 0)throw new c.DoomGitExpectedError(`not_a_repository`,`${t.cwd} is not inside a git repository.`,!1,`Run this from a repository checkout.`);let r=o.createWorktreeRegistry(i.registryFile(n,e.homeDir));return{root:n,store:r,records:r.list()}},b=(e,t)=>{let n=e.find(e=>e.id===t);if(n===void 0)throw new c.DoomGitExpectedError(`worktree_not_found`,`No worktree with id ${t}.`,!1,`Call list to see the current ids.`);return n},x=(e,t,n)=>{if(t.parentSessionId!==e.sessionId&&h(g,t.parentSessionId))throw new c.DoomGitExpectedError(`worktree_not_owned`,`Worktree ${t.id} belongs to another session.`,!1,`Ask the session that created it to ${n} it, or close that session first.`)};return{async spawn(t,a){let{root:o,store:l,records:m}=await y(t),h=s.refuseSpawn({branch:a.branch,existing:m});if(h!==void 0)throw new c.DoomGitExpectedError(`worktree_exists`,h,!1,`Pick another branch name, or close it.`);let v=a.baseRef??await d.currentBranch(o)??`HEAD`,b=r.shortId((0,u.randomUUID)()),x=s.worktreeDirectory({worktreesRoot:i.worktreesRoot(e.homeDir),repositoryLabel:r.repositoryLabel(o),repositoryId:r.repositoryId(o),branch:a.branch,shortId:b});await d.addWorktree({repositoryRoot:o,path:x,branch:a.branch,baseRef:v});let S=await p(x);if(S.kind===`failed`)throw await d.removeWorktree({repositoryRoot:o,path:x,force:!0}).catch(()=>void 0),new c.DoomGitExpectedError(`install_failed`,`${S.command} install failed in the new worktree (exit ${String(S.code??`unknown`)}).`,!0,`Run the install by hand in the repository to see what it reports, then try again.`);let C;try{C=await f({cwd:x,name:a.name??a.branch,parentSessionId:t.sessionId,registryDir:g})}catch(e){throw await d.removeWorktree({repositoryRoot:o,path:x,force:!0}).catch(()=>void 0),e instanceof n.HubUnavailableError?new c.DoomGitExpectedError(`hub_unavailable`,e.message,!0,`Start the cockpit and try again.`):e}let w={version:1,id:b,branch:a.branch,baseRef:v,path:x,repositoryRoot:o,sessionId:C,parentSessionId:t.sessionId,status:`running`,createdAt:_().toISOString()};return l.replace([...m,w]),w},async close(e,t,n){let{root:r,store:i,records:a}=await y(e),o=b(a,t);x(e,o,`close`);let u=l.default.existsSync(o.path)?await d.dirtyFiles(o.path):[],f=s.refuseClose({record:o,dirtyFiles:u,force:n});if(f!==void 0)throw new c.DoomGitExpectedError(`worktree_dirty`,f,!1,`Commit the work, or pass force: true.`);return await m(g,o.sessionId),await d.removeWorktree({repositoryRoot:r,path:o.path,force:n}),i.replace(a.filter(e=>e.id!==t)),o},async list(e){let{store:t,records:n}=await y(e),r=s.reconcile(n,v);return r.changed.length>0&&t.replace(r.records),r.records},async status(e,t){let{records:n}=await y(e),r=b(n,t);return{record:r,dirtyFiles:l.default.existsSync(r.path)?await d.dirtyFiles(r.path):[]}},async merge(e,t,n){let{root:r,records:i}=await y(e),a=b(i,t);x(e,a,`merge`);let o=await d.dirtyFiles(r);if(o.length>0)throw new c.DoomGitExpectedError(`worktree_dirty`,`The parent checkout has ${String(o.length)} uncommitted file(s): ${o.slice(0,10).join(`, `)}.`,!1,`Commit or stash the parent checkout first.`);return await d.mergeBranch({repositoryRoot:r,branch:a.branch,...n===void 0?{}:{message:n}}),a},async send(t,n,r){let{records:o}=await y(t),s=b(o,n),c=t.sessionId===s.sessionId?`parent`:`child`;a.createWorktreeChannel(i.channelRoot(s.id,e.homeDir)).send(c,r)},async messages(t,n){let{records:r}=await y(t),o=b(r,n),s=a.createWorktreeChannel(i.channelRoot(o.id,e.homeDir));s.sweep();let c=t.sessionId===o.sessionId?`child`:`parent`;return s.receive(c)},async prune(t,n){let{root:r,store:a,records:o}=await y(t),c=s.reconcile(o,v),u=await d.listWorktreePaths(r),f=new Map;for(let e of c.records)e.status===`orphaned`&&l.default.existsSync(e.path)&&f.set(e.path,await d.dirtyFiles(e.path));let p=s.planPrune({records:c.records,gitPaths:u,worktreesRoot:i.worktreesRoot(e.homeDir),dirtyByPath:f});if(n)return a.replace(c.records),p;for(let e of p.remove)await d.removeWorktree({repositoryRoot:r,path:e.path,force:!1});let m=new Set([...p.remove,...p.forget].map(e=>e.id));return a.replace(c.records.filter(e=>!m.has(e.id))),p}}}exports.createWorktreeOperations=d;
2
- //# sourceMappingURL=worktreeOperations.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worktreeOperations.cjs","names":["createWorktreeSession","installDependencies","stopWorktreeSession","sessionIsLive","hubRegistryDir","fs","DoomGitExpectedError","createWorktreeRegistry","registryFile","require","refuseSpawn","shortId","randomUUID","worktreeDirectory","worktreesRoot","repositoryLabel","repositoryId","HubUnavailableError","refuseClose","reconcile","channelRoot","createWorktreeChannel","planPrune"],"sources":["../../../src/adapters/worktree/worktreeOperations.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport { installDependencies } from './dependencyInstall.ts';\nimport { createWorktreeSession, HubUnavailableError, sessionIsLive, stopWorktreeSession } from '../hub/hubClient.ts';\nimport { channelRoot, hubRegistryDir, registryFile, worktreesRoot } from '../filesystem/paths.ts';\nimport { repositoryId, repositoryLabel, shortId } from './repositoryIdentity.ts';\nimport { createWorktreeChannel } from '../channel/worktreeChannel.ts';\nimport type { ChannelMessage, ChannelParty } from '../channel/worktreeChannel.ts';\nimport { createWorktreeRegistry } from './worktreeRegistry.ts';\nimport { WORKTREE_RECORD_VERSION } from '../../types/worktreeRegistry.ts';\nimport type { WorktreeGit, WorktreeRecord } from '../../types/worktreeRegistry.ts';\nimport { planPrune, reconcile, refuseClose, refuseSpawn, worktreeDirectory } from '../../services/worktreeNaming.ts';\nimport { DoomGitExpectedError } from '../../services/support/errors.ts';\n\nexport interface WorktreeContext {\n /** Where the calling session is working, used to find the repository. */\n cwd: string;\n /** The calling session, recorded as the new session's parent. */\n sessionId: string;\n}\n\nexport interface SpawnWorktreeRequest {\n branch: string;\n baseRef?: string;\n name?: string;\n}\n\nexport interface WorktreeOperations {\n spawn(context: WorktreeContext, request: SpawnWorktreeRequest): Promise<WorktreeRecord>;\n close(context: WorktreeContext, id: string, force: boolean): Promise<WorktreeRecord>;\n list(context: WorktreeContext): Promise<WorktreeRecord[]>;\n status(context: WorktreeContext, id: string): Promise<{ record: WorktreeRecord; dirtyFiles: string[] }>;\n merge(context: WorktreeContext, id: string, message?: string): Promise<WorktreeRecord>;\n prune(context: WorktreeContext, dryRun: boolean): Promise<ReturnType<typeof planPrune>>;\n send(context: WorktreeContext, id: string, message: string): Promise<void>;\n messages(context: WorktreeContext, id: string): Promise<ChannelMessage[]>;\n}\n\nexport interface WorktreeOperationsDeps {\n git: WorktreeGit;\n /** Injected so tests never reach the network. */\n createSession?: typeof createWorktreeSession;\n /** Injected so tests never run a package manager. */\n install?: typeof installDependencies;\n stopSession?: typeof stopWorktreeSession;\n isSessionLive?: typeof sessionIsLive;\n homeDir?: string;\n registryDir?: string;\n /** Injected so a record's createdAt is reproducible in a test. */\n now?: () => Date;\n}\n\nexport function createWorktreeOperations(deps: WorktreeOperationsDeps): WorktreeOperations {\n const { git } = deps;\n const createSession = deps.createSession ?? createWorktreeSession;\n const install = deps.install ?? installDependencies;\n const stopSession = deps.stopSession ?? stopWorktreeSession;\n const isSessionLive = deps.isSessionLive ?? sessionIsLive;\n const registryDir = deps.registryDir ?? hubRegistryDir();\n const now = deps.now ?? (() => new Date());\n const probe = {\n alive: (record: WorktreeRecord) => isSessionLive(registryDir, record.sessionId),\n exists: (path: string) => fs.existsSync(path),\n };\n\n /**\n * Everything an action needs about the repository it is acting on.\n *\n * Resolved per call rather than cached: a session's cwd is stable, but the\n * registry file is shared with the cockpit panel and another process may\n * have written it since the last call.\n */\n const resolve = async (context: WorktreeContext) => {\n const root = await git.repositoryRoot(context.cwd);\n if (root === undefined) {\n throw new DoomGitExpectedError(\n 'not_a_repository',\n `${context.cwd} is not inside a git repository.`,\n false,\n 'Run this from a repository checkout.',\n );\n }\n const store = createWorktreeRegistry(registryFile(root, deps.homeDir));\n return { root, store, records: store.list() };\n };\n\n const require = (records: readonly WorktreeRecord[], id: string): WorktreeRecord => {\n const record = records.find((entry) => entry.id === id);\n if (record === undefined) {\n throw new DoomGitExpectedError(\n 'worktree_not_found',\n `No worktree with id ${id}.`,\n false,\n 'Call list to see the current ids.',\n );\n }\n return record;\n };\n\n /**\n * Refuses to destroy or merge a worktree another live session is using.\n *\n * `parentSessionId` records who asked for the worktree. Two sessions in one\n * checkout share this registry, so without the check either could close the\n * other's work in progress by id.\n *\n * Once that session is gone there is no owner left to protect, and refusing\n * would strand the worktree: nothing else could ever close it.\n */\n const requireOwner = (context: WorktreeContext, record: WorktreeRecord, action: string): void => {\n if (record.parentSessionId === context.sessionId) return;\n if (!isSessionLive(registryDir, record.parentSessionId)) return;\n throw new DoomGitExpectedError(\n 'worktree_not_owned',\n `Worktree ${record.id} belongs to another session.`,\n false,\n `Ask the session that created it to ${action} it, or close that session first.`,\n );\n };\n\n return {\n async spawn(context, request) {\n const { root, store, records } = await resolve(context);\n const refusal = refuseSpawn({ branch: request.branch, existing: records });\n if (refusal !== undefined) {\n throw new DoomGitExpectedError('worktree_exists', refusal, false, 'Pick another branch name, or close it.');\n }\n const baseRef = request.baseRef ?? (await git.currentBranch(root)) ?? 'HEAD';\n const id = shortId(randomUUID());\n const path = worktreeDirectory({\n worktreesRoot: worktreesRoot(deps.homeDir),\n repositoryLabel: repositoryLabel(root),\n repositoryId: repositoryId(root),\n branch: request.branch,\n shortId: id,\n });\n\n await git.addWorktree({ repositoryRoot: root, path, branch: request.branch, baseRef });\n\n // Before the session, not after. The session resolves this repository's\n // composition, and every workspace package path in it points at a\n // directory that exists only once dependencies are linked. Starting the\n // session first makes it fall back to the global bundle and quietly lose\n // the repository's own packages.\n const installed = await install(path);\n if (installed.kind === 'failed') {\n await git.removeWorktree({ repositoryRoot: root, path, force: true }).catch(() => undefined);\n throw new DoomGitExpectedError(\n 'install_failed',\n `${installed.command} install failed in the new worktree (exit ${String(installed.code ?? 'unknown')}).`,\n true,\n 'Run the install by hand in the repository to see what it reports, then try again.',\n );\n }\n\n // The worktree exists from here on, so every later failure has to leave\n // it removed or recorded. An unrecorded directory on disk is the one\n // outcome with no way back: nothing would ever list it again.\n let sessionId: string;\n try {\n sessionId = await createSession({\n cwd: path,\n name: request.name ?? request.branch,\n parentSessionId: context.sessionId,\n registryDir,\n });\n } catch (error) {\n await git.removeWorktree({ repositoryRoot: root, path, force: true }).catch(() => undefined);\n if (error instanceof HubUnavailableError) {\n throw new DoomGitExpectedError('hub_unavailable', error.message, true, 'Start the cockpit and try again.');\n }\n throw error;\n }\n\n const record: WorktreeRecord = {\n version: WORKTREE_RECORD_VERSION,\n id,\n branch: request.branch,\n baseRef,\n path,\n repositoryRoot: root,\n sessionId,\n parentSessionId: context.sessionId,\n status: 'running',\n createdAt: now().toISOString(),\n };\n store.replace([...records, record]);\n return record;\n },\n\n async close(context, id, force) {\n const { root, store, records } = await resolve(context);\n const record = require(records, id);\n requireOwner(context, record, 'close');\n const dirtyFiles = fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];\n const refusal = refuseClose({ record, dirtyFiles, force });\n if (refusal !== undefined) {\n throw new DoomGitExpectedError('worktree_dirty', refusal, false, 'Commit the work, or pass force: true.');\n }\n // The session is stopped before the directory goes. Removing a checkout\n // while a process still holds files open in it is how a half-removed\n // worktree and a stale git administrative entry are made.\n await stopSession(registryDir, record.sessionId);\n await git.removeWorktree({ repositoryRoot: root, path: record.path, force });\n store.replace(records.filter((entry) => entry.id !== id));\n return record;\n },\n\n async list(context) {\n const { store, records } = await resolve(context);\n const outcome = reconcile(records, probe);\n if (outcome.changed.length > 0) store.replace(outcome.records);\n return outcome.records;\n },\n\n async status(context, id) {\n const { records } = await resolve(context);\n const record = require(records, id);\n const dirtyFiles = fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];\n return { record, dirtyFiles };\n },\n\n async merge(context, id, message) {\n const { root, records } = await resolve(context);\n const record = require(records, id);\n requireOwner(context, record, 'merge');\n // The merge lands in the parent, so the parent is what must be clean. A\n // dirty parent would mix the person's in-flight edits into a merge commit\n // they did not intend to make.\n const parentDirty = await git.dirtyFiles(root);\n if (parentDirty.length > 0) {\n throw new DoomGitExpectedError(\n 'worktree_dirty',\n `The parent checkout has ${String(parentDirty.length)} uncommitted file(s): ${parentDirty.slice(0, 10).join(', ')}.`,\n false,\n 'Commit or stash the parent checkout first.',\n );\n }\n await git.mergeBranch({\n repositoryRoot: root,\n branch: record.branch,\n ...(message === undefined ? {} : { message }),\n });\n return record;\n },\n\n /**\n * Which side of the channel the caller is.\n *\n * A worktree session is the child of the record it belongs to, and the\n * parent for everything else. Deciding from the session id rather than a\n * flag means neither side can address itself by mistake.\n */\n async send(context, id, message) {\n const { records } = await resolve(context);\n const record = require(records, id);\n const party: ChannelParty = context.sessionId === record.sessionId ? 'parent' : 'child';\n createWorktreeChannel(channelRoot(record.id, deps.homeDir)).send(party, message);\n },\n\n async messages(context, id) {\n const { records } = await resolve(context);\n const record = require(records, id);\n const channel = createWorktreeChannel(channelRoot(record.id, deps.homeDir));\n channel.sweep();\n const party: ChannelParty = context.sessionId === record.sessionId ? 'child' : 'parent';\n return channel.receive(party);\n },\n\n async prune(context, dryRun) {\n const { root, store, records } = await resolve(context);\n const reconciled = reconcile(records, probe);\n const gitPaths = await git.listWorktreePaths(root);\n const dirtyByPath = new Map<string, readonly string[]>();\n for (const record of reconciled.records) {\n if (record.status === 'orphaned' && fs.existsSync(record.path)) {\n dirtyByPath.set(record.path, await git.dirtyFiles(record.path));\n }\n }\n const plan = planPrune({\n records: reconciled.records,\n gitPaths,\n worktreesRoot: worktreesRoot(deps.homeDir),\n dirtyByPath,\n });\n if (dryRun) {\n store.replace(reconciled.records);\n return plan;\n }\n for (const record of plan.remove) {\n await git.removeWorktree({ repositoryRoot: root, path: record.path, force: false });\n }\n const dropped = new Set([...plan.remove, ...plan.forget].map((record) => record.id));\n store.replace(reconciled.records.filter((record) => !dropped.has(record.id)));\n return plan;\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"worktreeOperations.d.cts","names":[],"sources":["../../../src/adapters/worktree/worktreeOperations.ts"],"mappings":";;;;;UAciB;;EAEf;;EAEA;;UAGe;EACf;EACA;EACA;;UAGe;EACf,MAAM,SAAS,iBAAiB,SAAS,uBAAuB,QAAQ;EACxE,MAAM,SAAS,iBAAiB,YAAY,iBAAiB,QAAQ;EACrE,KAAK,SAAS,kBAAkB,QAAQ;EACxC,OAAO,SAAS,iBAAiB,aAAa;IAAU,QAAQ;IAAgB;;EAChF,MAAM,SAAS,iBAAiB,YAAY,mBAAmB,QAAQ;EACvE,MAAM,SAAS,iBAAiB,kBAAkB,QAAQ,kBAAkB;EAC5E,KAAK,SAAS,iBAAiB,YAAY,kBAAkB;EAC7D,SAAS,SAAS,iBAAiB,aAAa,QAAQ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"worktreeOperations.d.mts","names":[],"sources":["../../../src/adapters/worktree/worktreeOperations.ts"],"mappings":";;;;;UAciB;;EAEf;;EAEA;;UAGe;EACf;EACA;EACA;;UAGe;EACf,MAAM,SAAS,iBAAiB,SAAS,uBAAuB,QAAQ;EACxE,MAAM,SAAS,iBAAiB,YAAY,iBAAiB,QAAQ;EACrE,KAAK,SAAS,kBAAkB,QAAQ;EACxC,OAAO,SAAS,iBAAiB,aAAa;IAAU,QAAQ;IAAgB;;EAChF,MAAM,SAAS,iBAAiB,YAAY,mBAAmB,QAAQ;EACvE,MAAM,SAAS,iBAAiB,kBAAkB,QAAQ,kBAAkB;EAC5E,KAAK,SAAS,iBAAiB,YAAY,kBAAkB;EAC7D,SAAS,SAAS,iBAAiB,aAAa,QAAQ"}
@@ -1,2 +0,0 @@
1
- import{installDependencies as e}from"./dependencyInstall.mjs";import{HubUnavailableError as t,createWorktreeSession as n,sessionIsLive as r,stopWorktreeSession as i}from"../hub/hubClient.mjs";import{repositoryId as a,repositoryLabel as o,shortId as s}from"./repositoryIdentity.mjs";import{channelRoot as c,hubRegistryDir as l,registryFile as u,worktreesRoot as d}from"../filesystem/paths.mjs";import{createWorktreeChannel as f}from"../channel/worktreeChannel.mjs";import{createWorktreeRegistry as p}from"./worktreeRegistry.mjs";import{planPrune as m,reconcile as h,refuseClose as g,refuseSpawn as _,worktreeDirectory as v}from"../../services/worktreeNaming.mjs";import{DoomGitExpectedError as y}from"../../services/support/errors.mjs";import b from"node:fs";import{randomUUID as x}from"node:crypto";function S(S){let{git:C}=S,w=S.createSession??n,T=S.install??e,E=S.stopSession??i,D=S.isSessionLive??r,O=S.registryDir??l(),k=S.now??(()=>new Date),A={alive:e=>D(O,e.sessionId),exists:e=>b.existsSync(e)},j=async e=>{let t=await C.repositoryRoot(e.cwd);if(t===void 0)throw new y(`not_a_repository`,`${e.cwd} is not inside a git repository.`,!1,`Run this from a repository checkout.`);let n=p(u(t,S.homeDir));return{root:t,store:n,records:n.list()}},M=(e,t)=>{let n=e.find(e=>e.id===t);if(n===void 0)throw new y(`worktree_not_found`,`No worktree with id ${t}.`,!1,`Call list to see the current ids.`);return n},N=(e,t,n)=>{if(t.parentSessionId!==e.sessionId&&D(O,t.parentSessionId))throw new y(`worktree_not_owned`,`Worktree ${t.id} belongs to another session.`,!1,`Ask the session that created it to ${n} it, or close that session first.`)};return{async spawn(e,n){let{root:r,store:i,records:c}=await j(e),l=_({branch:n.branch,existing:c});if(l!==void 0)throw new y(`worktree_exists`,l,!1,`Pick another branch name, or close it.`);let u=n.baseRef??await C.currentBranch(r)??`HEAD`,f=s(x()),p=v({worktreesRoot:d(S.homeDir),repositoryLabel:o(r),repositoryId:a(r),branch:n.branch,shortId:f});await C.addWorktree({repositoryRoot:r,path:p,branch:n.branch,baseRef:u});let m=await T(p);if(m.kind===`failed`)throw await C.removeWorktree({repositoryRoot:r,path:p,force:!0}).catch(()=>void 0),new y(`install_failed`,`${m.command} install failed in the new worktree (exit ${String(m.code??`unknown`)}).`,!0,`Run the install by hand in the repository to see what it reports, then try again.`);let h;try{h=await w({cwd:p,name:n.name??n.branch,parentSessionId:e.sessionId,registryDir:O})}catch(e){throw await C.removeWorktree({repositoryRoot:r,path:p,force:!0}).catch(()=>void 0),e instanceof t?new y(`hub_unavailable`,e.message,!0,`Start the cockpit and try again.`):e}let g={version:1,id:f,branch:n.branch,baseRef:u,path:p,repositoryRoot:r,sessionId:h,parentSessionId:e.sessionId,status:`running`,createdAt:k().toISOString()};return i.replace([...c,g]),g},async close(e,t,n){let{root:r,store:i,records:a}=await j(e),o=M(a,t);N(e,o,`close`);let s=b.existsSync(o.path)?await C.dirtyFiles(o.path):[],c=g({record:o,dirtyFiles:s,force:n});if(c!==void 0)throw new y(`worktree_dirty`,c,!1,`Commit the work, or pass force: true.`);return await E(O,o.sessionId),await C.removeWorktree({repositoryRoot:r,path:o.path,force:n}),i.replace(a.filter(e=>e.id!==t)),o},async list(e){let{store:t,records:n}=await j(e),r=h(n,A);return r.changed.length>0&&t.replace(r.records),r.records},async status(e,t){let{records:n}=await j(e),r=M(n,t);return{record:r,dirtyFiles:b.existsSync(r.path)?await C.dirtyFiles(r.path):[]}},async merge(e,t,n){let{root:r,records:i}=await j(e),a=M(i,t);N(e,a,`merge`);let o=await C.dirtyFiles(r);if(o.length>0)throw new y(`worktree_dirty`,`The parent checkout has ${String(o.length)} uncommitted file(s): ${o.slice(0,10).join(`, `)}.`,!1,`Commit or stash the parent checkout first.`);return await C.mergeBranch({repositoryRoot:r,branch:a.branch,...n===void 0?{}:{message:n}}),a},async send(e,t,n){let{records:r}=await j(e),i=M(r,t),a=e.sessionId===i.sessionId?`parent`:`child`;f(c(i.id,S.homeDir)).send(a,n)},async messages(e,t){let{records:n}=await j(e),r=M(n,t),i=f(c(r.id,S.homeDir));i.sweep();let a=e.sessionId===r.sessionId?`child`:`parent`;return i.receive(a)},async prune(e,t){let{root:n,store:r,records:i}=await j(e),a=h(i,A),o=await C.listWorktreePaths(n),s=new Map;for(let e of a.records)e.status===`orphaned`&&b.existsSync(e.path)&&s.set(e.path,await C.dirtyFiles(e.path));let c=m({records:a.records,gitPaths:o,worktreesRoot:d(S.homeDir),dirtyByPath:s});if(t)return r.replace(a.records),c;for(let e of c.remove)await C.removeWorktree({repositoryRoot:n,path:e.path,force:!1});let l=new Set([...c.remove,...c.forget].map(e=>e.id));return r.replace(a.records.filter(e=>!l.has(e.id))),c}}}export{S as createWorktreeOperations};
2
- //# sourceMappingURL=worktreeOperations.mjs.map