@gajae-code/coding-agent 0.16.4 → 0.16.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 (198) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/customize-doctor.d.ts +22 -0
  4. package/dist/types/config/file-lock.d.ts +31 -3
  5. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  6. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  7. package/dist/types/discovery/helpers.d.ts +33 -0
  8. package/dist/types/exec/bash-executor.d.ts +17 -1
  9. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  10. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  11. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  12. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  13. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  14. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  15. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  16. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  17. package/dist/types/exec/isolated-shell.d.ts +19 -0
  18. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  19. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  20. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  21. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  22. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  23. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  24. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  25. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  26. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  27. package/dist/types/extensibility/runtime-skill-discovery.d.ts +4 -1
  28. package/dist/types/extensibility/skill-management.d.ts +9 -4
  29. package/dist/types/extensibility/skills.d.ts +2 -0
  30. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  31. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +12 -0
  33. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  34. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  35. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  36. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  37. package/dist/types/modes/interactive-mode.d.ts +2 -0
  38. package/dist/types/modes/types.d.ts +6 -0
  39. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  40. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  41. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  42. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  43. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  44. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  45. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  47. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  48. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  49. package/dist/types/sdk/client/client.d.ts +6 -0
  50. package/dist/types/sdk/host/host.d.ts +3 -1
  51. package/dist/types/sdk/host/session-runtime.d.ts +4 -2
  52. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  53. package/dist/types/sdk/router/session-router.d.ts +10 -2
  54. package/dist/types/sdk/session.d.ts +5 -1
  55. package/dist/types/session/agent-session.d.ts +2 -0
  56. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  57. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  58. package/dist/types/session/messages.d.ts +1 -0
  59. package/dist/types/session/session-manager.d.ts +3 -1
  60. package/dist/types/setup/paseo/announce.d.ts +4 -3
  61. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  62. package/dist/types/system-prompt.d.ts +8 -0
  63. package/dist/types/task/executor.d.ts +3 -0
  64. package/dist/types/tools/bash.d.ts +1 -0
  65. package/dist/types/tools/github-cache.d.ts +2 -1
  66. package/dist/types/tools/index.d.ts +2 -0
  67. package/package.json +9 -7
  68. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  69. package/src/capability/index.ts +14 -2
  70. package/src/capability/types.ts +4 -0
  71. package/src/cli/customize-doctor.ts +155 -12
  72. package/src/cli.ts +22 -0
  73. package/src/commands/sdk.ts +74 -31
  74. package/src/config/file-lock-gc.ts +40 -2
  75. package/src/config/file-lock.ts +198 -14
  76. package/src/config/model-preset-registry.ts +115 -16
  77. package/src/config/model-registry.ts +16 -12
  78. package/src/coordinator-mcp/question-state.ts +33 -2
  79. package/src/coordinator-mcp/server.ts +477 -101
  80. package/src/crash/sanitize.ts +1 -1
  81. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  82. package/src/discovery/builtin.ts +50 -62
  83. package/src/discovery/helpers.ts +297 -32
  84. package/src/exec/bash-executor.ts +330 -50
  85. package/src/exec/bash-shell-guardian.ts +388 -0
  86. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  87. package/src/exec/bash-shell-signals.ts +29 -0
  88. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  89. package/src/exec/bash-shell-supervisor.ts +359 -0
  90. package/src/exec/bash-shell-worker-entry.ts +5 -0
  91. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  92. package/src/exec/bash-shell-worker.ts +116 -0
  93. package/src/exec/isolated-shell.ts +393 -0
  94. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  95. package/src/extensibility/extensions/function-hooks.ts +959 -0
  96. package/src/extensibility/extensions/index.ts +22 -0
  97. package/src/extensibility/extensions/loader.ts +53 -2
  98. package/src/extensibility/extensions/runner.ts +895 -264
  99. package/src/extensibility/extensions/types.ts +42 -0
  100. package/src/extensibility/extensions/wrapper.ts +56 -15
  101. package/src/extensibility/gjc-plugins/compiler.ts +20 -2
  102. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  103. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  104. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  105. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  106. package/src/extensibility/gjc-plugins/types.ts +25 -0
  107. package/src/extensibility/hooks/loader.ts +133 -16
  108. package/src/extensibility/hooks/types.ts +28 -0
  109. package/src/extensibility/runtime-skill-discovery.ts +80 -47
  110. package/src/extensibility/skill-management.ts +200 -52
  111. package/src/extensibility/skills.ts +29 -9
  112. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  113. package/src/gjc-runtime/gc-runtime.ts +29 -0
  114. package/src/gjc-runtime/linux-proc.ts +14 -0
  115. package/src/gjc-runtime/session-state-lock.ts +51 -8
  116. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  117. package/src/gjc-runtime/state-writer.ts +30 -29
  118. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  119. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  120. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  121. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  122. package/src/harness-control-plane/session-lease.ts +2 -2
  123. package/src/internal-urls/docs-index.generated.ts +6 -6
  124. package/src/lsp/config.ts +10 -3
  125. package/src/lsp/index.ts +1 -1
  126. package/src/lsp/path-trust.ts +69 -14
  127. package/src/main.ts +6 -1
  128. package/src/modes/acp/acp-agent.ts +16 -3
  129. package/src/modes/components/bash-execution.ts +4 -1
  130. package/src/modes/components/execution-shared.ts +4 -1
  131. package/src/modes/components/session-observer-overlay.ts +1 -1
  132. package/src/modes/components/tool-status-header.ts +3 -1
  133. package/src/modes/controllers/command-controller.ts +1 -0
  134. package/src/modes/controllers/event-controller.ts +3 -2
  135. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  136. package/src/modes/controllers/input-controller.ts +6 -3
  137. package/src/modes/interactive-mode.ts +6 -5
  138. package/src/modes/print-mode.ts +5 -1
  139. package/src/modes/prompt-action-autocomplete.ts +54 -27
  140. package/src/modes/runtime-init.ts +2 -0
  141. package/src/modes/types.ts +16 -3
  142. package/src/modes/utils/ui-helpers.ts +1 -0
  143. package/src/prompts/tools/subagent.md +1 -0
  144. package/src/runtime-mcp/manager.ts +20 -2
  145. package/src/runtime-mcp/pool.test.ts +20 -11
  146. package/src/sdk/broker/broker.ts +119 -28
  147. package/src/sdk/broker/ensure.ts +248 -41
  148. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  149. package/src/sdk/broker/lifecycle.ts +56 -6
  150. package/src/sdk/broker/startup-failure.ts +15 -6
  151. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  152. package/src/sdk/bus/chat-daemon-runtime.ts +35 -5
  153. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  154. package/src/sdk/bus/conversation-store.ts +29 -0
  155. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  156. package/src/sdk/bus/slack-conversation.ts +1 -0
  157. package/src/sdk/bus/slack-daemon.ts +387 -48
  158. package/src/sdk/bus/telegram-daemon.ts +16 -2
  159. package/src/sdk/bus/topic-registry.ts +19 -0
  160. package/src/sdk/cli/master-cli.ts +1 -1
  161. package/src/sdk/cli/session-cli.ts +2 -2
  162. package/src/sdk/client/client.ts +66 -17
  163. package/src/sdk/host/host.ts +29 -9
  164. package/src/sdk/host/query/revision-store.ts +19 -13
  165. package/src/sdk/host/reverse-leases.ts +7 -4
  166. package/src/sdk/host/session-runtime.test.ts +50 -8
  167. package/src/sdk/host/session-runtime.ts +158 -51
  168. package/src/sdk/lifecycle/service.ts +4 -0
  169. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  170. package/src/sdk/router/session-router.ts +89 -38
  171. package/src/sdk/session.ts +155 -26
  172. package/src/sdk/transport/relay.ts +35 -13
  173. package/src/session/agent-session.ts +117 -52
  174. package/src/session/fallback-chain-controller.ts +38 -0
  175. package/src/session/internal/managed-session-storage.ts +19 -5
  176. package/src/session/messages.ts +5 -0
  177. package/src/session/session-manager.ts +29 -5
  178. package/src/session-import/redact.ts +5 -1
  179. package/src/setup/paseo/announce.ts +49 -43
  180. package/src/setup/paseo/setup-deps.ts +1 -1
  181. package/src/skill-state/canonical-skills.ts +12 -0
  182. package/src/skills/skills.test.ts +41 -21
  183. package/src/system-prompt.ts +25 -3
  184. package/src/task/executor.ts +5 -0
  185. package/src/task/index.ts +4 -0
  186. package/src/tools/bash.ts +28 -11
  187. package/src/tools/gh.ts +26 -3
  188. package/src/tools/github-cache.ts +68 -16
  189. package/src/tools/index.ts +2 -0
  190. package/src/tools/job.ts +13 -2
  191. package/src/tools/read.ts +14 -2
  192. package/src/tools/render-utils.ts +1 -3
  193. package/src/tools/resource-gc.ts +28 -8
  194. package/src/tools/skill-discovery.ts +5 -0
  195. package/src/tools/skill.ts +15 -0
  196. package/src/tools/subagent.ts +3 -2
  197. package/src/tools/tool-catalog.generated.ts +2 -2
  198. package/src/tools/ultragoal-ask-guard.ts +2 -2
