@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.
package/bin/ceal-host CHANGED
Binary file
@@ -1005,6 +1005,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
1005
1005
  var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
1006
1006
 
1007
1007
  // packages/ceal-client-protocol/src/resource-row.ts
1008
+ var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
1008
1009
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
1009
1010
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
1010
1011
  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"]);
@@ -1088,6 +1089,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1088
1089
  next: { required: true, timestamp: false },
1089
1090
  kind: { required: true, timestamp: false },
1090
1091
  matched_kinds: { required: false, timestamp: false },
1092
+ title_match: { required: false, timestamp: false },
1091
1093
  title: { required: true, timestamp: false },
1092
1094
  container_label: { required: true, timestamp: false },
1093
1095
  container_ref: { required: false, timestamp: false },
@@ -1573,7 +1575,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1573
1575
  invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1574
1576
  options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1575
1577
  session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1576
- 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" }),
1578
+ 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)" }),
1577
1579
  context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1578
1580
  no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1579
1581
  context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
@@ -1603,6 +1605,7 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1603
1605
  "watcher_turn_observed",
1604
1606
  "current_prompt_registered",
1605
1607
  "current_prompt_registration_failed",
1608
+ "current_prompt_capacity_exhausted",
1606
1609
  "activation_recorded",
1607
1610
  "activation_failed",
1608
1611
  "watcher_scan_failed",
@@ -2100,6 +2103,21 @@ var MAX_STATE_BYTES = 8 * 1024 * 1024;
2100
2103
  // host/relay/src/current-prompt-custody.ts
2101
2104
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
2102
2105
 
