@corca-org/ceal-host-linux-arm64 1.0.19 → 1.0.20

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.
@@ -977,6 +977,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
977
977
  var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
978
978
 
979
979
  // packages/ceal-client-protocol/src/resource-row.ts
980
+ var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
980
981
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
981
982
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
982
983
  var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "authored_by", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
@@ -1060,6 +1061,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1060
1061
  next: { required: true, timestamp: false },
1061
1062
  kind: { required: true, timestamp: false },
1062
1063
  matched_kinds: { required: false, timestamp: false },
1064
+ title_match: { required: false, timestamp: false },
1063
1065
  title: { required: true, timestamp: false },
1064
1066
  container_label: { required: true, timestamp: false },
1065
1067
  container_ref: { required: false, timestamp: false },
@@ -1545,7 +1547,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1545
1547
  invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1546
1548
  options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1547
1549
  session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1548
- context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "refresh_host_invocation_context", remedy: "the Host has no invocation context from the current transcript turn; run `ceal-host install` to repair the Host hook registration, then retry this command on a fresh Host turn" }),
1550
+ context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "refresh_host_invocation_context", remedy: "the Host has no invocation context from the current transcript turn; send a new message to start a fresh Host turn, then retry this command on that turn (run `ceal-host install` only if the Host hook registration is missing or drifted)" }),
1549
1551
  context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1550
1552
  no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1551
1553
  context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
@@ -1566,6 +1568,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1566
1568
  });
1567
1569
 
1568
1570
  // packages/ceal-client-protocol/src/host-service-diagnostic.ts
1571
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
1569
1572
  var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
1570
1573
  var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1571
1574
  "hook_invoked",
@@ -1574,6 +1577,7 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1574
1577
  "watcher_turn_observed",
1575
1578
  "current_prompt_registered",
1576
1579
  "current_prompt_registration_failed",
1580
+ "current_prompt_capacity_exhausted",
1577
1581
  "activation_recorded",
1578
1582
  "activation_failed",
1579
1583
  "watcher_scan_failed",
@@ -2071,6 +2075,21 @@ var MAX_STATE_BYTES = 8 * 1024 * 1024;
2071
2075
  // host/relay/src/current-prompt-custody.ts
2072
2076
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
2073
2077
 
2078
+ // host/service-log.ts
2079
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
2080
+ const line = `${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {}, ...event === "watcher_scan_failed" && cause !== void 0 ? { cause } : {} })}
2081
+ `;
2082
+ try {
2083
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
2084
+ } catch {
2085
+ }
2086
+ }
2087
+ function defaultHostServiceDiagnosticSink(line) {
2088
+ process.stderr.write(line);
2089
+ }
2090
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2091
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2092
+
2074
2093
  // host/relay/src/relay-ipc.ts
2075
2094
  var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2076
2095
  "schema_version",
@@ -2089,6 +2108,7 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2089
2108
  ]);
2090
2109
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
2091
2110
  var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2111
+ var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
2092
2112
  var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
2093
2113
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2094
2114
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
@@ -2147,6 +2167,12 @@ var HostRelayIpcError = class extends Error {
2147
2167
  this.failure = failure;
2148
2168
  }
2149
2169
  };
2170
+ var HostRelayCurrentPromptCapacityRefusal = class extends Error {
2171
+ constructor() {
2172
+ super("current_prompt_capacity_exhausted");
2173
+ this.name = "HostRelayCurrentPromptCapacityRefusal";
2174
+ }
2175
+ };
2150
2176
  function hostRelayExchangeFailure(error) {
2151
2177
  return error instanceof HostRelayIpcError ? error.failure : void 0;
2152
2178
  }
@@ -2186,8 +2212,14 @@ var HostRelayCurrentPromptPort = class {
2186
2212
  async register(input) {
2187
2213
  const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2188
2214
  const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2215
+ if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
2189
2216
  if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2190
2217
  }
2218
+ async release(identity) {
2219
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
2220
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2221
+ if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
2222
+ }
2191
2223
  };
