@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.
@@ -1016,6 +1016,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
1016
1016
  var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
1017
1017
 
1018
1018
  // packages/ceal-client-protocol/src/resource-row.ts
1019
+ var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
1019
1020
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
1020
1021
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
1021
1022
  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"]);
@@ -1099,6 +1100,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1099
1100
  next: { required: true, timestamp: false },
1100
1101
  kind: { required: true, timestamp: false },
1101
1102
  matched_kinds: { required: false, timestamp: false },
1103
+ title_match: { required: false, timestamp: false },
1102
1104
  title: { required: true, timestamp: false },
1103
1105
  container_label: { required: true, timestamp: false },
1104
1106
  container_ref: { required: false, timestamp: false },
@@ -1584,7 +1586,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1584
1586
  invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1585
1587
  options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1586
1588
  session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1587
- 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" }),
1589
+ 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)" }),
1588
1590
  context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1589
1591
  no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1590
1592
  context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
@@ -1605,6 +1607,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1605
1607
  });
1606
1608
 
1607
1609
  // packages/ceal-client-protocol/src/host-service-diagnostic.ts
1610
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
1608
1611
  var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
1609
1612
  var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1610
1613
  "hook_invoked",
@@ -1613,6 +1616,7 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1613
1616
  "watcher_turn_observed",
1614
1617
  "current_prompt_registered",
1615
1618
  "current_prompt_registration_failed",
1619
+ "current_prompt_capacity_exhausted",
1616
1620
  "activation_recorded",
1617
1621
  "activation_failed",
1618
1622
  "watcher_scan_failed",