2106
+ // host/service-log.ts
2107
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
2108
+ 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 } : {} })}
2109
+ `;
2110
+ try {
2111
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
2112
+ } catch {
2113
+ }
2114
+ }
2115
+ function defaultHostServiceDiagnosticSink(line) {
2116
+ process.stderr.write(line);
2117
+ }
2118
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2119
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2120
+
2103
2121
  // host/relay/src/relay-ipc.ts
2104
2122
  var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2105
2123
  "schema_version",
@@ -2118,6 +2136,7 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2118
2136
  ]);
2119
2137
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
2120
2138
  var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2139
+ var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
2121
2140
  var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
2122
2141
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2123
2142
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
@@ -2176,6 +2195,12 @@ var HostRelayIpcError = class extends Error {
2176
2195
  this.failure = failure;
2177
2196
  }
2178
2197
  };
2198
+ var HostRelayCurrentPromptCapacityRefusal = class extends Error {
2199
+ constructor() {
2200
+ super("current_prompt_capacity_exhausted");
2201
+ this.name = "HostRelayCurrentPromptCapacityRefusal";
2202
+ }
2203
+ };
2179
2204
  function hostRelayExchangeFailure(error) {
2180
2205
  return error instanceof HostRelayIpcError ? error.failure : void 0;
2181
2206
  }
@@ -2215,8 +2240,14 @@ var HostRelayCurrentPromptPort = class {
2215
2240
  async register(input) {
2216
2241
  const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2217
2242
  const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2243
+ if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
2218
2244
  if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2219
2245
  }
2246
+ async release(identity) {
2247
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
2248
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2249
+ if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
2250
+ }
2220
2251
  };
2221
2252
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2222
2253
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
@@ -2299,7 +2330,7 @@ function decodeResponseBuffer(buffer) {
2299
2330
  function decodeCurrentPromptResponseBuffer(buffer) {
2300
2331
  const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2301
2332
  if (value === null) return null;
2302
- if (!record3(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");
2333
+ if (!record3(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");
2303
2334
  return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2304
2335
  }
2305
2336
  function decodeLengthPrefixedJson(input, maximum) {
@@ -2335,6 +2366,29 @@ function isHostTranscriptCursorReason(value) {
2335
2366
  return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2336
2367
  }
2337
2368
  var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2369
+ var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
2370
+ function transcriptScanFailureKind(error, adapterFailureKind) {
2371
+ if (transcriptErrorCode(error) === "ELOOP") return "symlink";
2372
+ const adapter = adapterFailureKind(error);
2373
+ if (adapter !== void 0) return adapter;
2374
+ const code2 = transcriptErrorCode(error);
2375
+ if (code2 === "EFBIG") return "size_budget";
2376
+ if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
2377
+ return "other";
2378
+ }
2379
+ async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
2380
+ try {
2381
+ await register();
2382
+ diagnostic("current_prompt_registered");
2383
+ return true;
2384
+ } catch (error) {
2385
+ if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
2386
+ throw error;
2387
+ }
2388
+ }
2389
+ function transcriptErrorCode(error) {
2390
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2391
+ }
2338
2392
  var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2339
2393
  async function readTranscriptCompleteChunk(handle, offset, size) {
2340
2394
  const available = Math.max(0, size - offset);
@@ -2443,10 +2497,15 @@ var HostTranscriptWatcher = class {
2443
2497
  return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2444
2498
  }
2445
2499
  async scanRegistration(registration) {
2446
- return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2447
- missing: () => this.retireMissing(registration),
2448
- failed: (handle, error) => this.scanFailed(handle, registration, error)
2449
- });
2500
+ try {
2501
+ return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2502
+ missing: () => this.retireMissing(registration),
2503
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2504
+ });
2505
+ } catch (error) {
2506
+ if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
2507
+ throw error;
2508
+ }
2450
2509
  }
2451
2510
  async openTranscript(registration, scan, handlers) {
2452
2511
  let handle;
@@ -2455,7 +2514,7 @@ var HostTranscriptWatcher = class {
2455
2514
  return await scan(handle);
2456
2515
  } catch (error) {
2457
2516
  if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2458
- if (hostRelayExchangeFailure(error) !== void 0) throw error;
2517
+ if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
2459
2518
  return handlers.failed(handle, error);
2460
2519
  } finally {
2461
2520
  await handle?.close();
@@ -3095,6 +3154,7 @@ var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
3095
3154
  var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3096
3155
  store;
3097
3156
  registerCurrentPrompt;
3157
+ releaseCurrentPrompt;
3098
3158
  diagnostic;
3099
3159
  constructor(options) {
3100
3160
  super(options, {
@@ -3104,6 +3164,7 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3104
3164
  });
3105
3165
  this.store = new ClaudeOutboxStore(options.outbox_dir);
3106
3166
  this.registerCurrentPrompt = options.register_current_prompt;
3167
+ this.releaseCurrentPrompt = options.release_current_prompt;
3107
3168
  this.diagnostic = options.diagnostic ?? (() => void 0);
3108
3169
  }
3109
3170
  transcriptPathOf(registration) {
@@ -3116,19 +3177,23 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3116
3177
  const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3117
3178
  const cursor = await this.preflight(registration, handle, size, device, inode);
3118
3179
  if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
3119
- if (cursor.status === "quarantined") return { records: 0, enqueued: 0, quarantined: true };
3180
+ if (cursor.status === "quarantined") {
3181
+ if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
3182
+ return { records: 0, enqueued: 0, quarantined: true };
3183
+ }
3120
3184
  const draft = mutableCursor(cursor);
3121
3185
  await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
3122
3186
  return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
3123
3187
  }
3124
3188
  async scanFailed(handle, registration, error) {
3125
- this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
3189
+ this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
3126
3190
  if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
3127
3191
  if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
3128
3192
  await this.quarantineFailure(registration, handle);
3129
3193
  return { records: 0, enqueued: 0, quarantined: true };
3130
3194
  }
3131
3195
  async retireMissing(registration) {
3196
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
3132
3197
  return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
3133
3198
  }
3134
3199
  initialCursor(registration) {
@@ -3175,10 +3240,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3175
3240
  if (isUserTurnLine(record6)) await this.openTurn(registration, draft, record6);
3176
3241
  else if (isAssistantUsageLine(record6)) this.accumulateUsage(draft, toUsage(record6.uuid, record6.timestamp, record6.message.usage));
3177
3242
  else if (isAssistantLine(record6) && record6.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3243
+ if (isAssistantTerminalLine(record6)) await this.releaseOpenTurn(registration, draft);
3178
3244
  }
3179
3245
  }
3180
3246
  async openTurn(registration, draft, record6) {
3181
- this.diagnostic("watcher_turn_observed");
3182
3247
  await this.closeTurn(registration, draft, record6.timestamp);
3183
3248
  const sessionRef = registration.host_session_ref;
3184
3249
  const turnRef = claudeLocalTurnRef(registration.claude_session_id, record6.uuid);
@@ -3194,23 +3259,40 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3194
3259
  if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record6.timestamp, value: session.value });
3195
3260
  draft.session_observed = true;
3196
3261
  }
3197
- await this.store.putEnvelope({
3198
- schema_version: "ceal.claude_observation_outbox.v1",
3199
- identity,
3200
- host_input_ref: inputRef,
3201
- input_kind: "human",
3202
- prompt_ref: promptRef,
3203
- observations,
3204
- origin_input: true,
3205
- submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3206
- created_at: record6.timestamp,
3207
- expires_at: this.expire(this.outboxRetentionMs)
3208
- });
3209
- draft.enqueued += 1;
3210
- await this.registerPrompt(sessionRef, turnRef, promptRef, record6.message.content);
3262
+ await this.persistOpenedTurnContext(registration, draft, record6, inputRef, turnRef, promptRef, identity, observations);
3263
+ }
3264
+ async persistOpenedTurnContext(registration, draft, record6, inputRef, turnRef, promptRef, identity, observations) {
3265
+ const sessionRef = registration.host_session_ref;
3266
+ const expiresAt = this.expire(this.outboxRetentionMs);
3267
+ const custodyRegistered = await this.registerPrompt(sessionRef, turnRef, promptRef, record6.message.content, expiresAt);
3268
+ if (custodyRegistered) this.diagnostic("watcher_turn_observed");
3269
+ try {
3270
+ await this.store.putEnvelope({
3271
+ schema_version: "ceal.claude_observation_outbox.v1",
3272
+ identity,
3273
+ host_input_ref: inputRef,
3274
+ input_kind: "human",
3275
+ prompt_ref: promptRef,
3276
+ observations,
3277
+ origin_input: true,
3278
+ submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3279
+ created_at: record6.timestamp,
3280
+ expires_at: expiresAt
3281
+ });
3282
+ draft.enqueued += 1;
3283
+ await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
3284
+ this.setOpenedTurn(draft, record6, inputRef, turnRef, promptRef);
3285
+ } catch (error) {
3286
+ if (custodyRegistered) await this.releasePrompt(sessionRef, { turn_key: turnRef, prompt_ref: promptRef }).catch(() => void 0);
3287
+ throw error;
3288
+ }
3289
+ }
3290
+ async activateOpenedTurnContext(identity, promptRef, expiresAt) {
3211
3291
  const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3212
3292
  const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3213
- await this.activateContext(identity, promptRef, turnGenerationRef);
3293
+ await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
3294
+ }
3295
+ setOpenedTurn(draft, record6, inputRef, turnRef, promptRef) {
3214
3296
  const folded = foldUnattributed(draft.unattributed);
3215
3297
  draft.open_turn = {
3216
3298
  turn_key: turnRef,
@@ -3229,9 +3311,18 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3229
3311
  draft.open_turn = void 0;
3230
3312
  if (turn === void 0) return;
3231
3313
  const digest2 = terminalDigest(turn);
3232
- if (digest2 === turn.last_terminal_digest) return;
3233
- await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3234
- draft.enqueued += 1;
3314
+ if (digest2 !== turn.last_terminal_digest) {
3315
+ await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3316
+ draft.enqueued += 1;
3317
+ }
3318
+ await this.releasePrompt(registration.host_session_ref, turn);
3319
+ }
3320
+ async releaseOpenTurn(registration, draft) {
3321
+ if (draft.open_turn !== void 0) await this.releasePrompt(registration.host_session_ref, draft.open_turn);
3322
+ }
3323
+ async releasePrompt(sessionRef, turn) {
3324
+ if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
3325
+ 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 });
3235
3326
  }
3236
3327
  accumulateUsage(draft, usage) {
3237
3328
  const turn = draft.open_turn;
@@ -3265,6 +3356,8 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3265
3356
  if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
3266
3357
  throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
3267
3358
  }
3359
+ const listed = new Set(names);
3360
+ for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
3268
3361
  if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
3269
3362
  this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
3270
3363
  names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
@@ -3379,27 +3472,33 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3379
3472
  updated_at: this.stamp()
3380
3473
  });
3381
3474
  }
3382
- async registerPrompt(sessionRef, turnRef, promptRef, prompt) {
3383
- if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
3384
- try {
3385
- await this.registerCurrentPrompt({
3475
+ async registerPrompt(sessionRef, turnRef, promptRef, prompt, expiresAt) {
3476
+ if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
3477
+ const register = this.registerCurrentPrompt;
3478
+ const installationRef = this.producerInstallationRef;
3479
+ return registerHostCurrentPromptDiagnosed(
3480
+ () => register({
3386
3481
  producer_id: "claude",
3387
- producer_installation_ref: this.producerInstallationRef,
3482
+ producer_installation_ref: installationRef,
3388
3483
  local_session_ref: sessionRef,
3389
3484
  local_turn_ref: turnRef,
3390
3485
  prompt_ref: promptRef,
3391
3486
  current_prompt: prompt,
3392
- context_expires_at: this.expire(this.outboxRetentionMs)
3393
- });
3394
- this.diagnostic("current_prompt_registered");
3395
- } catch (error) {
3396
- this.diagnostic("current_prompt_registration_failed");
3397
- throw error;
3398
- }
3487
+ context_expires_at: expiresAt
3488
+ }),
3489
+ (event) => this.diagnostic(event)
3490
+ );
3491
+ }
3492
+ async releaseStoredPromptBestEffort(sessionRef, cursor) {
3493
+ await this.releaseOpenTurnBestEffort(sessionRef, cursor?.open_turn);
3399
3494
  }
3400
- async activateContext(identity, promptRef, turnGenerationRef) {
3495
+ async releaseOpenTurnBestEffort(sessionRef, turn) {
3496
+ if (turn === void 0) return;
3497
+ await this.releasePrompt(sessionRef, turn).catch(() => void 0);
3498
+ }
3499
+ async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
3401
3500
  try {
3402
- await this.store.activateInvocationContext(identity, promptRef, this.expire(this.outboxRetentionMs), void 0, turnGenerationRef);
3501
+ await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
3403
3502
  this.diagnostic("activation_recorded");
3404
3503
  } catch (error) {
3405
3504
  this.diagnostic("activation_failed");
@@ -3407,6 +3506,7 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3407
3506
  }
3408
3507
  }
3409
3508
  async quarantineStored(cursor, device, inode, reason) {
3509
+ await this.releaseStoredPromptBestEffort(cursor.host_session_ref, cursor);
3410
3510
  await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
3411
3511
  return null;
3412
3512
  }
@@ -3523,21 +3623,12 @@ function isUserTurnLine(record6) {
3523
3623
  if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
3524
3624
  return true;
3525
3625
  }
3526
- function claudeTranscriptScanFailureKind(error) {
3527
- if (isSymlinkTranscript(error)) return "symlink";
3528
- if (error instanceof ClaudeHostError) {
3529
- if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
3530
- if (error.code === "payload_rejected") return "payload_rejected";
3531
- if (error.code === "storage") return "read_error";
3532
- }
3533
- const code2 = transcriptErrorCode(error);
3534
- if (code2 === "EFBIG") return "size_budget";
3535
- if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
3536
- return "other";
3537
- }
3538
3626
  function isAssistantLine(record6) {
3539
3627
  return isTranscriptRecord(record6) && record6.type === "assistant" && isTranscriptRecord(record6.message);
3540
3628
  }
3629
+ function isAssistantTerminalLine(record6) {
3630
+ return isAssistantLine(record6) && (record6.message.stop_reason === "end_turn" || record6.message.stop_reason === "stop_sequence");
3631
+ }
3541
3632
  function isAssistantUsageLine(record6) {
3542
3633
  if (!isTranscriptRecord(record6)) return false;
3543
3634
  if (!isAssistantLine(record6) || record6.message.usage === void 0) return false;
@@ -3565,17 +3656,29 @@ async function ownedClaudeTranscriptFile(handle) {
3565
3656
  function isNotDirectory(error) {
3566
3657
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
3567
3658
  }
3659
+ function claudeTranscriptScanCause(handle, error) {
3660
+ if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
3661
+ const code2 = transcriptErrorCode(error);
3662
+ if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
3663
+ return `${handle === void 0 ? "open" : "scan"}:unknown`;
3664
+ }
3665
+ function claudeTranscriptScanFailureKind(error) {
3666
+ return transcriptScanFailureKind(error, (candidate) => {
3667
+ if (!(candidate instanceof ClaudeHostError)) return void 0;
3668
+ if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
3669
+ if (candidate.code === "payload_rejected") return "payload_rejected";
3670
+ if (candidate.code === "storage") return "read_error";
3671
+ return void 0;
3672
+ });
3673
+ }
3568
3674
  function isSymlinkTranscript(error) {
3569
3675
  return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
3570
3676
  }
3571
- function transcriptErrorCode(error) {
3572
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
3573
- }
3574
3677
 
3575
3678
  // host/claude/src/claude-adapter.ts
3576
3679
  import { createHash as createHash4, randomUUID } from "node:crypto";
3577
3680
  import { O_NOFOLLOW as O_NOFOLLOW3, O_RDONLY as O_RDONLY4 } from "node:constants";
3578
- import { open as open4 } from "node:fs/promises";
3681
+ import { open as open4, readdir as readdir3 } from "node:fs/promises";
3579
3682
  import { homedir } from "node:os";
3580
3683
  import { isAbsolute as isAbsolute2, join as join5, relative, resolve } from "node:path";
3581
3684
  var ClaudeHookAdapter = class {
@@ -3743,11 +3846,43 @@ async function makeClaudeBaselineCursor(handle, registration, now, file, offset)
3743
3846
  offset,
3744
3847
  anchor_start: anchorStart,
3745
3848
  anchor_sha256: createHash4("sha256").update(anchor).digest("hex"),
3746
- subagents: {},
3849
+ subagents: await readClaudeSubagentBaseline(registration.transcript_path),
3747
3850
  status: "active",
3748
3851
  updated_at: timestamp4(now)
3749
3852
  };
3750
3853
  }
3854
+ async function readClaudeSubagentBaseline(transcriptPath) {
3855
+ const directory = subagentsDirectory(transcriptPath);
3856
+ let names;
3857
+ try {
3858
+ names = (await readdir3(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
3859
+ } catch {
3860
+ return {};
3861
+ }
3862
+ const cursors = {};
3863
+ for (const name of names) {
3864
+ const seed = await readClaudeSubagentSeed(directory, name).catch(() => void 0);
3865
+ if (seed !== void 0) cursors[name] = seed;
3866
+ }
3867
+ return cursors;
3868
+ }
3869
+ async function readClaudeSubagentSeed(directory, name) {
3870
+ let handle;
3871
+ try {
3872
+ handle = await open4(join5(directory, name), O_RDONLY4 | O_NOFOLLOW3);
3873
+ const stats = await handle.stat();
3874
+ if (!stats.isFile()) throw new ClaudeHostError("storage", "Claude subagent baseline source is not a regular file.");
3875
+ return {
3876
+ device: String(stats.dev),
3877
+ inode: String(stats.ino),
3878
+ offset: stats.size,
3879
+ anchor_start: 0,
3880
+ anchor_sha256: createHash4("sha256").update(Buffer.alloc(0)).digest("hex")
3881
+ };
3882
+ } finally {
3883
+ await handle?.close();
3884
+ }
3885
+ }
3751
3886
  async function readLastClaudeTranscriptLine(handle, size) {
3752
3887
  if (size === 0) return null;
3753
3888
  const last = Buffer.alloc(1);
@@ -3925,17 +4060,6 @@ function isRecord3(value) {
3925
4060
  return value !== null && typeof value === "object" && !Array.isArray(value);
3926
4061
  }
3927
4062
 
3928
- // host/service-log.ts
3929
- function writeHostServiceDiagnostic(producer_id, event, failure_kind) {
3930
- try {
3931
- 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 } : {} })}
3932
- `);
3933
- } catch {
3934
- }
3935
- }
3936
- var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
3937
- var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
3938
-
3939
4063
  // host/claude/src/claude-producer-runtime.ts