2192
2224
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2193
2225
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
@@ -2270,7 +2302,7 @@ function decodeResponseBuffer(buffer) {
2270
2302
  function decodeCurrentPromptResponseBuffer(buffer) {
2271
2303
  const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2272
2304
  if (value === null) return null;
2273
- if (!record2(value) || !exactKeys4(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "current_prompt_unavailable") throw new HostRelayIpcError("relay_unavailable");
2305
+ if (!record2(value) || !exactKeys4(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "capacity_exhausted" && value.status !== "current_prompt_unavailable" && value.status !== "released") throw new HostRelayIpcError("relay_unavailable", "Host Relay current-prompt response is invalid.", "response_invalid");
2274
2306
  return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2275
2307
  }
2276
2308
  function decodeLengthPrefixedJson(input, maximum) {
@@ -2306,6 +2338,29 @@ function isHostTranscriptCursorReason(value) {
2306
2338
  return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2307
2339
  }
2308
2340
  var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2341
+ var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
2342
+ function transcriptScanFailureKind(error, adapterFailureKind) {
2343
+ if (transcriptErrorCode(error) === "ELOOP") return "symlink";
2344
+ const adapter = adapterFailureKind(error);
2345
+ if (adapter !== void 0) return adapter;
2346
+ const code = transcriptErrorCode(error);
2347
+ if (code === "EFBIG") return "size_budget";
2348
+ if (code !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code)) return "read_error";
2349
+ return "other";
2350
+ }
2351
+ async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
2352
+ try {
2353
+ await register();
2354
+ diagnostic("current_prompt_registered");
2355
+ return true;
2356
+ } catch (error) {
2357
+ if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
2358
+ throw error;
2359
+ }
2360
+ }
2361
+ function transcriptErrorCode(error) {
2362
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2363
+ }
2309
2364
  var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2310
2365
  async function readTranscriptCompleteChunk(handle, offset, size) {
2311
2366
  const available = Math.max(0, size - offset);
@@ -2414,10 +2469,15 @@ var HostTranscriptWatcher = class {
2414
2469
  return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2415
2470
  }
2416
2471
  async scanRegistration(registration) {
2417
- return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2418
- missing: () => this.retireMissing(registration),
2419
- failed: (handle, error) => this.scanFailed(handle, registration, error)
2420
- });
2472
+ try {
2473
+ return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2474
+ missing: () => this.retireMissing(registration),
2475
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2476
+ });
2477
+ } catch (error) {
2478
+ if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
2479
+ throw error;
2480
+ }
2421
2481
  }
