@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" }),
@@ -1614,6 +1616,7 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1614
1616
  "watcher_turn_observed",
1615
1617
  "current_prompt_registered",
1616
1618
  "current_prompt_registration_failed",
1619
+ "current_prompt_capacity_exhausted",
1617
1620
  "activation_recorded",
1618
1621
  "activation_failed",
1619
1622
  "watcher_scan_failed",
@@ -2086,7 +2089,7 @@ function exactOptional(value, required, optional) {
2086
2089
  // host/relay/src/host-producer-core.ts
2087
2090
  import { createHash as createHash3 } from "node:crypto";
2088
2091
  import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
2089
- 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";
2090
2093
  import { join as join2 } from "node:path";
2091
2094
 
2092
2095
  // host/relay/src/relay-ipc.ts
@@ -2183,6 +2186,64 @@ var MAX_STATE_BYTES = 8 * 1024 * 1024;
2183
2186
  // host/relay/src/current-prompt-custody.ts
2184
2187
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
2185
2188
 
2189
+ // host/service-log.ts
2190
+ import { copyFile, open as open3, stat, truncate } from "node:fs/promises";
2191
+ import { isAbsolute } from "node:path";
2192
+ var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
2193
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
2194
+ 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 } : {} })}
2195
+ `;
2196
+ try {
2197
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
2198
+ } catch {
2199
+ }
2200
+ }
2201
+ function defaultHostServiceDiagnosticSink(line) {
2202
+ process.stderr.write(line);
2203
+ }
2204
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2205
+ var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
2206
+ function hostServiceLogPreviousPath(path) {
2207
+ return `${path}.1`;
2208
+ }
2209
+ async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
2210
+ let size;
2211
+ try {
2212
+ size = (await stat(path)).size;
2213
+ } catch (error) {
2214
+ if (isNotFound(error)) return false;
2215
+ throw error;
2216
+ }
2217
+ if (size <= maxBytes) return false;
2218
+ await copyFile(path, hostServiceLogPreviousPath(path));
2219
+ await truncate(path, 0);
2220
+ return true;
2221
+ }
2222
+ function boundHostServiceLog(environment = process.env) {
2223
+ const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
2224
+ if (path === void 0 || !isAbsolute(path)) return () => void 0;
2225
+ let reported = false;
2226
+ const check = () => {
2227
+ rotateHostServiceLog(path).catch((error) => {
2228
+ if (reported) return;
2229
+ reported = true;
2230
+ process.stderr.write(`[host-service-log] rotation_failed code=${errorCode2(error)}
2231
+ `);
2232
+ });
2233
+ };
2234
+ check();
2235
+ const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
2236
+ timer.unref();
2237
+ return () => clearInterval(timer);
2238
+ }
2239
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2240
+ function isNotFound(error) {
2241
+ return errorCode2(error) === "ENOENT";
2242
+ }
2243
+ function errorCode2(error) {
2244
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
2245
+ }
2246
+
2186
2247
  // host/relay/src/relay-ipc.ts
2187
2248
  var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2188
2249
  "schema_version",
@@ -2201,6 +2262,7 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2201
2262
  ]);
2202
2263
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
2203
2264
  var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2265
+ var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
2204
2266
  var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
2205
2267
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2206
2268
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
@@ -2259,11 +2321,17 @@ var HostRelayIpcError = class extends Error {
2259
2321
  this.failure = failure;
2260
2322
  }
2261
2323
  };
2324
+ var HostRelayCurrentPromptCapacityRefusal = class extends Error {
2325
+ constructor() {
2326
+ super("current_prompt_capacity_exhausted");
2327
+ this.name = "HostRelayCurrentPromptCapacityRefusal";
2328
+ }
2329
+ };
2262
2330
  function hostRelayExchangeFailure(error) {
2263
2331
  return error instanceof HostRelayIpcError ? error.failure : void 0;
2264
2332
  }
2265
2333
  function socketErrorFailure(error) {
2266
- switch (errorCode2(error)) {
2334
+ switch (errorCode3(error)) {
2267
2335
  case "EACCES":
2268
2336
  case "EPERM":
2269
2337
  return "permission_denied";
@@ -2298,8 +2366,14 @@ var HostRelayCurrentPromptPort = class {
2298
2366
  async register(input) {
2299
2367
  const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2300
2368
  const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2369
+ if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
2301
2370
  if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2302
2371
  }
2372
+ async release(identity) {
2373
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
2374
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2375
+ if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
2376
+ }
2303
2377
  };
2304
2378
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2305
2379
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
@@ -2382,7 +2456,7 @@ function decodeResponseBuffer(buffer) {
2382
2456
  function decodeCurrentPromptResponseBuffer(buffer) {
2383
2457
  const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2384
2458
  if (value === null) return null;
2385
- 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");
2459
+ 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");
2386
2460
  return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2387
2461
  }
2388
2462
  function decodeLengthPrefixedJson(input, maximum) {
@@ -2408,7 +2482,7 @@ function exactKeys2(value, required, optional = []) {
2408
2482
  const keys = Object.keys(value);
2409
2483
  return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2410
2484
  }
2411
- function errorCode2(error) {
2485
+ function errorCode3(error) {
2412
2486
  return record2(error) && typeof error.code === "string" ? error.code : null;
2413
2487
  }
2414
2488
 
@@ -2418,6 +2492,29 @@ function isHostTranscriptCursorReason(value) {
2418
2492
  return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2419
2493
  }
2420
2494
  var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2495
+ var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
2496
+ function transcriptScanFailureKind(error, adapterFailureKind) {
2497
+ if (transcriptErrorCode(error) === "ELOOP") return "symlink";
2498
+ const adapter = adapterFailureKind(error);
2499
+ if (adapter !== void 0) return adapter;
2500
+ const code2 = transcriptErrorCode(error);
2501
+ if (code2 === "EFBIG") return "size_budget";
2502
+ if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
2503
+ return "other";
2504
+ }
2505
+ async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
2506
+ try {
2507
+ await register();
2508
+ diagnostic("current_prompt_registered");
2509
+ return true;
2510
+ } catch (error) {
2511
+ if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
2512
+ throw error;
2513
+ }
2514
+ }
2515
+ function transcriptErrorCode(error) {
2516
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2517
+ }
2421
2518
  var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2422
2519
  async function readTranscriptCompleteChunk(handle, offset, size) {
2423
2520
  const available = Math.max(0, size - offset);
@@ -2526,19 +2623,24 @@ var HostTranscriptWatcher = class {
2526
2623
  return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2527
2624
  }
2528
2625
  async scanRegistration(registration) {
2529
- return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2530
- missing: () => this.retireMissing(registration),
2531
- failed: (handle, error) => this.scanFailed(handle, registration, error)
2532
- });
2626
+ try {
2627
+ return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2628
+ missing: () => this.retireMissing(registration),
2629
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2630
+ });
2631
+ } catch (error) {
2632
+ if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
2633
+ throw error;
2634
+ }
2533
2635
  }
2534
2636
  async openTranscript(registration, scan, handlers) {
2535
2637
  let handle;
2536
2638
  try {
2537
- handle = await open3(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2639
+ handle = await open4(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2538
2640
  return await scan(handle);
2539
2641
  } catch (error) {
2540
2642
  if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2541
- if (hostRelayExchangeFailure(error) !== void 0) throw error;
2643
+ if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
2542
2644
  return handlers.failed(handle, error);
2543
2645
  } finally {
2544
2646
  await handle?.close();
@@ -2679,7 +2781,7 @@ var HostProducerDrainCore = class {
2679
2781
  const value = JSON.parse(await readFile(this.statePath, "utf8"));
2680
2782
  return this.decodeState(value);
2681
2783
  } catch (error) {
2682
- if (errorCode3(error) !== "ENOENT") throw error;
2784
+ if (errorCode4(error) !== "ENOENT") throw error;
2683
2785
  return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2684
2786
  }
2685
2787
  }
@@ -2725,7 +2827,7 @@ var HostProducerDrainCore = class {
2725
2827
  function isDrainStateHeader(value, schema, installationRef) {
2726
2828
  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);
2727
2829
  }
2728
- function errorCode3(error) {
2830
+ function errorCode4(error) {
2729
2831
  return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2730
2832
  }
2731
2833
 
@@ -2739,60 +2841,6 @@ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, produ
2739
2841
  var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
2740
2842
  var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
2741
2843
 
2742
- // host/service-log.ts
2743
- import { copyFile, open as open4, stat, truncate } from "node:fs/promises";
2744
- import { isAbsolute } from "node:path";
2745
- var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
2746
- function writeHostServiceDiagnostic(producer_id, event, failure_kind) {
2747
- try {
2748
- 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 } : {} })}
2749
- `);
2750
- } catch {
2751
- }
2752
- }
2753
- var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2754
- var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
2755
- function hostServiceLogPreviousPath(path) {
2756
- return `${path}.1`;
2757
- }
2758
- async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
2759
- let size;
2760
- try {
2761
- size = (await stat(path)).size;
2762
- } catch (error) {
2763
- if (isNotFound(error)) return false;
2764
- throw error;
2765
- }
2766
- if (size <= maxBytes) return false;
2767
- await copyFile(path, hostServiceLogPreviousPath(path));
2768
- await truncate(path, 0);
2769
- return true;
2770
- }
2771
- function boundHostServiceLog(environment = process.env) {
2772
- const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
2773
- if (path === void 0 || !isAbsolute(path)) return () => void 0;
2774
- let reported = false;
2775
- const check = () => {
2776
- rotateHostServiceLog(path).catch((error) => {
2777
- if (reported) return;
2778
- reported = true;
2779
- process.stderr.write(`[host-service-log] rotation_failed code=${errorCode4(error)}
2780
- `);
2781
- });
2782
- };
2783
- check();
2784
- const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
2785
- timer.unref();
2786
- return () => clearInterval(timer);
2787
- }
2788
- var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2789
- function isNotFound(error) {
2790
- return errorCode4(error) === "ENOENT";
2791
- }
2792
- function errorCode4(error) {
2793
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
2794
- }
2795
-
2796
2844
  // host/claude/src/claude-decoder.ts