@@ -11,7 +11,11 @@ import {
11
11
  COORDINATOR_MCP_TOOL_NAMES,
12
12
  type CoordinatorToolName,
13
13
  } from "../coordinator/contract";
14
- import { SessionStateLockUnavailableError, withSessionStateFileLock } from "../gjc-runtime/session-state-lock";
14
+ import {
15
+ SessionStateLockUnavailableError,
16
+ tryWithSessionStateFileLock,
17
+ withSessionStateFileLock,
18
+ } from "../gjc-runtime/session-state-lock";
15
19
  import {
16
20
  canonicalCoordinatorSidecarPayload,
17
21
  classifyRuntimeToolActivity,
@@ -311,6 +315,13 @@ interface CoordinatorServices {
311
315
  afterCanonicalReportCommit?: (sessionId: string) => void | Promise<void>;
312
316
  /** Test barrier invoked after a canonical report safe response is durable and before outer idempotency completion. */
313
317
  afterCanonicalReportSafeResponse?: (sessionId: string, response: Record<string, unknown>) => void | Promise<void>;
318
+ /** Test barriers around delegate admission and outer response durability. */
319
+ afterDelegateAdmission?: (sessionId: string) => void | Promise<void>;
320
+ afterDelegateCreationWalCommitted?: (sessionId: string) => void | Promise<void>;
321
+ afterDelegateCreationCompleted?: () => void | Promise<void>;
322
+ beforeDelegateResponseCommit?: () => void | Promise<void>;
323
+ afterDelegateResponseCommit?: () => void | Promise<void>;
324
+ readCoordinatorSessionEntries?: (directory: string) => Promise<string[]>;
314
325
  }
315
326
 
316
327
  type CoordinatorModelResolution =
@@ -942,7 +953,11 @@ function toolSchema(name: CoordinatorToolName): {
942
953
  mpreset,
943
954
  model: modelPin,
944
955
  worktree,
945
- await_completion: { type: "boolean", description: "If true, poll the turn until terminal or timeout." },
956
+ await_completion: {
957
+ type: "boolean",
958
+ description:
959
+ "Defaults to false: return after durable admission without completion observation. If true, observe until terminal, waiting_for_answer, or timeout. Once a complete response is committed, an identical admitted retry replays it instead of observing again.",
960
+ },
946
961
  timeout_ms: {
947
962
  type: "number",
948
963
  description:
@@ -995,9 +1010,9 @@ function workflowSkill(workflow: DelegateWorkflow): "ralplan" | "ultragoal" {
995
1010
  function delegateToolDescription(workflow: DelegateWorkflow): string {
996
1011
  switch (workflow) {
997
1012
  case "plan":
998
- return "Delegate consensus planning to GJC: start a session and run /skill:ralplan to completion, returning durable turn status and artifact references.";
1013
+ return "Delegate consensus planning to GJC: start or reuse a session and submit /skill:ralplan. Return durable admission by default; await_completion=true additionally returns bounded completion observation, stopping at terminal, waiting_for_answer, or timeout. Replay remains subject to current authorization, path, model, mpreset, and worktree validation.";
999
1014
  case "execute":
1000
- return "Delegate execution to GJC: start a session and run /skill:ultragoal to completion, returning durable turn status and artifact references.";
1015
+ return "Delegate execution to GJC: start or reuse a session and submit /skill:ultragoal. Return durable admission by default; await_completion=true additionally returns bounded completion observation, stopping at terminal, waiting_for_answer, or timeout. Replay remains subject to current authorization, path, model, mpreset, and worktree validation.";
1001
1016
  }
1002
1017
  }
1003
1018
 
@@ -1267,6 +1282,21 @@ const PUBLIC_ERROR_MESSAGES: Record<string, string> = {
1267
1282
  ownership_mismatch: "Coordinator ownership does not match the request.",
1268
1283
  };
1269
1284
 
1285
+ interface DelegateAdmissionCheckpoint {
1286
+ kind: "delegate_admission";
1287
+ session_id: string;
1288
+ turn_id: string;
1289
+ response: Record<string, unknown>;
1290
+ }
1291
+
1292
+ interface DelegateResponsePinV1 {
1293
+ session_id: string;
1294
+ coordinator_turn_id: string;
1295
+ prompt_key_digest: string;
1296
+ runtime_command_id: string;
1297
+ runtime_turn_id: string;
1298
+ }
1299
+
1270
1300
  interface CoordinatorToolIdempotencyRecord {
1271
1301
  schema_version: 1;
1272
1302
  tool: string;
@@ -1274,6 +1304,8 @@ interface CoordinatorToolIdempotencyRecord {
1274
1304
  request_digest: string;
1275
1305
  state: "in_progress" | "completed";
1276
1306
  response?: Record<string, unknown>;
1307
+ admission?: DelegateAdmissionCheckpoint;
1308
+ delegate_response_pin?: DelegateResponsePinV1;
1277
1309
  created_at: string;
1278
1310
  completed_at?: string;
1279
1311
  }
@@ -3340,10 +3372,22 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3340
3372
  // gate canonical question-state initialization.
3341
3373
  void startupCodexWakeReplay.catch(() => undefined);
3342
3374
  let questionStateReady: Promise<void> | null = null;
3375
+ let delegatePinRecovery: Promise<void> | null = null;
3376
+ let delegatePinCursor: { sessionId: string; promptKey: string | null } | null = null;
3343
3377
 
3344
- function ensureQuestionStateReady(): Promise<void> {
3378
+ async function ensureQuestionStateReady(): Promise<void> {
3345
3379
  questionStateReady ??= initializeCoordinatorNamespace(questionPaths);
3346
- return questionStateReady;
3380
+ await questionStateReady;
3381
+ delegatePinRecovery ??= reconcileCompletedDelegateResponsePins().finally(() => {
3382
+ delegatePinRecovery = null;
3383
+ });
3384
+ try {
3385
+ await delegatePinRecovery;
3386
+ } catch (error) {
3387
+ // Pin cleanup is maintenance after a durable response commit. Keep the
3388
+ // server available, but leave recovery incomplete so the next call retries.
3389
+ logger.warn("Coordinator delegate response pin recovery failed", { error: String(error) });
3390
+ }
3347
3391
  }
3348
3392
 
3349
3393
  let retainedDeliveryRecovery: Promise<void> | null = null;
@@ -5138,7 +5182,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5138
5182
  tool: string,
5139
5183
  idempotencyKey: string,
5140
5184
  canonicalArgs: Record<string, unknown>,
5141
- operation: () => Promise<Record<string, unknown>>,
5185
+ operation: (recovering: boolean) => Promise<Record<string, unknown>>,
5142
5186
  recoverInProgress = false,
5143
5187
  isNonterminal: (response: Record<string, unknown>) => boolean = isRouterRequestAmbiguous,
5144
5188
  lockAlreadyHeld = false,
@@ -5150,6 +5194,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5150
5194
  .digest("hex");
5151
5195
  const file = idempotencyFile(idempotencyKey);
5152
5196
  const lockFile = idempotencyLockFile(idempotencyKey);
5197
+ const releaseResponsePin = async (record: Partial<CoordinatorToolIdempotencyRecord>): Promise<void> => {
5198
+ if (!workflowForDelegateTool(tool) || record.delegate_response_pin === undefined) return;
5199
+ try {
5200
+ if (!(await releaseDelegateResponsePin(record.delegate_response_pin, idempotencyKey)))
5201
+ throw new Error("delegate_response_pin_mismatch");
5202
+ } catch (error) {
5203
+ // Cleanup cannot invalidate an already committed snapshot. Keep the
5204
+ // pin on failure and retry cleanup during readiness or replay.
5205
+ logger.warn("Coordinator delegate response pin cleanup failed", {
5206
+ sessionId: record.delegate_response_pin?.session_id,
5207
+ error: String(error),
5208
+ });
5209
+ }
5210
+ };
5211
+ const commitResponse = async (record: CoordinatorToolIdempotencyRecord): Promise<void> => {
5212
+ if (workflowForDelegateTool(tool)) await services.beforeDelegateResponseCommit?.();
5213
+ let durableRecord = record;
5214
+ if (workflowForDelegateTool(tool)) {
5215
+ const latestFile = await readCoordinatorIdempotencyFile(file);
5216
+ if (latestFile.kind !== "record") throw new Error("terminal_uncertain");
5217
+ const latest = latestFile.value;
5218
+ const pin = delegateResponsePin(latest.delegate_response_pin);
5219
+ if (
5220
+ latest.schema_version !== 1 ||
5221
+ latest.tool !== tool ||
5222
+ latest.key_digest !== keyDigest ||
5223
+ latest.request_digest !== requestDigest ||
5224
+ latest.state !== "in_progress"
5225
+ )
5226
+ throw new Error("terminal_uncertain");
5227
+ if (record.response?.ok === true && !pin) throw new Error("terminal_uncertain");
5228
+ durableRecord = pin
5229
+ ? {
5230
+ ...record,
5231
+ ...(latest.admission ? { admission: latest.admission as DelegateAdmissionCheckpoint } : {}),
5232
+ delegate_response_pin: pin,
5233
+ }
5234
+ : record;
5235
+ }
5236
+ await writeCoordinatorIdempotencyFile(file, durableRecord);
5237
+ if (workflowForDelegateTool(tool)) await services.afterDelegateResponseCommit?.();
5238
+ await releaseResponsePin(durableRecord);
5239
+ };
5153
5240
  const execute = async (): Promise<Record<string, unknown>> => {
5154
5241
  const existingFile = await readCoordinatorIdempotencyFile(file);
5155
5242
  if (existingFile.kind === "corrupt")
@@ -5175,7 +5262,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5175
5262
  };
5176
5263
  if (existing.state === "completed") {
5177
5264
  const replay = asRecord(existing.response);
5178
- if (replay) return boundedToolResponse(tool, replay);
5265
+ if (replay) {
5266
+ await releaseResponsePin(existing);
5267
+ return boundedToolResponse(tool, replay);
5268
+ }
5179
5269
  return {
5180
5270
  ok: false,
5181
5271
  error: {
@@ -5198,7 +5288,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5198
5288
  // operation and changing volatile projection timestamps.
5199
5289
  const persistedResponse = asRecord(existing.response);
5200
5290
  if (persistedResponse && !isNonterminal(persistedResponse)) {
5201
- await writeCoordinatorIdempotencyFile(file, {
5291
+ await commitResponse({
5202
5292
  ...existing,
5203
5293
  state: "completed",
5204
5294
  response: persistedResponse,
@@ -5206,12 +5296,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5206
5296
  } as CoordinatorToolIdempotencyRecord);
5207
5297
  return boundedToolResponse(tool, persistedResponse);
5208
5298
  }
5209
- const rawResponse = await operation().catch(error => sdkError(error));
5299
+ const rawResponse = await operation(true).catch(error => sdkError(error));
5210
5300
  const response = boundedToolResponse(tool, rawResponse);
5211
5301
  // The receipt keeps its original key and request digests, so a
5212
5302
  // reused key still conflicts and a later settled answer still seals.
5213
5303
  if (isNonterminal(rawResponse)) return response;
5214
- await writeCoordinatorIdempotencyFile(file, {
5304
+ await commitResponse({
5215
5305
  ...existing,
5216
5306
  state: "completed",
5217
5307
  response,
@@ -5236,10 +5326,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5236
5326
  created_at: new Date().toISOString(),
5237
5327
  };
5238
5328
  await writeCoordinatorIdempotencyFile(file, started);
5239
- const rawResponse = await operation().catch(error => sdkError(error));
5329
+ const rawResponse = await operation(false).catch(error => sdkError(error));
5240
5330
  const response = boundedToolResponse(tool, rawResponse);
5241
5331
  if (isNonterminal(rawResponse)) return response;
5242
- await writeCoordinatorIdempotencyFile(file, {
5332
+ await commitResponse({
5243
5333
  ...started,
5244
5334
  state: "completed",
5245
5335
  response,
@@ -6336,11 +6426,159 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6336
6426
  return payload;
6337
6427
  }
6338
6428
 
6429
+ function delegateResponsePin(value: unknown): DelegateResponsePinV1 | null {
6430
+ const pin = asRecord(value);
6431
+ if (
6432
+ !pin ||
6433
+ typeof pin.session_id !== "string" ||
6434
+ !SAFE_EXTERNAL_ID_PATTERN.test(pin.session_id) ||
6435
+ typeof pin.coordinator_turn_id !== "string" ||
6436
+ !TURN_ID_PATTERN.test(pin.coordinator_turn_id) ||
6437
+ typeof pin.prompt_key_digest !== "string" ||
6438
+ !/^[a-f0-9]{64}$/.test(pin.prompt_key_digest) ||
6439
+ typeof pin.runtime_command_id !== "string" ||
6440
+ !SAFE_EXTERNAL_ID_PATTERN.test(pin.runtime_command_id) ||
6441
+ typeof pin.runtime_turn_id !== "string" ||
6442
+ !SAFE_EXTERNAL_ID_PATTERN.test(pin.runtime_turn_id)
6443
+ )
6444
+ return null;
6445
+ return pin as unknown as DelegateResponsePinV1;
6446
+ }
6447
+
6448
+ /** A persisted final response, never admission alone, permits source compaction. */
6449
+ async function releaseDelegateResponsePin(rawPin: unknown, idempotencyKey: string): Promise<boolean> {
6450
+ const pin = delegateResponsePin(rawPin);
6451
+ if (!pin) return false;
6452
+ if (!(await readSessionTransaction(questionPaths, pin.session_id))) return true;
6453
+ let released = false;
6454
+ await withSessionTransaction(questionPaths, pin.session_id, async transaction => {
6455
+ const request = transaction.requests.prompts[pin.prompt_key_digest];
6456
+ if (request?.delegate_response_pending !== true) {
6457
+ released = true;
6458
+ return;
6459
+ }
6460
+ if (
6461
+ request.sdk_idempotency_key === idempotencyKey &&
6462
+ request.coordinator_turn_id === pin.coordinator_turn_id &&
6463
+ request.runtime_receipt?.accepted === true &&
6464
+ request.runtime_receipt.command_id === pin.runtime_command_id &&
6465
+ request.runtime_receipt.turn_id === pin.runtime_turn_id
6466
+ ) {
6467
+ delete request.delegate_response_pending;
6468
+ released = true;
6469
+ }
6470
+ }).catch(error => {
6471
+ if (!(error instanceof Error) || error.message !== "resource_gone") throw error;
6472
+ released = true;
6473
+ });
6474
+ return released;
6475
+ }
6476
+
6477
+ async function reconcileCompletedDelegateResponsePins(): Promise<void> {
6478
+ const roster = await readSchedulerRoster(questionPaths);
6479
+ let persisted: string[];
6480
+ try {
6481
+ persisted = await (services.readCoordinatorSessionEntries ?? fs.readdir)(questionPaths.sessions);
6482
+ } catch (error) {
6483
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
6484
+ persisted = [];
6485
+ }
6486
+ const sessionIds = [
6487
+ ...new Set([
6488
+ ...roster.roster.map(entry => entry.session_id),
6489
+ ...persisted.filter(entry => COORDINATOR_SESSION_ID_PATTERN.test(entry)),
6490
+ ]),
6491
+ ].sort();
6492
+ // Readiness recurs even after a clean scan: another coordinator may create
6493
+ // a pin later. Bound WAL reads and receipt inspections per pass, and resume
6494
+ // within a session when its pins exhaust the shared budget.
6495
+ const cursor = delegatePinCursor;
6496
+ const nextIndex = sessionIds.findIndex(
6497
+ id => cursor === null || (cursor.promptKey !== null ? id >= cursor.sessionId : id > cursor.sessionId),
6498
+ );
6499
+ const start = nextIndex < 0 ? 0 : nextIndex;
6500
+ let remainingPins = 32;
6501
+ const failures: unknown[] = [];
6502
+ for (let offset = 0; offset < Math.min(sessionIds.length, 32) && remainingPins > 0; offset++) {
6503
+ const sessionId = sessionIds[(start + offset) % sessionIds.length]!;
6504
+ const afterPrompt = offset === 0 && sessionId === cursor?.sessionId ? cursor.promptKey : null;
6505
+ delegatePinCursor = { sessionId, promptKey: null };
6506
+ let transaction: CoordinatorSessionTransactionV1 | null;
6507
+ try {
6508
+ transaction = await readSessionTransaction(questionPaths, sessionId);
6509
+ } catch (error) {
6510
+ failures.push(error);
6511
+ continue;
6512
+ }
6513
+ if (!transaction) continue;
6514
+ for (const promptKey of Object.keys(transaction.requests.prompts).sort()) {
6515
+ if (afterPrompt !== null && promptKey <= afterPrompt) continue;
6516
+ const request = transaction.requests.prompts[promptKey]!;
6517
+ if (request.delegate_response_pending !== true) continue;
6518
+ if (remainingPins === 0) break;
6519
+ remainingPins--;
6520
+ delegatePinCursor = { sessionId, promptKey };
6521
+ const keyDigest = createHash("sha256").update(request.sdk_idempotency_key).digest("hex");
6522
+ const file = path.join(namespaceDir, "idempotency", `${keyDigest}.json`);
6523
+ try {
6524
+ // Receipts are atomically replaced. An in-progress delegate may own
6525
+ // its caller-key lock throughout observation; maintenance must not wait
6526
+ // for it. Completed candidates still need a locked authoritative read.
6527
+ const candidate = await readCoordinatorIdempotencyFile(file);
6528
+ if (candidate.kind === "corrupt") throw new Error("delegate_response_receipt_corrupt");
6529
+ if (candidate.kind !== "record" || candidate.value.state !== "completed") continue;
6530
+ // A completed receipt can still have a live writer finishing cleanup.
6531
+ // Busy candidates remain pinned until a later round-robin pass.
6532
+ await tryWithSessionStateFileLock(
6533
+ path.join(namespaceDir, "idempotency-locks", `${keyDigest}.json`),
6534
+ async () => {
6535
+ const current = await readCoordinatorIdempotencyFile(file);
6536
+ if (current.kind === "missing") return;
6537
+ if (current.kind === "corrupt") throw new Error("delegate_response_receipt_corrupt");
6538
+ if (current.value.state !== "completed") return;
6539
+ // Match replay's response requirement before relinquishing canonical
6540
+ // recovery evidence. Public response subfields may be truncated.
6541
+ if (!asRecord(current.value.response)) throw new Error("delegate_response_receipt_corrupt");
6542
+ const pin = delegateResponsePin(current.value.delegate_response_pin);
6543
+ if (
6544
+ current.value.key_digest !== keyDigest ||
6545
+ pin?.session_id !== sessionId ||
6546
+ pin.prompt_key_digest !== promptKey ||
6547
+ !(await releaseDelegateResponsePin(pin, request.sdk_idempotency_key))
6548
+ )
6549
+ throw new Error("delegate_response_pin_mismatch");
6550
+ },
6551
+ );
6552
+ } catch (error) {
6553
+ failures.push(error);
6554
+ }
6555
+ }
6556
+ if (remainingPins > 0) delegatePinCursor = { sessionId, promptKey: null };
6557
+ }
6558
+ if (failures.length > 0) throw new AggregateError(failures, "delegate_response_pin_recovery_incomplete");
6559
+ }
6560
+
6561
+ function hasInitialDelegatePromptAuthority(
6562
+ transaction: CoordinatorSessionTransactionV1 | null,
6563
+ request: CoordinatorSessionTransactionV1["recovery"]["initial_delegate_request"],
6564
+ ): boolean {
6565
+ const pending = transaction?.recovery.initial_delegate_request;
6566
+ return (
6567
+ pending !== undefined &&
6568
+ request !== undefined &&
6569
+ pending.key_digest === request.key_digest &&
6570
+ pending.request_digest === request.request_digest
6571
+ );
6572
+ }
6573
+
6339
6574
  async function claimCanonicalPrompt(
6340
6575
  sessionId: string,
6341
6576
  prompt: string,
6342
6577
  operation: "turn.prompt" | "turn.follow_up" | "turn.abort_and_prompt",
6343
6578
  idempotencyKey: string,
6579
+ retainDelegateResponse = false,
6580
+ requireExisting = false,
6581
+ initialDelegateRequest?: CoordinatorSessionTransactionV1["recovery"]["initial_delegate_request"],
6344
6582
  ): Promise<string> {
6345
6583
  await ensureQuestionTransaction(sessionId);
6346
6584
  const keyDigest = createHash("sha256").update(`${idempotencyKey}\0${operation}`).digest("hex");
@@ -6350,6 +6588,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6350
6588
  if (existing) {
6351
6589
  if (existing.request_digest !== requestDigest) throw new Error("idempotency_conflict");
6352
6590
  if (existing.phase === "uncertain") throw new Error("terminal_uncertain");
6591
+ if (retainDelegateResponse) existing.delegate_response_pending = true;
6353
6592
  // An accepted receipt is durable authority. It is recovered below instead
6354
6593
  // of sending the same remote idempotency key a second time.
6355
6594
  if (existing.phase === "accepted" || existing.phase === "completed" || existing.phase === "claimed") return;
@@ -6358,6 +6597,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6358
6597
  return;
6359
6598
  }
6360
6599
  }
6600
+ // Check under the WAL lock as well as at the delegate boundary: an
6601
+ // unpinned historical receipt could compact between those two reads.
6602
+ const initialAuthority = hasInitialDelegatePromptAuthority(transaction, initialDelegateRequest);
6603
+ if (requireExisting && !initialAuthority) throw new Error("terminal_uncertain");
6361
6604
  const activeTurnId = transaction.canonical.queue.active_turn_id;
6362
6605
  const anotherActiveTurn = Object.values(transaction.canonical.turns).some(turn =>
6363
6606
  ACTIVE_TURN_STATUSES.has(turn.status as TurnStatus),
@@ -6413,9 +6656,13 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6413
6656
  sdk_idempotency_key: idempotencyKey,
6414
6657
  phase: "claimed",
6415
6658
  coordinator_turn_id: reserved.turn_id,
6659
+ ...(retainDelegateResponse ? { delegate_response_pending: true } : {}),
6416
6660
  created_at: now,
6417
6661
  updated_at: now,
6418
6662
  };
6663
+ // This transaction replaces pre-dispatch authority with the pinned prompt
6664
+ // reservation. Missing historical requests can never reuse that authority.
6665
+ if (initialAuthority) delete transaction.recovery.initial_delegate_request;
6419
6666
  });
6420
6667
  return keyDigest;
6421
6668
  }
@@ -7399,6 +7646,9 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7399
7646
 
7400
7647
  async function callTool(name: string, args: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
7401
7648
  try {
7649
+ // Run startup maintenance before any tool-specific idempotency/session
7650
+ // lock is acquired. Nested state initialization must never invert those locks.
7651
+ await ensureQuestionStateReady();
7402
7652
  if (name === "gjc_coordinator_list_sessions") {
7403
7653
  // listSessions() enumerates the broker across allowed roots, but every other
7404
7654
  // coordinator tool resolves a session through its durable projection. Without
@@ -8196,18 +8446,54 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8196
8446
  ...(delegateWorktree.name ? { worktree: delegateWorktree.name } : {}),
8197
8447
  allow_mutation: true,
8198
8448
  };
8449
+ const operation =
8450
+ args.force === true ? "turn.abort_and_prompt" : args.queue === true ? "turn.follow_up" : "turn.prompt";
8451
+ const requestKey = createHash("sha256").update(`${idempotencyKey}\0${operation}`).digest("hex");
8199
8452
  let creationRemoteStarted = false;
8453
+ let delegateEffectStarted = false;
8454
+ let delegateResponseComplete = false;
8200
8455
  return await withToolIdempotency(
8201
8456
  name,
8202
8457
  idempotencyKey,
8203
8458
  canonicalArgs,
8204
- async () => {
8205
- const delegate = async () => {
8459
+ async recovering => {
8460
+ // An older in-progress request can have archived authority. Missing
8461
+ // live state is not proof that this key never dispatched a prompt.
8462
+ delegateEffectStarted = recovering;
8463
+ const delegate = async (): Promise<Record<string, unknown>> => {
8464
+ // The outer key lock validates identity before entering here. Admission
8465
+ // is not a final response: an interrupted await must still be observed.
8466
+ const outer = await readCoordinatorIdempotencyFile(idempotencyFile(idempotencyKey));
8467
+ if (outer.kind !== "record") throw new Error("terminal_uncertain");
8468
+ const checkpoint = asRecord(outer.value.admission);
8469
+ if (checkpoint) {
8470
+ delegateEffectStarted = true;
8471
+ const response = asRecord(checkpoint.response);
8472
+ if (
8473
+ checkpoint.kind !== "delegate_admission" ||
8474
+ typeof checkpoint.session_id !== "string" ||
8475
+ !SAFE_EXTERNAL_ID_PATTERN.test(checkpoint.session_id) ||
8476
+ typeof checkpoint.turn_id !== "string" ||
8477
+ !TURN_ID_PATTERN.test(checkpoint.turn_id) ||
8478
+ !response ||
8479
+ response.ok !== true ||
8480
+ response.tool_name !== name ||
8481
+ response.workflow !== delegateWorkflow ||
8482
+ response.session_id !== checkpoint.session_id ||
8483
+ response.turn_id !== checkpoint.turn_id
8484
+ )
8485
+ throw new Error("terminal_uncertain");
8486
+ return response;
8487
+ }
8206
8488
  let sessionId: string;
8207
8489
  let session: Record<string, unknown>;
8208
8490
  let creationKey: string | null = null;
8491
+ let initialDelegateRequest: CoordinatorSessionTransactionV1["recovery"]["initial_delegate_request"];
8209
8492
  if (reusedSessionId) {
8210
8493
  sessionId = reusedSessionId;
8494
+ const prior = await readSessionTransaction(questionPaths, sessionId);
8495
+ const prompt = prior?.requests.prompts[requestKey];
8496
+ delegateEffectStarted ||= Boolean(prompt && prompt.phase !== "claimed");
8211
8497
  const existing = asRecord(await readJsonFile(sessionFile(sessionId)));
8212
8498
  if (!existing)
8213
8499
  return {
@@ -8264,10 +8550,20 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8264
8550
  });
8265
8551
  } else {
8266
8552
  const creation = await claimProductionCreation(name, idempotencyKey, canonicalArgs);
8267
- if (creation.request.phase === "completed" && creation.request.safe_response)
8553
+ creationRemoteStarted = creation.request.phase !== "claimed";
8554
+ if (creation.request.phase === "completed" && creation.request.safe_response) {
8555
+ delegateEffectStarted = true;
8268
8556
  return creation.request.safe_response;
8557
+ }
8269
8558
  creationKey = creation.keyDigest;
8559
+ // claimProductionCreation has validated the caller key and all
8560
+ // canonical arguments, including the prompt mode and workflow.
8561
+ initialDelegateRequest = {
8562
+ key_digest: creation.keyDigest,
8563
+ request_digest: creation.request.request_digest,
8564
+ };
8270
8565
  if (creation.request.canonical_create_intent) {
8566
+ delegateEffectStarted = true;
8271
8567
  session = sessionFromCreationSnapshot(creation.request.canonical_create_intent.session);
8272
8568
  await bindCreationRequest(
8273
8569
  questionPaths,
@@ -8353,63 +8649,134 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8353
8649
  await bindCreationRequest(questionPaths, creation.keyDigest, intent);
8354
8650
  await commitCreationWal(questionPaths, creation.keyDigest, intent);
8355
8651
  }
8652
+ await services.afterDelegateCreationWalCommitted?.(sessionId);
8356
8653
  }
8357
- await writeJsonFile(sessionFile(sessionId), session);
8358
- const previousActiveTurn =
8359
- (await readActiveTurn(namespaceDir, sessionId)) ?? (await readCanonicalActiveTurn(sessionId));
8360
- if (previousActiveTurn && args.queue !== true && args.force !== true) {
8361
- return {
8362
- ok: false,
8363
- error: {
8364
- code: "active_turn_exists",
8365
- message: `Session ${sessionId} already has active turn ${previousActiveTurn.turn_id}.`,
8654
+ const admit = async (): Promise<Record<string, unknown>> => {
8655
+ await writeJsonFile(sessionFile(sessionId), session);
8656
+ const previousActiveTurn =
8657
+ (await readActiveTurn(namespaceDir, sessionId)) ??
8658
+ (await readCanonicalActiveTurn(sessionId));
8659
+ const priorTransaction = await readSessionTransaction(questionPaths, sessionId);
8660
+ const priorPrompt = priorTransaction?.requests.prompts[requestKey];
8661
+ if (
8662
+ recovering &&
8663
+ !priorPrompt &&
8664
+ !hasInitialDelegatePromptAuthority(priorTransaction, initialDelegateRequest)
8665
+ )
8666
+ throw new Error("terminal_uncertain");
8667
+ if (priorPrompt && ["remote_started", "accepted", "completed"].includes(priorPrompt.phase))
8668
+ delegateEffectStarted = true;
8669
+ const reserved = priorPrompt?.coordinator_turn_id
8670
+ ? priorTransaction?.canonical.turns[priorPrompt.coordinator_turn_id]
8671
+ : null;
8672
+ if (priorPrompt && !reserved) throw new Error("terminal_uncertain");
8673
+ if (
8674
+ priorPrompt &&
8675
+ priorPrompt.phase !== "completed" &&
8676
+ operation !== "turn.follow_up" &&
8677
+ !reserved?.terminal_fence &&
8678
+ priorTransaction?.canonical.queue.active_turn_id !== reserved?.turn_id
8679
+ )
8680
+ throw new Error("terminal_uncertain");
8681
+ // Recover this key's reservation before rejecting a busy session.
8682
+ if (!priorPrompt && previousActiveTurn && args.queue !== true && args.force !== true) {
8683
+ return {
8684
+ ok: false,
8685
+ error: {
8686
+ code: "active_turn_exists",
8687
+ message: `Session ${sessionId} already has active turn ${previousActiveTurn.turn_id}.`,
8688
+ },
8689
+ turn_id: previousActiveTurn.turn_id,
8690
+ };
8691
+ }
8692
+ const promptKey = await claimCanonicalPrompt(
8693
+ sessionId,
8694
+ taggedPrompt,
8695
+ operation,
8696
+ idempotencyKey,
8697
+ true,
8698
+ recovering,
8699
+ initialDelegateRequest,
8700
+ );
8701
+ if (reserved?.terminal_fence && !(await promptReceipt(sessionId, promptKey))) {
8702
+ delegateEffectStarted = true;
8703
+ throw new Error("terminal_uncertain");
8704
+ }
8705
+ let acknowledgement: RuntimePromptAcknowledgement;
8706
+ try {
8707
+ acknowledgement = await dispatchOrRecoverPrompt(
8708
+ session,
8709
+ sessionId,
8710
+ operation,
8711
+ taggedPrompt,
8712
+ idempotencyKey,
8713
+ promptKey,
8714
+ );
8715
+ } catch (error) {
8716
+ // Receipt-first failures on reused sessions must remain recoverable.
8717
+ delegateEffectStarted ||= !(error instanceof SdkClientError);
8718
+ delegateEffectStarted ||= (await promptReceipt(sessionId, promptKey)) !== null;
8719
+ throw error;
8720
+ }
8721
+ delegateEffectStarted = true;
8722
+ const turn = await recordAcceptedPrompt(
8723
+ sessionId,
8724
+ taggedPrompt,
8725
+ operation,
8726
+ reserved?.terminal_fence || previousActiveTurn?.turn_id === reserved?.turn_id
8727
+ ? null
8728
+ : previousActiveTurn,
8729
+ acknowledgement,
8730
+ promptKey,
8731
+ );
8732
+ const codexHandoff = await autoBindDelegateCodexHandoff(
8733
+ namespaceDir,
8734
+ canonicalCwd,
8735
+ sessionId,
8736
+ turn.turn_id,
8737
+ delegateWorkflow,
8738
+ explicitHostWorkUnit,
8739
+ );
8740
+ await appendCoordinatorEvent(namespaceDir, {
8741
+ stableId: `delegation:${sessionId}:${turn.turn_id}`,
8742
+ kind: "delegation.started",
8743
+ sessionId,
8744
+ turnId: turn.turn_id,
8745
+ summary: `Delegated ${delegateWorkflow} via ${name} on session ${sessionId}`,
8746
+ metadata: {
8747
+ workflow: delegateWorkflow,
8748
+ tool_name: name,
8749
+ session_id: sessionId,
8750
+ turn_id: turn.turn_id,
8751
+ active_turn_id: turn.delivery.queued
8752
+ ? (previousActiveTurn?.turn_id ?? null)
8753
+ : turn.turn_id,
8754
+ status: turn.status,
8755
+ queued: turn.delivery.queued,
8756
+ delivered: turn.delivery.delivered,
8757
+ delivery: turn.delivery,
8758
+ session: publicCoordinatorSession(session),
8759
+ session_state: publicCoordinatorSessionState(
8760
+ await readSessionState(namespaceDir, sessionId),
8761
+ ),
8762
+ turn: boundedPublicValue(turn, { remaining: COORDINATOR_IDEMPOTENCY_RESPONSE_BYTE_CAP }),
8763
+ result: publicSdkAcknowledgement(acknowledgement),
8764
+ codex_handoff: codexHandoff,
8765
+ endpoint_incarnation: session.endpoint_incarnation,
8766
+ ...(hasTask && hasPrompt ? { prompt_alias_ignored: true } : {}),
8366
8767
  },
8367
- turn_id: previousActiveTurn.turn_id,
8368
- };
8369
- }
8370
- const operation =
8371
- args.force === true
8372
- ? "turn.abort_and_prompt"
8373
- : args.queue === true
8374
- ? "turn.follow_up"
8375
- : "turn.prompt";
8376
- const promptKey = await claimCanonicalPrompt(sessionId, taggedPrompt, operation, idempotencyKey);
8377
- const acknowledgement = await dispatchOrRecoverPrompt(
8378
- session,
8379
- sessionId,
8380
- operation,
8381
- taggedPrompt,
8382
- idempotencyKey,
8383
- promptKey,
8384
- );
8385
- const turn = await recordAcceptedPrompt(
8386
- sessionId,
8387
- taggedPrompt,
8388
- operation,
8389
- previousActiveTurn,
8390
- acknowledgement,
8391
- promptKey,
8392
- );
8393
- const codexHandoff = await autoBindDelegateCodexHandoff(
8394
- namespaceDir,
8395
- canonicalCwd,
8396
- sessionId,
8397
- turn.turn_id,
8398
- delegateWorkflow,
8399
- explicitHostWorkUnit,
8400
- );
8401
- await appendCoordinatorEvent(namespaceDir, {
8402
- stableId: `delegation:${sessionId}:${turn.turn_id}`,
8403
- kind: "delegation.started",
8404
- sessionId,
8405
- turnId: turn.turn_id,
8406
- summary: `Delegated ${delegateWorkflow} via ${name} on session ${sessionId}`,
8407
- metadata: {
8768
+ });
8769
+ const response = {
8770
+ ok: true,
8408
8771
  workflow: delegateWorkflow,
8409
8772
  tool_name: name,
8410
8773
  session_id: sessionId,
8411
8774
  turn_id: turn.turn_id,
8412
- active_turn_id: turn.delivery.queued ? (previousActiveTurn?.turn_id ?? null) : turn.turn_id,
8775
+ active_turn_id: turn.delivery.queued
8776
+ ? (previousActiveTurn?.turn_id ?? null)
8777
+ : TERMINAL_TURN_STATUSES.has(turn.status)
8778
+ ? null
8779
+ : turn.turn_id,
8413
8780
  status: turn.status,
8414
8781
  queued: turn.delivery.queued,
8415
8782
  delivered: turn.delivery.delivered,
@@ -8421,52 +8788,61 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8421
8788
  turn: boundedPublicValue(turn, { remaining: COORDINATOR_IDEMPOTENCY_RESPONSE_BYTE_CAP }),
8422
8789
  result: publicSdkAcknowledgement(acknowledgement),
8423
8790
  codex_handoff: codexHandoff,
8424
- endpoint_incarnation: session.endpoint_incarnation,
8425
8791
  ...(hasTask && hasPrompt ? { prompt_alias_ignored: true } : {}),
8426
- },
8427
- });
8428
- const response = {
8429
- ok: true,
8430
- workflow: delegateWorkflow,
8431
- tool_name: name,
8432
- session_id: sessionId,
8433
- turn_id: turn.turn_id,
8434
- active_turn_id: turn.delivery.queued
8435
- ? (previousActiveTurn?.turn_id ?? null)
8436
- : TERMINAL_TURN_STATUSES.has(turn.status)
8437
- ? null
8438
- : turn.turn_id,
8439
- status: turn.status,
8440
- queued: turn.delivery.queued,
8441
- delivered: turn.delivery.delivered,
8442
- delivery: turn.delivery,
8443
- session: publicCoordinatorSession(session),
8444
- session_state: publicCoordinatorSessionState(await readSessionState(namespaceDir, sessionId)),
8445
- turn: boundedPublicValue(turn, { remaining: COORDINATOR_IDEMPOTENCY_RESPONSE_BYTE_CAP }),
8446
- result: publicSdkAcknowledgement(acknowledgement),
8447
- codex_handoff: codexHandoff,
8448
- ...(hasTask && hasPrompt ? { prompt_alias_ignored: true } : {}),
8792
+ };
8793
+ const admission: DelegateAdmissionCheckpoint = {
8794
+ kind: "delegate_admission",
8795
+ session_id: sessionId,
8796
+ turn_id: turn.turn_id,
8797
+ response: boundedToolResponse(name, response),
8798
+ };
8799
+ const delegateResponsePin: DelegateResponsePinV1 = {
8800
+ session_id: sessionId,
8801
+ coordinator_turn_id: turn.turn_id,
8802
+ prompt_key_digest: promptKey,
8803
+ runtime_command_id: acknowledgement.command_id,
8804
+ runtime_turn_id: acknowledgement.turn_id,
8805
+ };
8806
+ await writeCoordinatorIdempotencyFile(idempotencyFile(idempotencyKey), {
8807
+ ...(outer.value as unknown as CoordinatorToolIdempotencyRecord),
8808
+ admission,
8809
+ delegate_response_pin: delegateResponsePin,
8810
+ });
8811
+ // A completed creation must never outlive the outer recovery identity.
8812
+ if (creationKey) {
8813
+ await advanceCreationReceipt(questionPaths, creationKey, "projected", response);
8814
+ await advanceCreationReceipt(questionPaths, creationKey, "completed", response);
8815
+ await services.afterDelegateCreationCompleted?.();
8816
+ }
8817
+ await services.afterDelegateAdmission?.(sessionId);
8818
+ return admission.response;
8449
8819
  };
8450
- if (creationKey) {
8451
- await advanceCreationReceipt(questionPaths, creationKey, "projected", response);
8452
- await advanceCreationReceipt(questionPaths, creationKey, "completed", response);
8453
- }
8454
- return args.await_completion === true
8820
+ return reusedSessionId ? await admit() : await withSessionTransition(sessionId, admit);
8821
+ };
8822
+ const response = reusedSessionId
8823
+ ? await withSessionTransition(reusedSessionId, delegate)
8824
+ : await delegate();
8825
+ // Observation owns the same key flight, not SESSION: force/queue may
8826
+ // now dispatch while the full response awaits its durable commit.
8827
+ const complete =
8828
+ response.ok === true && args.await_completion === true
8455
8829
  ? {
8456
8830
  ...response,
8457
8831
  completion: await awaitTurnPayload(
8458
- turn.turn_id,
8459
- sessionId,
8832
+ response.turn_id,
8833
+ response.session_id,
8460
8834
  args.timeout_ms,
8461
8835
  args.poll_interval_ms,
8462
8836
  ),
8463
8837
  }
8464
8838
  : response;
8465
- };
8466
- return reusedSessionId ? await withSessionTransition(reusedSessionId, delegate) : await delegate();
8839
+ delegateResponseComplete = response.ok === true;
8840
+ return complete;
8467
8841
  },
8468
8842
  true,
8469
- response => creationRemoteStarted || isRouterRequestAmbiguous(response),
8843
+ response =>
8844
+ (!delegateResponseComplete && (creationRemoteStarted || delegateEffectStarted)) ||
8845
+ isRouterRequestAmbiguous(response),
8470
8846
  );
8471
8847
  }
8472
8848
  if (name === "gjc_coordinator_stop_session") {