2422
2482
  async openTranscript(registration, scan, handlers) {
2423
2483
  let handle;
@@ -2426,7 +2486,7 @@ var HostTranscriptWatcher = class {
2426
2486
  return await scan(handle);
2427
2487
  } catch (error) {
2428
2488
  if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2429
- if (hostRelayExchangeFailure(error) !== void 0) throw error;
2489
+ if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
2430
2490
  return handlers.failed(handle, error);
2431
2491
  } finally {
2432
2492
  await handle?.close();
@@ -3576,6 +3636,9 @@ async function registerSession(event, options, store, now, retentionMs) {
3576
3636
  await store.writeTranscriptCursor(await captureCodexTranscriptBaseline(registration, now));
3577
3637
  }
3578
3638
  await store.registerTranscript(registration);
3639
+ if (existing !== void 0 && currentTurn !== void 0 && (await store.readTranscriptCursor(registration))?.status === "quarantined") {
3640
+ await store.writeTranscriptCursor(await captureCodexTranscriptBaseline(registration, now));
3641
+ }
3579
3642
  if (event.event === "SessionStart") {
3580
3643
  await store.saveSession({
3581
3644
  schema_version: "ceal.codex_session.v1",
@@ -3592,8 +3655,11 @@ async function registerSession(event, options, store, now, retentionMs) {
3592
3655
  }
3593
3656
  async function selectCurrentTurn(event, options, store, hostSessionRef) {
3594
3657
  if (event.event !== "UserPromptSubmit") return void 0;
3658
+ const host_turn_ref = hostTurnReference(options.host_source_ref, hostSessionRef, event.turn_id);
3659
+ const steered = await liveSteeredTurn(options, store, hostSessionRef, host_turn_ref);
3660
+ if (steered !== void 0) return steered;
3595
3661
  const currentTurn = {
3596
- host_turn_ref: hostTurnReference(options.host_source_ref, hostSessionRef, event.turn_id),
3662
+ host_turn_ref,
3597
3663
  prompt_ref: hostProducerPromptRef(promptObservationValue(event.prompt).body_sha256),
3598
3664
  turn_generation_ref: options.random_uuid?.() ?? randomUUID()
3599
3665
  };
@@ -3606,6 +3672,18 @@ async function selectCurrentTurn(event, options, store, hostSessionRef) {
3606
3672
  });
3607
3673
  return currentTurn;
3608
3674
  }
3675
+ async function liveSteeredTurn(options, store, hostSessionRef, hostTurnRef) {
3676
+ let live;
3677
+ try {
3678
+ live = await store.readInvocationContext(options.host_source_ref, hostSessionRef, (options.now ?? (() => /* @__PURE__ */ new Date()))());
3679
+ } catch {
3680
+ return void 0;
3681
+ }
3682
+ if (live === null || live.identity.host_turn_ref !== hostTurnRef) return void 0;
3683
+ const selection = await store.readInvocationTurnSelection(options.host_source_ref, hostSessionRef).catch(() => null);
3684
+ if (selection === null || selection.prompt_ref !== live.prompt_ref || selection.turn_generation_ref !== live.turn_generation_ref) return void 0;
3685
+ return { host_turn_ref: hostTurnRef, prompt_ref: selection.prompt_ref, turn_generation_ref: selection.turn_generation_ref };
3686
+ }
3609
3687
  function sourceBoundTranscriptPath(path, sessionsRoot) {
3610
3688
  if (!isAbsolute3(path) || !isAbsolute3(sessionsRoot)) throw new CodexHostAdapterError("configuration", "Codex transcript registration paths must be absolute.");
3611
3689
  const transcript = resolve2(path);
@@ -4101,6 +4179,8 @@ function isRecord8(value) {
4101
4179
  var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
4102
4180
  store;
4103
4181
  registerCurrentPrompt;
4182
+ releaseCurrentPrompt;
4183
+ diagnostic;
4104
4184
  constructor(options) {
4105
4185
  super(options, {
4106
4186
  default_retention_ms: CODEX_DEFAULT_RETENTION_MS,
@@ -4109,6 +4189,8 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
4109
4189
  });
4110
4190
  this.store = new CodexOutboxStore(options.outbox_dir);
4111
4191
  this.registerCurrentPrompt = options.register_current_prompt;
4192
+ this.releaseCurrentPrompt = options.release_current_prompt;
4193
+ this.diagnostic = options.diagnostic ?? (() => void 0);
4112
4194
  }
4113
4195
  transcriptPathOf(registration) {
4114
4196
  return registration.transcript_path;
@@ -4124,17 +4206,22 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
4124
4206
  if (complete === "empty" || complete === "partial") return { records: 0, enqueued: 0, quarantined: false };
4125
4207
  return await this.consumeCompleteChunk(registration, handle, preflight.cursor, complete, device, inode);
4126
4208
  }
4127
- async scanFailed(handle, registration, _error) {
4209
+ async scanFailed(handle, registration, error) {
4210
+ this.diagnostic("watcher_scan_failed", codexTranscriptScanFailureKind(error));
4128
4211
  await this.quarantineFailure(registration, handle);
4129
4212
  return { records: 0, enqueued: 0, quarantined: true };
4130
4213
  }
4131
4214
  async retireMissing(registration) {
4215
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
4132
4216
  return this.retireMissingTranscript(registration.transcript_path, "Codex", () => this.store.retireTranscriptRegistration(registration));
4133
4217
  }
4134
4218
  async preflight(registration, handle, size, device, inode) {
4135
4219
  const existing = await this.store.readTranscriptCursor(registration);
4136
4220
  let cursor = existing ?? codexTranscriptInitialCursor(registration, device, inode, this.now);
4137
- if (cursor.status === "quarantined") return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
4221
+ if (cursor.status === "quarantined") {
4222
+ if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
4223
+ return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
4224
+ }
4138
4225
  const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
4139
4226
  if (reason === null) return { cursor, result: { records: 0, enqueued: 0, quarantined: false } };
4140
4227
  await this.quarantine(registration, cursor, device, inode, reason);
@@ -4173,25 +4260,41 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
4173
4260
  const stats = await handle.stat().catch(() => null);
4174
4261
  if (stats === null) return;
4175
4262
  const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
4176
- await this.quarantine(registration, existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now), String(stats.dev), String(stats.ino), "transcript_malformed");
4263
+ const quarantined = {
4264
+ ...existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now),
4265
+ transcript_path: registration.transcript_path,
4266
+ device: String(stats.dev),
4267
+ inode: String(stats.ino),
4268
+ status: "quarantined",
4269
+ reason: "transcript_malformed",
4270
+ updated_at: this.stamp()
4271
+ };
4272
+ await this.store.writeTranscriptCursor(quarantined);
4177
4273
  }
4178
4274
  async processRecord(registration, activeTask, record3) {
4179
- if (record3.kind === "task_started") {
4180
- return { activeTask: { codex_turn_id: record3.turn_id, host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id), started_at: record3.timestamp }, enqueued: 0 };
4181
- }
4275
+ if (record3.kind === "task_started") return this.processTaskStarted(registration, activeTask, record3);
4182
4276
  if (record3.kind === "turn_context") return { ...activeTask === void 0 ? {} : { activeTask: record3.turn_id === activeTask.codex_turn_id ? { ...activeTask, model: record3.model } : activeTask }, enqueued: 0 };
4183
4277
  if (record3.kind === "token_count") return { ...activeTask === void 0 ? {} : { activeTask: accumulateTokenCount(activeTask, record3) }, enqueued: 0 };
4184
4278
  if (record3.kind === "user_text") return this.processUserInput(registration, activeTask, record3);
4185
4279
  if (record3.kind === "task_complete" || record3.kind === "turn_aborted") return this.processTerminal(registration, activeTask, record3);
4186
4280
  return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
4187
4281
  }
4282
+ async processTaskStarted(registration, activeTask, record3) {
4283
+ if (activeTask !== void 0 && activeTask.codex_turn_id !== record3.turn_id && activeTask.prompt_ref !== void 0) await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
4284
+ return { activeTask: { codex_turn_id: record3.turn_id, host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id), started_at: record3.timestamp }, enqueued: 0 };
4285
+ }
4188
4286
  async processUserInput(registration, activeTask, record3) {
4189
- if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
4287
+ if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) {
4288
+ const adopted = await this.adoptHookCorrelatedTurn(registration, activeTask, record3);
4289
+ if (adopted === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
4290
+ activeTask = adopted;
4291
+ }
4190
4292
  const hostInputRef = hostInputReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id, record3.message_id, record3.ordinal, record3.prompt);
4191
4293
  const origin = activeTask.origin_host_input_ref === void 0;
4192
4294
  const promptValue = promptObservationValue(record3.prompt);
4193
4295
  const currentPromptRef = hostProducerPromptRef(promptValue.body_sha256);
4194
4296
  const promptRef = activeTask.prompt_ref ?? currentPromptRef;
4297
+ const steeredPromptRef = await this.steeredPromptRef(registration, activeTask, origin, currentPromptRef);
4195
4298
  const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: activeTask.host_turn_ref };
4196
4299
  const observations = [{
4197
4300
  observation_ref: observationRef("transcript_user_text", hostInputRef, record3.prompt),
@@ -4217,14 +4320,88 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
4217
4320
  created_at: record3.timestamp,
4218
4321
  expires_at: this.expire(this.outboxRetentionMs)
4219
4322
  };
4220
- await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
4221
- await this.store.put(envelope);
4222
- if (origin) await activateTranscriptTurnContext(this.store, identity, promptRef, envelope.expires_at);
4323
+ const persisted = await this.persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope);
4324
+ if (steeredPromptRef !== null) await this.activateTranscriptTurnContext(identity, steeredPromptRef, envelope.expires_at);
4325
+ return persisted;
4326
+ }
4327
+ /**
4328
+ * The steered prompt to re-activate under the running task, or null when
4329
+ * there is nothing to repair. A steered message joins the running turn
4330
+ * with new prompt bytes but no task_started. When the steer hook already
4331
+ * replaced the turn selection (a turn id the hook did not recognize as
4332
+ * the running one, or a hook that ran before any context was live), the
4333
+ * running task's context no longer matches and the next invocation would
4334
+ * wait for a boundary that never comes. When the hook kept the live
4335
+ * selection, the context still matches and this stays null.
4336
+ */
4337
+ async steeredPromptRef(registration, activeTask, origin, currentPromptRef) {
4338
+ if (origin || activeTask.prompt_ref === void 0 || activeTask.prompt_ref === currentPromptRef) return null;
4339
+ const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref).catch(() => null);
4340
+ return selection?.prompt_ref === currentPromptRef ? currentPromptRef : null;
4341
+ }
4342
+ /**
4343
+ * Adopt the live hooked turn when its transcript message arrives before
4344
+ * (or without) its task_started, as an interrupted turn's follow-up can.
4345
+ * The UserPromptSubmit hook names the exact hooked prompt, so only a
4346
+ * message carrying those bytes adopts; inherited history and other turns
4347
+ * stay ignored. A stale active task for another turn releases first, the
4348
+ * same as a task_started supersession.
4349
+ */
4350
+ async adoptHookCorrelatedTurn(registration, activeTask, record3) {
4351
+ const currentPromptRef = hostProducerPromptRef(promptObservationValue(record3.prompt).body_sha256);
4352
+ const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref);
4353
+ if (selection?.prompt_ref !== currentPromptRef) return void 0;
4354
+ if (activeTask !== void 0 && activeTask.codex_turn_id !== record3.turn_id && activeTask.prompt_ref !== void 0) {
4355
+ await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
4356
+ }
4357
+ return {
4358
+ codex_turn_id: record3.turn_id,
4359
+ host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id),
4360
+ started_at: record3.timestamp
4361
+ };
4362
+ }
4363
+ async persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope) {
4364
+ const custodyRegistered = await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
4365
+ if (custodyRegistered) this.diagnostic("watcher_turn_observed");
4366
+ try {
4367
+ await this.store.put(envelope);
4368
+ if (origin) await this.activateTranscriptTurnContext(identity, promptRef, envelope.expires_at);
4369
+ } catch (error) {
4370
+ if (custodyRegistered) await this.releasePrompt(registration.host_session_ref, activeTask, currentPromptRef).catch(() => void 0);
4371
+ throw error;
4372
+ }
4223
4373
  return { activeTask: { ...activeTask, origin_host_input_ref: activeTask.origin_host_input_ref ?? hostInputRef, prompt_ref: activeTask.prompt_ref ?? currentPromptRef }, enqueued: 1 };
4224
4374
  }