2797
2845
  import { createHash as createHash4 } from "node:crypto";
2798
2846
 
@@ -3447,6 +3495,7 @@ var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
3447
3495
  var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3448
3496
  store;
3449
3497
  registerCurrentPrompt;
3498
+ releaseCurrentPrompt;
3450
3499
  diagnostic;
3451
3500
  constructor(options) {
3452
3501
  super(options, {
@@ -3456,6 +3505,7 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3456
3505
  });
3457
3506
  this.store = new ClaudeOutboxStore(options.outbox_dir);
3458
3507
  this.registerCurrentPrompt = options.register_current_prompt;
3508
+ this.releaseCurrentPrompt = options.release_current_prompt;
3459
3509
  this.diagnostic = options.diagnostic ?? (() => void 0);
3460
3510
  }
3461
3511
  transcriptPathOf(registration) {
@@ -3468,19 +3518,23 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3468
3518
  const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3469
3519
  const cursor = await this.preflight(registration, handle, size, device, inode);
3470
3520
  if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
3471
- if (cursor.status === "quarantined") return { records: 0, enqueued: 0, quarantined: true };
3521
+ if (cursor.status === "quarantined") {
3522
+ if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
3523
+ return { records: 0, enqueued: 0, quarantined: true };
3524
+ }
3472
3525
  const draft = mutableCursor(cursor);
3473
3526
  await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
3474
3527
  return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
3475
3528
  }
