@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
|
@@ -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;
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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")
|
|
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.
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
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
|
|
3585
|
-
|
|
3586
|
-
|
|
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
|
-
|
|
3737
|
-
|
|
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:
|
|
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:
|
|
3745
|
-
})
|
|
3746
|
-
this.diagnostic(
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
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,
|
|
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
|
-
|
|
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 } });
|