4225
4375
  async registerPrompt(origin, registration, activeTask, promptRef, prompt, expiresAt) {
4226
- if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
4227
- await this.registerCurrentPrompt({ producer_id: "codex", producer_installation_ref: this.producerInstallationRef, local_session_ref: registration.host_session_ref, local_turn_ref: activeTask.host_turn_ref, prompt_ref: promptRef, current_prompt: prompt, context_expires_at: expiresAt });
4376
+ if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
4377
+ const register = this.registerCurrentPrompt;
4378
+ const installationRef = this.producerInstallationRef;
4379
+ return registerHostCurrentPromptDiagnosed(
4380
+ () => register({ producer_id: "codex", producer_installation_ref: installationRef, local_session_ref: registration.host_session_ref, local_turn_ref: activeTask.host_turn_ref, prompt_ref: promptRef, current_prompt: prompt, context_expires_at: expiresAt }),
4381
+ (event) => this.diagnostic(event)
4382
+ );
4383
+ }
4384
+ async activateTranscriptTurnContext(identity, promptRef, expiresAt) {
4385
+ try {
4386
+ const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
4387
+ const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
4388
+ await this.store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
4389
+ this.diagnostic("activation_recorded");
4390
+ } catch (error) {
4391
+ this.diagnostic("activation_failed");
4392
+ throw error;
4393
+ }
4394
+ }
4395
+ async releasePrompt(sessionRef, activeTask, promptRef) {
4396
+ if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
4397
+ await this.releaseCurrentPrompt({ producer_id: "codex", producer_installation_ref: this.producerInstallationRef, local_session_ref: sessionRef, local_turn_ref: activeTask.host_turn_ref, prompt_ref: promptRef });
4398
+ }
4399
+ async releaseStoredPromptBestEffort(sessionRef, cursor) {
4400
+ await this.releaseActiveTaskBestEffort(sessionRef, cursor?.active_task);
4401
+ }
4402
+ async releaseActiveTaskBestEffort(sessionRef, activeTask) {
4403
+ if (activeTask?.prompt_ref === void 0) return;
4404
+ await this.releasePrompt(sessionRef, activeTask, activeTask.prompt_ref).catch(() => void 0);
4228
4405
  }