@@ -2085,7 +2089,7 @@ function exactOptional(value, required, optional) {
2085
2089
  // host/relay/src/host-producer-core.ts
2086
2090
  import { createHash as createHash3 } from "node:crypto";
2087
2091
  import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
2088
- import { mkdir as mkdir2, open as open3, readFile, rename as rename2, writeFile } from "node:fs/promises";
2092
+ import { mkdir as mkdir2, open as open4, readFile, rename as rename2, writeFile } from "node:fs/promises";
2089
2093
  import { join as join2 } from "node:path";
2090
2094
 
2091
2095
  // host/relay/src/relay-ipc.ts
@@ -2111,6 +2115,64 @@ var MAX_STATE_BYTES = 8 * 1024 * 1024;
2111
2115
  // host/relay/src/current-prompt-custody.ts
2112
2116
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
2113
2117
 
2118
+ // host/service-log.ts
2119
+ import { copyFile, open as open3, stat, truncate } from "node:fs/promises";
2120
+ import { isAbsolute } from "node:path";
2121
+ var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
2122
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
2123
+ 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 } : {} })}
2124
+ `;
2125
+ try {
2126
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
2127
+ } catch {
2128
+ }
2129
+ }
2130
+ function defaultHostServiceDiagnosticSink(line) {
2131
+ process.stderr.write(line);
2132
+ }
2133
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2134
+ var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
2135
+ function hostServiceLogPreviousPath(path) {
2136
+ return `${path}.1`;
2137
+ }
2138
+ async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
2139
+ let size;
2140
+ try {
2141
+ size = (await stat(path)).size;
2142
+ } catch (error) {
2143
+ if (isNotFound(error)) return false;
2144
+ throw error;
2145
+ }
2146
+ if (size <= maxBytes) return false;
2147
+ await copyFile(path, hostServiceLogPreviousPath(path));
2148
+ await truncate(path, 0);
2149
+ return true;
2150
+ }
2151
+ function boundHostServiceLog(environment = process.env) {
2152
+ const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
2153
+ if (path === void 0 || !isAbsolute(path)) return () => void 0;
2154
+ let reported = false;
2155
+ const check = () => {
2156
+ rotateHostServiceLog(path).catch((error) => {
2157
+ if (reported) return;
2158
+ reported = true;
2159
+ process.stderr.write(`[host-service-log] rotation_failed code=${errorCode(error)}
2160
+ `);
2161
+ });
2162
+ };
2163
+ check();
2164
+ const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
2165
+ timer.unref();
2166
+ return () => clearInterval(timer);
2167
+ }
2168
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2169
+ function isNotFound(error) {
2170
+ return errorCode(error) === "ENOENT";
2171
+ }
2172
+ function errorCode(error) {
2173
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
2174
+ }
2175
+
2114
2176
  // host/relay/src/relay-ipc.ts
2115
2177
  var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2116
2178
  "schema_version",
@@ -2129,6 +2191,7 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2129
2191
  ]);
2130
2192
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
2131
2193
  var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2194
+ var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
2132
2195
  var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
2133
2196
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2134
2197
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
@@ -2187,11 +2250,17 @@ var HostRelayIpcError = class extends Error {
2187
2250
  this.failure = failure;
2188
2251
  }
2189
2252
  };
2253
+ var HostRelayCurrentPromptCapacityRefusal = class extends Error {
2254
+ constructor() {
2255
+ super("current_prompt_capacity_exhausted");
2256
+ this.name = "HostRelayCurrentPromptCapacityRefusal";
2257
+ }
2258
+ };
2190
2259
  function hostRelayExchangeFailure(error) {
2191
2260
  return error instanceof HostRelayIpcError ? error.failure : void 0;
2192
2261
  }
2193
2262
  function socketErrorFailure(error) {
2194
- switch (errorCode(error)) {
2263
+ switch (errorCode2(error)) {
2195
2264
  case "EACCES":
2196
2265
  case "EPERM":
2197
2266
  return "permission_denied";
@@ -2226,8 +2295,14 @@ var HostRelayCurrentPromptPort = class {
2226
2295
  async register(input) {
2227
2296
  const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2228
2297
  const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2298
+ if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
2229
2299
  if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2230
2300
  }
2301
+ async release(identity) {
2302
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
2303
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2304
+ if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
2305
+ }
2231
2306
  };
2232
2307
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2233
2308
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
@@ -2310,7 +2385,7 @@ function decodeResponseBuffer(buffer) {
2310
2385
  function decodeCurrentPromptResponseBuffer(buffer) {
2311
2386
  const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2312
2387
  if (value === null) return null;
2313
- if (!record2(value) || !exactKeys2(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");
2388
+ if (!record2(value) || !exactKeys2(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");
2314
2389
  return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2315
2390
  }
2316
2391
  function decodeLengthPrefixedJson(input, maximum) {
@@ -2336,7 +2411,7 @@ function exactKeys2(value, required, optional = []) {
2336
2411
  const keys = Object.keys(value);
2337
2412
  return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2338
2413
  }
2339
- function errorCode(error) {
2414
+ function errorCode2(error) {
2340
2415
  return record2(error) && typeof error.code === "string" ? error.code : null;
2341
2416
  }
2342
2417
 
@@ -2346,6 +2421,29 @@ function isHostTranscriptCursorReason(value) {
2346
2421
  return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2347
2422
  }
2348
2423
  var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2424
+ var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
2425
+ function transcriptScanFailureKind(error, adapterFailureKind) {
2426
+ if (transcriptErrorCode(error) === "ELOOP") return "symlink";
2427
+ const adapter = adapterFailureKind(error);
2428
+ if (adapter !== void 0) return adapter;
2429
+ const code = transcriptErrorCode(error);
2430
+ if (code === "EFBIG") return "size_budget";
2431
+ if (code !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code)) return "read_error";
2432
+ return "other";
2433
+ }
2434
+ async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
2435
+ try {
2436
+ await register();
2437
+ diagnostic("current_prompt_registered");
2438
+ return true;
2439
+ } catch (error) {
2440
+ if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
2441
+ throw error;
2442
+ }
2443
+ }
2444
+ function transcriptErrorCode(error) {
2445
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2446
+ }
2349
2447
  var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2350
2448
  async function readTranscriptCompleteChunk(handle, offset, size) {
2351
2449
  const available = Math.max(0, size - offset);
@@ -2454,19 +2552,24 @@ var HostTranscriptWatcher = class {
2454
2552
  return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2455
2553
  }
2456
2554
  async scanRegistration(registration) {
2457
- return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2458
- missing: () => this.retireMissing(registration),
2459
- failed: (handle, error) => this.scanFailed(handle, registration, error)
2460
- });
2555
+ try {
2556
+ return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2557
+ missing: () => this.retireMissing(registration),
2558
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2559
+ });
2560
+ } catch (error) {
2561
+ if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
2562
+ throw error;
2563
+ }
2461
2564
  }
2462
2565
  async openTranscript(registration, scan, handlers) {
2463
2566
  let handle;
2464
2567
  try {
2465
- handle = await open3(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2568
+ handle = await open4(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2466
2569
  return await scan(handle);
2467
2570
  } catch (error) {
2468
2571
  if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2469
- if (hostRelayExchangeFailure(error) !== void 0) throw error;
2572
+ if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
2470
2573
  return handlers.failed(handle, error);
2471
2574
  } finally {
2472
2575
  await handle?.close();
@@ -2607,7 +2710,7 @@ var HostProducerDrainCore = class {
2607
2710
  const value = JSON.parse(await readFile(this.statePath, "utf8"));
2608
2711
  return this.decodeState(value);
2609
2712
  } catch (error) {
2610
- if (errorCode2(error) !== "ENOENT") throw error;
2713
+ if (errorCode3(error) !== "ENOENT") throw error;
2611
2714
  return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2612
2715
  }
2613
2716
  }
@@ -2653,7 +2756,7 @@ var HostProducerDrainCore = class {
2653
2756
  function isDrainStateHeader(value, schema, installationRef) {
2654
2757
  return isTranscriptRecord(value) && value.schema_version === schema && value.producer_installation_ref === installationRef && Number.isSafeInteger(value.producer_epoch) && Number(value.producer_epoch) > 0 && Number.isSafeInteger(value.next_sequence) && Number(value.next_sequence) > 0 && isTranscriptRecord(value.pending);
2655
2758
  }
2656
- function errorCode2(error) {
2759
+ function errorCode3(error) {
2657
2760
  return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2658
2761
  }
2659
2762
 
@@ -2672,8 +2775,8 @@ import { createHash as createHash4 } from "node:crypto";
2672
2775
 
2673
2776
  // host/codex/src/codex-outbox.ts
2674
2777
  import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
2675
- import { lstat as lstat3, mkdir as mkdir3, open as open4, readdir, rename as rename3, unlink as unlink2 } from "node:fs/promises";
2676
- import { dirname, isAbsolute, join as join3 } from "node:path";
2778
+ import { lstat as lstat3, mkdir as mkdir3, open as open5, readdir, rename as rename3, unlink as unlink2 } from "node:fs/promises";
2779
+ import { dirname, isAbsolute as isAbsolute2, join as join3 } from "node:path";
2677
2780
 
2678
2781
  // host/invocation-context.ts
2679
2782
  var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
@@ -2859,7 +2962,7 @@ var CodexOutboxStore = class {
2859
2962
  try {
2860
2963
  await unlink2(this.entryPath(entry.submission_ref));
2861
2964
  } catch (error) {
2862
- if (!isNotFound(error)) throw storageError("Unable to remove the Codex outbox entry.");
2965
+ if (!isNotFound2(error)) throw storageError("Unable to remove the Codex outbox entry.");
2863
2966
  }
2864
2967
  }
2865
2968
  async writeBinding(binding) {
@@ -2882,7 +2985,7 @@ var CodexOutboxStore = class {
2882
2985
  try {
2883
2986
  await unlink2(path);
2884
2987
  } catch (unlinkError) {
2885
- if (!isNotFound(unlinkError)) throw storageError("Unable to clear a failed Codex Host context replacement.");
2988
+ if (!isNotFound2(unlinkError)) throw storageError("Unable to clear a failed Codex Host context replacement.");
2886
2989
  }
2887
2990
  throw error;
2888
2991
  }
@@ -2943,7 +3046,7 @@ var CodexOutboxStore = class {
2943
3046
  try {
2944
3047
  await unlink2(this.activeContextPath(identity));
2945
3048
  } catch (error) {
2946
- if (!isNotFound(error)) throw storageError("Unable to clear the Codex Host invocation context.");
3049
+ if (!isNotFound2(error)) throw storageError("Unable to clear the Codex Host invocation context.");
2947
3050
  }
2948
3051
  return true;
2949
3052
  }
@@ -3059,7 +3162,7 @@ async function removeOwnerState(path, what) {
3059
3162
  try {
3060
3163
  await unlink2(path);
3061
3164
  } catch (error) {
3062
- if (!isNotFound(error)) throw storageError(`Unable to retire the Codex transcript ${what}.`);
3165
+ if (!isNotFound2(error)) throw storageError(`Unable to retire the Codex transcript ${what}.`);
3063
3166
  }
3064
3167
  }
3065
3168
  function sessionStateKey(sourceRef, sessionRef) {
@@ -3109,7 +3212,7 @@ async function writeAtomicJson(path, value) {
3109
3212
  if (text === void 0) throw storageError("Codex owner state cannot be serialized.");
3110
3213
  const temp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
3111
3214
  try {
3112
- const handle = await open4(temp, "wx", 384);
3215
+ const handle = await open5(temp, "wx", 384);
3113
3216
  try {
3114
3217
  await handle.writeFile(text, "utf8");
3115
3218
  await handle.sync();
@@ -3129,7 +3232,7 @@ async function writeAtomicJson(path, value) {
3129
3232
  }
3130
3233
  async function syncDirectory(path) {
3131
3234
  try {
3132
- const handle = await open4(path, "r");
3235
+ const handle = await open5(path, "r");
3133
3236
  try {
3134
3237
  await handle.sync();
3135
3238
  } finally {
@@ -3144,7 +3247,7 @@ async function readOptionalJson(path, validate) {
3144
3247
  validate(value);
3145
3248
  return value;
3146
3249
  } catch (error) {
3147
- if (isNotFound(error)) return null;
3250
+ if (isNotFound2(error)) return null;
3148
3251
  if (error instanceof CodexHostAdapterError) throw error;
3149
3252
  throw storageError("Codex owner state is malformed.");
3150
3253
  }
@@ -3153,7 +3256,7 @@ async function ensurePrivateOwnerDirectory(path) {
3153
3256
  try {
3154
3257
  await lstat3(path);
3155
3258
  } catch (error) {
3156
- if (!isNotFound(error)) throw storageError("Codex owner state directory is unavailable.");
3259
+ if (!isNotFound2(error)) throw storageError("Codex owner state directory is unavailable.");
3157
3260
  await mkdir3(path, { recursive: true, mode: 448 });
3158
3261
  }
3159
3262
  const stats = await lstat3(path);
@@ -3165,7 +3268,7 @@ async function ensurePrivateOwnerDirectory(path) {
3165
3268
  async function readPrivateFile(path) {
3166
3269
  let handle;
3167
3270
  try {
3168
- handle = await open4(path, O_RDONLY3 | O_NOFOLLOW2);
3271
+ handle = await open5(path, O_RDONLY3 | O_NOFOLLOW2);
3169
3272
  const stats = await handle.stat();
3170
3273
  const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
3171
3274
  if (!stats.isFile()) throw storageError(`Codex owner state path is not a regular file (${refusalFact("mode", `0o${stats.mode.toString(8)}`, "a regular-file mode (0o100xxx)")}).`);
@@ -3208,7 +3311,7 @@ function validateRegistrationFields(value) {
3208
3311
  if (!isTimestamp(value.registered_at) || !isTimestamp(value.expires_at)) throw storageError(`Codex transcript registration timestamps are malformed (${refusalFacts([{ name: "registered_at", value: value.registered_at }, { name: "expires_at", value: value.expires_at }])}).`);
3209
3312
  }
3210
3313
  function validRegistrationCwd(value) {
3211
- return value === void 0 || typeof value === "string" && isAbsolute(value) && value.length <= CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS && !value.includes("\0");
3314
+ return value === void 0 || typeof value === "string" && isAbsolute2(value) && value.length <= CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS && !value.includes("\0");
3212
3315
  }
3213
3316
  function validateCursor(value) {
3214
3317
  if (!isRecord4(value)) throw storageError("Codex transcript cursor is malformed.");
@@ -3303,7 +3406,7 @@ function sameKeys(value, required, optional = []) {
3303
3406
  function isRecord4(value) {
3304
3407
  return value !== null && typeof value === "object" && !Array.isArray(value);
3305
3408
  }
3306
- function isNotFound(error) {
3409
+ function isNotFound2(error) {
3307
3410
  return isRecord4(error) && error.code === "ENOENT";
3308
3411
  }
3309
3412
  function storageError(message) {
@@ -3693,6 +3796,8 @@ function codexSubmissionReference(hostInputRef, event, ordinal) {
3693
3796
  var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
3694
3797
  store;
3695
3798
  registerCurrentPrompt;
3799
+ releaseCurrentPrompt;
3800
+ diagnostic;
3696
3801
  constructor(options) {
3697
3802
  super(options, {
3698
3803
  default_retention_ms: CODEX_DEFAULT_RETENTION_MS,
@@ -3701,6 +3806,8 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
3701
3806
  });
3702
3807
  this.store = new CodexOutboxStore(options.outbox_dir);
3703
3808
  this.registerCurrentPrompt = options.register_current_prompt;
3809
+ this.releaseCurrentPrompt = options.release_current_prompt;
3810
+ this.diagnostic = options.diagnostic ?? (() => void 0);
3704
3811
  }
3705
3812
  transcriptPathOf(registration) {
3706
3813
  return registration.transcript_path;
@@ -3716,17 +3823,22 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
3716
3823
  if (complete === "empty" || complete === "partial") return { records: 0, enqueued: 0, quarantined: false };
3717
3824
  return await this.consumeCompleteChunk(registration, handle, preflight.cursor, complete, device, inode);
3718
3825
  }
3719
- async scanFailed(handle, registration, _error) {
3826
+ async scanFailed(handle, registration, error) {
3827
+ this.diagnostic("watcher_scan_failed", codexTranscriptScanFailureKind(error));
3720
3828
  await this.quarantineFailure(registration, handle);
3721
3829
  return { records: 0, enqueued: 0, quarantined: true };
3722
3830
  }
3723
3831
  async retireMissing(registration) {
3832
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
3724
3833
  return this.retireMissingTranscript(registration.transcript_path, "Codex", () => this.store.retireTranscriptRegistration(registration));
3725
3834
  }
3726
3835
  async preflight(registration, handle, size, device, inode) {
3727
3836
  const existing = await this.store.readTranscriptCursor(registration);
3728
3837
  let cursor = existing ?? codexTranscriptInitialCursor(registration, device, inode, this.now);
3729
- if (cursor.status === "quarantined") return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
3838
+ if (cursor.status === "quarantined") {
3839
+ if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
3840
+ return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
3841
+ }
3730
3842
  const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
3731
3843
  if (reason === null) return { cursor, result: { records: 0, enqueued: 0, quarantined: false } };
3732
3844
  await this.quarantine(registration, cursor, device, inode, reason);
@@ -3765,25 +3877,41 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
3765
3877
  const stats = await handle.stat().catch(() => null);
3766
3878
  if (stats === null) return;
3767
3879
  const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
3768
- await this.quarantine(registration, existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now), String(stats.dev), String(stats.ino), "transcript_malformed");
3880
+ const quarantined = {
3881
+ ...existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now),
3882
+ transcript_path: registration.transcript_path,
3883
+ device: String(stats.dev),
3884
+ inode: String(stats.ino),
3885
+ status: "quarantined",
3886
+ reason: "transcript_malformed",
3887
+ updated_at: this.stamp()
3888
+ };
3889
+ await this.store.writeTranscriptCursor(quarantined);
3769
3890
  }
3770
3891
  async processRecord(registration, activeTask, record3) {
3771
- if (record3.kind === "task_started") {
3772
- 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 };
3773
- }
3892
+ if (record3.kind === "task_started") return this.processTaskStarted(registration, activeTask, record3);
3774
3893
  if (record3.kind === "turn_context") return { ...activeTask === void 0 ? {} : { activeTask: record3.turn_id === activeTask.codex_turn_id ? { ...activeTask, model: record3.model } : activeTask }, enqueued: 0 };
3775
3894
  if (record3.kind === "token_count") return { ...activeTask === void 0 ? {} : { activeTask: accumulateTokenCount(activeTask, record3) }, enqueued: 0 };
3776
3895
  if (record3.kind === "user_text") return this.processUserInput(registration, activeTask, record3);
3777
3896
  if (record3.kind === "task_complete" || record3.kind === "turn_aborted") return this.processTerminal(registration, activeTask, record3);
3778
3897
  return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
3779
3898
  }
3899
+ async processTaskStarted(registration, activeTask, record3) {
3900
+ 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);
3901
+ 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 };
3902
+ }
3780
3903
  async processUserInput(registration, activeTask, record3) {
3781
- if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
3904
+ if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) {
3905
+ const adopted = await this.adoptHookCorrelatedTurn(registration, activeTask, record3);
3906
+ if (adopted === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
3907
+ activeTask = adopted;
3908
+ }
3782
3909
  const hostInputRef = hostInputReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id, record3.message_id, record3.ordinal, record3.prompt);
3783
3910
  const origin = activeTask.origin_host_input_ref === void 0;
3784
3911
  const promptValue = promptObservationValue(record3.prompt);
3785
3912
  const currentPromptRef = hostProducerPromptRef(promptValue.body_sha256);
3786
3913
  const promptRef = activeTask.prompt_ref ?? currentPromptRef;
3914
+ const steeredPromptRef = await this.steeredPromptRef(registration, activeTask, origin, currentPromptRef);
3787
3915
  const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: activeTask.host_turn_ref };
3788
3916
  const observations = [{
3789
3917
  observation_ref: observationRef("transcript_user_text", hostInputRef, record3.prompt),
@@ -3809,14 +3937,88 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
3809
3937
  created_at: record3.timestamp,
3810
3938
  expires_at: this.expire(this.outboxRetentionMs)
3811
3939
  };
3812
- await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
3813
- await this.store.put(envelope);
3814
- if (origin) await activateTranscriptTurnContext(this.store, identity, promptRef, envelope.expires_at);
3940
+ const persisted = await this.persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope);
3941
+ if (steeredPromptRef !== null) await this.activateTranscriptTurnContext(identity, steeredPromptRef, envelope.expires_at);
3942
+ return persisted;
3943
+ }
3944
+ /**
3945
+ * The steered prompt to re-activate under the running task, or null when
3946
+ * there is nothing to repair. A steered message joins the running turn
3947
+ * with new prompt bytes but no task_started. When the steer hook already
3948
+ * replaced the turn selection (a turn id the hook did not recognize as
3949
+ * the running one, or a hook that ran before any context was live), the
3950
+ * running task's context no longer matches and the next invocation would
3951
+ * wait for a boundary that never comes. When the hook kept the live
3952
+ * selection, the context still matches and this stays null.
3953
+ */
3954
+ async steeredPromptRef(registration, activeTask, origin, currentPromptRef) {
3955
+ if (origin || activeTask.prompt_ref === void 0 || activeTask.prompt_ref === currentPromptRef) return null;
3956
+ const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref).catch(() => null);
3957
+ return selection?.prompt_ref === currentPromptRef ? currentPromptRef : null;
3958
+ }
3959
+ /**
3960
+ * Adopt the live hooked turn when its transcript message arrives before
3961
+ * (or without) its task_started, as an interrupted turn's follow-up can.
3962
+ * The UserPromptSubmit hook names the exact hooked prompt, so only a
3963
+ * message carrying those bytes adopts; inherited history and other turns
3964
+ * stay ignored. A stale active task for another turn releases first, the
3965
+ * same as a task_started supersession.
3966
+ */
3967
+ async adoptHookCorrelatedTurn(registration, activeTask, record3) {
3968
+ const currentPromptRef = hostProducerPromptRef(promptObservationValue(record3.prompt).body_sha256);
3969
+ const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref);
3970
+ if (selection?.prompt_ref !== currentPromptRef) return void 0;
3971
+ if (activeTask !== void 0 && activeTask.codex_turn_id !== record3.turn_id && activeTask.prompt_ref !== void 0) {
3972
+ await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
3973
+ }
3974
+ return {
3975
+ codex_turn_id: record3.turn_id,
3976
+ host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id),
3977
+ started_at: record3.timestamp
3978
+ };
3979
+ }
3980
+ async persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope) {
3981
+ const custodyRegistered = await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
3982
+ if (custodyRegistered) this.diagnostic("watcher_turn_observed");
3983
+ try {
3984
+ await this.store.put(envelope);
3985
+ if (origin) await this.activateTranscriptTurnContext(identity, promptRef, envelope.expires_at);
3986
+ } catch (error) {
3987
+ if (custodyRegistered) await this.releasePrompt(registration.host_session_ref, activeTask, currentPromptRef).catch(() => void 0);
3988
+ throw error;
3989
+ }
3815
3990
  return { activeTask: { ...activeTask, origin_host_input_ref: activeTask.origin_host_input_ref ?? hostInputRef, prompt_ref: activeTask.prompt_ref ?? currentPromptRef }, enqueued: 1 };
3816
3991
  }
3817
3992
  async registerPrompt(origin, registration, activeTask, promptRef, prompt, expiresAt) {
3818
- if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
3819
- 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 });
3993
+ if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
3994
+ const register = this.registerCurrentPrompt;
3995
+ const installationRef = this.producerInstallationRef;
3996
+ return registerHostCurrentPromptDiagnosed(
3997
+ () => 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 }),
3998
+ (event) => this.diagnostic(event)
3999
+ );
4000
+ }
4001
+ async activateTranscriptTurnContext(identity, promptRef, expiresAt) {
4002
+ try {
4003
+ const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
4004
+ const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
4005
+ await this.store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
4006
+ this.diagnostic("activation_recorded");
4007
+ } catch (error) {
4008
+ this.diagnostic("activation_failed");
4009
+ throw error;
4010
+ }
4011
+ }
4012
+ async releasePrompt(sessionRef, activeTask, promptRef) {
4013
+ if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
4014
+ 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 });
4015
+ }
4016
+ async releaseStoredPromptBestEffort(sessionRef, cursor) {
4017
+ await this.releaseActiveTaskBestEffort(sessionRef, cursor?.active_task);
4018
+ }
4019
+ async releaseActiveTaskBestEffort(sessionRef, activeTask) {
4020
+ if (activeTask?.prompt_ref === void 0) return;
4021
+ await this.releasePrompt(sessionRef, activeTask, activeTask.prompt_ref).catch(() => void 0);
3820
4022
  }
3821
4023
  async processTerminal(registration, activeTask, record3) {
3822
4024
  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 };
@@ -3836,18 +4038,23 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
3836
4038
  created_at: record3.timestamp,
3837
4039
  expires_at: this.expire(this.outboxRetentionMs)
3838
4040
  });
4041
+ await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
3839
4042
  await this.store.clearInvocationContext(identity, this.now());
3840
4043
  return { enqueued: 1 };
3841
4044
  }
3842
4045
  async quarantine(registration, cursor, device, inode, reason) {
4046
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
3843
4047
  const quarantined = { ...cursor, transcript_path: registration.transcript_path, device, inode, status: "quarantined", reason, updated_at: this.stamp() };
3844
4048
  await this.store.writeTranscriptCursor(quarantined);
3845
4049
  }
3846
4050
  };
3847
- async function activateTranscriptTurnContext(store, identity, promptRef, expiresAt) {
3848
- const selection = await store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3849
- const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3850
- await store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
4051
+ function codexTranscriptScanFailureKind(error) {
4052
+ return transcriptScanFailureKind(error, (candidate) => {
4053
+ if (!(candidate instanceof CodexHostAdapterError)) return void 0;
4054
+ if (candidate.code === "payload_rejected") return "payload_rejected";
4055
+ if (candidate.code === "storage") return "read_error";
4056
+ return void 0;
4057
+ });
3851
4058
  }
3852
4059
  function accumulateTokenCount(activeTask, record3) {
3853
4060
  if (record3.usage_gap_reason !== void 0) return { ...activeTask, token_usage_gap_reason: record3.usage_gap_reason };
@@ -3935,7 +4142,16 @@ function assertRecordSequence(registration, cursor, records, lastOrdinal, record
3935
4142
  // host/codex/src/codex-producer-runtime.ts
3936
4143
  async function drainCodexProducerOnce(options) {
3937
4144
  const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
3938
- 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();
4145
+ const scan = await new CodexTranscriptWatcher({
4146
+ outbox_dir: options.outbox_dir,
4147
+ producer_installation_ref: options.producer_installation_ref,
4148
+ register_current_prompt: (input) => promptPort.register(input),
4149
+ release_current_prompt: (identity) => promptPort.release(identity),
4150
+ diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("codex", event, failure_kind)
4151
+ }).scanOnce().catch((error) => {
4152
+ writeHostServiceDiagnostic("codex", "watcher_scan_failed", codexTranscriptScanFailureKind(error));
4153
+ throw error;
4154
+ });
3939
4155
  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 } });
3940
4156
  return { scanned: scan.enqueued, ...await producer.drainOutbox() };
3941
4157
  }
@@ -3961,53 +4177,6 @@ function runCodexProducerCli(args, signal, dependencies = {}) {
3961
4177
  return runHostProducerCli(CODEX_PRODUCER_DESCRIPTOR, args, signal, dependencies);
3962
4178
  }
3963
4179
 
3964
- // host/service-log.ts
3965
- import { copyFile, open as open5, stat, truncate } from "node:fs/promises";
3966
- import { isAbsolute as isAbsolute2 } from "node:path";
3967
- var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
3968
- var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
3969
- var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
3970
- function hostServiceLogPreviousPath(path) {
3971
- return `${path}.1`;
3972
- }
3973
- async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
3974
- let size;
3975
- try {
3976
- size = (await stat(path)).size;
3977
- } catch (error) {
3978
- if (isNotFound2(error)) return false;
3979
- throw error;
3980
- }
3981
- if (size <= maxBytes) return false;
3982
- await copyFile(path, hostServiceLogPreviousPath(path));
3983
- await truncate(path, 0);
3984
- return true;
3985
- }
3986
- function boundHostServiceLog(environment = process.env) {
3987
- const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
3988
- if (path === void 0 || !isAbsolute2(path)) return () => void 0;
3989
- let reported = false;
3990
- const check = () => {
3991
- rotateHostServiceLog(path).catch((error) => {
3992
- if (reported) return;
3993
- reported = true;
3994
- process.stderr.write(`[host-service-log] rotation_failed code=${errorCode3(error)}
3995
- `);
3996
- });
3997
- };
3998
- check();
3999
- const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
4000
- timer.unref();
4001
- return () => clearInterval(timer);
4002
- }
4003
- var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
4004
- function isNotFound2(error) {
4005
- return errorCode3(error) === "ENOENT";
4006
- }
4007
- function errorCode3(error) {
4008
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
4009
- }
4010
-
4011
4180
  // host/producer-command.ts
4012
4181
  async function runProducerCommand(schemaVersion, run) {
4013
4182
  const controller = new AbortController();