@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
|
@@ -977,6 +977,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
|
|
|
977
977
|
var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
|
|
978
978
|
|
|
979
979
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
980
|
+
var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
|
|
980
981
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
981
982
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
982
983
|
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"]);
|
|
@@ -1060,6 +1061,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1060
1061
|
next: { required: true, timestamp: false },
|
|
1061
1062
|
kind: { required: true, timestamp: false },
|
|
1062
1063
|
matched_kinds: { required: false, timestamp: false },
|
|
1064
|
+
title_match: { required: false, timestamp: false },
|
|
1063
1065
|
title: { required: true, timestamp: false },
|
|
1064
1066
|
container_label: { required: true, timestamp: false },
|
|
1065
1067
|
container_ref: { required: false, timestamp: false },
|
|
@@ -1545,7 +1547,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1545
1547
|
invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
|
|
1546
1548
|
options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
|
|
1547
1549
|
session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1548
|
-
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;
|
|
1550
|
+
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)" }),
|
|
1549
1551
|
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
|
|
1550
1552
|
no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
|
|
1551
1553
|
context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
|
|
@@ -1566,6 +1568,7 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1566
1568
|
});
|
|
1567
1569
|
|
|
1568
1570
|
// packages/ceal-client-protocol/src/host-service-diagnostic.ts
|
|
1571
|
+
var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
|
|
1569
1572
|
var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
|
|
1570
1573
|
var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
1571
1574
|
"hook_invoked",
|
|
@@ -1574,6 +1577,7 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
|
1574
1577
|
"watcher_turn_observed",
|
|
1575
1578
|
"current_prompt_registered",
|
|
1576
1579
|
"current_prompt_registration_failed",
|
|
1580
|
+
"current_prompt_capacity_exhausted",
|
|
1577
1581
|
"activation_recorded",
|
|
1578
1582
|
"activation_failed",
|
|
1579
1583
|
"watcher_scan_failed",
|
|
@@ -2071,6 +2075,21 @@ var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
|
2071
2075
|
// host/relay/src/current-prompt-custody.ts
|
|
2072
2076
|
var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
|
|
2073
2077
|
|
|
2078
|
+
// host/service-log.ts
|
|
2079
|
+
function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
|
|
2080
|
+
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 } : {} })}
|
|
2081
|
+
`;
|
|
2082
|
+
try {
|
|
2083
|
+
(sink ?? defaultHostServiceDiagnosticSink)(line);
|
|
2084
|
+
} catch {
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
function defaultHostServiceDiagnosticSink(line) {
|
|
2088
|
+
process.stderr.write(line);
|
|
2089
|
+
}
|
|
2090
|
+
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
2091
|
+
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
2092
|
+
|
|
2074
2093
|
// host/relay/src/relay-ipc.ts
|
|
2075
2094
|
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
2076
2095
|
"schema_version",
|
|
@@ -2089,6 +2108,7 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
|
2089
2108
|
]);
|
|
2090
2109
|
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
2091
2110
|
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
2111
|
+
var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
|
|
2092
2112
|
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
2093
2113
|
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2094
2114
|
var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
|
|
@@ -2147,6 +2167,12 @@ var HostRelayIpcError = class extends Error {
|
|
|
2147
2167
|
this.failure = failure;
|
|
2148
2168
|
}
|
|
2149
2169
|
};
|
|
2170
|
+
var HostRelayCurrentPromptCapacityRefusal = class extends Error {
|
|
2171
|
+
constructor() {
|
|
2172
|
+
super("current_prompt_capacity_exhausted");
|
|
2173
|
+
this.name = "HostRelayCurrentPromptCapacityRefusal";
|
|
2174
|
+
}
|
|
2175
|
+
};
|
|
2150
2176
|
function hostRelayExchangeFailure(error) {
|
|
2151
2177
|
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
2152
2178
|
}
|
|
@@ -2186,8 +2212,14 @@ var HostRelayCurrentPromptPort = class {
|
|
|
2186
2212
|
async register(input) {
|
|
2187
2213
|
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
|
|
2188
2214
|
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2215
|
+
if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
|
|
2189
2216
|
if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
|
|
2190
2217
|
}
|
|
2218
|
+
async release(identity) {
|
|
2219
|
+
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
|
|
2220
|
+
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2221
|
+
if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
|
|
2222
|
+
}
|
|
2191
2223
|
};
|
|
2192
2224
|
function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
|
|
2193
2225
|
return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
|
|
@@ -2270,7 +2302,7 @@ function decodeResponseBuffer(buffer) {
|
|
|
2270
2302
|
function decodeCurrentPromptResponseBuffer(buffer) {
|
|
2271
2303
|
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
|
|
2272
2304
|
if (value === null) return null;
|
|
2273
|
-
if (!record2(value) || !exactKeys4(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");
|
|
2305
|
+
if (!record2(value) || !exactKeys4(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");
|
|
2274
2306
|
return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
|
|
2275
2307
|
}
|
|
2276
2308
|
function decodeLengthPrefixedJson(input, maximum) {
|
|
@@ -2306,6 +2338,29 @@ function isHostTranscriptCursorReason(value) {
|
|
|
2306
2338
|
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2307
2339
|
}
|
|
2308
2340
|
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2341
|
+
var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
|
|
2342
|
+
function transcriptScanFailureKind(error, adapterFailureKind) {
|
|
2343
|
+
if (transcriptErrorCode(error) === "ELOOP") return "symlink";
|
|
2344
|
+
const adapter = adapterFailureKind(error);
|
|
2345
|
+
if (adapter !== void 0) return adapter;
|
|
2346
|
+
const code = transcriptErrorCode(error);
|
|
2347
|
+
if (code === "EFBIG") return "size_budget";
|
|
2348
|
+
if (code !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code)) return "read_error";
|
|
2349
|
+
return "other";
|
|
2350
|
+
}
|
|
2351
|
+
async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
|
|
2352
|
+
try {
|
|
2353
|
+
await register();
|
|
2354
|
+
diagnostic("current_prompt_registered");
|
|
2355
|
+
return true;
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
|
|
2358
|
+
throw error;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
function transcriptErrorCode(error) {
|
|
2362
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
2363
|
+
}
|
|
2309
2364
|
var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
|
|
2310
2365
|
async function readTranscriptCompleteChunk(handle, offset, size) {
|
|
2311
2366
|
const available = Math.max(0, size - offset);
|
|
@@ -2414,10 +2469,15 @@ var HostTranscriptWatcher = class {
|
|
|
2414
2469
|
return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
|
|
2415
2470
|
}
|
|
2416
2471
|
async scanRegistration(registration) {
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2472
|
+
try {
|
|
2473
|
+
return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
|
|
2474
|
+
missing: () => this.retireMissing(registration),
|
|
2475
|
+
failed: (handle, error) => this.scanFailed(handle, registration, error)
|
|
2476
|
+
});
|
|
2477
|
+
} catch (error) {
|
|
2478
|
+
if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
|
|
2479
|
+
throw error;
|
|
2480
|
+
}
|
|
2421
2481
|
}
|
|
2422
2482
|
async openTranscript(registration, scan, handlers) {
|
|
2423
2483
|
let handle;
|
|
@@ -2426,7 +2486,7 @@ var HostTranscriptWatcher = class {
|
|
|
2426
2486
|
return await scan(handle);
|
|
2427
2487
|
} catch (error) {
|
|
2428
2488
|
if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
|
|
2429
|
-
if (hostRelayExchangeFailure(error) !== void 0) throw error;
|
|
2489
|
+
if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
|
|
2430
2490
|
return handlers.failed(handle, error);
|
|
2431
2491
|
} finally {
|
|
2432
2492
|
await handle?.close();
|
|
@@ -3576,6 +3636,9 @@ async function registerSession(event, options, store, now, retentionMs) {
|
|
|
3576
3636
|
await store.writeTranscriptCursor(await captureCodexTranscriptBaseline(registration, now));
|
|
3577
3637
|
}
|
|
3578
3638
|
await store.registerTranscript(registration);
|
|
3639
|
+
if (existing !== void 0 && currentTurn !== void 0 && (await store.readTranscriptCursor(registration))?.status === "quarantined") {
|
|
3640
|
+
await store.writeTranscriptCursor(await captureCodexTranscriptBaseline(registration, now));
|
|
3641
|
+
}
|
|
3579
3642
|
if (event.event === "SessionStart") {
|
|
3580
3643
|
await store.saveSession({
|
|
3581
3644
|
schema_version: "ceal.codex_session.v1",
|
|
@@ -3592,8 +3655,11 @@ async function registerSession(event, options, store, now, retentionMs) {
|
|
|
3592
3655
|
}
|
|
3593
3656
|
async function selectCurrentTurn(event, options, store, hostSessionRef) {
|
|
3594
3657
|
if (event.event !== "UserPromptSubmit") return void 0;
|
|
3658
|
+
const host_turn_ref = hostTurnReference(options.host_source_ref, hostSessionRef, event.turn_id);
|
|
3659
|
+
const steered = await liveSteeredTurn(options, store, hostSessionRef, host_turn_ref);
|
|
3660
|
+
if (steered !== void 0) return steered;
|
|
3595
3661
|
const currentTurn = {
|
|
3596
|
-
host_turn_ref
|
|
3662
|
+
host_turn_ref,
|
|
3597
3663
|
prompt_ref: hostProducerPromptRef(promptObservationValue(event.prompt).body_sha256),
|
|
3598
3664
|
turn_generation_ref: options.random_uuid?.() ?? randomUUID()
|
|
3599
3665
|
};
|
|
@@ -3606,6 +3672,18 @@ async function selectCurrentTurn(event, options, store, hostSessionRef) {
|
|
|
3606
3672
|
});
|
|
3607
3673
|
return currentTurn;
|
|
3608
3674
|
}
|
|
3675
|
+
async function liveSteeredTurn(options, store, hostSessionRef, hostTurnRef) {
|
|
3676
|
+
let live;
|
|
3677
|
+
try {
|
|
3678
|
+
live = await store.readInvocationContext(options.host_source_ref, hostSessionRef, (options.now ?? (() => /* @__PURE__ */ new Date()))());
|
|
3679
|
+
} catch {
|
|
3680
|
+
return void 0;
|
|
3681
|
+
}
|
|
3682
|
+
if (live === null || live.identity.host_turn_ref !== hostTurnRef) return void 0;
|
|
3683
|
+
const selection = await store.readInvocationTurnSelection(options.host_source_ref, hostSessionRef).catch(() => null);
|
|
3684
|
+
if (selection === null || selection.prompt_ref !== live.prompt_ref || selection.turn_generation_ref !== live.turn_generation_ref) return void 0;
|
|
3685
|
+
return { host_turn_ref: hostTurnRef, prompt_ref: selection.prompt_ref, turn_generation_ref: selection.turn_generation_ref };
|
|
3686
|
+
}
|
|
3609
3687
|
function sourceBoundTranscriptPath(path, sessionsRoot) {
|
|
3610
3688
|
if (!isAbsolute3(path) || !isAbsolute3(sessionsRoot)) throw new CodexHostAdapterError("configuration", "Codex transcript registration paths must be absolute.");
|
|
3611
3689
|
const transcript = resolve2(path);
|
|
@@ -4101,6 +4179,8 @@ function isRecord8(value) {
|
|
|
4101
4179
|
var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
4102
4180
|
store;
|
|
4103
4181
|
registerCurrentPrompt;
|
|
4182
|
+
releaseCurrentPrompt;
|
|
4183
|
+
diagnostic;
|
|
4104
4184
|
constructor(options) {
|
|
4105
4185
|
super(options, {
|
|
4106
4186
|
default_retention_ms: CODEX_DEFAULT_RETENTION_MS,
|
|
@@ -4109,6 +4189,8 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4109
4189
|
});
|
|
4110
4190
|
this.store = new CodexOutboxStore(options.outbox_dir);
|
|
4111
4191
|
this.registerCurrentPrompt = options.register_current_prompt;
|
|
4192
|
+
this.releaseCurrentPrompt = options.release_current_prompt;
|
|
4193
|
+
this.diagnostic = options.diagnostic ?? (() => void 0);
|
|
4112
4194
|
}
|
|
4113
4195
|
transcriptPathOf(registration) {
|
|
4114
4196
|
return registration.transcript_path;
|
|
@@ -4124,17 +4206,22 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4124
4206
|
if (complete === "empty" || complete === "partial") return { records: 0, enqueued: 0, quarantined: false };
|
|
4125
4207
|
return await this.consumeCompleteChunk(registration, handle, preflight.cursor, complete, device, inode);
|
|
4126
4208
|
}
|
|
4127
|
-
async scanFailed(handle, registration,
|
|
4209
|
+
async scanFailed(handle, registration, error) {
|
|
4210
|
+
this.diagnostic("watcher_scan_failed", codexTranscriptScanFailureKind(error));
|
|
4128
4211
|
await this.quarantineFailure(registration, handle);
|
|
4129
4212
|
return { records: 0, enqueued: 0, quarantined: true };
|
|
4130
4213
|
}
|
|
4131
4214
|
async retireMissing(registration) {
|
|
4215
|
+
await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
|
|
4132
4216
|
return this.retireMissingTranscript(registration.transcript_path, "Codex", () => this.store.retireTranscriptRegistration(registration));
|
|
4133
4217
|
}
|
|
4134
4218
|
async preflight(registration, handle, size, device, inode) {
|
|
4135
4219
|
const existing = await this.store.readTranscriptCursor(registration);
|
|
4136
4220
|
let cursor = existing ?? codexTranscriptInitialCursor(registration, device, inode, this.now);
|
|
4137
|
-
if (cursor.status === "quarantined")
|
|
4221
|
+
if (cursor.status === "quarantined") {
|
|
4222
|
+
if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
|
|
4223
|
+
return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
|
|
4224
|
+
}
|
|
4138
4225
|
const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
|
|
4139
4226
|
if (reason === null) return { cursor, result: { records: 0, enqueued: 0, quarantined: false } };
|
|
4140
4227
|
await this.quarantine(registration, cursor, device, inode, reason);
|
|
@@ -4173,25 +4260,41 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4173
4260
|
const stats = await handle.stat().catch(() => null);
|
|
4174
4261
|
if (stats === null) return;
|
|
4175
4262
|
const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
|
|
4176
|
-
|
|
4263
|
+
const quarantined = {
|
|
4264
|
+
...existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now),
|
|
4265
|
+
transcript_path: registration.transcript_path,
|
|
4266
|
+
device: String(stats.dev),
|
|
4267
|
+
inode: String(stats.ino),
|
|
4268
|
+
status: "quarantined",
|
|
4269
|
+
reason: "transcript_malformed",
|
|
4270
|
+
updated_at: this.stamp()
|
|
4271
|
+
};
|
|
4272
|
+
await this.store.writeTranscriptCursor(quarantined);
|
|
4177
4273
|
}
|
|
4178
4274
|
async processRecord(registration, activeTask, record3) {
|
|
4179
|
-
if (record3.kind === "task_started")
|
|
4180
|
-
return { activeTask: { codex_turn_id: record3.turn_id, host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id), started_at: record3.timestamp }, enqueued: 0 };
|
|
4181
|
-
}
|
|
4275
|
+
if (record3.kind === "task_started") return this.processTaskStarted(registration, activeTask, record3);
|
|
4182
4276
|
if (record3.kind === "turn_context") return { ...activeTask === void 0 ? {} : { activeTask: record3.turn_id === activeTask.codex_turn_id ? { ...activeTask, model: record3.model } : activeTask }, enqueued: 0 };
|
|
4183
4277
|
if (record3.kind === "token_count") return { ...activeTask === void 0 ? {} : { activeTask: accumulateTokenCount(activeTask, record3) }, enqueued: 0 };
|
|
4184
4278
|
if (record3.kind === "user_text") return this.processUserInput(registration, activeTask, record3);
|
|
4185
4279
|
if (record3.kind === "task_complete" || record3.kind === "turn_aborted") return this.processTerminal(registration, activeTask, record3);
|
|
4186
4280
|
return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
4187
4281
|
}
|
|
4282
|
+
async processTaskStarted(registration, activeTask, record3) {
|
|
4283
|
+
if (activeTask !== void 0 && activeTask.codex_turn_id !== record3.turn_id && activeTask.prompt_ref !== void 0) await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
|
|
4284
|
+
return { activeTask: { codex_turn_id: record3.turn_id, host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id), started_at: record3.timestamp }, enqueued: 0 };
|
|
4285
|
+
}
|
|
4188
4286
|
async processUserInput(registration, activeTask, record3) {
|
|
4189
|
-
if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id)
|
|
4287
|
+
if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) {
|
|
4288
|
+
const adopted = await this.adoptHookCorrelatedTurn(registration, activeTask, record3);
|
|
4289
|
+
if (adopted === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
4290
|
+
activeTask = adopted;
|
|
4291
|
+
}
|
|
4190
4292
|
const hostInputRef = hostInputReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id, record3.message_id, record3.ordinal, record3.prompt);
|
|
4191
4293
|
const origin = activeTask.origin_host_input_ref === void 0;
|
|
4192
4294
|
const promptValue = promptObservationValue(record3.prompt);
|
|
4193
4295
|
const currentPromptRef = hostProducerPromptRef(promptValue.body_sha256);
|
|
4194
4296
|
const promptRef = activeTask.prompt_ref ?? currentPromptRef;
|
|
4297
|
+
const steeredPromptRef = await this.steeredPromptRef(registration, activeTask, origin, currentPromptRef);
|
|
4195
4298
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: activeTask.host_turn_ref };
|
|
4196
4299
|
const observations = [{
|
|
4197
4300
|
observation_ref: observationRef("transcript_user_text", hostInputRef, record3.prompt),
|
|
@@ -4217,14 +4320,88 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4217
4320
|
created_at: record3.timestamp,
|
|
4218
4321
|
expires_at: this.expire(this.outboxRetentionMs)
|
|
4219
4322
|
};
|
|
4220
|
-
await this.
|
|
4221
|
-
await this.
|
|
4222
|
-
|
|
4323
|
+
const persisted = await this.persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope);
|
|
4324
|
+
if (steeredPromptRef !== null) await this.activateTranscriptTurnContext(identity, steeredPromptRef, envelope.expires_at);
|
|
4325
|
+
return persisted;
|
|
4326
|
+
}
|
|
4327
|
+
/**
|
|
4328
|
+
* The steered prompt to re-activate under the running task, or null when
|
|
4329
|
+
* there is nothing to repair. A steered message joins the running turn
|
|
4330
|
+
* with new prompt bytes but no task_started. When the steer hook already
|
|
4331
|
+
* replaced the turn selection (a turn id the hook did not recognize as
|
|
4332
|
+
* the running one, or a hook that ran before any context was live), the
|
|
4333
|
+
* running task's context no longer matches and the next invocation would
|
|
4334
|
+
* wait for a boundary that never comes. When the hook kept the live
|
|
4335
|
+
* selection, the context still matches and this stays null.
|
|
4336
|
+
*/
|
|
4337
|
+
async steeredPromptRef(registration, activeTask, origin, currentPromptRef) {
|
|
4338
|
+
if (origin || activeTask.prompt_ref === void 0 || activeTask.prompt_ref === currentPromptRef) return null;
|
|
4339
|
+
const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref).catch(() => null);
|
|
4340
|
+
return selection?.prompt_ref === currentPromptRef ? currentPromptRef : null;
|
|
4341
|
+
}
|
|
4342
|
+
/**
|
|
4343
|
+
* Adopt the live hooked turn when its transcript message arrives before
|
|
4344
|
+
* (or without) its task_started, as an interrupted turn's follow-up can.
|
|
4345
|
+
* The UserPromptSubmit hook names the exact hooked prompt, so only a
|
|
4346
|
+
* message carrying those bytes adopts; inherited history and other turns
|
|
4347
|
+
* stay ignored. A stale active task for another turn releases first, the
|
|
4348
|
+
* same as a task_started supersession.
|
|
4349
|
+
*/
|
|
4350
|
+
async adoptHookCorrelatedTurn(registration, activeTask, record3) {
|
|
4351
|
+
const currentPromptRef = hostProducerPromptRef(promptObservationValue(record3.prompt).body_sha256);
|
|
4352
|
+
const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref);
|
|
4353
|
+
if (selection?.prompt_ref !== currentPromptRef) return void 0;
|
|
4354
|
+
if (activeTask !== void 0 && activeTask.codex_turn_id !== record3.turn_id && activeTask.prompt_ref !== void 0) {
|
|
4355
|
+
await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
|
|
4356
|
+
}
|
|
4357
|
+
return {
|
|
4358
|
+
codex_turn_id: record3.turn_id,
|
|
4359
|
+
host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id),
|
|
4360
|
+
started_at: record3.timestamp
|
|
4361
|
+
};
|
|
4362
|
+
}
|
|
4363
|
+
async persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope) {
|
|
4364
|
+
const custodyRegistered = await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
|
|
4365
|
+
if (custodyRegistered) this.diagnostic("watcher_turn_observed");
|
|
4366
|
+
try {
|
|
4367
|
+
await this.store.put(envelope);
|
|
4368
|
+
if (origin) await this.activateTranscriptTurnContext(identity, promptRef, envelope.expires_at);
|
|
4369
|
+
} catch (error) {
|
|
4370
|
+
if (custodyRegistered) await this.releasePrompt(registration.host_session_ref, activeTask, currentPromptRef).catch(() => void 0);
|
|
4371
|
+
throw error;
|
|
4372
|
+
}
|
|
4223
4373
|
return { activeTask: { ...activeTask, origin_host_input_ref: activeTask.origin_host_input_ref ?? hostInputRef, prompt_ref: activeTask.prompt_ref ?? currentPromptRef }, enqueued: 1 };
|
|
4224
4374
|
}
|
|
4225
4375
|
async registerPrompt(origin, registration, activeTask, promptRef, prompt, expiresAt) {
|
|
4226
|
-
if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
|
|
4227
|
-
|
|
4376
|
+
if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
|
|
4377
|
+
const register = this.registerCurrentPrompt;
|
|
4378
|
+
const installationRef = this.producerInstallationRef;
|
|
4379
|
+
return registerHostCurrentPromptDiagnosed(
|
|
4380
|
+
() => register({ producer_id: "codex", producer_installation_ref: installationRef, local_session_ref: registration.host_session_ref, local_turn_ref: activeTask.host_turn_ref, prompt_ref: promptRef, current_prompt: prompt, context_expires_at: expiresAt }),
|
|
4381
|
+
(event) => this.diagnostic(event)
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
async activateTranscriptTurnContext(identity, promptRef, expiresAt) {
|
|
4385
|
+
try {
|
|
4386
|
+
const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
4387
|
+
const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
|
|
4388
|
+
await this.store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
|
|
4389
|
+
this.diagnostic("activation_recorded");
|
|
4390
|
+
} catch (error) {
|
|
4391
|
+
this.diagnostic("activation_failed");
|
|
4392
|
+
throw error;
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
async releasePrompt(sessionRef, activeTask, promptRef) {
|
|
4396
|
+
if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
|
|
4397
|
+
await this.releaseCurrentPrompt({ producer_id: "codex", producer_installation_ref: this.producerInstallationRef, local_session_ref: sessionRef, local_turn_ref: activeTask.host_turn_ref, prompt_ref: promptRef });
|
|
4398
|
+
}
|
|
4399
|
+
async releaseStoredPromptBestEffort(sessionRef, cursor) {
|
|
4400
|
+
await this.releaseActiveTaskBestEffort(sessionRef, cursor?.active_task);
|
|
4401
|
+
}
|
|
4402
|
+
async releaseActiveTaskBestEffort(sessionRef, activeTask) {
|
|
4403
|
+
if (activeTask?.prompt_ref === void 0) return;
|
|
4404
|
+
await this.releasePrompt(sessionRef, activeTask, activeTask.prompt_ref).catch(() => void 0);
|
|
4228
4405
|
}
|
|
4229
4406
|
async processTerminal(registration, activeTask, record3) {
|
|
4230
4407
|
if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id || activeTask.origin_host_input_ref === void 0 || activeTask.prompt_ref === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
@@ -4244,18 +4421,23 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4244
4421
|
created_at: record3.timestamp,
|
|
4245
4422
|
expires_at: this.expire(this.outboxRetentionMs)
|
|
4246
4423
|
});
|
|
4424
|
+
await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
|
|
4247
4425
|
await this.store.clearInvocationContext(identity, this.now());
|
|
4248
4426
|
return { enqueued: 1 };
|
|
4249
4427
|
}
|
|
4250
4428
|
async quarantine(registration, cursor, device, inode, reason) {
|
|
4429
|
+
await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
|
|
4251
4430
|
const quarantined = { ...cursor, transcript_path: registration.transcript_path, device, inode, status: "quarantined", reason, updated_at: this.stamp() };
|
|
4252
4431
|
await this.store.writeTranscriptCursor(quarantined);
|
|
4253
4432
|
}
|
|
4254
4433
|
};
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4434
|
+
function codexTranscriptScanFailureKind(error) {
|
|
4435
|
+
return transcriptScanFailureKind(error, (candidate) => {
|
|
4436
|
+
if (!(candidate instanceof CodexHostAdapterError)) return void 0;
|
|
4437
|
+
if (candidate.code === "payload_rejected") return "payload_rejected";
|
|
4438
|
+
if (candidate.code === "storage") return "read_error";
|
|
4439
|
+
return void 0;
|
|
4440
|
+
});
|
|
4259
4441
|
}
|
|
4260
4442
|
function accumulateTokenCount(activeTask, record3) {
|
|
4261
4443
|
if (record3.usage_gap_reason !== void 0) return { ...activeTask, token_usage_gap_reason: record3.usage_gap_reason };
|
|
@@ -4343,7 +4525,16 @@ function assertRecordSequence(registration, cursor, records, lastOrdinal, record
|
|
|
4343
4525
|
// host/codex/src/codex-producer-runtime.ts
|
|
4344
4526
|
async function drainCodexProducerOnce(options) {
|
|
4345
4527
|
const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
|
|
4346
|
-
const scan = await new CodexTranscriptWatcher({
|
|
4528
|
+
const scan = await new CodexTranscriptWatcher({
|
|
4529
|
+
outbox_dir: options.outbox_dir,
|
|
4530
|
+
producer_installation_ref: options.producer_installation_ref,
|
|
4531
|
+
register_current_prompt: (input) => promptPort.register(input),
|
|
4532
|
+
release_current_prompt: (identity) => promptPort.release(identity),
|
|
4533
|
+
diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("codex", event, failure_kind)
|
|
4534
|
+
}).scanOnce().catch((error) => {
|
|
4535
|
+
writeHostServiceDiagnostic("codex", "watcher_scan_failed", codexTranscriptScanFailureKind(error));
|
|
4536
|
+
throw error;
|
|
4537
|
+
});
|
|
4347
4538
|
const producer = new CodexProducer({ 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 } });
|
|
4348
4539
|
return { scanned: scan.enqueued, ...await producer.drainOutbox() };
|
|
4349
4540
|
}
|