3940
4064
  async function drainClaudeProducerOnce(options) {
3941
4065
  const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
@@ -3943,9 +4067,10 @@ async function drainClaudeProducerOnce(options) {
3943
4067
  outbox_dir: options.outbox_dir,
3944
4068
  producer_installation_ref: options.producer_installation_ref,
3945
4069
  register_current_prompt: (input) => promptPort.register(input),
3946
- diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("claude", event, failure_kind)
4070
+ release_current_prompt: (identity) => promptPort.release(identity),
4071
+ diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
3947
4072
  }).scanOnce().catch((error) => {
3948
- writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
4073
+ writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
3949
4074
  throw error;
3950
4075
  });
3951
4076
  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 } });
@@ -4199,11 +4324,11 @@ function claudeHookEventFromArgs(args) {
4199
4324
  async function runClaudeHook(event, stdin, options) {
4200
4325
  try {
4201
4326
  const result = await runClaudeHookFromStdin(event, stdin, options);
4202
- if (result.disposition === "rejected") writeHostServiceDiagnostic("claude", "hook_failed");
4203
- else if (result.reason === "transcript_registered") writeHostServiceDiagnostic("claude", "registration_written");
4327
+ if (result.disposition === "rejected") writeHostServiceDiagnostic("claude", "hook_failed", void 0, void 0, options.diagnostic_sink);
4328
+ else if (result.reason === "transcript_registered") writeHostServiceDiagnostic("claude", "registration_written", void 0, void 0, options.diagnostic_sink);
4204
4329
  return 0;
4205
4330
  } catch {
4206
- writeHostServiceDiagnostic("claude", "hook_failed");
4331
+ writeHostServiceDiagnostic("claude", "hook_failed", void 0, void 0, options.diagnostic_sink);
4207
4332
  return 0;
4208
4333
  }
4209
4334
  }