4229
4406
  async processTerminal(registration, activeTask, record3) {
4230
4407
  if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id || activeTask.origin_host_input_ref === void 0 || activeTask.prompt_ref === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
@@ -4244,18 +4421,23 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
4244
4421
  created_at: record3.timestamp,
4245
4422
  expires_at: this.expire(this.outboxRetentionMs)
4246
4423
  });
4424
+ await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
4247
4425
  await this.store.clearInvocationContext(identity, this.now());
4248
4426
  return { enqueued: 1 };
4249
4427
  }
4250
4428
  async quarantine(registration, cursor, device, inode, reason) {
4429
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
4251
4430
  const quarantined = { ...cursor, transcript_path: registration.transcript_path, device, inode, status: "quarantined", reason, updated_at: this.stamp() };
4252
4431
  await this.store.writeTranscriptCursor(quarantined);
4253
4432
  }
4254
4433
  };
4255
- async function activateTranscriptTurnContext(store, identity, promptRef, expiresAt) {
4256
- const selection = await store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
4257
- const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
4258
- await store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
4434
+ function codexTranscriptScanFailureKind(error) {
4435
+ return transcriptScanFailureKind(error, (candidate) => {
4436
+ if (!(candidate instanceof CodexHostAdapterError)) return void 0;
4437
+ if (candidate.code === "payload_rejected") return "payload_rejected";
4438
+ if (candidate.code === "storage") return "read_error";
4439
+ return void 0;
4440
+ });
4259
4441
  }
4260
4442
  function accumulateTokenCount(activeTask, record3) {
4261
4443
  if (record3.usage_gap_reason !== void 0) return { ...activeTask, token_usage_gap_reason: record3.usage_gap_reason };
@@ -4343,7 +4525,16 @@ function assertRecordSequence(registration, cursor, records, lastOrdinal, record
4343
4525
  // host/codex/src/codex-producer-runtime.ts
4344
4526
  async function drainCodexProducerOnce(options) {
4345
4527
  const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
4346
- const scan = await new CodexTranscriptWatcher({ outbox_dir: options.outbox_dir, producer_installation_ref: options.producer_installation_ref, register_current_prompt: (input) => promptPort.register(input) }).scanOnce();
4528
+ const scan = await new CodexTranscriptWatcher({
4529
+ outbox_dir: options.outbox_dir,
4530
+ producer_installation_ref: options.producer_installation_ref,
4531
+ register_current_prompt: (input) => promptPort.register(input),
4532
+ release_current_prompt: (identity) => promptPort.release(identity),
4533
+ diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("codex", event, failure_kind)
4534
+ }).scanOnce().catch((error) => {
4535
+ writeHostServiceDiagnostic("codex", "watcher_scan_failed", codexTranscriptScanFailureKind(error));
4536
+ throw error;
4537
+ });
4347
4538
  const producer = new CodexProducer({ outbox_dir: options.outbox_dir, producer_installation_ref: options.producer_installation_ref, relay: new HostRelayIpcAdmissionPort(options.relay_endpoint, options.timeout_ms), ...options.producer_epoch === void 0 ? {} : { producer_epoch: options.producer_epoch }, ...options.now === void 0 ? {} : { now: options.now } });
4348
4539
  return { scanned: scan.enqueued, ...await producer.drainOutbox() };
4349
4540
  }