@corca-org/ceal-host-win32-x64 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.exe +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
package/bin/ceal-host.exe
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;
|
|
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
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
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")
|
|
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.
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
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
|
|
3233
|
-
|
|
3234
|
-
|
|
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
|
-
|
|
3385
|
-
|
|
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:
|
|
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:
|
|
3393
|
-
})
|
|
3394
|
-
this.diagnostic(
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
}
|