@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.
@@ -3009,6 +3009,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
3009
3009
  var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
3010
3010
 
3011
3011
  // packages/ceal-client-protocol/src/resource-row.ts
3012
+ var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
3012
3013
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
3013
3014
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
3014
3015
  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"]);
@@ -3092,6 +3093,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
3092
3093
  next: { required: true, timestamp: false },
3093
3094
  kind: { required: true, timestamp: false },
3094
3095
  matched_kinds: { required: false, timestamp: false },
3096
+ title_match: { required: false, timestamp: false },
3095
3097
  title: { required: true, timestamp: false },
3096
3098
  container_label: { required: true, timestamp: false },
3097
3099
  container_ref: { required: false, timestamp: false },
@@ -3578,7 +3580,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
3578
3580
  invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
3579
3581
  options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
3580
3582
  session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
3581
- 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" }),
3583
+ 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)" }),
3582
3584
  context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
3583
3585
  no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
3584
3586
  context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
@@ -3631,6 +3633,7 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
3631
3633
  "watcher_turn_observed",
3632
3634
  "current_prompt_registered",
3633
3635
  "current_prompt_registration_failed",
3636
+ "current_prompt_capacity_exhausted",
3634
3637
  "activation_recorded",
3635
3638
  "activation_failed",
3636
3639
  "watcher_scan_failed",
@@ -4186,6 +4189,21 @@ var MAX_STATE_BYTES = 8 * 1024 * 1024;
4186
4189
  // host/relay/src/current-prompt-custody.ts
4187
4190
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
4188
4191
 