3476
3529
  async scanFailed(handle, registration, error) {
3477
- this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
3530
+ this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
3478
3531
  if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
3479
3532
  if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
3480
3533
  await this.quarantineFailure(registration, handle);
3481
3534
  return { records: 0, enqueued: 0, quarantined: true };
3482
3535
  }
3483
3536
  async retireMissing(registration) {
3537
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
3484
3538
  return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
3485
3539
  }
3486
3540
  initialCursor(registration) {
@@ -3527,10 +3581,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3527
3581
  if (isUserTurnLine(record5)) await this.openTurn(registration, draft, record5);
3528
3582
  else if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
3529
3583
  else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3584
+ if (isAssistantTerminalLine(record5)) await this.releaseOpenTurn(registration, draft);
3530
3585
  }
3531
3586
  }
3532
3587
  async openTurn(registration, draft, record5) {
3533
- this.diagnostic("watcher_turn_observed");
3534
3588
  await this.closeTurn(registration, draft, record5.timestamp);
3535
3589
  const sessionRef = registration.host_session_ref;
3536
3590
  const turnRef = claudeLocalTurnRef(registration.claude_session_id, record5.uuid);
@@ -3546,23 +3600,40 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3546
3600
  if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record5.timestamp, value: session.value });
3547
3601
  draft.session_observed = true;
3548
3602
  }
