@cydm/happy-elves 0.1.0-beta.80 → 0.1.0-beta.81

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 (91) hide show
  1. package/apps/cli/dist/commands/daemon.js +3 -3
  2. package/apps/cli/dist/commands/lib/bootstrap.js +5 -2
  3. package/apps/cli/dist/commands/lib/exit.js +11 -0
  4. package/apps/cli/dist/commands/lib/session-output.d.ts +3 -1
  5. package/apps/cli/dist/commands/lib/session-output.js +8 -4
  6. package/apps/cli/dist/commands/lib/session-view.d.ts +6 -0
  7. package/apps/cli/dist/commands/lib/session-view.js +9 -1
  8. package/apps/cli/dist/commands/lib/usage.js +2 -2
  9. package/apps/cli/dist/commands/orchestrator.js +118 -14
  10. package/apps/cli/dist/commands/session.js +10 -3
  11. package/apps/cli/dist/commands/skill.js +14 -0
  12. package/apps/cli/dist/commands/turn.js +26 -16
  13. package/apps/cli/dist/index.js +8 -1
  14. package/apps/cli/dist/skills/auto-align.d.ts +21 -0
  15. package/apps/cli/dist/skills/auto-align.js +134 -0
  16. package/apps/cli/dist/skills/registry.d.ts +1 -1
  17. package/apps/cli/dist/skills/registry.js +8 -5
  18. package/apps/daemon/dist/gateway/runtime.js +7 -7
  19. package/apps/daemon/dist/lifecycle/detached-helper-bundle.mjs +50 -6
  20. package/apps/daemon/dist/loop/runner.js +19 -12
  21. package/apps/daemon/dist/memory/config.d.ts +7 -0
  22. package/apps/daemon/dist/memory/config.js +9 -0
  23. package/apps/daemon/dist/memory/flush.d.ts +11 -7
  24. package/apps/daemon/dist/memory/flush.js +16 -470
  25. package/apps/daemon/dist/paths.js +1 -1
  26. package/apps/daemon/dist/relay/register.js +1 -0
  27. package/apps/daemon/dist/runtime/catalog.d.ts +29 -7
  28. package/apps/daemon/dist/runtime/catalog.js +169 -60
  29. package/apps/daemon/dist/runtime/external-provider.js +1 -0
  30. package/apps/daemon/dist/runtime/selection.d.ts +2 -1
  31. package/apps/daemon/dist/runtime/selection.js +11 -3
  32. package/apps/daemon/dist/runtime/turn-projection.d.ts +14 -1
  33. package/apps/daemon/dist/runtime/turn-projection.js +22 -1
  34. package/apps/daemon/dist/runtime.d.ts +1 -1
  35. package/apps/daemon/dist/runtime.js +2 -2
  36. package/apps/daemon/dist/session/create-session.js +5 -1
  37. package/apps/daemon/dist/session/lifecycle.js +1 -0
  38. package/apps/daemon/dist/session/prompt.js +8 -7
  39. package/apps/daemon/dist/session/reconcile.js +20 -12
  40. package/apps/daemon/dist/session/retry-recovery.js +2 -2
  41. package/apps/daemon/dist/start.js +1 -1
  42. package/apps/daemon/package.json +1 -1
  43. package/apps/relay/dist/controller-handlers.d.ts +1 -0
  44. package/apps/relay/dist/controller-handlers.js +600 -201
  45. package/apps/relay/dist/db.js +263 -16
  46. package/apps/relay/dist/dispatch-outbox.d.ts +66 -0
  47. package/apps/relay/dist/dispatch-outbox.js +74 -0
  48. package/apps/relay/dist/event-visibility.d.ts +9 -1
  49. package/apps/relay/dist/event-visibility.js +42 -18
  50. package/apps/relay/dist/http-routes.js +44 -56
  51. package/apps/relay/dist/legacy-recovery.d.ts +94 -0
  52. package/apps/relay/dist/legacy-recovery.js +380 -0
  53. package/apps/relay/dist/machine-command-result-handlers.js +58 -3
  54. package/apps/relay/dist/machine-handler-context.d.ts +0 -6
  55. package/apps/relay/dist/projection-repository.d.ts +19 -0
  56. package/apps/relay/dist/projection-repository.js +144 -10
  57. package/apps/relay/dist/projections.js +11 -1
  58. package/apps/relay/dist/relay-context.d.ts +0 -6
  59. package/apps/relay/dist/relay-context.js +93 -100
  60. package/apps/relay/dist/repositories.js +46 -5
  61. package/apps/relay/dist/session-projection-reducer.js +253 -18
  62. package/apps/relay/dist/turn-repository.js +27 -0
  63. package/apps/relay/dist/types.d.ts +2 -0
  64. package/apps/relay/dist/websocket.js +1 -1
  65. package/build-identity.json +3 -3
  66. package/npm-shrinkwrap.json +18 -18
  67. package/package.json +1 -1
  68. package/packages/client/dist/client.js +23 -7
  69. package/packages/client/dist/parsers.js +2 -0
  70. package/packages/client/dist/recovery-context.d.ts +2 -2
  71. package/packages/client/dist/transport.js +20 -2
  72. package/packages/client/dist/types.d.ts +10 -1
  73. package/packages/runtime/dist/index.d.ts +20 -0
  74. package/packages/runtime-cli/dist/codex-app-server.d.ts +11 -0
  75. package/packages/runtime-cli/dist/codex-app-server.js +41 -12
  76. package/packages/runtime-cli/dist/codex-json-rpc.d.ts +3 -0
  77. package/packages/runtime-cli/dist/codex-json-rpc.js +29 -1
  78. package/packages/runtime-cli/dist/command-resolver.d.ts +10 -0
  79. package/packages/runtime-cli/dist/command-resolver.js +58 -0
  80. package/packages/runtime-cli/dist/index.d.ts +1 -1
  81. package/packages/runtime-cli/dist/index.js +93 -18
  82. package/packages/runtime-cli/dist/session-store.d.ts +5 -1
  83. package/packages/runtime-cli/dist/session-store.js +28 -6
  84. package/packages/shared/dist/protocol-schemas.d.ts +76 -3
  85. package/packages/shared/dist/protocol-schemas.js +33 -1
  86. package/packages/shared/dist/protocol-types.d.ts +22 -1
  87. package/packages/shared/dist/protocol.d.ts +6 -2
  88. package/packages/shared/dist/protocol.js +4 -2
  89. package/packages/shared/dist/turn-state.d.ts +16 -2
  90. package/packages/shared/dist/turn-state.js +12 -2
  91. package/packages/shared/dist/ui-probe.d.ts +2 -2