4192
+ // host/service-log.ts
4193
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
4194
+ 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 } : {} })}
4195
+ `;
4196
+ try {
4197
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
4198
+ } catch {
4199
+ }
4200
+ }
4201
+ function defaultHostServiceDiagnosticSink(line) {
4202
+ process.stderr.write(line);
4203
+ }
4204
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
4205
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
4206
+
4189
4207
  // host/relay/src/relay-ipc.ts
4190
4208
  var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
4191
4209
  "schema_version",
@@ -4206,6 +4224,7 @@ var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
4206
4224
  var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v1";
4207
4225
  var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v1";
4208
4226
  var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
4227
+ var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
4209
4228
  var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
4210
4229
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
4211
4230
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
@@ -4265,6 +4284,12 @@ var HostRelayIpcError = class extends Error {
4265
4284
  this.failure = failure2;
4266
4285
  }
4267
4286
  };
4287
+ var HostRelayCurrentPromptCapacityRefusal = class extends Error {
4288
+ constructor() {
4289
+ super("current_prompt_capacity_exhausted");
4290
+ this.name = "HostRelayCurrentPromptCapacityRefusal";
4291
+ }
4292
+ };
4268
4293
  function hostRelayExchangeFailure(error) {
4269
4294
  return error instanceof HostRelayIpcError ? error.failure : void 0;
4270
4295
  }
@@ -4313,8 +4338,14 @@ var HostRelayCurrentPromptPort = class {
4313
4338
  async register(input) {
4314
4339
  const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
4315
4340
  const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
4341
+ if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
4316
4342
  if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
4317
4343
  }
4344
+ async release(identity) {
4345
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
4346
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
4347
+ if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
4348
+ }
4318
4349
  };
4319
4350
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
4320
4351
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
@@ -4406,7 +4437,7 @@ function decodeInvocationContextResponseBuffer(buffer) {
4406
4437
  function decodeCurrentPromptResponseBuffer(buffer) {
4407
4438
  const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
4408
4439
  if (value === null) return null;
4409
- if (!record2(value) || !exactKeys3(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");
4440
+ if (!record2(value) || !exactKeys3(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");
4410
4441
  return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
4411
4442
  }
4412
4443
  function encodeInvocationContextQuery(query) {
@@ -4471,6 +4502,29 @@ function isHostTranscriptCursorReason(value) {
4471
4502
  return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
4472
4503
  }
4473
4504
  var TRANSCRIPT_MISSING_REASON = "transcript_missing";
4505
+ var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
4506
+ function transcriptScanFailureKind(error, adapterFailureKind) {
4507
+ if (transcriptErrorCode(error) === "ELOOP") return "symlink";
4508
+ const adapter = adapterFailureKind(error);
4509
+ if (adapter !== void 0) return adapter;
4510
+ const code = transcriptErrorCode(error);
4511
+ if (code === "EFBIG") return "size_budget";
4512
+ if (code !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code)) return "read_error";
4513
+ return "other";
4514
+ }
4515
+ async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
4516
+ try {
4517
+ await register();
4518
+ diagnostic("current_prompt_registered");
4519
+ return true;
4520
+ } catch (error) {
4521
+ if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
4522
+ throw error;
4523
+ }
4524
+ }
4525
+ function transcriptErrorCode(error) {
4526
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
4527
+ }
4474
4528
  var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
4475
4529
  async function readTranscriptCompleteChunk(handle, offset, size) {
4476
4530
  const available = Math.max(0, size - offset);
@@ -4579,10 +4633,15 @@ var HostTranscriptWatcher = class {
4579
4633
  return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
4580
4634
  }
4581
4635
  async scanRegistration(registration) {
4582
- return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
4583
- missing: () => this.retireMissing(registration),
4584
- failed: (handle, error) => this.scanFailed(handle, registration, error)
4585
- });
4636
+ try {
4637
+ return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
4638
+ missing: () => this.retireMissing(registration),
4639
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
4640
+ });
4641
+ } catch (error) {
4642
+ if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
4643
+ throw error;
4644
+ }
4586
4645
  }
4587
4646
  async openTranscript(registration, scan, handlers) {
4588
4647
  let handle;
@@ -4591,7 +4650,7 @@ var HostTranscriptWatcher = class {
4591
4650
  return await scan(handle);
4592
4651
  } catch (error) {
4593
4652
  if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
4594
- if (hostRelayExchangeFailure(error) !== void 0) throw error;
4653
+ if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
4595
4654
  return handlers.failed(handle, error);
4596
4655
  } finally {
4597
4656
  await handle?.close();
@@ -4792,17 +4851,6 @@ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, produ
4792
4851
  var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
4793
4852
  var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
4794
4853
 
4795
- // host/service-log.ts
4796
- function writeHostServiceDiagnostic(producer_id, event, failure_kind) {
4797
- try {
4798
- process.stderr.write(`${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {} })}
4799
- `);
4800
- } catch {
4801
- }
4802
- }
4803
- var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
4804
- var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
4805
-
4806
4854
  // host/invocation-authentication-command.ts
4807
4855
  var HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX = ["authenticate", "--client-nonce"];
4808
4856
  var HOST_AUTHENTICATION_BUDGET_ENVIRONMENT = "CEAL_HOST_AUTHENTICATION_BUDGET_MS";
@@ -4899,36 +4947,36 @@ async function currentInvocation(descriptor2, config, now) {
4899
4947
  let waitEngaged = false;
4900
4948
  while (true) {
4901
4949
  if (!firstCheck && monotonicNow() >= deadline) {
4902
- recordActivationWaitExhaustion(descriptor2.producer_id, waitEngaged, waitBudgetMs);
4950
+ recordActivationWaitExhaustion(descriptor2.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
4903
4951
  throw new TypeError("context_unavailable");
4904
4952
  }
4905
4953
  firstCheck = false;
4906
4954
  const observedAt = now();
4907
4955
  const current = await readLiveSessionContext(store, identity.identity.host_source_ref, identity.identity.host_session_ref, observedAt);
4908
4956
  if (current !== null) {
4909
- recordActivationWaitCompletion(descriptor2.producer_id, waitEngaged);
4957
+ recordActivationWaitCompletion(descriptor2.producer_id, waitEngaged, config.diagnostic_sink);
4910
4958
  return { store, current };
4911
4959
  }
4912
4960
  const remainingMs = deadline - monotonicNow();
4913
4961
  if (remainingMs <= 0) {
4914
- recordActivationWaitExhaustion(descriptor2.producer_id, waitEngaged, waitBudgetMs);
4962
+ recordActivationWaitExhaustion(descriptor2.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
4915
4963
  throw new TypeError("context_unavailable");
4916
4964
  }
4917
- recordActivationWaitEngagement(descriptor2.producer_id, waitEngaged);
4965
+ recordActivationWaitEngagement(descriptor2.producer_id, waitEngaged, config.diagnostic_sink);
4918
4966
  waitEngaged = true;
4919
4967
  const intervalMs = Math.min(INVOCATION_CONTEXT_POLL_INTERVAL_MS, remainingMs);
4920
4968
  await (config.wait ?? waitForContextActivation)(intervalMs);
4921
4969
  }
4922
4970
  }
4923
- function recordActivationWaitExhaustion(producer, engaged, budgetMs) {
4924
- if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted");
4925
- else writeHostServiceDiagnostic(producer, "activation_wait_skipped");
4971
+ function recordActivationWaitExhaustion(producer, engaged, budgetMs, sink) {
4972
+ if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted", void 0, void 0, sink);
4973
+ else writeHostServiceDiagnostic(producer, "activation_wait_skipped", void 0, void 0, sink);
4926
4974
  }
4927
- function recordActivationWaitCompletion(producer, engaged) {
4928
- if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed");
4975
+ function recordActivationWaitCompletion(producer, engaged, sink) {
4976
+ if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed", void 0, void 0, sink);
4929
4977
  }
4930
- function recordActivationWaitEngagement(producer, engaged) {
4931
- if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged");
4978
+ function recordActivationWaitEngagement(producer, engaged, sink) {
4979
+ if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged", void 0, void 0, sink);
4932
4980
  }
4933
4981
  async function readLiveSessionContext(store, hostSourceRef, hostSessionRef, now) {
4934
4982
  const current = await store.readInvocationContext(hostSourceRef, hostSessionRef, now);
@@ -6236,6 +6284,8 @@ function codexSubmissionReference(hostInputRef, event, ordinal) {
6236
6284
  var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
6237
6285
  store;
6238
6286
  registerCurrentPrompt;
6287
+ releaseCurrentPrompt;
6288
+ diagnostic;
6239
6289
  constructor(options) {
6240
6290
  super(options, {
6241
6291
  default_retention_ms: CODEX_DEFAULT_RETENTION_MS,
@@ -6244,6 +6294,8 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
6244
6294
  });
6245
6295
  this.store = new CodexOutboxStore(options.outbox_dir);
6246
6296
  this.registerCurrentPrompt = options.register_current_prompt;
6297
+ this.releaseCurrentPrompt = options.release_current_prompt;
6298
+ this.diagnostic = options.diagnostic ?? (() => void 0);
6247
6299
  }
6248
6300
  transcriptPathOf(registration) {
6249
6301
  return registration.transcript_path;
@@ -6259,17 +6311,22 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
6259
6311
  if (complete === "empty" || complete === "partial") return { records: 0, enqueued: 0, quarantined: false };
6260
6312
  return await this.consumeCompleteChunk(registration, handle, preflight.cursor, complete, device, inode);
6261
6313
  }
6262
- async scanFailed(handle, registration, _error) {
6314
+ async scanFailed(handle, registration, error) {
6315
+ this.diagnostic("watcher_scan_failed", codexTranscriptScanFailureKind(error));
6263
6316
  await this.quarantineFailure(registration, handle);
6264
6317
  return { records: 0, enqueued: 0, quarantined: true };
6265
6318
  }
6266
6319
  async retireMissing(registration) {
6320
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
6267
6321
  return this.retireMissingTranscript(registration.transcript_path, "Codex", () => this.store.retireTranscriptRegistration(registration));
6268
6322
  }
6269
6323
  async preflight(registration, handle, size, device, inode) {
6270
6324
  const existing = await this.store.readTranscriptCursor(registration);
6271
6325
  let cursor = existing ?? codexTranscriptInitialCursor(registration, device, inode, this.now);
6272
- if (cursor.status === "quarantined") return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
6326
+ if (cursor.status === "quarantined") {
6327
+ if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
6328
+ return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
6329
+ }
6273
6330
  const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
6274
6331
  if (reason === null) return { cursor, result: { records: 0, enqueued: 0, quarantined: false } };
6275
6332
  await this.quarantine(registration, cursor, device, inode, reason);
@@ -6308,25 +6365,41 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
6308
6365
  const stats = await handle.stat().catch(() => null);
6309
6366
  if (stats === null) return;
6310
6367
  const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
6311
- await this.quarantine(registration, existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now), String(stats.dev), String(stats.ino), "transcript_malformed");
6368
+ const quarantined = {
6369
+ ...existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now),
6370
+ transcript_path: registration.transcript_path,
6371
+ device: String(stats.dev),
6372
+ inode: String(stats.ino),
6373
+ status: "quarantined",
6374
+ reason: "transcript_malformed",
6375
+ updated_at: this.stamp()
6376
+ };
6377
+ await this.store.writeTranscriptCursor(quarantined);
6312
6378
  }
6313
6379
  async processRecord(registration, activeTask, record3) {
6314
- if (record3.kind === "task_started") {
6315
- 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 };
6316
- }
6380
+ if (record3.kind === "task_started") return this.processTaskStarted(registration, activeTask, record3);
6317
6381
  if (record3.kind === "turn_context") return { ...activeTask === void 0 ? {} : { activeTask: record3.turn_id === activeTask.codex_turn_id ? { ...activeTask, model: record3.model } : activeTask }, enqueued: 0 };
6318
6382
  if (record3.kind === "token_count") return { ...activeTask === void 0 ? {} : { activeTask: accumulateTokenCount(activeTask, record3) }, enqueued: 0 };
6319
6383
  if (record3.kind === "user_text") return this.processUserInput(registration, activeTask, record3);
6320
6384
  if (record3.kind === "task_complete" || record3.kind === "turn_aborted") return this.processTerminal(registration, activeTask, record3);
6321
6385
  return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
6322
6386
  }
6387
+ async processTaskStarted(registration, activeTask, record3) {
6388
+ 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);
6389
+ 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 };
6390
+ }
6323
6391
  async processUserInput(registration, activeTask, record3) {
6324
- if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
6392
+ if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) {
6393
+ const adopted = await this.adoptHookCorrelatedTurn(registration, activeTask, record3);
6394
+ if (adopted === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
6395
+ activeTask = adopted;
6396
+ }
6325
6397
  const hostInputRef = hostInputReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id, record3.message_id, record3.ordinal, record3.prompt);
6326
6398
  const origin = activeTask.origin_host_input_ref === void 0;
6327
6399
  const promptValue = promptObservationValue(record3.prompt);
6328
6400
  const currentPromptRef = hostProducerPromptRef(promptValue.body_sha256);
6329
6401
  const promptRef = activeTask.prompt_ref ?? currentPromptRef;
6402
+ const steeredPromptRef = await this.steeredPromptRef(registration, activeTask, origin, currentPromptRef);
6330
6403
  const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: activeTask.host_turn_ref };
6331
6404
  const observations = [{
6332
6405
  observation_ref: observationRef("transcript_user_text", hostInputRef, record3.prompt),
@@ -6352,14 +6425,88 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
6352
6425
  created_at: record3.timestamp,
6353
6426
  expires_at: this.expire(this.outboxRetentionMs)
6354
6427
  };
6355
- await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
6356
- await this.store.put(envelope);
6357
- if (origin) await activateTranscriptTurnContext(this.store, identity, promptRef, envelope.expires_at);
6428
+ const persisted = await this.persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope);
6429
+ if (steeredPromptRef !== null) await this.activateTranscriptTurnContext(identity, steeredPromptRef, envelope.expires_at);
6430
+ return persisted;
6431
+ }
6432
+ /**
6433
+ * The steered prompt to re-activate under the running task, or null when
6434
+ * there is nothing to repair. A steered message joins the running turn
6435
+ * with new prompt bytes but no task_started. When the steer hook already
6436
+ * replaced the turn selection (a turn id the hook did not recognize as
6437
+ * the running one, or a hook that ran before any context was live), the
6438
+ * running task's context no longer matches and the next invocation would
6439
+ * wait for a boundary that never comes. When the hook kept the live
6440
+ * selection, the context still matches and this stays null.
6441
+ */
6442
+ async steeredPromptRef(registration, activeTask, origin, currentPromptRef) {
6443
+ if (origin || activeTask.prompt_ref === void 0 || activeTask.prompt_ref === currentPromptRef) return null;
6444
+ const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref).catch(() => null);
6445
+ return selection?.prompt_ref === currentPromptRef ? currentPromptRef : null;
6446
+ }
6447
+ /**
6448
+ * Adopt the live hooked turn when its transcript message arrives before
6449
+ * (or without) its task_started, as an interrupted turn's follow-up can.
6450
+ * The UserPromptSubmit hook names the exact hooked prompt, so only a
6451
+ * message carrying those bytes adopts; inherited history and other turns
6452
+ * stay ignored. A stale active task for another turn releases first, the
6453
+ * same as a task_started supersession.
6454
+ */
6455
+ async adoptHookCorrelatedTurn(registration, activeTask, record3) {
6456
+ const currentPromptRef = hostProducerPromptRef(promptObservationValue(record3.prompt).body_sha256);
6457
+ const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref);
6458
+ if (selection?.prompt_ref !== currentPromptRef) return void 0;
6459
+ if (activeTask !== void 0 && activeTask.codex_turn_id !== record3.turn_id && activeTask.prompt_ref !== void 0) {
6460
+ await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
6461
+ }
6462
+ return {
6463
+ codex_turn_id: record3.turn_id,
6464
+ host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id),
6465
+ started_at: record3.timestamp
6466
+ };
6467
+ }
6468
+ async persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope) {
6469
+ const custodyRegistered = await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
6470
+ if (custodyRegistered) this.diagnostic("watcher_turn_observed");
6471
+ try {
6472
+ await this.store.put(envelope);
6473
+ if (origin) await this.activateTranscriptTurnContext(identity, promptRef, envelope.expires_at);
6474
+ } catch (error) {
6475
+ if (custodyRegistered) await this.releasePrompt(registration.host_session_ref, activeTask, currentPromptRef).catch(() => void 0);
6476
+ throw error;
6477
+ }
6358
6478
  return { activeTask: { ...activeTask, origin_host_input_ref: activeTask.origin_host_input_ref ?? hostInputRef, prompt_ref: activeTask.prompt_ref ?? currentPromptRef }, enqueued: 1 };
6359
6479
  }
6360
6480
  async registerPrompt(origin, registration, activeTask, promptRef, prompt, expiresAt) {
6361
- if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
6362
- 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 });
6481
+ if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
6482
+ const register = this.registerCurrentPrompt;
6483
+ const installationRef = this.producerInstallationRef;
6484
+ return registerHostCurrentPromptDiagnosed(
6485
+ () => 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 }),
6486
+ (event) => this.diagnostic(event)
6487
+ );
6488
+ }
6489
+ async activateTranscriptTurnContext(identity, promptRef, expiresAt) {
6490
+ try {
6491
+ const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
6492
+ const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
6493
+ await this.store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
6494
+ this.diagnostic("activation_recorded");
6495
+ } catch (error) {
6496
+ this.diagnostic("activation_failed");
6497
+ throw error;
6498
+ }
6499
+ }
6500
+ async releasePrompt(sessionRef, activeTask, promptRef) {
6501
+ if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
6502
+ 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 });
6503
+ }
6504
+ async releaseStoredPromptBestEffort(sessionRef, cursor) {
6505
+ await this.releaseActiveTaskBestEffort(sessionRef, cursor?.active_task);
6506
+ }
6507
+ async releaseActiveTaskBestEffort(sessionRef, activeTask) {
6508
+ if (activeTask?.prompt_ref === void 0) return;
6509
+ await this.releasePrompt(sessionRef, activeTask, activeTask.prompt_ref).catch(() => void 0);
6363
6510
  }
6364
6511
  async processTerminal(registration, activeTask, record3) {
6365
6512
  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 };
@@ -6379,18 +6526,23 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
6379
6526
  created_at: record3.timestamp,
6380
6527
  expires_at: this.expire(this.outboxRetentionMs)
6381
6528
  });
6529
+ await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
6382
6530
  await this.store.clearInvocationContext(identity, this.now());
6383
6531
  return { enqueued: 1 };
6384
6532
  }
6385
6533
  async quarantine(registration, cursor, device, inode, reason) {
6534
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
6386
6535
  const quarantined = { ...cursor, transcript_path: registration.transcript_path, device, inode, status: "quarantined", reason, updated_at: this.stamp() };
6387
6536
  await this.store.writeTranscriptCursor(quarantined);
6388
6537
  }
6389
6538
  };
6390
- async function activateTranscriptTurnContext(store, identity, promptRef, expiresAt) {
6391
- const selection = await store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
6392
- const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
6393
- await store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
6539
+ function codexTranscriptScanFailureKind(error) {
6540
+ return transcriptScanFailureKind(error, (candidate) => {
6541
+ if (!(candidate instanceof CodexHostAdapterError)) return void 0;
6542
+ if (candidate.code === "payload_rejected") return "payload_rejected";
6543
+ if (candidate.code === "storage") return "read_error";
6544
+ return void 0;
6545
+ });
6394
6546
  }
6395
6547
  function accumulateTokenCount(activeTask, record3) {
6396
6548
  if (record3.usage_gap_reason !== void 0) return { ...activeTask, token_usage_gap_reason: record3.usage_gap_reason };
@@ -6478,7 +6630,16 @@ function assertRecordSequence(registration, cursor, records, lastOrdinal, record
6478
6630
  // host/codex/src/codex-producer-runtime.ts
6479
6631
  async function drainCodexProducerOnce(options) {
6480
6632
  const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
6481
- 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();
6633
+ const scan = await new CodexTranscriptWatcher({
6634
+ outbox_dir: options.outbox_dir,
6635
+ producer_installation_ref: options.producer_installation_ref,
6636
+ register_current_prompt: (input) => promptPort.register(input),
6637
+ release_current_prompt: (identity) => promptPort.release(identity),
6638
+ diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("codex", event, failure_kind)
6639
+ }).scanOnce().catch((error) => {
6640
+ writeHostServiceDiagnostic("codex", "watcher_scan_failed", codexTranscriptScanFailureKind(error));
6641
+ throw error;
6642
+ });
6482
6643
  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 } });
6483
6644
  return { scanned: scan.enqueued, ...await producer.drainOutbox() };
6484
6645
  }