@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 +0 -0
- package/libexec/ceal-host/claude-hook.mjs +200 -75
- package/libexec/ceal-host/claude-invocation-authentication.mjs +174 -81
- package/libexec/ceal-host/claude-producer.mjs +212 -119
- package/libexec/ceal-host/codex-hook.mjs +216 -25
- package/libexec/ceal-host/codex-invocation-authentication.mjs +207 -46
- package/libexec/ceal-host/codex-producer.mjs +259 -90
- package/libexec/ceal-host/control.mjs +764 -212
- package/libexec/ceal-host/relay.mjs +92 -14
- package/package.json +1 -1
- package/share/ceal-host/bundle-manifest.json +12 -12
- package/share/ceal-host/bundle-manifest.sigstore.json +1 -1
|
@@ -979,6 +979,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
|
|
|
979
979
|
var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
|
|
980
980
|
|
|
981
981
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
982
|
+
var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
|
|
982
983
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
983
984
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
984
985
|
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"]);
|
|
@@ -1062,6 +1063,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1062
1063
|
next: { required: true, timestamp: false },
|
|
1063
1064
|
kind: { required: true, timestamp: false },
|
|
1064
1065
|
matched_kinds: { required: false, timestamp: false },
|
|
1066
|
+
title_match: { required: false, timestamp: false },
|
|
1065
1067
|
title: { required: true, timestamp: false },
|
|
1066
1068
|
container_label: { required: true, timestamp: false },
|
|
1067
1069
|
container_ref: { required: false, timestamp: false },
|
|
@@ -1548,7 +1550,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1548
1550
|
invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
|
|
1549
1551
|
options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
|
|
1550
1552
|
session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1551
|
-
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;
|
|
1553
|
+
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)" }),
|
|
1552
1554
|
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
|
|
1553
1555
|
no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
|
|
1554
1556
|
context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
|
|
@@ -1601,6 +1603,7 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
|
1601
1603
|
"watcher_turn_observed",
|
|
1602
1604
|
"current_prompt_registered",
|
|
1603
1605
|
"current_prompt_registration_failed",
|
|
1606
|
+
"current_prompt_capacity_exhausted",
|
|
1604
1607
|
"activation_recorded",
|
|
1605
1608
|
"activation_failed",
|
|
1606
1609
|
"watcher_scan_failed",
|
|
@@ -2056,6 +2059,21 @@ var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
|
2056
2059
|
// host/relay/src/current-prompt-custody.ts
|
|
2057
2060
|
var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
|
|
2058
2061
|
|
|
2062
|
+
// host/service-log.ts
|
|
2063
|
+
function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
|
|
2064
|
+
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 } : {} })}
|
|
2065
|
+
`;
|
|
2066
|
+
try {
|
|
2067
|
+
(sink ?? defaultHostServiceDiagnosticSink)(line);
|
|
2068
|
+
} catch {
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
function defaultHostServiceDiagnosticSink(line) {
|
|
2072
|
+
process.stderr.write(line);
|
|
2073
|
+
}
|
|
2074
|
+
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
2075
|
+
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
2076
|
+
|
|
2059
2077
|
// host/relay/src/relay-ipc.ts
|
|
2060
2078
|
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
2061
2079
|
"schema_version",
|
|
@@ -2076,6 +2094,7 @@ var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
|
2076
2094
|
var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v1";
|
|
2077
2095
|
var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v1";
|
|
2078
2096
|
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
2097
|
+
var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
|
|
2079
2098
|
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
2080
2099
|
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2081
2100
|
var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
|
|
@@ -2135,6 +2154,12 @@ var HostRelayIpcError = class extends Error {
|
|
|
2135
2154
|
this.failure = failure2;
|
|
2136
2155
|
}
|
|
2137
2156
|
};
|
|
2157
|
+
var HostRelayCurrentPromptCapacityRefusal = class extends Error {
|
|
2158
|
+
constructor() {
|
|
2159
|
+
super("current_prompt_capacity_exhausted");
|
|
2160
|
+
this.name = "HostRelayCurrentPromptCapacityRefusal";
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2138
2163
|
function hostRelayExchangeFailure(error) {
|
|
2139
2164
|
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
2140
2165
|
}
|
|
@@ -2183,8 +2208,14 @@ var HostRelayCurrentPromptPort = class {
|
|
|
2183
2208
|
async register(input) {
|
|
2184
2209
|
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
|
|
2185
2210
|
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2211
|
+
if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
|
|
2186
2212
|
if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
|
|
2187
2213
|
}
|
|
2214
|
+
async release(identity) {
|
|
2215
|
+
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
|
|
2216
|
+
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2217
|
+
if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
|
|
2218
|
+
}
|
|
2188
2219
|
};
|
|
2189
2220
|
function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
|
|
2190
2221
|
return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
|
|
@@ -2276,7 +2307,7 @@ function decodeInvocationContextResponseBuffer(buffer) {
|
|
|
2276
2307
|
function decodeCurrentPromptResponseBuffer(buffer) {
|
|
2277
2308
|
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
|
|
2278
2309
|
if (value === null) return null;
|
|
2279
|
-
if (!record2(value) || !exactKeys3(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "current_prompt_unavailable") throw new HostRelayIpcError("relay_unavailable");
|
|
2310
|
+
if (!record2(value) || !exactKeys3(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "capacity_exhausted" && value.status !== "current_prompt_unavailable" && value.status !== "released") throw new HostRelayIpcError("relay_unavailable", "Host Relay current-prompt response is invalid.", "response_invalid");
|
|
2280
2311
|
return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
|
|
2281
2312
|
}
|
|
2282
2313
|
function encodeInvocationContextQuery(query) {
|
|
@@ -2341,6 +2372,29 @@ function isHostTranscriptCursorReason(value) {
|
|
|
2341
2372
|
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2342
2373
|
}
|
|
2343
2374
|
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2375
|
+
var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
|
|
2376
|
+
function transcriptScanFailureKind(error, adapterFailureKind) {
|
|
2377
|
+
if (transcriptErrorCode(error) === "ELOOP") return "symlink";
|
|
2378
|
+
const adapter = adapterFailureKind(error);
|
|
2379
|
+
if (adapter !== void 0) return adapter;
|
|
2380
|
+
const code2 = transcriptErrorCode(error);
|
|
2381
|
+
if (code2 === "EFBIG") return "size_budget";
|
|
2382
|
+
if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
|
|
2383
|
+
return "other";
|
|
2384
|
+
}
|
|
2385
|
+
async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
|
|
2386
|
+
try {
|
|
2387
|
+
await register();
|
|
2388
|
+
diagnostic("current_prompt_registered");
|
|
2389
|
+
return true;
|
|
2390
|
+
} catch (error) {
|
|
2391
|
+
if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
|
|
2392
|
+
throw error;
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
function transcriptErrorCode(error) {
|
|
2396
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
2397
|
+
}
|
|
2344
2398
|
var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
|
|
2345
2399
|
async function readTranscriptCompleteChunk(handle, offset, size) {
|
|
2346
2400
|
const available = Math.max(0, size - offset);
|
|
@@ -2449,10 +2503,15 @@ var HostTranscriptWatcher = class {
|
|
|
2449
2503
|
return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
|
|
2450
2504
|
}
|
|
2451
2505
|
async scanRegistration(registration) {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2506
|
+
try {
|
|
2507
|
+
return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
|
|
2508
|
+
missing: () => this.retireMissing(registration),
|
|
2509
|
+
failed: (handle, error) => this.scanFailed(handle, registration, error)
|
|
2510
|
+
});
|
|
2511
|
+
} catch (error) {
|
|
2512
|
+
if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
|
|
2513
|
+
throw error;
|
|
2514
|
+
}
|
|
2456
2515
|
}
|
|
2457
2516
|
async openTranscript(registration, scan, handlers) {
|
|
2458
2517
|
let handle;
|
|
@@ -2461,7 +2520,7 @@ var HostTranscriptWatcher = class {
|
|
|
2461
2520
|
return await scan(handle);
|
|
2462
2521
|
} catch (error) {
|
|
2463
2522
|
if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
|
|
2464
|
-
if (hostRelayExchangeFailure(error) !== void 0) throw error;
|
|
2523
|
+
if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
|
|
2465
2524
|
return handlers.failed(handle, error);
|
|
2466
2525
|
} finally {
|
|
2467
2526
|
await handle?.close();
|
|
@@ -2662,17 +2721,6 @@ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, produ
|
|
|
2662
2721
|
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2663
2722
|
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2664
2723
|
|
|
2665
|
-
// host/service-log.ts
|
|
2666
|
-
function writeHostServiceDiagnostic(producer_id, event, failure_kind) {
|
|
2667
|
-
try {
|
|
2668
|
-
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 } : {} })}
|
|
2669
|
-
`);
|
|
2670
|
-
} catch {
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
2674
|
-
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
2675
|
-
|
|
2676
2724
|
// host/invocation-authentication-command.ts
|
|
2677
2725
|
var HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX = ["authenticate", "--client-nonce"];
|
|
2678
2726
|
var HOST_AUTHENTICATION_BUDGET_ENVIRONMENT = "CEAL_HOST_AUTHENTICATION_BUDGET_MS";
|
|
@@ -2769,36 +2817,36 @@ async function currentInvocation(descriptor, config, now) {
|
|
|
2769
2817
|
let waitEngaged = false;
|
|
2770
2818
|
while (true) {
|
|
2771
2819
|
if (!firstCheck && monotonicNow() >= deadline) {
|
|
2772
|
-
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs);
|
|
2820
|
+
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
|
|
2773
2821
|
throw new TypeError("context_unavailable");
|
|
2774
2822
|
}
|
|
2775
2823
|
firstCheck = false;
|
|
2776
2824
|
const observedAt = now();
|
|
2777
2825
|
const current = await readLiveSessionContext(store, identity.identity.host_source_ref, identity.identity.host_session_ref, observedAt);
|
|
2778
2826
|
if (current !== null) {
|
|
2779
|
-
recordActivationWaitCompletion(descriptor.producer_id, waitEngaged);
|
|
2827
|
+
recordActivationWaitCompletion(descriptor.producer_id, waitEngaged, config.diagnostic_sink);
|
|
2780
2828
|
return { store, current };
|
|
2781
2829
|
}
|
|
2782
2830
|
const remainingMs = deadline - monotonicNow();
|
|
2783
2831
|
if (remainingMs <= 0) {
|
|
2784
|
-
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs);
|
|
2832
|
+
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
|
|
2785
2833
|
throw new TypeError("context_unavailable");
|
|
2786
2834
|
}
|
|
2787
|
-
recordActivationWaitEngagement(descriptor.producer_id, waitEngaged);
|
|
2835
|
+
recordActivationWaitEngagement(descriptor.producer_id, waitEngaged, config.diagnostic_sink);
|
|
2788
2836
|
waitEngaged = true;
|
|
2789
2837
|
const intervalMs = Math.min(INVOCATION_CONTEXT_POLL_INTERVAL_MS, remainingMs);
|
|
2790
2838
|
await (config.wait ?? waitForContextActivation)(intervalMs);
|
|
2791
2839
|
}
|
|
2792
2840
|
}
|
|
2793
|
-
function recordActivationWaitExhaustion(producer, engaged, budgetMs) {
|
|
2794
|
-
if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted");
|
|
2795
|
-
else writeHostServiceDiagnostic(producer, "activation_wait_skipped");
|
|
2841
|
+
function recordActivationWaitExhaustion(producer, engaged, budgetMs, sink) {
|
|
2842
|
+
if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted", void 0, void 0, sink);
|
|
2843
|
+
else writeHostServiceDiagnostic(producer, "activation_wait_skipped", void 0, void 0, sink);
|
|
2796
2844
|
}
|
|
2797
|
-
function recordActivationWaitCompletion(producer, engaged) {
|
|
2798
|
-
if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed");
|
|
2845
|
+
function recordActivationWaitCompletion(producer, engaged, sink) {
|
|
2846
|
+
if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed", void 0, void 0, sink);
|
|
2799
2847
|
}
|
|
2800
|
-
function recordActivationWaitEngagement(producer, engaged) {
|
|
2801
|
-
if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged");
|
|
2848
|
+
function recordActivationWaitEngagement(producer, engaged, sink) {
|
|
2849
|
+
if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged", void 0, void 0, sink);
|
|
2802
2850
|
}
|
|
2803
2851
|
async function readLiveSessionContext(store, hostSourceRef, hostSessionRef, now) {
|
|
2804
2852
|
const current = await store.readInvocationContext(hostSourceRef, hostSessionRef, now);
|
|
@@ -3734,6 +3782,7 @@ var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
|
|
|
3734
3782
|
var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
3735
3783
|
store;
|
|
3736
3784
|
registerCurrentPrompt;
|
|
3785
|
+
releaseCurrentPrompt;
|
|
3737
3786
|
diagnostic;
|
|
3738
3787
|
constructor(options) {
|
|
3739
3788
|
super(options, {
|
|
@@ -3743,6 +3792,7 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3743
3792
|
});
|
|
3744
3793
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3745
3794
|
this.registerCurrentPrompt = options.register_current_prompt;
|
|
3795
|
+
this.releaseCurrentPrompt = options.release_current_prompt;
|
|
3746
3796
|
this.diagnostic = options.diagnostic ?? (() => void 0);
|
|
3747
3797
|
}
|
|
3748
3798
|
transcriptPathOf(registration) {
|
|
@@ -3755,19 +3805,23 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3755
3805
|
const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
|
|
3756
3806
|
const cursor = await this.preflight(registration, handle, size, device, inode);
|
|
3757
3807
|
if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
|
|
3758
|
-
if (cursor.status === "quarantined")
|
|
3808
|
+
if (cursor.status === "quarantined") {
|
|
3809
|
+
if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
|
|
3810
|
+
return { records: 0, enqueued: 0, quarantined: true };
|
|
3811
|
+
}
|
|
3759
3812
|
const draft = mutableCursor(cursor);
|
|
3760
3813
|
await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
|
|
3761
3814
|
return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
|
|
3762
3815
|
}
|
|
3763
3816
|
async scanFailed(handle, registration, error) {
|
|
3764
|
-
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
3817
|
+
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
|
|
3765
3818
|
if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
|
|
3766
3819
|
if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
|
|
3767
3820
|
await this.quarantineFailure(registration, handle);
|
|
3768
3821
|
return { records: 0, enqueued: 0, quarantined: true };
|
|
3769
3822
|
}
|
|
3770
3823
|
async retireMissing(registration) {
|
|
3824
|
+
await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
|
|
3771
3825
|
return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
|
|
3772
3826
|
}
|
|
3773
3827
|
initialCursor(registration) {
|
|
@@ -3814,10 +3868,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3814
3868
|
if (isUserTurnLine(record5)) await this.openTurn(registration, draft, record5);
|
|
3815
3869
|
else if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
|
|
3816
3870
|
else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
|
|
3871
|
+
if (isAssistantTerminalLine(record5)) await this.releaseOpenTurn(registration, draft);
|
|
3817
3872
|
}
|
|
3818
3873
|
}
|
|
3819
3874
|
async openTurn(registration, draft, record5) {
|
|
3820
|
-
this.diagnostic("watcher_turn_observed");
|
|
3821
3875
|
await this.closeTurn(registration, draft, record5.timestamp);
|
|
3822
3876
|
const sessionRef = registration.host_session_ref;
|
|
3823
3877
|
const turnRef = claudeLocalTurnRef(registration.claude_session_id, record5.uuid);
|
|
@@ -3833,23 +3887,40 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3833
3887
|
if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record5.timestamp, value: session.value });
|
|
3834
3888
|
draft.session_observed = true;
|
|
3835
3889
|
}
|
|
3836
|
-
await this.
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3890
|
+
await this.persistOpenedTurnContext(registration, draft, record5, inputRef, turnRef, promptRef, identity, observations);
|
|
3891
|
+
}
|
|
3892
|
+
async persistOpenedTurnContext(registration, draft, record5, inputRef, turnRef, promptRef, identity, observations) {
|
|
3893
|
+
const sessionRef = registration.host_session_ref;
|
|
3894
|
+
const expiresAt = this.expire(this.outboxRetentionMs);
|
|
3895
|
+
const custodyRegistered = await this.registerPrompt(sessionRef, turnRef, promptRef, record5.message.content, expiresAt);
|
|
3896
|
+
if (custodyRegistered) this.diagnostic("watcher_turn_observed");
|
|
3897
|
+
try {
|
|
3898
|
+
await this.store.putEnvelope({
|
|
3899
|
+
schema_version: "ceal.claude_observation_outbox.v1",
|
|
3900
|
+
identity,
|
|
3901
|
+
host_input_ref: inputRef,
|
|
3902
|
+
input_kind: "human",
|
|
3903
|
+
prompt_ref: promptRef,
|
|
3904
|
+
observations,
|
|
3905
|
+
origin_input: true,
|
|
3906
|
+
submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
|
|
3907
|
+
created_at: record5.timestamp,
|
|
3908
|
+
expires_at: expiresAt
|
|
3909
|
+
});
|
|
3910
|
+
draft.enqueued += 1;
|
|
3911
|
+
await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
|
|
3912
|
+
this.setOpenedTurn(draft, record5, inputRef, turnRef, promptRef);
|
|
3913
|
+
} catch (error) {
|
|
3914
|
+
if (custodyRegistered) await this.releasePrompt(sessionRef, { turn_key: turnRef, prompt_ref: promptRef }).catch(() => void 0);
|
|
3915
|
+
throw error;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
async activateOpenedTurnContext(identity, promptRef, expiresAt) {
|
|
3850
3919
|
const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
3851
3920
|
const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
|
|
3852
|
-
await this.activateContext(identity, promptRef, turnGenerationRef);
|
|
3921
|
+
await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
|
|
3922
|
+
}
|
|
3923
|
+
setOpenedTurn(draft, record5, inputRef, turnRef, promptRef) {
|
|
3853
3924
|
const folded = foldUnattributed(draft.unattributed);
|
|
3854
3925
|
draft.open_turn = {
|
|
3855
3926
|
turn_key: turnRef,
|
|
@@ -3868,9 +3939,18 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3868
3939
|
draft.open_turn = void 0;
|
|
3869
3940
|
if (turn === void 0) return;
|
|
3870
3941
|
const digest2 = terminalDigest(turn);
|
|
3871
|
-
if (digest2
|
|
3872
|
-
|
|
3873
|
-
|
|
3942
|
+
if (digest2 !== turn.last_terminal_digest) {
|
|
3943
|
+
await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
|
|
3944
|
+
draft.enqueued += 1;
|
|
3945
|
+
}
|
|
3946
|
+
await this.releasePrompt(registration.host_session_ref, turn);
|
|
3947
|
+
}
|
|
3948
|
+
async releaseOpenTurn(registration, draft) {
|
|
3949
|
+
if (draft.open_turn !== void 0) await this.releasePrompt(registration.host_session_ref, draft.open_turn);
|
|
3950
|
+
}
|
|
3951
|
+
async releasePrompt(sessionRef, turn) {
|
|
3952
|
+
if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
|
|
3953
|
+
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 });
|
|
3874
3954
|
}
|
|
3875
3955
|
accumulateUsage(draft, usage) {
|
|
3876
3956
|
const turn = draft.open_turn;
|
|
@@ -3904,6 +3984,8 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3904
3984
|
if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
|
|
3905
3985
|
throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
|
|
3906
3986
|
}
|
|
3987
|
+
const listed = new Set(names);
|
|
3988
|
+
for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
|
|
3907
3989
|
if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
|
|
3908
3990
|
this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
|
|
3909
3991
|
names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
|
|
@@ -4018,27 +4100,33 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4018
4100
|
updated_at: this.stamp()
|
|
4019
4101
|
});
|
|
4020
4102
|
}
|
|
4021
|
-
async registerPrompt(sessionRef, turnRef, promptRef, prompt) {
|
|
4022
|
-
if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
|
|
4023
|
-
|
|
4024
|
-
|
|
4103
|
+
async registerPrompt(sessionRef, turnRef, promptRef, prompt, expiresAt) {
|
|
4104
|
+
if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
|
|
4105
|
+
const register = this.registerCurrentPrompt;
|
|
4106
|
+
const installationRef = this.producerInstallationRef;
|
|
4107
|
+
return registerHostCurrentPromptDiagnosed(
|
|
4108
|
+
() => register({
|
|
4025
4109
|
producer_id: "claude",
|
|
4026
|
-
producer_installation_ref:
|
|
4110
|
+
producer_installation_ref: installationRef,
|
|
4027
4111
|
local_session_ref: sessionRef,
|
|
4028
4112
|
local_turn_ref: turnRef,
|
|
4029
4113
|
prompt_ref: promptRef,
|
|
4030
4114
|
current_prompt: prompt,
|
|
4031
|
-
context_expires_at:
|
|
4032
|
-
})
|
|
4033
|
-
this.diagnostic(
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4115
|
+
context_expires_at: expiresAt
|
|
4116
|
+
}),
|
|
4117
|
+
(event) => this.diagnostic(event)
|
|
4118
|
+
);
|
|
4119
|
+
}
|
|
4120
|
+
async releaseStoredPromptBestEffort(sessionRef, cursor) {
|
|
4121
|
+
await this.releaseOpenTurnBestEffort(sessionRef, cursor?.open_turn);
|
|
4122
|
+
}
|
|
4123
|
+
async releaseOpenTurnBestEffort(sessionRef, turn) {
|
|
4124
|
+
if (turn === void 0) return;
|
|
4125
|
+
await this.releasePrompt(sessionRef, turn).catch(() => void 0);
|
|
4038
4126
|
}
|
|
4039
|
-
async activateContext(identity, promptRef, turnGenerationRef) {
|
|
4127
|
+
async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
|
|
4040
4128
|
try {
|
|
4041
|
-
await this.store.activateInvocationContext(identity, promptRef,
|
|
4129
|
+
await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
|
|
4042
4130
|
this.diagnostic("activation_recorded");
|
|
4043
4131
|
} catch (error) {
|
|
4044
4132
|
this.diagnostic("activation_failed");
|
|
@@ -4046,6 +4134,7 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4046
4134
|
}
|
|
4047
4135
|
}
|
|
4048
4136
|
async quarantineStored(cursor, device, inode, reason) {
|
|
4137
|
+
await this.releaseStoredPromptBestEffort(cursor.host_session_ref, cursor);
|
|
4049
4138
|
await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
|
|
4050
4139
|
return null;
|
|
4051
4140
|
}
|
|
@@ -4162,21 +4251,12 @@ function isUserTurnLine(record5) {
|
|
|
4162
4251
|
if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
|
|
4163
4252
|
return true;
|
|
4164
4253
|
}
|
|
4165
|
-
function claudeTranscriptScanFailureKind(error) {
|
|
4166
|
-
if (isSymlinkTranscript(error)) return "symlink";
|
|
4167
|
-
if (error instanceof ClaudeHostError) {
|
|
4168
|
-
if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
|
|
4169
|
-
if (error.code === "payload_rejected") return "payload_rejected";
|
|
4170
|
-
if (error.code === "storage") return "read_error";
|
|
4171
|
-
}
|
|
4172
|
-
const code2 = transcriptErrorCode(error);
|
|
4173
|
-
if (code2 === "EFBIG") return "size_budget";
|
|
4174
|
-
if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
|
|
4175
|
-
return "other";
|
|
4176
|
-
}
|
|
4177
4254
|
function isAssistantLine(record5) {
|
|
4178
4255
|
return isTranscriptRecord(record5) && record5.type === "assistant" && isTranscriptRecord(record5.message);
|
|
4179
4256
|
}
|
|
4257
|
+
function isAssistantTerminalLine(record5) {
|
|
4258
|
+
return isAssistantLine(record5) && (record5.message.stop_reason === "end_turn" || record5.message.stop_reason === "stop_sequence");
|
|
4259
|
+
}
|
|
4180
4260
|
function isAssistantUsageLine(record5) {
|
|
4181
4261
|
if (!isTranscriptRecord(record5)) return false;
|
|
4182
4262
|
if (!isAssistantLine(record5) || record5.message.usage === void 0) return false;
|
|
@@ -4204,12 +4284,24 @@ async function ownedClaudeTranscriptFile(handle) {
|
|
|
4204
4284
|
function isNotDirectory(error) {
|
|
4205
4285
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
|
|
4206
4286
|
}
|
|
4287
|
+
function claudeTranscriptScanCause(handle, error) {
|
|
4288
|
+
if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
|
|
4289
|
+
const code2 = transcriptErrorCode(error);
|
|
4290
|
+
if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
|
|
4291
|
+
return `${handle === void 0 ? "open" : "scan"}:unknown`;
|
|
4292
|
+
}
|
|
4293
|
+
function claudeTranscriptScanFailureKind(error) {
|
|
4294
|
+
return transcriptScanFailureKind(error, (candidate) => {
|
|
4295
|
+
if (!(candidate instanceof ClaudeHostError)) return void 0;
|
|
4296
|
+
if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
|
|
4297
|
+
if (candidate.code === "payload_rejected") return "payload_rejected";
|
|
4298
|
+
if (candidate.code === "storage") return "read_error";
|
|
4299
|
+
return void 0;
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4207
4302
|
function isSymlinkTranscript(error) {
|
|
4208
4303
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
|
|
4209
4304
|
}
|
|
4210
|
-
function transcriptErrorCode(error) {
|
|
4211
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
4212
|
-
}
|
|
4213
4305
|
|
|
4214
4306
|
// host/claude/src/claude-producer-runtime.ts
|
|
4215
4307
|
async function drainClaudeProducerOnce(options) {
|
|
@@ -4218,9 +4310,10 @@ async function drainClaudeProducerOnce(options) {
|
|
|
4218
4310
|
outbox_dir: options.outbox_dir,
|
|
4219
4311
|
producer_installation_ref: options.producer_installation_ref,
|
|
4220
4312
|
register_current_prompt: (input) => promptPort.register(input),
|
|
4221
|
-
|
|
4313
|
+
release_current_prompt: (identity) => promptPort.release(identity),
|
|
4314
|
+
diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
|
|
4222
4315
|
}).scanOnce().catch((error) => {
|
|
4223
|
-
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
4316
|
+
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
|
|
4224
4317
|
throw error;
|
|
4225
4318
|
});
|
|
4226
4319
|
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 } });
|