@@ -2,7 +2,7 @@ import { createHash, timingSafeEqual } from "node:crypto";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { daemonLifecycleRequestIdV1Schema, } from "../../../../packages/shared/dist/index.js";
5
- import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, assertNoPendingEnrollmentAuthority, canonicalRelayUrl, cliErrorFromFetch, CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, diagnoseInstallation, emitDoctorResult, ensureDaemonRunning, localDaemonStatus, machineAvailabilityLabel, machineIsAvailable, ok, parsePairingClaimResponse, randomId, readConfig, readDaemonConfig, readDaemonProjectionBaseline, readLocalAuditLog, readRelayJson, redactedDaemonConfig, requirePositional, requireRelayUrl, requireString, restartDaemonForBootstrap, showMachine, startDaemonTimeoutMs, stopLocalDaemon, stopSpawnedDaemon, throwRelayHttpError, waitForDaemonReadiness, waitForDaemonSelfReadiness, wantsJson, withAccountConfigTransaction } from "./lib/index.js";
5
+ import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, assertNoPendingEnrollmentAuthority, canonicalRelayUrl, cliErrorFromFetch, CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, diagnoseInstallation, emitDoctorResult, ensureDaemonRunning, localDaemonStatus, machineAvailabilityLabel, machineIsAvailable, ok, parsePairingClaimResponse, randomId, readConfig, readDaemonConfig, readDaemonProjectionBaseline, readLocalAuditLog, readRelayJson, redactedDaemonConfig, requirePositional, requireRelayUrl, requireString, showMachine, startDaemonTimeoutMs, stopLocalDaemon, stopSpawnedDaemon, throwRelayHttpError, waitForDaemonReadiness, waitForDaemonSelfReadiness, wantsJson, withAccountConfigTransaction } from "./lib/index.js";
6
6
  function positiveLimit(flags, fallback) {
7
7
  const value = flags.limit;
8
8
  if (value === undefined)
@@ -328,8 +328,8 @@ export async function handleDaemon({ domain, action, flags }) {
328
328
  const baseline = client
329
329
  ? await readDaemonProjectionBaseline(client, config.machineId, { timeoutMs: startDaemonTimeoutMs })
330
330
  : undefined;
331
- const daemon = await restartDaemonForBootstrap(config);
332
- const readiness = await waitForLocalDaemonReadiness(client, config, baseline, daemon, true);
331
+ const daemon = await ensureDaemonRunning(config);
332
+ const readiness = await waitForLocalDaemonReadiness(client, config, baseline, daemon, daemon.started);
333
333
  return {
334
334
  ...readiness.local,
335
335
  started: daemon.started,
@@ -10,7 +10,7 @@ import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, inspe
10
10
  import { redactedRelayUrl, withAccountConfigTransaction, } from "./config.js";
11
11
  import { cliErrorFromFetch } from "./relay-http.js";
12
12
  import { controllerBaseUrl, ensureControllerConfig, ensureDaemonPaired, readStartConfigPreflight } from "./bootstrap-config.js";
13
- import { readDaemonProjectionBaseline, restartDaemonForBootstrap, startFailureMayUseCommittedConfig, waitForDaemonReadiness, } from "./bootstrap-daemon.js";
13
+ import { ensureDaemonRunning, readDaemonProjectionBaseline, startFailureMayUseCommittedConfig, waitForDaemonReadiness, } from "./bootstrap-daemon.js";
14
14
  import { openUrl, writeHumanStartOutput } from "./bootstrap-output.js";
15
15
  import { startJoinBootstrap } from "./bootstrap-join.js";
16
16
  import { reconcileSatisfiedDeviceRecoveryPending, startDeviceRecoveryBootstrap } from "./bootstrap-device-recovery.js";
@@ -194,7 +194,10 @@ export async function startBootstrap(flags) {
194
194
  daemonProcessNeedsAttention,
195
195
  };
196
196
  }
197
- const daemon = await restartDaemonForBootstrap(daemonState.config, cwd);
197
+ const daemon = {
198
+ ...await ensureDaemonRunning(daemonState.config, cwd),
199
+ restarted: false,
200
+ };
198
201
  startedHandle = daemon.startedHandle;
199
202
  committedConfigMayBeInUse = daemon.committedConfigMayBeInUse === true;
200
203
  const readiness = await waitForDaemonReadiness(client, daemonState.config.machineId, {
@@ -146,6 +146,16 @@ export function renderCliError(error) {
146
146
  }
147
147
  return 1;
148
148
  }
149
+ const recoveryCommand = code === "RECOVERY_CONFIRMATION_REQUIRED" && enriched.sessionId && enriched.requestId
150
+ ? `happy-elves turn retry-unknown ${enriched.sessionId} --request-id ${enriched.requestId} --confirm-risk --json`
151
+ : undefined;
152
+ if (recoveryCommand && !wantsJsonOutput()) {
153
+ console.error(`Error: ${message}`);
154
+ console.error("");
155
+ console.error("Only the newly persisted continuation will run in a separate Recovery Session; the unknown prompt will not be replayed.");
156
+ printRunCommands([recoveryCommand]);
157
+ return 1;
158
+ }
149
159
  writeError(code, message, {
150
160
  requestId: enriched.requestId,
151
161
  machineId: enriched.machineId,
@@ -160,6 +170,7 @@ export function renderCliError(error) {
160
170
  staleRunningTurnId: enriched.staleRunningTurnId,
161
171
  lastTransientError,
162
172
  retryCount: enriched.retryCount,
173
+ diagnostics: recoveryCommand ? { recoveryCommand } : undefined,
163
174
  }, enriched.capability, enriched.retryable, detailCode);
164
175
  return 1;
165
176
  }
@@ -43,6 +43,8 @@ export type StructuredCommandOutput = {
43
43
  sessionId: string;
44
44
  turnId?: string;
45
45
  status?: string;
46
+ admission?: "accepted" | "replayed";
47
+ dispatch?: "queued" | "delivered";
46
48
  finalOutput?: string;
47
49
  error?: string;
48
50
  artifactPaths?: {
@@ -56,7 +58,7 @@ export declare function readStoredTurnOutput(sessionId: string, turnId?: string)
56
58
  export declare function summarizeTurns(events: DecodedSessionEvent[]): TurnSummary[];
57
59
  export declare function structuredOutputForTurn(sessionId: string, turn: TurnSummary): StructuredCommandOutput;
58
60
  export declare function structuredOutputForStoredTurn(output: StoredTurnOutput): StructuredCommandOutput;
59
- export declare function structuredOutputForRunningTurn(sessionId: string, turnId: string): StructuredCommandOutput;
61
+ export declare function structuredOutputForRunningTurn(sessionId: string, turnId: string, dispatch?: "queued" | "delivered"): StructuredCommandOutput;
60
62
  export declare function collectExactTurnEvents(client: ControllerClient, sessionId: string, turnId: string, limit?: number): Promise<DecodedSessionEvent[]>;
61
63
  export declare function rebuildTurnOutput(client: ControllerClient, sessionId: string, turnId?: string): Promise<StoredTurnOutput>;
62
64
  export declare function conciseEvents(events: DecodedSessionEvent[]): Array<{
@@ -294,12 +294,13 @@ export function structuredOutputForStoredTurn(output) {
294
294
  },
295
295
  };
296
296
  }
297
- export function structuredOutputForRunningTurn(sessionId, turnId) {
297
+ export function structuredOutputForRunningTurn(sessionId, turnId, dispatch = "delivered") {
298
+ const status = dispatch === "queued" ? "queued" : "running";
298
299
  return {
299
- state: "running",
300
+ state: status,
300
301
  sessionId,
301
302
  turnId,
302
- status: "running",
303
+ status,
303
304
  };
304
305
  }
305
306
  function foldAliasedRuntimeEvents(events) {
@@ -393,7 +394,10 @@ export async function rebuildTurnOutput(client, sessionId, turnId) {
393
394
  if (exactTurnId) {
394
395
  events = await collectExactTurnEvents(client, sessionId, exactTurnId, 1000);
395
396
  folded = foldAliasedRuntimeEvents(events);
396
- resolvedTurnId = turnId ? (folded.runtimeToRequest.get(turnId) ?? resolvedTurnId) : exactTurnId;
397
+ const canonicalExactTurnId = events[0]?.turnId ?? exactTurnId;
398
+ resolvedTurnId = turnId
399
+ ? (folded.runtimeToRequest.get(turnId) ?? canonicalExactTurnId)
400
+ : canonicalExactTurnId;
397
401
  turns = summarizeTurns(events);
398
402
  latestCompleted = turns.find((turn) => turn.status === "completed") ?? latestCompleted;
399
403
  summary = resolvedTurnId ? turns.find((turn) => turn.turnId === resolvedTurnId) : turns[0];
@@ -25,6 +25,12 @@ export declare function structuredSessionProjection(client: ControllerClient, se
25
25
  lastTurnId?: string;
26
26
  sourceSessionId?: string;
27
27
  sourceCheckpointId?: string;
28
+ daemon: SessionSnapshot["daemon"];
29
+ connection: SessionSnapshot["connection"];
30
+ supervision: SessionSnapshot["supervision"];
31
+ execution: SessionSnapshot["execution"];
32
+ admission: SessionSnapshot["admission"];
33
+ dispatch: SessionSnapshot["dispatch"];
28
34
  capabilities: SessionSnapshot["capabilities"];
29
35
  orchestration: OrchestrationSummary;
30
36
  }>;
@@ -33,6 +33,8 @@ function orchestrationStateFromSnapshot(session) {
33
33
  return "blocked";
34
34
  if (session.execution.status === "failed")
35
35
  return "blocked";
36
+ if (session.execution.status === "idle" && (session.dispatch === "queued" || session.dispatch === "delivered"))
37
+ return "pending";
36
38
  if (session.execution.status === "active" || session.supervision.status === "watching")
37
39
  return "active";
38
40
  if (session.supervision.status === "reconciling" || session.supervision.status === "settling")
@@ -45,7 +47,7 @@ function orchestrationStateFromSnapshot(session) {
45
47
  }
46
48
  function canTrustVisibleRunningTurn(session) {
47
49
  return session.status !== "closed" &&
48
- (session.execution.status === "active" || session.supervision.status === "watching");
50
+ (session.execution.status === "active" || session.dispatch === "started");
49
51
  }
50
52
  function operationalProjection(session) {
51
53
  return {
@@ -138,6 +140,12 @@ export async function structuredSessionProjection(client, session, events = [])
138
140
  lastTurnId: session.lastTurnId,
139
141
  sourceSessionId: session.sourceSessionId,
140
142
  sourceCheckpointId: session.sourceCheckpointId,
143
+ daemon: session.daemon,
144
+ connection: session.connection,
145
+ supervision: session.supervision,
146
+ execution: session.execution,
147
+ admission: session.admission,
148
+ dispatch: session.dispatch,
141
149
  capabilities: session.capabilities,
142
150
  orchestration,
143
151
  };
@@ -129,7 +129,7 @@ Session management:
129
129
  happy-elves turn wait <sessionId> <turnId> [--timeout 1000s] --json
130
130
  happy-elves turn cancel <sessionId> <turnId> [--reason <reason>] --json
131
131
  happy-elves turn reconcile <sessionId> <turnId> [--request-id <stable-id>] --json
132
- happy-elves turn retry-unknown <sessionId> <turnId> --request-id <stable-id> --confirm-risk [--name <recovery-name>] --json
132
+ happy-elves turn retry-unknown <sessionId> [turnId] --request-id <original-continuation-request-id> --confirm-risk [--name <recovery-name>] --json
133
133
  happy-elves turn rewind <sessionId> <turn-or-checkpoint-id> --json
134
134
  happy-elves turn fork <sessionId> <turn-or-checkpoint-id> --name <name> --json
135
135
 
@@ -283,7 +283,7 @@ Core commands:
283
283
  turn result <sessionId> [<turnId>] [--json]
284
284
  turn cancel <sessionId> <turnId> [--reason <reason>] --json
285
285
  turn reconcile <sessionId> <turnId> [--request-id <id>] --json
286
- turn retry-unknown <sessionId> <turnId> --request-id <id> --confirm-risk [--name <name>] --json
286
+ turn retry-unknown <sessionId> [turnId] --request-id <original-continuation-request-id> --confirm-risk [--name <name>] --json
287
287
  turn rewind <sessionId> <turn-or-checkpoint-id> --json
288
288
  turn fork <sessionId> <turn-or-checkpoint-id> --name <name> --json
289
289
 
@@ -505,14 +505,15 @@ async function readExactTurn(client, threadId, turnId, metadata) {
505
505
  collectExactTurnEvents(client, threadId, turnId, defaultTurnHistoryLimit),
506
506
  client.getTurn(threadId, turnId),
507
507
  ]);
508
- const exact = summarizeTurns(events).find((turn) => turn.turnId === turnId);
508
+ const canonicalTurnId = turnSnapshot?.turnId ?? events[0]?.turnId ?? turnId;
509
+ const exact = summarizeTurns(events).find((turn) => turn.turnId === canonicalTurnId);
509
510
  const projectedExact = applyAuthoritativeTurnSnapshot(exact, turnSnapshot, turnId);
510
511
  if (projectedExact && projectedExact.status !== "running")
511
512
  return projectedExact;
512
513
  if (!metadata)
513
514
  return projectedExact;
514
515
  const allEvents = await client.history(threadId, defaultTurnHistoryLimit);
515
- const folded = summarizeOrchestratorTurns(allEvents, metadata).find((turn) => turn.turnId === turnId);
516
+ const folded = summarizeOrchestratorTurns(allEvents, metadata).find((turn) => turn.turnId === canonicalTurnId);
516
517
  return applyAuthoritativeTurnSnapshot(folded ?? projectedExact, turnSnapshot, turnId);
517
518
  }
518
519
  async function readExactTurnOrThrow(client, threadId, turnId, metadata) {
@@ -538,12 +539,13 @@ async function rebuildOutputForTurn(client, threadId, turn) {
538
539
  }
539
540
  async function rebuildRuntimeTurnOutput(client, threadId, runtimeTurnId) {
540
541
  const events = await collectExactTurnEvents(client, threadId, runtimeTurnId, defaultTurnHistoryLimit);
541
- const exact = events.filter((event) => event.turnId === runtimeTurnId);
542
- const exactSummary = summarizeTurns(exact).find((turn) => turn.turnId === runtimeTurnId);
542
+ const canonicalTurnId = events[0]?.turnId ?? runtimeTurnId;
543
+ const exact = events.filter((event) => event.turnId === canonicalTurnId);
544
+ const exactSummary = summarizeTurns(exact).find((turn) => turn.turnId === canonicalTurnId);
543
545
  if (exactSummary?.output)
544
- return await writeTurnOutput(threadId, runtimeTurnId, exact);
546
+ return await writeTurnOutput(threadId, canonicalTurnId, exact);
545
547
  const history = await client.history(threadId, defaultTurnHistoryLimit);
546
- return await writeTurnOutput(threadId, runtimeTurnId, history.filter((event) => event.turnId === runtimeTurnId));
548
+ return await writeTurnOutput(threadId, canonicalTurnId, history.filter((event) => event.turnId === canonicalTurnId));
547
549
  }
548
550
  async function readTurnDetails(client, threadId, turnId, metadata) {
549
551
  const turn = await readExactTurnOrThrow(client, threadId, turnId, metadata);
@@ -827,7 +829,20 @@ async function waitForTurn(client, threadId, turnId, timeoutMs, metadata) {
827
829
  if (turn?.status === "running") {
828
830
  const session = await client.getSession(threadId).catch(() => undefined);
829
831
  if (session && classifyRunningTurn(session, [], turn).state === "stale") {
830
- throw turnOrphaned(threadId, turnId, turn.status, operationalSessionStatus(session));
832
+ // The exact turn and Session projections are fetched independently.
833
+ // A provider terminal may commit between those reads, making the first
834
+ // turn snapshot look running while the Session already references the
835
+ // same completed execution. Re-read the immutable turn before calling
836
+ // it orphaned, and let that same-turn convergence window retry.
837
+ const refreshedTurn = await readExactTurn(client, threadId, turnId, metadata);
838
+ if (refreshedTurn && refreshedTurn.status !== "running")
839
+ return refreshedTurn;
840
+ const canonicalTurnId = refreshedTurn?.turnId ?? turn.turnId;
841
+ const sameTerminalExecution = session.execution.turnId === canonicalTurnId &&
842
+ ["completed", "failed", "cancelled"].includes(session.execution.status);
843
+ if (!sameTerminalExecution) {
844
+ throw turnOrphaned(threadId, turnId, turn.status, operationalSessionStatus(session));
845
+ }
831
846
  }
832
847
  }
833
848
  if (Date.now() - startedAt >= timeoutMs) {
@@ -876,11 +891,20 @@ async function sendOrchestratorMessage(client, flags, input) {
876
891
  model,
877
892
  thoughtLevel,
878
893
  });
879
- const self = await client.tokenSelf();
894
+ const [self, directSession] = await Promise.all([
895
+ client.tokenSelf(),
896
+ client.getSession(input.threadId),
897
+ ]);
880
898
  if (!self.full) {
881
899
  throw new OrchestratorError("orchestrator send-message requires a full controller token.", "SCOPED_TOKEN_FORBIDDEN", { retryable: false, sessionId: input.threadId, turnId });
882
900
  }
883
- const { session } = await readThreadContext(client, input.threadId);
901
+ if (!directSession) {
902
+ throw new OrchestratorError(`Thread not found: ${input.threadId}`, "THREAD_NOT_FOUND", {
903
+ sessionId: input.threadId,
904
+ retryable: false,
905
+ });
906
+ }
907
+ const session = directSession;
884
908
  const metadata = await client.decodeSessionMetadata(session);
885
909
  const resolvedPermission = resolvePermissionMode(requestedPermissionMode, metadata);
886
910
  let activePermissionMode = resolvedPermission.permissionMode;
@@ -1053,6 +1077,85 @@ async function sendOrchestratorMessage(client, flags, input) {
1053
1077
  includeOutput: includeContextOutput,
1054
1078
  },
1055
1079
  } : {};
1080
+ // Contract 7 makes the immutable Relay admission the sole idempotency
1081
+ // authority. Older orchestrator versions stored a second request ledger in
1082
+ // encrypted Session metadata and performed several read/CAS round trips
1083
+ // before and after dispatch. Besides being redundant, that made a detached
1084
+ // send take tens of seconds on a high-latency Relay. Dispatch directly with
1085
+ // the stable request/turn identity; the Relay returns only after admission
1086
+ // and its delivery outbox are durable.
1087
+ if (!existingRequest) {
1088
+ const inactivityTimeoutMs = parseDurationMs(flags["inactivity-timeout"], DEFAULT_COMMAND_TIMEOUT_MS);
1089
+ let admissionResult;
1090
+ try {
1091
+ admissionResult = await client.run(input.threadId, text, {
1092
+ wait: false,
1093
+ permissionMode: activePermissionMode,
1094
+ requestId: relayRequestId,
1095
+ turnId,
1096
+ model,
1097
+ thoughtLevel,
1098
+ ackTimeoutMs: 15_000,
1099
+ inactivityTimeoutMs,
1100
+ });
1101
+ }
1102
+ catch (error) {
1103
+ const enriched = error;
1104
+ if (enriched.code === "REQUEST_ID_CONFLICT" || enriched.code === "TURN_ADMISSION_CONFLICT") {
1105
+ throw requestConflict(input.threadId, requestId, turnId);
1106
+ }
1107
+ if (enriched.code === "SESSION_BUSY") {
1108
+ const activeTurnId = enriched.activeTurnId ?? session.execution.turnId;
1109
+ throw new OrchestratorError(enriched.message, "THREAD_BUSY", {
1110
+ retryable: true,
1111
+ sessionId: input.threadId,
1112
+ turnId: activeTurnId ?? turnId,
1113
+ activeTurnId,
1114
+ latestCompletedTurnId: enriched.latestCompletedTurnId,
1115
+ rawStatus: "running",
1116
+ });
1117
+ }
1118
+ throw error;
1119
+ }
1120
+ if (admissionResult.admission === "replayed") {
1121
+ const replayed = await readExactTurn(client, input.threadId, turnId, metadata);
1122
+ if (replayed && replayed.status !== "running") {
1123
+ const output = await rebuildOutputForTurn(client, input.threadId, replayed);
1124
+ ok(input.responseType, {
1125
+ threadId: input.threadId,
1126
+ sessionId: input.threadId,
1127
+ turnId,
1128
+ requestId,
1129
+ admission: "replayed",
1130
+ dispatch: admissionResult.dispatch ?? "delivered",
1131
+ status: terminalStatusForTurn(replayed),
1132
+ rawStatus: replayed.status,
1133
+ finalOutput: output.output || undefined,
1134
+ artifactPaths: {
1135
+ output: output.outputPath,
1136
+ latest: output.latestPath,
1137
+ metadata: output.metadataPath,
1138
+ },
1139
+ ...permissionEvidence(requestedPermissionMode, undefined, resolvedPermission),
1140
+ ...contextData,
1141
+ }, { requestId, sessionId: input.threadId, turnId });
1142
+ return;
1143
+ }
1144
+ }
1145
+ ok(input.responseType, {
1146
+ threadId: input.threadId,
1147
+ sessionId: input.threadId,
1148
+ turnId,
1149
+ requestId,
1150
+ admission: admissionResult.admission ?? "accepted",
1151
+ dispatch: admissionResult.dispatch ?? "queued",
1152
+ status: "inProgress",
1153
+ rawStatus: admissionResult.dispatch ?? "queued",
1154
+ ...permissionEvidence(requestedPermissionMode, undefined, resolvedPermission),
1155
+ ...contextData,
1156
+ }, { requestId, sessionId: input.threadId, turnId });
1157
+ return;
1158
+ }
1056
1159
  const events = await client.history(input.threadId, defaultTurnHistoryLimit);
1057
1160
  const turns = summarizeOrchestratorTurns(events, metadata);
1058
1161
  const existingTurn = await readExactTurn(client, input.threadId, turnId, metadata);
@@ -1134,11 +1237,12 @@ async function sendOrchestratorMessage(client, flags, input) {
1134
1237
  if (enriched.code === "REQUEST_ID_CONFLICT")
1135
1238
  throw requestConflict(input.threadId, requestId, turnId);
1136
1239
  if (enriched.code === "SESSION_BUSY") {
1240
+ const activeTurnId = enriched.activeTurnId ?? session.execution.turnId;
1137
1241
  throw new OrchestratorError(enriched.message, "THREAD_BUSY", {
1138
1242
  retryable: true,
1139
1243
  sessionId: input.threadId,
1140
- turnId,
1141
- activeTurnId: enriched.activeTurnId,
1244
+ turnId: activeTurnId ?? turnId,
1245
+ activeTurnId,
1142
1246
  latestCompletedTurnId: enriched.latestCompletedTurnId,
1143
1247
  rawStatus: "running",
1144
1248
  });
@@ -1256,7 +1360,7 @@ export async function handleOrchestrator({ domain, action, positional, flags })
1256
1360
  ok("orchestrator.readTurn", {
1257
1361
  thread: await threadSummary(client, session, machines, { focusTurn: details.turn }),
1258
1362
  turn: projectTurn(threadId, details, includeOutput),
1259
- }, { sessionId: threadId, turnId });
1363
+ }, { sessionId: threadId, turnId: details.turn.turnId });
1260
1364
  return true;
1261
1365
  }
1262
1366
  if (action === "send-message") {
@@ -1306,7 +1410,7 @@ export async function handleOrchestrator({ domain, action, positional, flags })
1306
1410
  ok("orchestrator.waitThread", {
1307
1411
  threadId,
1308
1412
  sessionId: threadId,
1309
- turnId,
1413
+ turnId: turn.turnId,
1310
1414
  status: terminalStatusForTurn(turn),
1311
1415
  rawStatus: turn.status,
1312
1416
  finalOutput: output.output || undefined,
@@ -1315,7 +1419,7 @@ export async function handleOrchestrator({ domain, action, positional, flags })
1315
1419
  latest: output.latestPath,
1316
1420
  metadata: output.metadataPath,
1317
1421
  },
1318
- }, { sessionId: threadId, turnId });
1422
+ }, { sessionId: threadId, turnId: turn.turnId });
1319
1423
  return true;
1320
1424
  }
1321
1425
  if (action === "set-thread-tags") {
@@ -506,7 +506,11 @@ export async function handleSession({ domain, action, positional, flags }) {
506
506
  // silently run in the controller's local repository.
507
507
  const cwd = requireString(flags, "cwd");
508
508
  const requestedRuntimeName = typeof flags.name === "string" ? flags.name : undefined;
509
- const name = "main";
509
+ // Relay admission is now the create command's completion boundary. Keep
510
+ // the requested product-facing name in that durable projection instead
511
+ // of publishing a transient "main" name and relying on a later,
512
+ // blocking repair rename after provider readiness.
513
+ const name = requestedRuntimeName ?? "main";
510
514
  const permissionMode = parsePermissionMode(flags["permission-mode"] ?? flags.permission);
511
515
  const model = parseRuntimeSelection(flags.model, "model");
512
516
  const thoughtLevel = parseThoughtLevel(flags);
@@ -591,9 +595,10 @@ export async function handleSession({ domain, action, positional, flags }) {
591
595
  session.execution.status === "unknown" ||
592
596
  session.execution.status === "legacy-unresolved" ||
593
597
  machine?.archivedAt !== undefined;
598
+ const verboseSession = wantsVerbose(flags) ? await showSession(client, session) : undefined;
594
599
  return {
595
- ...(wantsVerbose(flags)
596
- ? await showSession(client, session)
600
+ ...(verboseSession
601
+ ? { ...verboseSession, state: verboseSession.orchestration.state }
597
602
  : await compactSessionListItem(client, session, {
598
603
  includeMachine,
599
604
  includeCwd,
@@ -652,6 +657,8 @@ export async function handleSession({ domain, action, positional, flags }) {
652
657
  connection: session.connection,
653
658
  supervision: session.supervision,
654
659
  execution: session.execution,
660
+ admission: session.admission,
661
+ dispatch: session.dispatch,
655
662
  activeTurnId,
656
663
  latestCompletedTurnId: latestCompletedTurn?.turnId,
657
664
  latestTerminalTurnId: latestTerminalTurn?.turnId,
@@ -6,6 +6,7 @@ import { pathToFileURL } from "node:url";
6
6
  import { ControllerClient, buildIdentity, CliExit, ok, machineIsAvailable, readConfig, redactedRelayUrl, requireString, } from "./lib/index.js";
7
7
  import { CliError } from "../errors.js";
8
8
  import { ORCHESTRATOR_SKILL_NAME, builtinSkillNames, resolveBuiltinSkill } from "../skills/registry.js";
9
+ import { readSkillAutoAlignmentReport } from "../skills/auto-align.js";
9
10
  function optionalString(flags, name) {
10
11
  const value = flags[name];
11
12
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
@@ -303,6 +304,19 @@ async function skillDoctor(flags) {
303
304
  detail: String(skill.contractVersion),
304
305
  });
305
306
  await addInstalledSkillCheck(checks, warnings, flags, name, skill);
307
+ if (skill.doctorKind === "orchestrator") {
308
+ const alignment = await readSkillAutoAlignmentReport();
309
+ if (alignment?.contractVersion === skill.contractVersion) {
310
+ checks.push({
311
+ name: "skill-reload",
312
+ status: "warning",
313
+ compatible: true,
314
+ aligned: true,
315
+ detail: `Official skill was aligned at ${new Date(alignment.alignedAt).toISOString()}; reload Codex or open a fresh task to use it`,
316
+ });
317
+ warnings.push("The official orchestrator skill was updated. Reload Codex or open a fresh task before relying on the new guidance.");
318
+ }
319
+ }
306
320
  if (skill.doctorKind === "orchestrator") {
307
321
  await addOrchestratorChecks(checks, warnings, flags);
308
322
  await addGitCommitCapabilityCheck(checks, warnings);
@@ -50,7 +50,8 @@ async function readTurnSummary(client, sessionId, turnId) {
50
50
  client.history(sessionId, 1000),
51
51
  client.getTurn(sessionId, turnId),
52
52
  ]);
53
- const summary = summarizeTurns(events).find((turn) => turn.turnId === turnId);
53
+ const canonicalTurnId = turnSnapshot?.turnId ?? events[0]?.turnId ?? turnId;
54
+ const summary = summarizeTurns(events).find((turn) => turn.turnId === canonicalTurnId);
54
55
  return applyAuthoritativeTurnSnapshot(summary, turnSnapshot, turnId);
55
56
  }
56
57
  async function readExactTurnSummary(client, sessionId, turnId) {
@@ -58,7 +59,8 @@ async function readExactTurnSummary(client, sessionId, turnId) {
58
59
  collectExactTurnEvents(client, sessionId, turnId, 1000),
59
60
  client.getTurn(sessionId, turnId),
60
61
  ]);
61
- const summary = summarizeTurns(events).find((turn) => turn.turnId === turnId);
62
+ const canonicalTurnId = turnSnapshot?.turnId ?? events[0]?.turnId ?? turnId;
63
+ const summary = summarizeTurns(events).find((turn) => turn.turnId === canonicalTurnId);
62
64
  return applyAuthoritativeTurnSnapshot(summary, turnSnapshot, turnId);
63
65
  }
64
66
  async function resolveTurnOrCheckpointId(client, sessionId, turnOrCheckpointId) {
@@ -134,7 +136,7 @@ export async function handleTurn({ domain, action, positional, flags }) {
134
136
  const turn = await readTurnSummary(client, sessionId, turnId);
135
137
  if (!turn)
136
138
  throw new CliError(`Turn not found: ${turnId}`, "TURN_NOT_FOUND");
137
- ok("turn.status", wantsVerbose(flags) ? { sessionId, turn } : { sessionId, ...compactTurn(turn) }, { sessionId, turnId });
139
+ ok("turn.status", wantsVerbose(flags) ? { sessionId, turn } : { sessionId, ...compactTurn(turn) }, { sessionId, turnId: turn.turnId });
138
140
  return true;
139
141
  }
140
142
  if (action === "run") {
@@ -175,10 +177,12 @@ export async function handleTurn({ domain, action, positional, flags }) {
175
177
  const structured = output
176
178
  ? structuredOutputForStoredTurn(output)
177
179
  : result.turnId
178
- ? structuredOutputForRunningTurn(sessionId, result.turnId)
180
+ ? structuredOutputForRunningTurn(sessionId, result.turnId, result.dispatch)
179
181
  : { state: "accepted", sessionId };
180
182
  ok("turn.run", {
181
183
  ...structured,
184
+ admission: result.admission ?? "accepted",
185
+ dispatch: result.dispatch ?? "delivered",
182
186
  effectivePermissionMode: permissionMode,
183
187
  permissionSource,
184
188
  ...(wantsVerbose(flags) ? { events, output } : {}),
@@ -207,7 +211,7 @@ export async function handleTurn({ domain, action, positional, flags }) {
207
211
  ...structuredOutputForTurn(sessionId, turn),
208
212
  until: "turn",
209
213
  ...(wantsVerbose(flags) ? { turn } : {}),
210
- }, { sessionId, turnId });
214
+ }, { sessionId, turnId: turn.turnId });
211
215
  }
212
216
  else {
213
217
  console.log(`Turn ${turn.turnId} ${turn.status}`);
@@ -270,7 +274,6 @@ export async function handleTurn({ domain, action, positional, flags }) {
270
274
  }
271
275
  if (action === "retry-unknown") {
272
276
  const sessionId = requirePositional(positional[0], "sessionId");
273
- const turnId = requirePositional(positional[1], "turnId");
274
277
  if (flags["confirm-risk"] !== true) {
275
278
  throw new CliError("Retrying an unknown execution can duplicate external side effects. Re-run with --confirm-risk to create a Recovery Session.", "CONFIRMATION_REQUIRED");
276
279
  }
@@ -278,15 +281,22 @@ export async function handleTurn({ domain, action, positional, flags }) {
278
281
  const session = await client.getSession(sessionId);
279
282
  if (!session)
280
283
  throw new CliError("Session not found", "SESSION_NOT_FOUND");
281
- if (session.supervision.status !== "failed" || session.execution.status !== "unknown") {
282
- throw new CliError("Unknown-turn retry requires supervision=failed and execution=unknown", "TURN_NOT_RETRYABLE");
284
+ const sourceTurnId = positional[1]?.trim() ||
285
+ session.execution.turnId ||
286
+ session.supervision.turnId ||
287
+ session.historyRecovery?.turnId;
288
+ const legacyRiskRequired = session.historyRecovery?.state === "risk-required" ||
289
+ session.historyRecovery?.state === "uninspectable";
290
+ if (!legacyRiskRequired && (session.supervision.status !== "failed" || session.execution.status !== "unknown")) {
291
+ throw new CliError("Unknown-turn retry requires failed supervision with unknown execution, or an uninspectable history recovery", "TURN_NOT_RETRYABLE");
283
292
  }
284
- const recoverySessionId = deterministicRecoverySessionId(sessionId, turnId, requestId);
285
- const recoveryTurnId = deterministicRecoveryTurnId(sessionId, turnId, requestId);
293
+ const recoveryIdentity = sourceTurnId ?? "legacy-uninspectable";
294
+ const recoverySessionId = deterministicRecoverySessionId(sessionId, recoveryIdentity, requestId);
295
+ const recoveryTurnId = deterministicRecoveryTurnId(sessionId, recoveryIdentity, requestId);
286
296
  const sourceCheckpointId = session.currentHead && isProviderCheckpoint(session.currentHead) ? session.currentHead : undefined;
287
297
  const name = typeof flags.name === "string" && flags.name.trim() ? flags.name.trim() : `Recovery: ${session.name}`;
288
298
  const result = await client.retryUnknownTurn(sessionId, {
289
- turnId,
299
+ ...(sourceTurnId ? { turnId: sourceTurnId } : {}),
290
300
  requestId,
291
301
  riskAcknowledged: true,
292
302
  recoverySessionId,
@@ -295,7 +305,7 @@ export async function handleTurn({ domain, action, positional, flags }) {
295
305
  ...(sourceCheckpointId ? { sourceCheckpointId } : {}),
296
306
  context: {
297
307
  sourceSessionId: sessionId,
298
- sourceTurnId: turnId,
308
+ ...(sourceTurnId ? { sourceTurnId } : {}),
299
309
  sourceExecution: "unknown",
300
310
  riskAcknowledged: true,
301
311
  checkpointAvailable: Boolean(sourceCheckpointId),
@@ -303,20 +313,20 @@ export async function handleTurn({ domain, action, positional, flags }) {
303
313
  metadata: {
304
314
  name,
305
315
  recoverySourceSessionId: sessionId,
306
- recoverySourceTurnId: turnId,
316
+ ...(sourceTurnId ? { recoverySourceTurnId: sourceTurnId } : {}),
307
317
  recoveryRiskAcknowledged: true,
308
318
  },
309
319
  });
310
320
  ok("turn.retryUnknown", {
311
321
  sourceSessionId: sessionId,
312
- sourceTurnId: turnId,
322
+ sourceTurnId,
313
323
  sessionId: result.sessionId ?? recoverySessionId,
314
324
  recoverySessionId: result.sessionId ?? recoverySessionId,
315
- recoveryTurnId,
325
+ recoveryTurnId: result.turnId ?? recoveryTurnId,
316
326
  sourceCheckpointId,
317
327
  status: "accepted",
318
328
  riskAcknowledged: true,
319
- }, { requestId: result.requestId, sessionId: result.sessionId ?? recoverySessionId, turnId: recoveryTurnId });
329
+ }, { requestId: result.requestId, sessionId: result.sessionId ?? recoverySessionId, turnId: result.turnId ?? recoveryTurnId });
320
330
  return true;
321
331
  }
322
332
  if (action === "rewind") {
@@ -2,6 +2,7 @@
2
2
  import { main } from "./commands/app.js";
3
3
  import { renderCliError } from "./commands/lib/exit.js";
4
4
  import { isCliExit } from "./commands/lib/cli-exit.js";
5
+ import { autoAlignOfficialOrchestratorSkill } from "./skills/auto-align.js";
5
6
  async function flushWritable(stream) {
6
7
  await new Promise((resolve) => {
7
8
  stream.write("", () => resolve());
@@ -11,4 +12,10 @@ async function exitAfterFlush(code) {
11
12
  await Promise.all([flushWritable(process.stdout), flushWritable(process.stderr)]);
12
13
  process.exit(code);
13
14
  }
14
- void main().then(() => exitAfterFlush(0), (error) => exitAfterFlush(isCliExit(error) ? error.exitCode : renderCliError(error)));
15
+ void autoAlignOfficialOrchestratorSkill().then((report) => {
16
+ if (report && process.stderr.isTTY && !process.argv.includes("--json")) {
17
+ const backups = report.actions.flatMap((action) => action.backupPath ? [action.backupPath] : []);
18
+ console.error(`Happy Elves aligned the official orchestrator skill to contract ${report.contractVersion}. Reload Codex to use it.${backups.length > 0 ? ` Modified copies were backed up at: ${backups.join(", ")}` : ""}`);
19
+ }
20
+ return main();
21
+ }).then(() => exitAfterFlush(0), (error) => exitAfterFlush(isCliExit(error) ? error.exitCode : renderCliError(error)));
@@ -0,0 +1,21 @@
1
+ type SkillAutoAlignmentAction = {
2
+ path: string;
3
+ action: "installed" | "updated" | "updated-with-backup";
4
+ backupPath?: string;
5
+ };
6
+ export type SkillAutoAlignmentReport = {
7
+ version: 1;
8
+ cliVersion: string;
9
+ contractVersion: number;
10
+ alignedAt: number;
11
+ reloadRequired: boolean;
12
+ actions: SkillAutoAlignmentAction[];
13
+ };
14
+ /**
15
+ * Keeps only the official orchestrator skill aligned with the installed CLI.
16
+ * Other skill directories are never enumerated or modified. The operation is
17
+ * idempotent and each replacement is committed with an atomic rename.
18
+ */
19
+ export declare function autoAlignOfficialOrchestratorSkill(): Promise<SkillAutoAlignmentReport | undefined>;
20
+ export declare function readSkillAutoAlignmentReport(): Promise<SkillAutoAlignmentReport | undefined>;
21
+ export {};