3549
- await this.store.putEnvelope({
3550
- schema_version: "ceal.claude_observation_outbox.v1",
3551
- identity,
3552
- host_input_ref: inputRef,
3553
- input_kind: "human",
3554
- prompt_ref: promptRef,
3555
- observations,
3556
- origin_input: true,
3557
- submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3558
- created_at: record5.timestamp,
3559
- expires_at: this.expire(this.outboxRetentionMs)
3560
- });
3561
- draft.enqueued += 1;
3562
- await this.registerPrompt(sessionRef, turnRef, promptRef, record5.message.content);
3603
+ await this.persistOpenedTurnContext(registration, draft, record5, inputRef, turnRef, promptRef, identity, observations);
3604
+ }
3605
+ async persistOpenedTurnContext(registration, draft, record5, inputRef, turnRef, promptRef, identity, observations) {
3606
+ const sessionRef = registration.host_session_ref;
3607
+ const expiresAt = this.expire(this.outboxRetentionMs);
3608
+ const custodyRegistered = await this.registerPrompt(sessionRef, turnRef, promptRef, record5.message.content, expiresAt);
3609
+ if (custodyRegistered) this.diagnostic("watcher_turn_observed");
3610
+ try {
3611
+ await this.store.putEnvelope({
3612
+ schema_version: "ceal.claude_observation_outbox.v1",
3613
+ identity,
3614
+ host_input_ref: inputRef,
3615
+ input_kind: "human",
3616
+ prompt_ref: promptRef,
3617
+ observations,
3618
+ origin_input: true,
3619
+ submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3620
+ created_at: record5.timestamp,
3621
+ expires_at: expiresAt
3622
+ });
3623
+ draft.enqueued += 1;
3624
+ await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
3625
+ this.setOpenedTurn(draft, record5, inputRef, turnRef, promptRef);
3626
+ } catch (error) {
3627
+ if (custodyRegistered) await this.releasePrompt(sessionRef, { turn_key: turnRef, prompt_ref: promptRef }).catch(() => void 0);
3628
+ throw error;
3629
+ }
3630
+ }
3631
+ async activateOpenedTurnContext(identity, promptRef, expiresAt) {
3563
3632
  const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3564
3633
  const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3565
- await this.activateContext(identity, promptRef, turnGenerationRef);
3634
+ await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
3635
+ }
3636
+ setOpenedTurn(draft, record5, inputRef, turnRef, promptRef) {
3566
3637
  const folded = foldUnattributed(draft.unattributed);
3567
3638
  draft.open_turn = {
3568
3639
  turn_key: turnRef,
@@ -3581,9 +3652,18 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3581
3652
  draft.open_turn = void 0;
3582
3653
  if (turn === void 0) return;
3583
3654
  const digest2 = terminalDigest(turn);
3584
- if (digest2 === turn.last_terminal_digest) return;
3585
- await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3586
- draft.enqueued += 1;
3655
+ if (digest2 !== turn.last_terminal_digest) {
3656
+ await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3657
+ draft.enqueued += 1;
3658
+ }
3659
+ await this.releasePrompt(registration.host_session_ref, turn);
3660
+ }
3661
+ async releaseOpenTurn(registration, draft) {
3662
+ if (draft.open_turn !== void 0) await this.releasePrompt(registration.host_session_ref, draft.open_turn);
3663
+ }
3664
+ async releasePrompt(sessionRef, turn) {
3665
+ if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
3666
+ await this.releaseCurrentPrompt({ producer_id: "claude", producer_installation_ref: this.producerInstallationRef, local_session_ref: sessionRef, local_turn_ref: turn.turn_key, prompt_ref: turn.prompt_ref });
3587
3667
  }
3588
3668
  accumulateUsage(draft, usage) {
3589
3669
  const turn = draft.open_turn;
@@ -3617,6 +3697,8 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3617
3697
  if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
3618
3698
  throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
3619
3699
  }
3700
+ const listed = new Set(names);
3701
+ for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
3620
3702
  if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
3621
3703
  this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
3622
3704
  names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
@@ -3731,27 +3813,33 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3731
3813
  updated_at: this.stamp()
3732
3814
  });
3733
3815
  }
