@gajae-code/coding-agent 0.15.5 → 0.16.0

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 (272) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  5. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  6. package/dist/types/cli/root-flags.d.ts +14 -0
  7. package/dist/types/cli/update-cli.d.ts +15 -0
  8. package/dist/types/cli.d.ts +7 -0
  9. package/dist/types/commands/auth-gateway.d.ts +3 -0
  10. package/dist/types/commands/gc.d.ts +13 -0
  11. package/dist/types/commands/launch.d.ts +10 -0
  12. package/dist/types/commands/model-presets.d.ts +21 -0
  13. package/dist/types/config/autorouting-tier-map.d.ts +120 -0
  14. package/dist/types/config/file-lock.d.ts +54 -2
  15. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  16. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  17. package/dist/types/config/keybindings.d.ts +3 -2
  18. package/dist/types/config/model-preset-registry.d.ts +368 -0
  19. package/dist/types/config/model-profile-activation.d.ts +39 -0
  20. package/dist/types/config/model-profiles.d.ts +5 -3
  21. package/dist/types/config/model-registry.d.ts +17 -5
  22. package/dist/types/config/settings-schema.d.ts +19 -1
  23. package/dist/types/config/settings.d.ts +2 -0
  24. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  25. package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
  26. package/dist/types/crash/record-loader.d.ts +15 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +2 -5
  28. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  29. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  30. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  31. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  33. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
  34. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  36. package/dist/types/index.d.ts +1 -0
  37. package/dist/types/main.d.ts +3 -1
  38. package/dist/types/master-mode/context.d.ts +21 -0
  39. package/dist/types/master-mode/first-request.d.ts +34 -0
  40. package/dist/types/master-mode/snapshot.d.ts +32 -0
  41. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  42. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  43. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  44. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  45. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  46. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  47. package/dist/types/modes/jobs-observer.d.ts +36 -0
  48. package/dist/types/modes/print-mode.d.ts +17 -0
  49. package/dist/types/modes/ui-language.d.ts +7 -1
  50. package/dist/types/sdk/broker/broker.d.ts +80 -1
  51. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  52. package/dist/types/sdk/broker/index.d.ts +3 -0
  53. package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
  54. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  56. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  57. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  58. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  59. package/dist/types/sdk/broker/state-version.d.ts +4 -1
  60. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  61. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  62. package/dist/types/sdk/bus/index.d.ts +6 -0
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  65. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  66. package/dist/types/sdk/cli/index.d.ts +1 -0
  67. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  68. package/dist/types/sdk/cli/rows.d.ts +4 -3
  69. package/dist/types/sdk/cli/session-cli.d.ts +18 -2
  70. package/dist/types/sdk/client/client.d.ts +3 -2
  71. package/dist/types/sdk/client/discovery.d.ts +1 -0
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/host.d.ts +10 -0
  77. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  78. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +67 -1
  80. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  81. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  82. package/dist/types/sdk/lifecycle-session.d.ts +2 -0
  83. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  84. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  85. package/dist/types/sdk/router/session-router.d.ts +11 -0
  86. package/dist/types/sdk/session.d.ts +16 -0
  87. package/dist/types/sdk/startup-capability.d.ts +2 -1
  88. package/dist/types/session/account-inventory.d.ts +1 -0
  89. package/dist/types/session/agent-session.d.ts +24 -7
  90. package/dist/types/session/credential-store-errors.d.ts +1 -0
  91. package/dist/types/session/fold-coordinator.d.ts +201 -0
  92. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  93. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  94. package/dist/types/session/session-manager.d.ts +65 -0
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/telemetry/control.d.ts +11 -0
  98. package/dist/types/telemetry/events.d.ts +20 -0
  99. package/dist/types/telemetry/index.d.ts +3 -0
  100. package/dist/types/telemetry/transport.d.ts +22 -0
  101. package/dist/types/tools/bash-interactive.d.ts +23 -0
  102. package/dist/types/tools/bash.d.ts +4 -0
  103. package/dist/types/tools/gh.d.ts +8 -0
  104. package/dist/types/tools/index.d.ts +10 -5
  105. package/dist/types/web/search/providers/base.d.ts +9 -4
  106. package/dist/types/web/search/providers/xai.d.ts +2 -1
  107. package/dist/types/web/search/types.d.ts +14 -0
  108. package/package.json +10 -7
  109. package/scripts/generate-hotkeys-docs.ts +4 -0
  110. package/scripts/generate-sdk-operation-inventory.ts +9 -3
  111. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  112. package/src/async/job-manager.ts +671 -39
  113. package/src/cli/accounts-cli.ts +9 -5
  114. package/src/cli/args.ts +17 -0
  115. package/src/cli/auth-broker-cli.ts +40 -12
  116. package/src/cli/auth-gateway-cli.ts +273 -97
  117. package/src/cli/config-cli.ts +2 -2
  118. package/src/cli/fast-help.ts +4 -0
  119. package/src/cli/model-presets-cli.ts +100 -0
  120. package/src/cli/root-flags.ts +5 -0
  121. package/src/cli/update-cli.ts +117 -16
  122. package/src/cli.ts +6 -2
  123. package/src/commands/auth-gateway.ts +12 -8
  124. package/src/commands/gc.ts +19 -0
  125. package/src/commands/launch.ts +86 -18
  126. package/src/commands/model-presets.ts +24 -0
  127. package/src/commands/sdk.ts +113 -21
  128. package/src/config/autorouting-tier-map.ts +44 -0
  129. package/src/config/file-lock-gc.ts +9 -4
  130. package/src/config/file-lock.ts +890 -99
  131. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  132. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  133. package/src/config/keybindings.ts +6 -2
  134. package/src/config/model-preset-registry.ts +2795 -0
  135. package/src/config/model-profile-activation.ts +115 -28
  136. package/src/config/model-profile-contract.ts +4 -1
  137. package/src/config/model-profiles.ts +56 -14
  138. package/src/config/model-registry.ts +1044 -122
  139. package/src/config/model-resolver.ts +5 -0
  140. package/src/config/settings-schema.ts +19 -1
  141. package/src/config/settings.ts +11 -1
  142. package/src/coordinator-mcp/model-preset.ts +32 -17
  143. package/src/coordinator-mcp/projection-scan.ts +592 -0
  144. package/src/coordinator-mcp/question-state.ts +9 -11
  145. package/src/coordinator-mcp/server.ts +111 -42
  146. package/src/coordinator-mcp/session-reaper.ts +9 -3
  147. package/src/crash/index-store.ts +2 -0
  148. package/src/crash/record-loader.ts +10 -0
  149. package/src/extensibility/extensions/loader.ts +0 -1
  150. package/src/extensibility/extensions/types.ts +2 -3
  151. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  152. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  153. package/src/gjc-runtime/gc-render.ts +29 -0
  154. package/src/gjc-runtime/gc-runtime.ts +174 -11
  155. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  156. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  157. package/src/gjc-runtime/session-state-lock.ts +1321 -147
  158. package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
  159. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  160. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
  161. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  162. package/src/hooks/skill-state.ts +6 -0
  163. package/src/index.ts +1 -0
  164. package/src/internal-urls/docs-index.generated.ts +14 -13
  165. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  166. package/src/lsp/config.ts +4 -3
  167. package/src/lsp/path-trust.ts +12 -6
  168. package/src/main.ts +66 -4
  169. package/src/master-mode/context.ts +50 -0
  170. package/src/master-mode/first-request.ts +65 -0
  171. package/src/master-mode/snapshot.ts +205 -0
  172. package/src/modes/acp/acp-agent.ts +616 -161
  173. package/src/modes/components/footer.ts +4 -2
  174. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  175. package/src/modes/components/jobs-overlay-model.ts +99 -16
  176. package/src/modes/components/model-selector.ts +158 -18
  177. package/src/modes/components/oauth-selector.ts +6 -1
  178. package/src/modes/components/settings-selector.ts +1 -0
  179. package/src/modes/components/status-line/model-name.ts +53 -0
  180. package/src/modes/components/status-line/priority-row.ts +107 -0
  181. package/src/modes/components/status-line/segments.ts +29 -10
  182. package/src/modes/components/tool-status-header.ts +151 -15
  183. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  184. package/src/modes/controllers/input-controller.ts +51 -6
  185. package/src/modes/controllers/selector-controller.ts +191 -96
  186. package/src/modes/interactive-mode.ts +14 -1
  187. package/src/modes/jobs-observer.ts +149 -16
  188. package/src/modes/print-mode.ts +64 -0
  189. package/src/modes/runtime-init.ts +1 -1
  190. package/src/modes/ui-language.ts +49 -2
  191. package/src/modes/utils/injected-user-submission.ts +15 -1
  192. package/src/prompts/system/master-mode.md +28 -0
  193. package/src/prompts/tools/bash.md +2 -2
  194. package/src/prompts/tools/fold-receipt.md +3 -0
  195. package/src/prompts/tools/job.md +1 -1
  196. package/src/sdk/acp/adapter.ts +17 -2
  197. package/src/sdk/broker/broker.ts +1671 -34
  198. package/src/sdk/broker/endpoint-authority.ts +86 -0
  199. package/src/sdk/broker/ensure.ts +5 -0
  200. package/src/sdk/broker/index.ts +3 -0
  201. package/src/sdk/broker/lifecycle.ts +217 -46
  202. package/src/sdk/broker/master-capability.ts +232 -0
  203. package/src/sdk/broker/session-index.ts +121 -17
  204. package/src/sdk/broker/session-scope.ts +272 -0
  205. package/src/sdk/broker/spawn-authority.ts +1031 -0
  206. package/src/sdk/broker/spawn-substrate.ts +548 -0
  207. package/src/sdk/broker/state-version.ts +26 -14
  208. package/src/sdk/broker/transport.ts +1 -0
  209. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  210. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  211. package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
  212. package/src/sdk/bus/index.ts +51 -4
  213. package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
  214. package/src/sdk/bus/prompt-reconciliation.ts +28 -4
  215. package/src/sdk/bus/slack-conversation.ts +5 -1
  216. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  217. package/src/sdk/cli/index.ts +1 -0
  218. package/src/sdk/cli/master-cli.ts +259 -0
  219. package/src/sdk/cli/rows.ts +11 -8
  220. package/src/sdk/cli/session-cli.ts +276 -6
  221. package/src/sdk/client/client.ts +14 -8
  222. package/src/sdk/client/discovery.ts +3 -1
  223. package/src/sdk/host/control/dispatch.ts +101 -37
  224. package/src/sdk/host/control/index.ts +2 -0
  225. package/src/sdk/host/control/operations.ts +2 -0
  226. package/src/sdk/host/control/runtime-gate.ts +203 -11
  227. package/src/sdk/host/host.ts +27 -2
  228. package/src/sdk/host/model-pin.ts +185 -25
  229. package/src/sdk/host/sdk-run-capability.ts +1 -0
  230. package/src/sdk/host/session-runtime.test.ts +581 -43
  231. package/src/sdk/host/session-runtime.ts +655 -101
  232. package/src/sdk/lifecycle/broker-client.ts +28 -0
  233. package/src/sdk/lifecycle/service.ts +279 -20
  234. package/src/sdk/lifecycle-session.ts +10 -2
  235. package/src/sdk/prompt-deadline-manager.ts +16 -1
  236. package/src/sdk/protocol/adapter-validation.ts +27 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +53 -2
  238. package/src/sdk/protocol/operation-registry.ts +17 -4
  239. package/src/sdk/router/session-router.ts +294 -154
  240. package/src/sdk/session.ts +440 -167
  241. package/src/sdk/startup-capability.ts +1 -0
  242. package/src/session/account-inventory.ts +27 -11
  243. package/src/session/agent-session.ts +1292 -180
  244. package/src/session/blob-store.ts +15 -5
  245. package/src/session/credential-store-errors.ts +2 -0
  246. package/src/session/fold-coordinator.ts +433 -0
  247. package/src/session/internal/managed-session-storage.ts +153 -78
  248. package/src/session/sdk-run-capability-internal.ts +15 -0
  249. package/src/session/sdk-run-capability.ts +2 -0
  250. package/src/session/session-manager.ts +342 -28
  251. package/src/session/yield-queue.ts +24 -10
  252. package/src/setup/frictionless-onboarding.ts +2 -0
  253. package/src/setup/provider-presets.json +1 -1
  254. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  255. package/src/task/executor.ts +19 -3
  256. package/src/telemetry/control.ts +26 -0
  257. package/src/telemetry/events.ts +1268 -0
  258. package/src/telemetry/index.ts +3 -0
  259. package/src/telemetry/transport.ts +87 -0
  260. package/src/tools/bash-interactive.ts +182 -85
  261. package/src/tools/bash.ts +1028 -109
  262. package/src/tools/gh.ts +104 -48
  263. package/src/tools/github-cache.ts +2 -2
  264. package/src/tools/index.ts +11 -5
  265. package/src/tools/tool-catalog.generated.ts +1 -1
  266. package/src/web/search/provider.ts +120 -11
  267. package/src/web/search/providers/anthropic.ts +89 -17
  268. package/src/web/search/providers/base.ts +9 -4
  269. package/src/web/search/providers/gemini.ts +87 -15
  270. package/src/web/search/providers/openai-compatible.ts +23 -8
  271. package/src/web/search/providers/xai.ts +98 -12
  272. package/src/web/search/types.ts +16 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Cross-process reservation for a deterministic launch-worktree path.
3
+ *
4
+ * The durable session index is the long-lived ownership record, but a newly
5
+ * planned worktree has no index row yet. This reservation closes that interval:
6
+ * it is acquired before preparation and removed after `host_registered` is
7
+ * durably appended to the index.
8
+ */
9
+
10
+ import { createHash, randomUUID } from "node:crypto";
11
+ import type { Dirent } from "node:fs";
12
+ import * as fs from "node:fs/promises";
13
+ import * as path from "node:path";
14
+ import { resolveEquivalentPath } from "@gajae-code/utils";
15
+ import {
16
+ type FileLockGcObservation,
17
+ processStartTime,
18
+ readFileLockObservationForGc,
19
+ removeFileLockDirForGc,
20
+ } from "../config/file-lock";
21
+ import { processIncarnation } from "../sdk/broker/process-incarnation";
22
+
23
+ const RESERVATION_VERSION = 1;
24
+ const RESERVATION_DIRECTORY = "launch-worktree-reservations";
25
+
26
+ interface LaunchWorktreeReservationRecord {
27
+ version: typeof RESERVATION_VERSION;
28
+ worktreePath: string;
29
+ pid: number;
30
+ start_time?: string;
31
+ processIncarnation: string | null;
32
+ timestamp: number;
33
+ reservationId: string;
34
+ }
35
+
36
+ export interface LaunchWorktreeReservation {
37
+ release(): Promise<void>;
38
+ }
39
+
40
+ function canonicalWorktreePath(worktreePath: string): string {
41
+ return resolveEquivalentPath(path.resolve(worktreePath));
42
+ }
43
+
44
+ function reservationRoot(agentDir: string): string {
45
+ return path.join(agentDir, "sdk", "sessions", RESERVATION_DIRECTORY);
46
+ }
47
+
48
+ function launchWorktreeReservationDirectory(agentDir: string, worktreePath: string): string {
49
+ const digest = createHash("sha256").update(canonicalWorktreePath(worktreePath)).digest("hex");
50
+ return path.join(reservationRoot(agentDir), `${digest}.lock`);
51
+ }
52
+
53
+ /** Test seam for seeding a dead-owner reservation at the exact production path. */
54
+ export function launchWorktreeReservationDirectoryForTest(agentDir: string, worktreePath: string): string {
55
+ return launchWorktreeReservationDirectory(agentDir, worktreePath);
56
+ }
57
+
58
+ function isErrno(error: unknown, code: string): boolean {
59
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
60
+ }
61
+
62
+ function isReservationRecord(value: unknown): value is LaunchWorktreeReservationRecord {
63
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
64
+ const record = value as Partial<LaunchWorktreeReservationRecord>;
65
+ return (
66
+ record.version === RESERVATION_VERSION &&
67
+ typeof record.worktreePath === "string" &&
68
+ record.worktreePath.length > 0 &&
69
+ typeof record.pid === "number" &&
70
+ Number.isSafeInteger(record.pid) &&
71
+ record.pid > 0 &&
72
+ (record.start_time === undefined || (typeof record.start_time === "string" && record.start_time.length > 0)) &&
73
+ (record.processIncarnation === null ||
74
+ (typeof record.processIncarnation === "string" && record.processIncarnation.length > 0)) &&
75
+ typeof record.timestamp === "number" &&
76
+ Number.isFinite(record.timestamp) &&
77
+ typeof record.reservationId === "string" &&
78
+ record.reservationId.length > 0
79
+ );
80
+ }
81
+
82
+ async function readReservation(lockDir: string): Promise<LaunchWorktreeReservationRecord | null> {
83
+ let value: unknown;
84
+ try {
85
+ value = await Bun.file(path.join(lockDir, "info")).json();
86
+ } catch (error) {
87
+ if (isErrno(error, "ENOENT") || error instanceof SyntaxError) return null;
88
+ throw error;
89
+ }
90
+ return isReservationRecord(value) ? value : null;
91
+ }
92
+
93
+ function ownerDefinitelyExited(record: LaunchWorktreeReservationRecord): boolean {
94
+ try {
95
+ process.kill(record.pid, 0);
96
+ } catch (error) {
97
+ return isErrno(error, "ESRCH");
98
+ }
99
+ if (record.processIncarnation !== null) {
100
+ const actualIncarnation = processIncarnation(record.pid);
101
+ if (actualIncarnation !== undefined) return actualIncarnation !== record.processIncarnation;
102
+ }
103
+ if (record.start_time) {
104
+ const actualStartTime = processStartTime(record.pid);
105
+ if (actualStartTime !== null) return actualStartTime !== record.start_time;
106
+ }
107
+ return false;
108
+ }
109
+
110
+ async function removeReservation(
111
+ lockDir: string,
112
+ record: LaunchWorktreeReservationRecord,
113
+ ): Promise<"removed" | "kept"> {
114
+ // Capture the exact on-disk identity BEFORE authorizing removal: the generic
115
+ // GC remover only deletes a tree whose pre-verdict identity it was handed,
116
+ // so a successor reservation published at the same path is never reaped.
117
+ let observation: FileLockGcObservation | null;
118
+ try {
119
+ observation = await readFileLockObservationForGc(lockDir);
120
+ } catch {
121
+ return "kept";
122
+ }
123
+ if (!observation) return "kept";
124
+ let onDisk: unknown;
125
+ try {
126
+ onDisk = JSON.parse(observation.bytes);
127
+ } catch {
128
+ return "kept";
129
+ }
130
+ if (
131
+ !isReservationRecord(onDisk) ||
132
+ onDisk.reservationId !== record.reservationId ||
133
+ onDisk.pid !== record.pid ||
134
+ onDisk.timestamp !== record.timestamp ||
135
+ canonicalWorktreePath(onDisk.worktreePath) !== canonicalWorktreePath(record.worktreePath)
136
+ )
137
+ return "kept";
138
+ const result = await removeFileLockDirForGc(lockDir, observation.info, observation.identity);
139
+ return result === "removed" ? "removed" : "kept";
140
+ }
141
+
142
+ async function writeReservation(lockDir: string, record: LaunchWorktreeReservationRecord): Promise<void> {
143
+ try {
144
+ await Bun.write(path.join(lockDir, "info"), JSON.stringify(record));
145
+ } catch (error) {
146
+ await fs.rm(lockDir, { recursive: true, force: true }).catch(() => undefined);
147
+ throw error;
148
+ }
149
+ }
150
+
151
+ async function tryPublishReservation(lockDir: string, record: LaunchWorktreeReservationRecord): Promise<boolean> {
152
+ const pendingDir = `${lockDir}.pending.${process.pid}.${randomUUID()}`;
153
+ await fs.mkdir(pendingDir, { mode: 0o700 });
154
+ try {
155
+ await writeReservation(pendingDir, record);
156
+ try {
157
+ await fs.rename(pendingDir, lockDir);
158
+ return true;
159
+ } catch (error) {
160
+ if (isErrno(error, "EEXIST") || isErrno(error, "ENOTEMPTY")) return false;
161
+ if (isErrno(error, "EPERM")) {
162
+ try {
163
+ await fs.stat(lockDir);
164
+ return false;
165
+ } catch (statError) {
166
+ if (!isErrno(statError, "ENOENT")) throw statError;
167
+ }
168
+ }
169
+ throw error;
170
+ }
171
+ } finally {
172
+ await fs.rm(pendingDir, { recursive: true, force: true }).catch(() => undefined);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Atomically claim `worktreePath`, returning `null` only when another live or
178
+ * unverified launch already owns the same deterministic target. A dead or
179
+ * PID-reused owner is reclaimed before retrying the exclusive directory create.
180
+ */
181
+ export async function reserveLaunchWorktree(
182
+ agentDir: string,
183
+ worktreePath: string,
184
+ ): Promise<LaunchWorktreeReservation | null> {
185
+ const canonicalPath = canonicalWorktreePath(worktreePath);
186
+ const lockDir = launchWorktreeReservationDirectory(agentDir, canonicalPath);
187
+ await fs.mkdir(path.dirname(lockDir), { recursive: true, mode: 0o700 });
188
+ const ownStartTime = processStartTime(process.pid) ?? undefined;
189
+ const ownIncarnation = processIncarnation(process.pid) ?? null;
190
+
191
+ for (let attempt = 0; attempt < 2; attempt += 1) {
192
+ const record: LaunchWorktreeReservationRecord = {
193
+ version: RESERVATION_VERSION,
194
+ worktreePath: canonicalPath,
195
+ pid: process.pid,
196
+ ...(ownStartTime ? { start_time: ownStartTime } : {}),
197
+ processIncarnation: ownIncarnation,
198
+ timestamp: Date.now(),
199
+ reservationId: randomUUID(),
200
+ };
201
+ if (await tryPublishReservation(lockDir, record)) {
202
+ return {
203
+ async release(): Promise<void> {
204
+ await removeReservation(lockDir, record);
205
+ },
206
+ };
207
+ }
208
+
209
+ const existing = await readReservation(lockDir);
210
+ if (
211
+ !existing ||
212
+ canonicalWorktreePath(existing.worktreePath) !== canonicalPath ||
213
+ !ownerDefinitelyExited(existing)
214
+ )
215
+ return null;
216
+ if ((await removeReservation(lockDir, existing)) !== "removed") return null;
217
+ }
218
+
219
+ return null;
220
+ }
221
+
222
+ /** Remove the transient reservation only after the matching worktree is indexed. */
223
+ export async function releaseLaunchWorktreeReservationAfterRegistration(
224
+ agentDir: string,
225
+ worktreePath: string,
226
+ ): Promise<void> {
227
+ const canonicalPath = canonicalWorktreePath(worktreePath);
228
+ let entries: Dirent[];
229
+ try {
230
+ entries = await fs.readdir(reservationRoot(agentDir), { withFileTypes: true });
231
+ } catch (error) {
232
+ if (isErrno(error, "ENOENT")) return;
233
+ throw error;
234
+ }
235
+ for (const entry of entries) {
236
+ if (!entry.isDirectory() || !entry.name.endsWith(".lock")) continue;
237
+ const lockDir = path.join(reservationRoot(agentDir), entry.name);
238
+ const record = await readReservation(lockDir);
239
+ if (!record || canonicalWorktreePath(record.worktreePath) !== canonicalPath) continue;
240
+ await removeReservation(lockDir, record);
241
+ }
242
+ }
@@ -15,6 +15,8 @@ export const MANAGED_OWNER_GENERATION_ENV = "GJC_TMUX_OWNER_GENERATION";
15
15
  export const MANAGED_OWNER_STATE_DIR_ENV = "GJC_TMUX_OWNER_STATE_DIR";
16
16
  export const MANAGED_OWNER_RUN_ID_ENV = "GJC_MANAGED_OWNER_RUN_ID";
17
17
  export const MANAGED_OWNER_INCARNATION_ENV = "GJC_MANAGED_OWNER_INCARNATION";
18
+ /** Suppresses command-derived durable artifacts for Broker-owned opaque spawn children. */
19
+ export const MANAGED_OWNER_REDACT_COMMAND_ENV = "GJC_MANAGED_OWNER_REDACT_COMMAND";
18
20
 
19
21
  let bootstrapSigtermPending = false;
20
22
  const captureBootstrapSigterm = () => {
@@ -140,26 +142,32 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
140
142
  if (!supervisorStartTime) throw new Error("managed_owner_supervisor_start_time_unavailable");
141
143
  await fs.mkdir(root, { recursive: true, mode: 0o700 });
142
144
  const childToken = crypto.randomUUID();
143
- const binding: ManagedOwnerBinding = {
144
- schema_version: 2,
145
- generation,
146
- session_id: sessionId,
147
- run_id: runId,
148
- endpoint_incarnation: incarnation,
149
- child_token: childToken,
150
- command,
151
- command_sha256: commandDigest(command),
152
- supervisor_pid: process.pid,
153
- supervisor_start_time: supervisorStartTime,
154
- created_at: new Date().toISOString(),
155
- };
156
- await writeDurableExclusive(path.join(root, `child-${childToken}.binding.json`), binding);
145
+ const redactCommand = process.env[MANAGED_OWNER_REDACT_COMMAND_ENV] === "1";
146
+ const binding: ManagedOwnerBinding | null = redactCommand
147
+ ? null
148
+ : {
149
+ schema_version: 2,
150
+ generation,
151
+ session_id: sessionId,
152
+ run_id: runId,
153
+ endpoint_incarnation: incarnation,
154
+ child_token: childToken,
155
+ command,
156
+ command_sha256: commandDigest(command),
157
+ supervisor_pid: process.pid,
158
+ supervisor_start_time: supervisorStartTime,
159
+ created_at: new Date().toISOString(),
160
+ };
161
+ if (binding) await writeDurableExclusive(path.join(root, `child-${childToken}.binding.json`), binding);
162
+ const childEnvironment: NodeJS.ProcessEnv = { ...process.env, [MANAGED_OWNER_CHILD_TOKEN_ENV]: childToken };
163
+ delete childEnvironment[MANAGED_OWNER_COMMAND_ENV];
164
+ delete childEnvironment[MANAGED_OWNER_REDACT_COMMAND_ENV];
157
165
  const child = Bun.spawn({
158
166
  cmd: command,
159
167
  stdin: "inherit",
160
168
  stdout: "inherit",
161
169
  stderr: "inherit",
162
- env: { ...process.env, [MANAGED_OWNER_CHILD_TOKEN_ENV]: childToken },
170
+ env: childEnvironment,
163
171
  });
164
172
  const childStartTime = await managedOwnerProcessProvenance(child.pid);
165
173
  if (!childStartTime) throw new Error("managed_owner_child_start_time_unavailable");
@@ -167,24 +175,26 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
167
175
  if (!childProcess) {
168
176
  const exitCode = await child.exited;
169
177
  if (child.signalCode === "SIGABRT") {
170
- const receipt: ManagedOwnerSigabrtReceipt = {
171
- schema_version: 2,
172
- generation,
173
- session_id: sessionId,
174
- run_id: runId,
175
- endpoint_incarnation: incarnation,
176
- child_token: childToken,
177
- command_sha256: binding.command_sha256,
178
- supervisor_pid: process.pid,
179
- supervisor_start_time: supervisorStartTime,
180
- child_pid: child.pid,
181
- child_start_time: childStartTime,
182
- signal: "SIGABRT",
183
- signal_number: 6,
184
- exit_code: exitCode,
185
- received_at: new Date().toISOString(),
186
- };
187
- await writeDurableExclusive(path.join(root, `sigabrt-${childToken}.receipt.json`), receipt);
178
+ if (binding) {
179
+ const receipt: ManagedOwnerSigabrtReceipt = {
180
+ schema_version: 2,
181
+ generation,
182
+ session_id: sessionId,
183
+ run_id: runId,
184
+ endpoint_incarnation: incarnation,
185
+ child_token: childToken,
186
+ command_sha256: binding.command_sha256,
187
+ supervisor_pid: process.pid,
188
+ supervisor_start_time: supervisorStartTime,
189
+ child_pid: child.pid,
190
+ child_start_time: childStartTime,
191
+ signal: "SIGABRT",
192
+ signal_number: 6,
193
+ exit_code: exitCode,
194
+ received_at: new Date().toISOString(),
195
+ };
196
+ await writeDurableExclusive(path.join(root, `sigabrt-${childToken}.receipt.json`), receipt);
197
+ }
188
198
  process.exitCode = 134;
189
199
  return;
190
200
  }
@@ -246,24 +256,26 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
246
256
  });
247
257
  }
248
258
  if (child.signalCode === "SIGABRT") {
249
- const receipt: ManagedOwnerSigabrtReceipt = {
250
- schema_version: 2,
251
- generation,
252
- session_id: sessionId,
253
- run_id: runId,
254
- endpoint_incarnation: incarnation,
255
- child_token: childToken,
256
- command_sha256: binding.command_sha256,
257
- supervisor_pid: process.pid,
258
- supervisor_start_time: supervisorStartTime,
259
- child_pid: child.pid,
260
- child_start_time: childStartTime,
261
- signal: "SIGABRT",
262
- signal_number: 6,
263
- exit_code: exitCode,
264
- received_at: new Date().toISOString(),
265
- };
266
- await writeDurableExclusive(path.join(root, `sigabrt-${childToken}.receipt.json`), receipt);
259
+ if (binding) {
260
+ const receipt: ManagedOwnerSigabrtReceipt = {
261
+ schema_version: 2,
262
+ generation,
263
+ session_id: sessionId,
264
+ run_id: runId,
265
+ endpoint_incarnation: incarnation,
266
+ child_token: childToken,
267
+ command_sha256: binding.command_sha256,
268
+ supervisor_pid: process.pid,
269
+ supervisor_start_time: supervisorStartTime,
270
+ child_pid: child.pid,
271
+ child_start_time: childStartTime,
272
+ signal: "SIGABRT",
273
+ signal_number: 6,
274
+ exit_code: exitCode,
275
+ received_at: new Date().toISOString(),
276
+ };
277
+ await writeDurableExclusive(path.join(root, `sigabrt-${childToken}.receipt.json`), receipt);
278
+ }
267
279
  process.exitCode = 134;
268
280
  return;
269
281
  }