3734
- async registerPrompt(sessionRef, turnRef, promptRef, prompt) {
3735
- if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
3736
- try {
3737
- await this.registerCurrentPrompt({
3816
+ async registerPrompt(sessionRef, turnRef, promptRef, prompt, expiresAt) {
3817
+ if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
3818
+ const register = this.registerCurrentPrompt;
3819
+ const installationRef = this.producerInstallationRef;
3820
+ return registerHostCurrentPromptDiagnosed(
3821
+ () => register({
3738
3822
  producer_id: "claude",
3739
- producer_installation_ref: this.producerInstallationRef,
3823
+ producer_installation_ref: installationRef,
3740
3824
  local_session_ref: sessionRef,
3741
3825
  local_turn_ref: turnRef,
3742
3826
  prompt_ref: promptRef,
3743
3827
  current_prompt: prompt,
3744
- context_expires_at: this.expire(this.outboxRetentionMs)
3745
- });
3746
- this.diagnostic("current_prompt_registered");
3747
- } catch (error) {
3748
- this.diagnostic("current_prompt_registration_failed");
3749
- throw error;
3750
- }
3828
+ context_expires_at: expiresAt
3829
+ }),
3830
+ (event) => this.diagnostic(event)
3831
+ );
3832
+ }
3833
+ async releaseStoredPromptBestEffort(sessionRef, cursor) {
3834
+ await this.releaseOpenTurnBestEffort(sessionRef, cursor?.open_turn);
3835
+ }
3836
+ async releaseOpenTurnBestEffort(sessionRef, turn) {
3837
+ if (turn === void 0) return;
3838
+ await this.releasePrompt(sessionRef, turn).catch(() => void 0);
3751
3839
  }
3752
- async activateContext(identity, promptRef, turnGenerationRef) {
3840
+ async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
3753
3841
  try {
3754
- await this.store.activateInvocationContext(identity, promptRef, this.expire(this.outboxRetentionMs), void 0, turnGenerationRef);
3842
+ await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
3755
3843
  this.diagnostic("activation_recorded");
3756
3844
  } catch (error) {
3757
3845
  this.diagnostic("activation_failed");
@@ -3759,6 +3847,7 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3759
3847
  }
3760
3848
  }
3761
3849
  async quarantineStored(cursor, device, inode, reason) {
3850
+ await this.releaseStoredPromptBestEffort(cursor.host_session_ref, cursor);
3762
3851
  await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
3763
3852
  return null;
3764
3853
  }
@@ -3875,21 +3964,12 @@ function isUserTurnLine(record5) {
3875
3964
  if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
3876
3965
  return true;
3877
3966
  }
3878
- function claudeTranscriptScanFailureKind(error) {
3879
- if (isSymlinkTranscript(error)) return "symlink";
3880
- if (error instanceof ClaudeHostError) {
3881
- if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
3882
- if (error.code === "payload_rejected") return "payload_rejected";
3883
- if (error.code === "storage") return "read_error";
3884
- }
3885
- const code2 = transcriptErrorCode(error);
3886
- if (code2 === "EFBIG") return "size_budget";
3887
- if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
3888
- return "other";
3889
- }
3890
3967
  function isAssistantLine(record5) {
3891
3968
  return isTranscriptRecord(record5) && record5.type === "assistant" && isTranscriptRecord(record5.message);
3892
3969
  }
3970
+ function isAssistantTerminalLine(record5) {
3971
+ return isAssistantLine(record5) && (record5.message.stop_reason === "end_turn" || record5.message.stop_reason === "stop_sequence");
3972
+ }
3893
3973
  function isAssistantUsageLine(record5) {
3894
3974
  if (!isTranscriptRecord(record5)) return false;
3895
3975
  if (!isAssistantLine(record5) || record5.message.usage === void 0) return false;
@@ -3917,12 +3997,24 @@ async function ownedClaudeTranscriptFile(handle) {
3917
3997
  function isNotDirectory(error) {
3918
3998
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
3919
3999
  }
4000
+ function claudeTranscriptScanCause(handle, error) {
4001
+ if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
4002
+ const code2 = transcriptErrorCode(error);
4003
+ if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
4004
+ return `${handle === void 0 ? "open" : "scan"}:unknown`;
4005
+ }
4006
+ function claudeTranscriptScanFailureKind(error) {
4007
+ return transcriptScanFailureKind(error, (candidate) => {
4008
+ if (!(candidate instanceof ClaudeHostError)) return void 0;
4009
+ if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
4010
+ if (candidate.code === "payload_rejected") return "payload_rejected";
4011
+ if (candidate.code === "storage") return "read_error";
4012
+ return void 0;
4013
+ });
4014
+ }
3920
4015
  function isSymlinkTranscript(error) {
3921
4016
  return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
3922
4017
  }
3923
- function transcriptErrorCode(error) {
3924
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
3925
- }
3926
4018
 
3927
4019
  // host/claude/src/claude-producer-runtime.ts
3928
4020
  async function drainClaudeProducerOnce(options) {
@@ -3931,9 +4023,10 @@ async function drainClaudeProducerOnce(options) {
3931
4023
  outbox_dir: options.outbox_dir,
3932
4024
  producer_installation_ref: options.producer_installation_ref,
3933
4025
  register_current_prompt: (input) => promptPort.register(input),
3934
- diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("claude", event, failure_kind)
4026
+ release_current_prompt: (identity) => promptPort.release(identity),
4027
+ diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
3935
4028
  }).scanOnce().catch((error) => {
3936
- writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
4029
+ writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
3937
4030
  throw error;
3938
4031
  });
3939
4032
  const producer = new ClaudeProducer({ 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 } });