@corca-org/ceal-host-linux-arm64 1.0.18 → 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 +2041 -514
- package/libexec/ceal-host/claude-invocation-authentication.mjs +1899 -330
- package/libexec/ceal-host/claude-producer.mjs +1860 -406
- package/libexec/ceal-host/codex-hook.mjs +1618 -1013
- package/libexec/ceal-host/codex-invocation-authentication.mjs +3222 -505
- package/libexec/ceal-host/codex-producer.mjs +1126 -581
- package/libexec/ceal-host/control.mjs +2857 -1134
- package/libexec/ceal-host/relay.mjs +559 -116
- package/package.json +1 -1
- package/share/ceal-host/bundle-manifest.json +12 -12
- package/share/ceal-host/bundle-manifest.sigstore.json +1 -1
|
@@ -153,6 +153,9 @@ var client_wire_contract_default = {
|
|
|
153
153
|
"document"
|
|
154
154
|
],
|
|
155
155
|
optional_fields: [
|
|
156
|
+
"operation_id",
|
|
157
|
+
"visible_output_max_bytes",
|
|
158
|
+
"local_delivery",
|
|
156
159
|
"verified_artifact"
|
|
157
160
|
],
|
|
158
161
|
bare_document_fields: [
|
|
@@ -266,6 +269,7 @@ var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verifie
|
|
|
266
269
|
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
267
270
|
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
268
271
|
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
272
|
+
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
269
273
|
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
270
274
|
"audited_call_pending",
|
|
271
275
|
"continuation_read_pending",
|
|
@@ -289,6 +293,7 @@ var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
|
289
293
|
// packages/ceal-client-protocol/src/operation-next-actions.ts
|
|
290
294
|
var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
|
|
291
295
|
var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
|
|
296
|
+
var CEAL_ANSWER_WITH_GAP_NEXT_ACTION = "answer_with_gap";
|
|
292
297
|
var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
|
|
293
298
|
"select_did_you_mean",
|
|
294
299
|
"choose_new_output_path",
|
|
@@ -342,10 +347,20 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
|
|
|
342
347
|
"retry_after",
|
|
343
348
|
// The ref names a container of rows (a Notion database or data source), which
|
|
344
349
|
// has no body of its own; the caller searches its rows instead of reading it.
|
|
345
|
-
"search_container_rows"
|
|
350
|
+
"search_container_rows",
|
|
351
|
+
// A settled unknown write names the exact receipt Operation that can inspect
|
|
352
|
+
// its outcome without issuing another provider mutation.
|
|
353
|
+
"write_receipt.get",
|
|
354
|
+
// Command resolution carries the exact required Operation sequence beside
|
|
355
|
+
// this action, so the caller can execute each declared input in order.
|
|
356
|
+
"follow_required_action_sequence",
|
|
357
|
+
// The command needs caller-owned local materialization (for example an
|
|
358
|
+
// @path file) before the Gateway can receive the argument.
|
|
359
|
+
"materialize_local_argument"
|
|
346
360
|
];
|
|
347
361
|
var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
|
|
348
362
|
var NON_REMEDY_NEXT_ACTION_VALUES = [
|
|
363
|
+
CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
349
364
|
// Only an administrator can take the named step -- approving a place for an
|
|
350
365
|
// Operation, granting a withheld capability -- so a non-administrator asks
|
|
351
366
|
// one. The refusal names the administrator step; this word is the ask.
|
|
@@ -393,7 +408,58 @@ var NON_REMEDY_NEXT_ACTION_VALUES = [
|
|
|
393
408
|
"select_exact_approval"
|
|
394
409
|
];
|
|
395
410
|
var CEAL_NON_REMEDY_NEXT_ACTIONS = Object.freeze(NON_REMEDY_NEXT_ACTION_VALUES);
|
|
396
|
-
var
|
|
411
|
+
var CEAL_DECLARED_NEXT_ACTIONS = Object.freeze([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
|
|
412
|
+
var DECLARED = new Set(CEAL_DECLARED_NEXT_ACTIONS);
|
|
413
|
+
function defineCealNonModelNextActions(actions) {
|
|
414
|
+
return actions;
|
|
415
|
+
}
|
|
416
|
+
var NON_MODEL_NEXT_ACTION_VALUES = defineCealNonModelNextActions([
|
|
417
|
+
CEAL_INSPECT_RECEIPT_NEXT_ACTION,
|
|
418
|
+
"await_pending_approval",
|
|
419
|
+
"inspect_gateway",
|
|
420
|
+
"operator_action",
|
|
421
|
+
"none",
|
|
422
|
+
"check_gateway",
|
|
423
|
+
"inspect_context_store",
|
|
424
|
+
"inspect_local_cache",
|
|
425
|
+
"inspect_local_client",
|
|
426
|
+
"inspect_local_command",
|
|
427
|
+
"inspect_local_output",
|
|
428
|
+
"inspect_local_route",
|
|
429
|
+
"inspect_request",
|
|
430
|
+
"reconcile",
|
|
431
|
+
"reconcile_occurrence",
|
|
432
|
+
"retry_after_owner_recovery",
|
|
433
|
+
"select_exact_approval"
|
|
434
|
+
]);
|
|
435
|
+
var NON_MODEL_NEXT_ACTIONS = new Set(NON_MODEL_NEXT_ACTION_VALUES);
|
|
436
|
+
function isCealModelActionableNonRemedyNextAction(action) {
|
|
437
|
+
return !NON_MODEL_NEXT_ACTIONS.has(action);
|
|
438
|
+
}
|
|
439
|
+
var CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS = Object.freeze([
|
|
440
|
+
...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS,
|
|
441
|
+
...CEAL_NON_REMEDY_NEXT_ACTIONS.filter(isCealModelActionableNonRemedyNextAction)
|
|
442
|
+
]);
|
|
443
|
+
var MODEL_ACTIONABLE = new Set(CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS);
|
|
444
|
+
var NON_MODEL_ACTION_TO_MODEL_ACTION = Object.freeze({
|
|
445
|
+
[CEAL_INSPECT_RECEIPT_NEXT_ACTION]: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
446
|
+
await_pending_approval: "ask_admin",
|
|
447
|
+
inspect_gateway: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
448
|
+
operator_action: "ask_admin",
|
|
449
|
+
none: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
450
|
+
check_gateway: "ask_admin",
|
|
451
|
+
inspect_context_store: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
452
|
+
inspect_local_cache: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
453
|
+
inspect_local_client: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
454
|
+
inspect_local_command: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
455
|
+
inspect_local_output: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
456
|
+
inspect_local_route: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
457
|
+
inspect_request: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
458
|
+
reconcile: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
|
|
459
|
+
reconcile_occurrence: "ask_admin",
|
|
460
|
+
retry_after_owner_recovery: "ask_admin",
|
|
461
|
+
select_exact_approval: "ask_admin"
|
|
462
|
+
});
|
|
397
463
|
var RETRY_PROMISE_NEXT_ACTIONS = ["retry", "retry_after"];
|
|
398
464
|
var RETRY_PROMISE = new Set(RETRY_PROMISE_NEXT_ACTIONS);
|
|
399
465
|
var RECOVERABLE = new Set(CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS);
|
|
@@ -663,21 +729,15 @@ var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
|
663
729
|
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
664
730
|
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
665
731
|
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
666
|
-
var CEAL_PROVIDER_BOUNDARY_KINDS = vocabulary([
|
|
667
|
-
"github_account",
|
|
668
|
-
"github_organization",
|
|
669
|
-
"google_workspace_domain",
|
|
670
|
-
"notion_workspace",
|
|
671
|
-
"slack_workspace"
|
|
672
|
-
]);
|
|
673
|
-
var CEAL_PROVIDER_BOUNDARY_EVIDENCE = vocabulary(["provider_readback", "operator_stated"]);
|
|
674
732
|
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
675
733
|
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
676
734
|
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
677
735
|
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
678
736
|
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
679
|
-
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "
|
|
737
|
+
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
738
|
+
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
680
739
|
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
740
|
+
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
681
741
|
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
682
742
|
|
|
683
743
|
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
@@ -853,7 +913,7 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
853
913
|
"ceal.operation_receipt.v1",
|
|
854
914
|
"ceal.operation_result.v1",
|
|
855
915
|
"ceal.operation_result.compact.v1",
|
|
856
|
-
"ceal.
|
|
916
|
+
"ceal.local_result_file.v1",
|
|
857
917
|
"ceal.local_result_root.v1",
|
|
858
918
|
"ceal.owner_bootstrap_exchange_request.v1",
|
|
859
919
|
"ceal.owner_bootstrap_exchange_result.v1",
|
|
@@ -947,41 +1007,6 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
|
|
|
947
1007
|
}
|
|
948
1008
|
});
|
|
949
1009
|
|
|
950
|
-
// packages/ceal-client-protocol/src/provider-operation-vocabulary.ts
|
|
951
|
-
var CEAL_FILE_TYPE_FAMILIES = vocabulary(["all", "image", "pdf"]);
|
|
952
|
-
var CEAL_GITHUB_ISSUE_STATES = vocabulary(["open", "closed", "all"]);
|
|
953
|
-
var CEAL_NOTION_UPDATABLE_BLOCK_TYPES = vocabulary([
|
|
954
|
-
"paragraph",
|
|
955
|
-
"heading_1",
|
|
956
|
-
"heading_2",
|
|
957
|
-
"heading_3",
|
|
958
|
-
"bulleted_list_item",
|
|
959
|
-
"numbered_list_item",
|
|
960
|
-
"to_do",
|
|
961
|
-
"code"
|
|
962
|
-
]);
|
|
963
|
-
var CEAL_NOTION_NEUTRAL_TYPES = vocabulary([
|
|
964
|
-
"text",
|
|
965
|
-
"number",
|
|
966
|
-
"boolean",
|
|
967
|
-
"date",
|
|
968
|
-
"select",
|
|
969
|
-
"multi_select",
|
|
970
|
-
"people",
|
|
971
|
-
"url",
|
|
972
|
-
"email",
|
|
973
|
-
"phone",
|
|
974
|
-
"unsupported"
|
|
975
|
-
]);
|
|
976
|
-
var CEAL_NOTION_IDENTITY_GAP_REASONS = vocabulary([
|
|
977
|
-
"identity_unavailable",
|
|
978
|
-
"identity_stale",
|
|
979
|
-
"identity_ambiguous",
|
|
980
|
-
"identity_revoked",
|
|
981
|
-
"identity_unlinked"
|
|
982
|
-
]);
|
|
983
|
-
var CEAL_NOTION_STRING_VALUE_TYPES = vocabulary(["text", "select", "url", "email", "phone"]);
|
|
984
|
-
|
|
985
1010
|
// packages/ceal-client-protocol/src/opaque-reference.ts
|
|
986
1011
|
var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
|
|
987
1012
|
var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
|
|
@@ -991,6 +1016,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
|
|
|
991
1016
|
var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
|
|
992
1017
|
|
|
993
1018
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
1019
|
+
var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
|
|
994
1020
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
995
1021
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
996
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"]);
|
|
@@ -1014,6 +1040,7 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
|
|
|
1014
1040
|
"resource_modified"
|
|
1015
1041
|
]);
|
|
1016
1042
|
var CEAL_ACTIVITY_CONNECTOR_KINDS = vocabulary(["slack", "github", "google-calendar", "google-drive", "notion"]);
|
|
1043
|
+
var CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE = "unsupported_event_kind";
|
|
1017
1044
|
var CEAL_ACTIVITY_GAP_CODES = vocabulary([
|
|
1018
1045
|
"provider_page_budget",
|
|
1019
1046
|
"call_deadline",
|
|
@@ -1021,7 +1048,7 @@ var CEAL_ACTIVITY_GAP_CODES = vocabulary([
|
|
|
1021
1048
|
"provider_cursor_unavailable",
|
|
1022
1049
|
"malformed_provider_page",
|
|
1023
1050
|
"visibility_incomplete",
|
|
1024
|
-
|
|
1051
|
+
CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE,
|
|
1025
1052
|
"direct_messages_excluded"
|
|
1026
1053
|
]);
|
|
1027
1054
|
var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
|
|
@@ -1073,6 +1100,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1073
1100
|
next: { required: true, timestamp: false },
|
|
1074
1101
|
kind: { required: true, timestamp: false },
|
|
1075
1102
|
matched_kinds: { required: false, timestamp: false },
|
|
1103
|
+
title_match: { required: false, timestamp: false },
|
|
1076
1104
|
title: { required: true, timestamp: false },
|
|
1077
1105
|
container_label: { required: true, timestamp: false },
|
|
1078
1106
|
container_ref: { required: false, timestamp: false },
|
|
@@ -1086,6 +1114,8 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1086
1114
|
modified_at: { required: false, timestamp: true },
|
|
1087
1115
|
modified_at_local: { required: false, timestamp: true },
|
|
1088
1116
|
viewed_by_me_at: { required: false, timestamp: true },
|
|
1117
|
+
own_ceal_write: { required: false, timestamp: false },
|
|
1118
|
+
owned_by_me: { required: false, timestamp: false },
|
|
1089
1119
|
visibility: { required: false, timestamp: false },
|
|
1090
1120
|
start: { required: false, timestamp: false },
|
|
1091
1121
|
end: { required: false, timestamp: false },
|
|
@@ -1119,6 +1149,15 @@ function cealExcludedTargetCoverageJsonSchema() {
|
|
|
1119
1149
|
};
|
|
1120
1150
|
}
|
|
1121
1151
|
|
|
1152
|
+
// packages/ceal-client-protocol/src/gateway-write-identity.ts
|
|
1153
|
+
var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
|
|
1154
|
+
var CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN = /^gateway-write-request:[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/u;
|
|
1155
|
+
var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
|
|
1156
|
+
replay_identity: "idempotency_claim_sha256",
|
|
1157
|
+
lookup_handle: "write_request_sha256",
|
|
1158
|
+
collision_evidence: "normalized_mutation_sha256"
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1122
1161
|
// packages/ceal-client-protocol/src/write-safety-finding.ts
|
|
1123
1162
|
var CEAL_WRITE_SAFETY_FINDING_STATUSES = vocabulary(["warning", "unchecked"]);
|
|
1124
1163
|
var CEAL_WRITE_SAFETY_AUDIENCES = vocabulary(["workspace", "person", "members", "unknown"]);
|
|
@@ -1146,7 +1185,11 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
|
|
|
1146
1185
|
"refresh_revoked",
|
|
1147
1186
|
"authority_replaced",
|
|
1148
1187
|
"refresh_recovery_unavailable",
|
|
1149
|
-
"refresh_recovery_expired"
|
|
1188
|
+
"refresh_recovery_expired",
|
|
1189
|
+
// The serving gate answers this while the Gateway is still starting and the
|
|
1190
|
+
// refresh was never attempted; the same attempt stays retryable. It is never
|
|
1191
|
+
// stored by the session owner, only answered at the serving edge.
|
|
1192
|
+
"gateway_starting"
|
|
1150
1193
|
]);
|
|
1151
1194
|
|
|
1152
1195
|
// packages/ceal-client-protocol/src/request-validation-primitives.ts
|
|
@@ -1179,7 +1222,7 @@ var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
|
|
|
1179
1222
|
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority", "prompt_ref", "host_session_ref", "host_turn_ref", "host_source_ref"]);
|
|
1180
1223
|
var CEAL_REQUEST_JSON_SCHEMA = {
|
|
1181
1224
|
type: "object",
|
|
1182
|
-
maxProperties:
|
|
1225
|
+
maxProperties: 11,
|
|
1183
1226
|
additionalProperties: false,
|
|
1184
1227
|
properties: {
|
|
1185
1228
|
schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
|
|
@@ -1203,7 +1246,8 @@ var CEAL_REQUEST_JSON_SCHEMA = {
|
|
|
1203
1246
|
host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1204
1247
|
host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1205
1248
|
host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1206
|
-
requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." }
|
|
1249
|
+
requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
|
|
1250
|
+
device_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source, description: "Device that submitted this Request through its client session; absent for Host turns and older records." }
|
|
1207
1251
|
},
|
|
1208
1252
|
required: CEAL_REQUEST_KEYS
|
|
1209
1253
|
};
|
|
@@ -1262,6 +1306,7 @@ var REQUEST_EXECUTION_PREPARE_REFUSAL_PROPERTIES = {
|
|
|
1262
1306
|
retryable: { type: "boolean" },
|
|
1263
1307
|
source: { type: "string", enum: ["provider", "gateway"] },
|
|
1264
1308
|
retry_after_ms: { type: "integer", minimum: 0, maximum: CEAL_RETRY_AFTER_MAX_MS },
|
|
1309
|
+
write_request_ref: { type: "string", pattern: CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN.source, maxLength: 128 },
|
|
1265
1310
|
excluded_targets: cealExcludedTargetCoverageJsonSchema(),
|
|
1266
1311
|
repaired_input: {},
|
|
1267
1312
|
unrepaired_pointers: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 256 } },
|
|
@@ -1326,6 +1371,43 @@ function cealHostObservationRef(producer, digest2) {
|
|
|
1326
1371
|
if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(digest2)) invalid("Ceal Host observation reference parts are invalid.");
|
|
1327
1372
|
return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${digest2}`;
|
|
1328
1373
|
}
|
|
1374
|
+
var HOST_TOKEN_OBSERVATION_BREAKDOWN_KEYS = vocabulary(["cached_input", "cache_write_input", "reasoning_output"]);
|
|
1375
|
+
function createHostTokenObservationValue(counts, source) {
|
|
1376
|
+
const core = validTokenCore(counts);
|
|
1377
|
+
if (typeof source !== "string" || source.length === 0 || source.length > 256) invalid("Ceal Host token observation source is invalid.");
|
|
1378
|
+
const value = { status: "available", unit: "tokens", input: core.input, output: core.output, total: core.total, source };
|
|
1379
|
+
for (const breakdown of validTokenBreakdowns(counts)) value[breakdown.key] = breakdown.tokens;
|
|
1380
|
+
assertHostObservationValue(value, "token");
|
|
1381
|
+
return value;
|
|
1382
|
+
}
|
|
1383
|
+
function validTokenCore(counts) {
|
|
1384
|
+
if (!isOperationRecord(counts) || !safeCount(counts.input) || !safeCount(counts.output) || !safeCount(counts.total)) invalid("Ceal Host token observation counts are invalid.");
|
|
1385
|
+
if (counts.total !== counts.input + counts.output) invalid("Ceal Host token observation counts are invalid.");
|
|
1386
|
+
return { input: counts.input, output: counts.output, total: counts.total };
|
|
1387
|
+
}
|
|
1388
|
+
function validTokenBreakdowns(counts) {
|
|
1389
|
+
const breakdowns = [
|
|
1390
|
+
{ key: "cached_input", tokens: counts.cached_input },
|
|
1391
|
+
{ key: "cache_write_input", tokens: counts.cache_write_input },
|
|
1392
|
+
{ key: "reasoning_output", tokens: counts.reasoning_output }
|
|
1393
|
+
];
|
|
1394
|
+
const stated = breakdowns.filter((breakdown) => breakdown.tokens !== void 0).map((breakdown) => ({ key: breakdown.key, tokens: Number(breakdown.tokens) }));
|
|
1395
|
+
for (const breakdown of stated) {
|
|
1396
|
+
if (!safeCount(breakdown.tokens)) invalid("Ceal Host token observation counts are invalid.");
|
|
1397
|
+
}
|
|
1398
|
+
const cached = counts.cached_input ?? 0;
|
|
1399
|
+
const created = counts.cache_write_input ?? 0;
|
|
1400
|
+
const reasoning = counts.reasoning_output ?? 0;
|
|
1401
|
+
if (cached + created > counts.input || reasoning > counts.output) invalid("Ceal Host token observation counts are invalid.");
|
|
1402
|
+
return stated;
|
|
1403
|
+
}
|
|
1404
|
+
function hostObservationNotRecorded(reason) {
|
|
1405
|
+
if (typeof reason !== "string" || reason.length === 0 || reason.length > 256) invalid("Ceal Host observation gap reason is invalid.");
|
|
1406
|
+
return { status: "not_recorded", reason };
|
|
1407
|
+
}
|
|
1408
|
+
function safeCount(value) {
|
|
1409
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
1410
|
+
}
|
|
1329
1411
|
function validateCealHostObservationDraft(value) {
|
|
1330
1412
|
const record3 = operationRecord(value, "Ceal Host observation draft is invalid.");
|
|
1331
1413
|
requirePrefixedRef(record3.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
|
|
@@ -1477,12 +1559,85 @@ var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
|
|
|
1477
1559
|
"host_input_mismatch",
|
|
1478
1560
|
"host_prompt_mismatch"
|
|
1479
1561
|
]);
|
|
1562
|
+
var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
|
|
1563
|
+
"invalid_arguments",
|
|
1564
|
+
"options_invalid",
|
|
1565
|
+
"session_unavailable",
|
|
1566
|
+
"context_unavailable",
|
|
1567
|
+
"context_replaced",
|
|
1568
|
+
"no_binding",
|
|
1569
|
+
"context_stale",
|
|
1570
|
+
"context_expired",
|
|
1571
|
+
"host_source_refused",
|
|
1572
|
+
"host_credential_refused",
|
|
1573
|
+
"gateway_unavailable",
|
|
1574
|
+
"gateway_rate_limited",
|
|
1575
|
+
"gateway_refused",
|
|
1576
|
+
"gateway_response_invalid",
|
|
1577
|
+
"producer_frame_invalid",
|
|
1578
|
+
"relay_permission_denied",
|
|
1579
|
+
"relay_unavailable",
|
|
1580
|
+
"transient_retryable",
|
|
1581
|
+
"mint_failed",
|
|
1582
|
+
"relay_failed",
|
|
1583
|
+
"host_refusal_invalid"
|
|
1584
|
+
]);
|
|
1585
|
+
var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
1586
|
+
invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
|
|
1587
|
+
options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
|
|
1588
|
+
session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
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)" }),
|
|
1590
|
+
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
|
|
1591
|
+
no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
|
|
1592
|
+
context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
|
|
1593
|
+
context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "refresh_host_invocation_context" }),
|
|
1594
|
+
host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
|
|
1595
|
+
host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
|
|
1596
|
+
gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
|
|
1597
|
+
gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
|
|
1598
|
+
gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
|
|
1599
|
+
gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
|
|
1600
|
+
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin", remedy: "ask an administrator to inspect and repair the producer frame, then start a fresh Host turn" }),
|
|
1601
|
+
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox", remedy: "an agent sandbox denied the Host Relay socket; run `ceal` as the only command, with no pipe, redirect, or `&&`, so the Host allow rule runs it outside the sandbox" }),
|
|
1602
|
+
relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
|
|
1603
|
+
transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
|
|
1604
|
+
mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
|
|
1605
|
+
relay_failed: Object.freeze({ classification: "host_relay_failed", next_action: "answer_with_gap" }),
|
|
1606
|
+
host_refusal_invalid: Object.freeze({ classification: "host_refusal_invalid", next_action: "answer_with_gap" })
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
// packages/ceal-client-protocol/src/host-service-diagnostic.ts
|
|
1610
|
+
var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
|
|
1611
|
+
var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
|
|
1612
|
+
var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
1613
|
+
"hook_invoked",
|
|
1614
|
+
"registration_written",
|
|
1615
|
+
"hook_failed",
|
|
1616
|
+
"watcher_turn_observed",
|
|
1617
|
+
"current_prompt_registered",
|
|
1618
|
+
"current_prompt_registration_failed",
|
|
1619
|
+
"current_prompt_capacity_exhausted",
|
|
1620
|
+
"activation_recorded",
|
|
1621
|
+
"activation_failed",
|
|
1622
|
+
"watcher_scan_failed",
|
|
1623
|
+
"activation_wait_skipped",
|
|
1624
|
+
"activation_wait_engaged",
|
|
1625
|
+
"activation_wait_completed",
|
|
1626
|
+
"activation_wait_exhausted"
|
|
1627
|
+
]);
|
|
1628
|
+
var CEAL_HOST_SERVICE_DIAGNOSTIC_FAILURE_KINDS = vocabulary([
|
|
1629
|
+
"malformed_line",
|
|
1630
|
+
"other",
|
|
1631
|
+
"payload_rejected",
|
|
1632
|
+
"read_error",
|
|
1633
|
+
"size_budget",
|
|
1634
|
+
"symlink"
|
|
1635
|
+
]);
|
|
1480
1636
|
|
|
1481
1637
|
// packages/ceal-client-protocol/src/client-result-materialization.ts
|
|
1482
1638
|
var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
|
|
1483
|
-
var
|
|
1484
|
-
var
|
|
1485
|
-
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "local_result_root", "continue_argv", "model_disclosure"]);
|
|
1639
|
+
var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
|
|
1640
|
+
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
|
|
1486
1641
|
|
|
1487
1642
|
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1488
1643
|
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -1512,14 +1667,6 @@ var CEAL_GATEWAY_ANNOUNCEMENT_POLICY_NON_CLAIMS = vocabulary([
|
|
|
1512
1667
|
"target_specific_scope_not_declared"
|
|
1513
1668
|
]);
|
|
1514
1669
|
|
|
1515
|
-
// packages/ceal-client-protocol/src/gateway-write-identity.ts
|
|
1516
|
-
var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
|
|
1517
|
-
var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
|
|
1518
|
-
replay_identity: "idempotency_claim_sha256",
|
|
1519
|
-
lookup_handle: "write_request_sha256",
|
|
1520
|
-
collision_evidence: "normalized_mutation_sha256"
|
|
1521
|
-
});
|
|
1522
|
-
|
|
1523
1670
|
// packages/ceal-client-protocol/src/gateway-scoped-identity-projection-validation.ts
|
|
1524
1671
|
var PROJECTION_REVISION_KEYS = Object.freeze(["graph_revision", "subject_key_revision", "projection_revision"]);
|
|
1525
1672
|
|
|
@@ -1532,6 +1679,18 @@ var CEAL_SUPPORTED_GATEWAY_PROTOCOL_RANGE = Object.freeze({
|
|
|
1532
1679
|
// packages/ceal-client-protocol/src/admin-projection-vocabulary.ts
|
|
1533
1680
|
var ADMIN_PROJECTION_STATES = vocabulary(["ready", "empty", "partial", "unknown"]);
|
|
1534
1681
|
var ADMIN_VIEW_KINDS = vocabulary(["request_detail", "resource_access_timeline", "weekly_usage"]);
|
|
1682
|
+
var ADMIN_RECORD_PROJECTION_KINDS = vocabulary([
|
|
1683
|
+
"request_list",
|
|
1684
|
+
"usage_series",
|
|
1685
|
+
"connector_health",
|
|
1686
|
+
"person_connections",
|
|
1687
|
+
"client_sessions",
|
|
1688
|
+
"knowledge_base",
|
|
1689
|
+
"change_history",
|
|
1690
|
+
"approval_queue",
|
|
1691
|
+
"access_group_list",
|
|
1692
|
+
"capability_catalogue"
|
|
1693
|
+
]);
|
|
1535
1694
|
var ADMIN_GAP_KINDS = vocabulary(["unknown", "unavailable", "partial"]);
|
|
1536
1695
|
var ADMIN_UNAVAILABLE_REASONS = vocabulary([
|
|
1537
1696
|
"not_recorded",
|
|
@@ -1555,7 +1714,11 @@ var ADMIN_SOURCE_OWNERS = vocabulary([
|
|
|
1555
1714
|
"person",
|
|
1556
1715
|
"resource",
|
|
1557
1716
|
"prompt",
|
|
1558
|
-
"usage"
|
|
1717
|
+
"usage",
|
|
1718
|
+
"connector",
|
|
1719
|
+
"session",
|
|
1720
|
+
"knowledge",
|
|
1721
|
+
"policy"
|
|
1559
1722
|
]);
|
|
1560
1723
|
var ADMIN_COMPLETENESS_STATES = vocabulary(["complete", "partial", "unknown"]);
|
|
1561
1724
|
var ADMIN_OPERATION_OUTCOMES = vocabulary(["completed", "failed", "denied", "cancelled", "unknown"]);
|
|
@@ -1790,6 +1953,15 @@ var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
|
|
|
1790
1953
|
function isHostProducerFrameKind(value) {
|
|
1791
1954
|
return typeof value === "string" && FRAME_KINDS.has(value);
|
|
1792
1955
|
}
|
|
1956
|
+
var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
1957
|
+
"invalid_frame_encoding",
|
|
1958
|
+
"invalid_frame_length",
|
|
1959
|
+
"frame_truncated",
|
|
1960
|
+
"unsupported_frame_schema",
|
|
1961
|
+
"invalid_frame_shape",
|
|
1962
|
+
"invalid_producer_identity",
|
|
1963
|
+
"frame_too_large"
|
|
1964
|
+
];
|
|
1793
1965
|
var HostProducerFrameError = class extends Error {
|
|
1794
1966
|
code;
|
|
1795
1967
|
constructor(code, message = code) {
|
|
@@ -1824,6 +1996,9 @@ function decodeHostProducerFrame(input) {
|
|
|
1824
1996
|
validateFrame(value);
|
|
1825
1997
|
return value;
|
|
1826
1998
|
}
|
|
1999
|
+
function isHostSafeRef(value) {
|
|
2000
|
+
return safeRef(value);
|
|
2001
|
+
}
|
|
1827
2002
|
function validateFrame(value) {
|
|
1828
2003
|
if (!record(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1829
2004
|
if (value.schema_version !== HOST_PRODUCER_FRAME_SCHEMA) throw new HostProducerFrameError("unsupported_frame_schema", `unsupported_frame_schema: ${refusalFact("schema_version", value.schema_version, HOST_PRODUCER_FRAME_SCHEMA)}`);
|
|
@@ -1911,6 +2086,15 @@ function exactOptional(value, required, optional) {
|
|
|
1911
2086
|
return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
1912
2087
|
}
|
|
1913
2088
|
|
|
2089
|
+
// host/relay/src/host-producer-core.ts
|
|
2090
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2091
|
+
import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
|
|
2092
|
+
import { mkdir as mkdir2, open as open4, readFile, rename as rename2, writeFile } from "node:fs/promises";
|
|
2093
|
+
import { join as join2 } from "node:path";
|
|
2094
|
+
|
|
2095
|
+
// host/relay/src/relay-ipc.ts
|
|
2096
|
+
import { createConnection, createServer } from "node:net";
|
|
2097
|
+
|
|
1914
2098
|
// host/relay/src/durable-atomic-replacement.ts
|
|
1915
2099
|
import { O_RDONLY } from "node:constants";
|
|
1916
2100
|
import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
|
|
@@ -1928,20 +2112,67 @@ var DEFAULT_PORT = {
|
|
|
1928
2112
|
// host/relay/src/relay-request-binding-store.ts
|
|
1929
2113
|
var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
1930
2114
|
|
|
1931
|
-
// host/relay/src/relay-quarantine-store.ts
|
|
1932
|
-
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
1933
|
-
|
|
1934
|
-
// host/relay/src/relay-admission-store.ts
|
|
1935
|
-
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
1936
|
-
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
1937
|
-
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
1938
|
-
|
|
1939
|
-
// host/relay/src/relay-ipc.ts
|
|
1940
|
-
import { createConnection, createServer } from "node:net";
|
|
1941
|
-
|
|
1942
2115
|
// host/relay/src/current-prompt-custody.ts
|
|
1943
2116
|
var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
|
|
1944
2117
|
|
|
2118
|
+
// host/service-log.ts
|
|
2119
|
+
import { copyFile, open as open3, stat, truncate } from "node:fs/promises";
|
|
2120
|
+
import { isAbsolute } from "node:path";
|
|
2121
|
+
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
2122
|
+
function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
|
|
2123
|
+
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 } : {} })}
|
|
2124
|
+
`;
|
|
2125
|
+
try {
|
|
2126
|
+
(sink ?? defaultHostServiceDiagnosticSink)(line);
|
|
2127
|
+
} catch {
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
function defaultHostServiceDiagnosticSink(line) {
|
|
2131
|
+
process.stderr.write(line);
|
|
2132
|
+
}
|
|
2133
|
+
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
2134
|
+
var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
|
|
2135
|
+
function hostServiceLogPreviousPath(path) {
|
|
2136
|
+
return `${path}.1`;
|
|
2137
|
+
}
|
|
2138
|
+
async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
|
|
2139
|
+
let size;
|
|
2140
|
+
try {
|
|
2141
|
+
size = (await stat(path)).size;
|
|
2142
|
+
} catch (error) {
|
|
2143
|
+
if (isNotFound(error)) return false;
|
|
2144
|
+
throw error;
|
|
2145
|
+
}
|
|
2146
|
+
if (size <= maxBytes) return false;
|
|
2147
|
+
await copyFile(path, hostServiceLogPreviousPath(path));
|
|
2148
|
+
await truncate(path, 0);
|
|
2149
|
+
return true;
|
|
2150
|
+
}
|
|
2151
|
+
function boundHostServiceLog(environment = process.env) {
|
|
2152
|
+
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
2153
|
+
if (path === void 0 || !isAbsolute(path)) return () => void 0;
|
|
2154
|
+
let reported = false;
|
|
2155
|
+
const check = () => {
|
|
2156
|
+
rotateHostServiceLog(path).catch((error) => {
|
|
2157
|
+
if (reported) return;
|
|
2158
|
+
reported = true;
|
|
2159
|
+
process.stderr.write(`[host-service-log] rotation_failed code=${errorCode(error)}
|
|
2160
|
+
`);
|
|
2161
|
+
});
|
|
2162
|
+
};
|
|
2163
|
+
check();
|
|
2164
|
+
const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
|
|
2165
|
+
timer.unref();
|
|
2166
|
+
return () => clearInterval(timer);
|
|
2167
|
+
}
|
|
2168
|
+
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
2169
|
+
function isNotFound(error) {
|
|
2170
|
+
return errorCode(error) === "ENOENT";
|
|
2171
|
+
}
|
|
2172
|
+
function errorCode(error) {
|
|
2173
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
2174
|
+
}
|
|
2175
|
+
|
|
1945
2176
|
// host/relay/src/relay-ipc.ts
|
|
1946
2177
|
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
1947
2178
|
"schema_version",
|
|
@@ -1960,6 +2191,7 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
|
1960
2191
|
]);
|
|
1961
2192
|
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
1962
2193
|
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
2194
|
+
var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
|
|
1963
2195
|
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
1964
2196
|
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
1965
2197
|
var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
|
|
@@ -1971,6 +2203,8 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
|
1971
2203
|
"gateway_unavailable",
|
|
1972
2204
|
"gateway_refused",
|
|
1973
2205
|
"gateway_response_invalid",
|
|
2206
|
+
"gateway_rate_limited",
|
|
2207
|
+
"producer_frame_invalid",
|
|
1974
2208
|
"deadline",
|
|
1975
2209
|
"cancelled",
|
|
1976
2210
|
"mint_failed",
|
|
@@ -1984,6 +2218,17 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
|
1984
2218
|
"host_input_mismatch",
|
|
1985
2219
|
"host_prompt_mismatch"
|
|
1986
2220
|
]);
|
|
2221
|
+
var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
|
|
2222
|
+
"context_stale",
|
|
2223
|
+
"context_expired",
|
|
2224
|
+
"host_source_refused",
|
|
2225
|
+
"host_credential_refused",
|
|
2226
|
+
"gateway_unavailable",
|
|
2227
|
+
"gateway_rate_limited",
|
|
2228
|
+
"gateway_refused",
|
|
2229
|
+
"gateway_response_invalid",
|
|
2230
|
+
"producer_frame_invalid"
|
|
2231
|
+
]);
|
|
1987
2232
|
var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
|
|
1988
2233
|
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
1989
2234
|
"permission_denied",
|
|
@@ -2005,8 +2250,17 @@ var HostRelayIpcError = class extends Error {
|
|
|
2005
2250
|
this.failure = failure;
|
|
2006
2251
|
}
|
|
2007
2252
|
};
|
|
2253
|
+
var HostRelayCurrentPromptCapacityRefusal = class extends Error {
|
|
2254
|
+
constructor() {
|
|
2255
|
+
super("current_prompt_capacity_exhausted");
|
|
2256
|
+
this.name = "HostRelayCurrentPromptCapacityRefusal";
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
function hostRelayExchangeFailure(error) {
|
|
2260
|
+
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
2261
|
+
}
|
|
2008
2262
|
function socketErrorFailure(error) {
|
|
2009
|
-
switch (
|
|
2263
|
+
switch (errorCode2(error)) {
|
|
2010
2264
|
case "EACCES":
|
|
2011
2265
|
case "EPERM":
|
|
2012
2266
|
return "permission_denied";
|
|
@@ -2041,8 +2295,14 @@ var HostRelayCurrentPromptPort = class {
|
|
|
2041
2295
|
async register(input) {
|
|
2042
2296
|
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
|
|
2043
2297
|
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2298
|
+
if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
|
|
2044
2299
|
if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
|
|
2045
2300
|
}
|
|
2301
|
+
async release(identity) {
|
|
2302
|
+
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
|
|
2303
|
+
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2304
|
+
if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
|
|
2305
|
+
}
|
|
2046
2306
|
};
|
|
2047
2307
|
function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
|
|
2048
2308
|
return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
|
|
@@ -2125,7 +2385,7 @@ function decodeResponseBuffer(buffer) {
|
|
|
2125
2385
|
function decodeCurrentPromptResponseBuffer(buffer) {
|
|
2126
2386
|
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
|
|
2127
2387
|
if (value === null) return null;
|
|
2128
|
-
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");
|
|
2388
|
+
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");
|
|
2129
2389
|
return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
|
|
2130
2390
|
}
|
|
2131
2391
|
function decodeLengthPrefixedJson(input, maximum) {
|
|
@@ -2147,131 +2407,503 @@ function encodeLengthPrefixed(value) {
|
|
|
2147
2407
|
body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
|
|
2148
2408
|
return output;
|
|
2149
2409
|
}
|
|
2150
|
-
function exactKeys2(value,
|
|
2151
|
-
|
|
2410
|
+
function exactKeys2(value, required, optional = []) {
|
|
2411
|
+
const keys = Object.keys(value);
|
|
2412
|
+
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2152
2413
|
}
|
|
2153
|
-
function
|
|
2414
|
+
function errorCode2(error) {
|
|
2154
2415
|
return record2(error) && typeof error.code === "string" ? error.code : null;
|
|
2155
2416
|
}
|
|
2156
2417
|
|
|
2157
|
-
// host/
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
// host/codex/src/codex-outbox.ts
|
|
2163
|
-
import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
|
|
2164
|
-
import { lstat as lstat3, mkdir as mkdir2, open as open3, readdir, rename as rename2, unlink as unlink2 } from "node:fs/promises";
|
|
2165
|
-
import { dirname, isAbsolute, join as join2 } from "node:path";
|
|
2166
|
-
|
|
2167
|
-
// host/invocation-context.ts
|
|
2168
|
-
var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
|
|
2169
|
-
var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
|
|
2170
|
-
function invocationContextKey(identity) {
|
|
2171
|
-
return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
|
|
2418
|
+
// host/relay/src/host-producer-core.ts
|
|
2419
|
+
var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
|
|
2420
|
+
function isHostTranscriptCursorReason(value) {
|
|
2421
|
+
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2172
2422
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2423
|
+
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2424
|
+
var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
|
|
2425
|
+
function transcriptScanFailureKind(error, adapterFailureKind) {
|
|
2426
|
+
if (transcriptErrorCode(error) === "ELOOP") return "symlink";
|
|
2427
|
+
const adapter = adapterFailureKind(error);
|
|
2428
|
+
if (adapter !== void 0) return adapter;
|
|
2429
|
+
const code = transcriptErrorCode(error);
|
|
2430
|
+
if (code === "EFBIG") return "size_budget";
|
|
2431
|
+
if (code !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code)) return "read_error";
|
|
2432
|
+
return "other";
|
|
2433
|
+
}
|
|
2434
|
+
async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
|
|
2435
|
+
try {
|
|
2436
|
+
await register();
|
|
2437
|
+
diagnostic("current_prompt_registered");
|
|
2438
|
+
return true;
|
|
2439
|
+
} catch (error) {
|
|
2440
|
+
if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
|
|
2441
|
+
throw error;
|
|
2442
|
+
}
|
|
2175
2443
|
}
|
|
2176
|
-
function
|
|
2177
|
-
return
|
|
2444
|
+
function transcriptErrorCode(error) {
|
|
2445
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
2178
2446
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2447
|
+
var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
|
|
2448
|
+
async function readTranscriptCompleteChunk(handle, offset, size) {
|
|
2449
|
+
const available = Math.max(0, size - offset);
|
|
2450
|
+
if (available === 0) return "empty";
|
|
2451
|
+
const chunks = [];
|
|
2452
|
+
let position = offset;
|
|
2453
|
+
let total = 0;
|
|
2454
|
+
let lastNewline = -1;
|
|
2455
|
+
while (position < size) {
|
|
2456
|
+
const buffer = Buffer.alloc(Math.min(HOST_TRANSCRIPT_READ_CHUNK_BYTES, size - position));
|
|
2457
|
+
const read = await handle.read(buffer, 0, buffer.length, position);
|
|
2458
|
+
if (read.bytesRead === 0) break;
|
|
2459
|
+
const chunk = buffer.subarray(0, read.bytesRead);
|
|
2460
|
+
chunks.push(chunk);
|
|
2461
|
+
const newline = chunk.lastIndexOf(10);
|
|
2462
|
+
if (newline >= 0) lastNewline = total + newline;
|
|
2463
|
+
total += chunk.length;
|
|
2464
|
+
position += chunk.length;
|
|
2465
|
+
if (lastNewline >= 0) break;
|
|
2466
|
+
}
|
|
2467
|
+
if (lastNewline < 0) return "partial";
|
|
2468
|
+
return Buffer.concat(chunks, total).subarray(0, lastNewline + 1);
|
|
2189
2469
|
}
|
|
2190
|
-
function
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2470
|
+
async function readTranscriptAnchor(handle, offset, anchorBytes) {
|
|
2471
|
+
const start = Math.max(0, offset - anchorBytes);
|
|
2472
|
+
const anchor = Buffer.alloc(offset - start);
|
|
2473
|
+
if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, start);
|
|
2474
|
+
return { start, sha256: transcriptSha256(anchor) };
|
|
2193
2475
|
}
|
|
2194
|
-
function
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
return
|
|
2476
|
+
async function transcriptAnchorMatches(handle, cursor) {
|
|
2477
|
+
const buffer = Buffer.alloc(cursor.offset - cursor.anchor_start);
|
|
2478
|
+
if (buffer.length > 0) await handle.read(buffer, 0, buffer.length, cursor.anchor_start);
|
|
2479
|
+
return transcriptSha256(buffer) === cursor.anchor_sha256;
|
|
2198
2480
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
var CODEX_TURN_BINDING_SCHEMA_VERSION = "ceal.codex_turn_binding.v1";
|
|
2203
|
-
var CODEX_SESSION_SCHEMA_VERSION = "ceal.codex_session.v1";
|
|
2204
|
-
var CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.codex_invocation_context.v1";
|
|
2205
|
-
var CODEX_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.codex_transcript_registration.v1";
|
|
2206
|
-
var CODEX_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.codex_transcript_cursor.v1";
|
|
2207
|
-
var CODEX_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
2208
|
-
var CODEX_TRANSCRIPT_CURSOR_REASONS = ["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"];
|
|
2209
|
-
function isCodexTranscriptCursorReason(value) {
|
|
2210
|
-
return typeof value === "string" && CODEX_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2481
|
+
function transcriptCursorIdentityReason(cursor, transcriptPath, device, inode) {
|
|
2482
|
+
if (cursor.device !== device || cursor.inode !== inode || cursor.transcript_path !== transcriptPath) return "transcript_replaced";
|
|
2483
|
+
return null;
|
|
2211
2484
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
super(message);
|
|
2217
|
-
this.code = code;
|
|
2218
|
-
}
|
|
2219
|
-
};
|
|
2220
|
-
|
|
2221
|
-
// host/codex/src/codex-invocation-context.ts
|
|
2222
|
-
function codexInvocationContextFromTurn(identity, promptRef, expiresAt) {
|
|
2223
|
-
return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateCodexHostInvocationContext });
|
|
2485
|
+
async function transcriptCursorBoundsReason(handle, cursor, size) {
|
|
2486
|
+
if (size < cursor.offset) return "transcript_truncated";
|
|
2487
|
+
if (cursor.offset > 0 && !await transcriptAnchorMatches(handle, cursor)) return "transcript_rewritten";
|
|
2488
|
+
return null;
|
|
2224
2489
|
}
|
|
2225
|
-
function
|
|
2226
|
-
|
|
2227
|
-
throw new CodexHostAdapterError("storage", "Codex Host invocation context is malformed.");
|
|
2228
|
-
}
|
|
2229
|
-
validateIdentity2(value.identity);
|
|
2230
|
-
validateContextReferences(value);
|
|
2231
|
-
validateContextTimes(value);
|
|
2490
|
+
function transcriptSha256(value) {
|
|
2491
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
2232
2492
|
}
|
|
2233
|
-
function
|
|
2234
|
-
|
|
2235
|
-
schema_version: CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION,
|
|
2236
|
-
identity: binding.identity,
|
|
2237
|
-
prompt_ref: binding.prompt_ref,
|
|
2238
|
-
request_ref: binding.request_ref,
|
|
2239
|
-
bound_at: binding.bound_at,
|
|
2240
|
-
expires_at: binding.expires_at
|
|
2241
|
-
};
|
|
2242
|
-
validateCodexHostInvocationContext(context);
|
|
2243
|
-
return Object.freeze(context);
|
|
2493
|
+
function transcriptObjectSha256(value) {
|
|
2494
|
+
return createHash3("sha256").update(JSON.stringify(value), "utf8").digest("hex");
|
|
2244
2495
|
}
|
|
2245
|
-
function
|
|
2246
|
-
|
|
2247
|
-
throw new CodexHostAdapterError("storage", "Codex Host invocation identity is malformed.");
|
|
2248
|
-
}
|
|
2496
|
+
function isTranscriptRecord(value) {
|
|
2497
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2249
2498
|
}
|
|
2250
|
-
function
|
|
2251
|
-
return
|
|
2499
|
+
function isMissingTranscriptError(error) {
|
|
2500
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2252
2501
|
}
|
|
2253
|
-
function
|
|
2254
|
-
|
|
2502
|
+
function transcriptTimestamp(now, fail) {
|
|
2503
|
+
const value = now();
|
|
2504
|
+
if (!Number.isFinite(value.getTime())) throw fail();
|
|
2505
|
+
return value.toISOString();
|
|
2255
2506
|
}
|
|
2256
|
-
function
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2507
|
+
function transcriptExpiration(now, retentionMs, fail) {
|
|
2508
|
+
return new Date(Date.parse(transcriptTimestamp(now, fail)) + retentionMs).toISOString();
|
|
2509
|
+
}
|
|
2510
|
+
function createTranscriptWatcherBase(options, owner) {
|
|
2511
|
+
if (typeof options.outbox_dir !== "string" || options.outbox_dir.length === 0) throw owner.invalid_outbox();
|
|
2512
|
+
return {
|
|
2513
|
+
outboxRetentionMs: options.retention_ms ?? owner.default_retention_ms,
|
|
2514
|
+
now: options.now ?? (() => /* @__PURE__ */ new Date()),
|
|
2515
|
+
log: options.log ?? ((line) => {
|
|
2516
|
+
process.stderr.write(`${line}
|
|
2517
|
+
`);
|
|
2518
|
+
}),
|
|
2519
|
+
producerInstallationRef: options.producer_installation_ref
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
async function scanTranscriptRegistrations(registrations, scanOne) {
|
|
2523
|
+
let records = 0;
|
|
2524
|
+
let enqueued = 0;
|
|
2525
|
+
let quarantined = 0;
|
|
2526
|
+
for (const registration of registrations) {
|
|
2527
|
+
const result = await scanOne(registration);
|
|
2528
|
+
records += result.records;
|
|
2529
|
+
enqueued += result.enqueued;
|
|
2530
|
+
if (result.quarantined) quarantined += 1;
|
|
2531
|
+
}
|
|
2532
|
+
return { registrations: registrations.length, records, enqueued, quarantined };
|
|
2533
|
+
}
|
|
2534
|
+
function formatRetiredTranscriptLog(producerLabel, transcriptPath) {
|
|
2535
|
+
return `${producerLabel} retired a transcript registration: transcript_path=${JSON.stringify(transcriptPath)}, reason=${TRANSCRIPT_MISSING_REASON}.`;
|
|
2536
|
+
}
|
|
2537
|
+
var HostTranscriptWatcher = class {
|
|
2538
|
+
outboxRetentionMs;
|
|
2539
|
+
now;
|
|
2540
|
+
log;
|
|
2541
|
+
producerInstallationRef;
|
|
2542
|
+
clockFailure;
|
|
2543
|
+
constructor(options, owner) {
|
|
2544
|
+
const base = createTranscriptWatcherBase(options, owner);
|
|
2545
|
+
this.outboxRetentionMs = base.outboxRetentionMs;
|
|
2546
|
+
this.now = base.now;
|
|
2547
|
+
this.log = base.log;
|
|
2548
|
+
this.producerInstallationRef = base.producerInstallationRef;
|
|
2549
|
+
this.clockFailure = owner.clock_failure;
|
|
2550
|
+
}
|
|
2551
|
+
async scanOnce() {
|
|
2552
|
+
return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
|
|
2553
|
+
}
|
|
2554
|
+
async scanRegistration(registration) {
|
|
2555
|
+
try {
|
|
2556
|
+
return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
|
|
2557
|
+
missing: () => this.retireMissing(registration),
|
|
2558
|
+
failed: (handle, error) => this.scanFailed(handle, registration, error)
|
|
2559
|
+
});
|
|
2560
|
+
} catch (error) {
|
|
2561
|
+
if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
|
|
2562
|
+
throw error;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
async openTranscript(registration, scan, handlers) {
|
|
2566
|
+
let handle;
|
|
2567
|
+
try {
|
|
2568
|
+
handle = await open4(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
|
|
2569
|
+
return await scan(handle);
|
|
2570
|
+
} catch (error) {
|
|
2571
|
+
if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
|
|
2572
|
+
if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
|
|
2573
|
+
return handlers.failed(handle, error);
|
|
2574
|
+
} finally {
|
|
2575
|
+
await handle?.close();
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* A transcript the Host deleted never comes back, so retrying its
|
|
2580
|
+
* registration once per reconcile interval is a failure with no end.
|
|
2581
|
+
* Retire it at the scan that finds it missing and say so once. Only
|
|
2582
|
+
* ENOENT means gone; a permission or busy failure is about this moment,
|
|
2583
|
+
* not about the file, and keeps the registration for the next scan.
|
|
2584
|
+
*/
|
|
2585
|
+
async retireMissingTranscript(transcriptPath, producerLabel, retire) {
|
|
2586
|
+
await retire();
|
|
2587
|
+
this.log(formatRetiredTranscriptLog(producerLabel, transcriptPath));
|
|
2588
|
+
return { records: 0, enqueued: 0, quarantined: false };
|
|
2589
|
+
}
|
|
2590
|
+
stamp() {
|
|
2591
|
+
return transcriptTimestamp(this.now, this.clockFailure);
|
|
2592
|
+
}
|
|
2593
|
+
expire(retentionMs) {
|
|
2594
|
+
return transcriptExpiration(this.now, retentionMs, this.clockFailure);
|
|
2595
|
+
}
|
|
2596
|
+
sha(value) {
|
|
2597
|
+
return transcriptSha256(value);
|
|
2598
|
+
}
|
|
2599
|
+
objectSha(value) {
|
|
2600
|
+
return transcriptObjectSha256(value);
|
|
2601
|
+
}
|
|
2602
|
+
isRecord(value) {
|
|
2603
|
+
return isTranscriptRecord(value);
|
|
2604
|
+
}
|
|
2605
|
+
readChunk(handle, offset, size) {
|
|
2606
|
+
return readTranscriptCompleteChunk(handle, offset, size);
|
|
2607
|
+
}
|
|
2608
|
+
readAnchor(handle, offset, anchorBytes) {
|
|
2609
|
+
return readTranscriptAnchor(handle, offset, anchorBytes);
|
|
2610
|
+
}
|
|
2611
|
+
cursorIdentityReason(cursor, transcriptPath, device, inode) {
|
|
2612
|
+
return transcriptCursorIdentityReason(cursor, transcriptPath, device, inode);
|
|
2613
|
+
}
|
|
2614
|
+
cursorBoundsReason(handle, cursor, size) {
|
|
2615
|
+
return transcriptCursorBoundsReason(handle, cursor, size);
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
var HOST_PRODUCER_STATE_IDENTITY = {
|
|
2619
|
+
codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
|
|
2620
|
+
claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
|
|
2621
|
+
};
|
|
2622
|
+
var HostProducerDrainCore = class {
|
|
2623
|
+
host;
|
|
2624
|
+
relay;
|
|
2625
|
+
statePath;
|
|
2626
|
+
stateSchema;
|
|
2627
|
+
installationRef;
|
|
2628
|
+
epoch;
|
|
2629
|
+
now;
|
|
2630
|
+
tail = Promise.resolve();
|
|
2631
|
+
constructor(host, runtime) {
|
|
2632
|
+
if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
|
|
2633
|
+
const epoch = runtime.producer_epoch ?? 1;
|
|
2634
|
+
if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
|
|
2635
|
+
this.host = host;
|
|
2636
|
+
this.relay = runtime.relay;
|
|
2637
|
+
this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
|
|
2638
|
+
this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
|
|
2639
|
+
this.installationRef = runtime.producer_installation_ref;
|
|
2640
|
+
this.epoch = epoch;
|
|
2641
|
+
this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
|
|
2642
|
+
}
|
|
2643
|
+
drainOutbox() {
|
|
2644
|
+
const run = this.tail.then(() => this.drainLocked());
|
|
2645
|
+
this.tail = run.then(() => void 0, () => void 0);
|
|
2646
|
+
return run;
|
|
2647
|
+
}
|
|
2648
|
+
async drainLocked() {
|
|
2649
|
+
const read = await this.host.listEnvelopes();
|
|
2650
|
+
let admitted = 0;
|
|
2651
|
+
let expired = 0;
|
|
2652
|
+
let retained = 0;
|
|
2653
|
+
for (const envelope of read.entries) {
|
|
2654
|
+
const reserved = await this.hasReservation(this.host.submissionRefOf(envelope));
|
|
2655
|
+
if (!reserved && Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) {
|
|
2656
|
+
await this.host.removeEnvelope(envelope);
|
|
2657
|
+
expired += 1;
|
|
2658
|
+
continue;
|
|
2659
|
+
}
|
|
2660
|
+
const frames = await this.reserve(envelope);
|
|
2661
|
+
let complete = true;
|
|
2662
|
+
for (const frame of frames) {
|
|
2663
|
+
const outcome = await this.relay.admit(frame);
|
|
2664
|
+
if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
|
|
2665
|
+
complete = false;
|
|
2666
|
+
break;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
if (!complete) {
|
|
2670
|
+
retained += 1;
|
|
2671
|
+
break;
|
|
2672
|
+
}
|
|
2673
|
+
await this.host.removeEnvelope(envelope);
|
|
2674
|
+
await this.release(this.host.submissionRefOf(envelope));
|
|
2675
|
+
admitted += 1;
|
|
2676
|
+
}
|
|
2677
|
+
return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
|
|
2678
|
+
}
|
|
2679
|
+
async hasReservation(submissionRef) {
|
|
2680
|
+
return (await this.readState()).pending[submissionRef] !== void 0;
|
|
2681
|
+
}
|
|
2682
|
+
async reserve(envelope) {
|
|
2683
|
+
const state = await this.readState();
|
|
2684
|
+
const submissionRef = this.host.submissionRefOf(envelope);
|
|
2685
|
+
const existing = state.pending[submissionRef];
|
|
2686
|
+
if (existing !== void 0) return existing;
|
|
2687
|
+
const drafts = await this.host.mapEnvelope(envelope);
|
|
2688
|
+
const frames = drafts.map((draft, offset) => ({
|
|
2689
|
+
schema_version: "ceal.host_producer_frame.v1",
|
|
2690
|
+
producer_id: this.host.producer_id,
|
|
2691
|
+
producer_installation_ref: this.installationRef,
|
|
2692
|
+
producer_epoch: this.epoch,
|
|
2693
|
+
sequence: state.next_sequence + offset,
|
|
2694
|
+
frame_kind: draft.frame_kind,
|
|
2695
|
+
occurred_at: this.host.createdAtOf(envelope),
|
|
2696
|
+
payload: draft.payload
|
|
2697
|
+
}));
|
|
2698
|
+
await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef]: frames } });
|
|
2699
|
+
return frames;
|
|
2700
|
+
}
|
|
2701
|
+
async release(submissionRef) {
|
|
2702
|
+
const state = await this.readState();
|
|
2703
|
+
if (state.pending[submissionRef] === void 0) return;
|
|
2704
|
+
const pending = { ...state.pending };
|
|
2705
|
+
delete pending[submissionRef];
|
|
2706
|
+
await this.writeState({ ...state, pending });
|
|
2707
|
+
}
|
|
2708
|
+
async readState() {
|
|
2709
|
+
try {
|
|
2710
|
+
const value = JSON.parse(await readFile(this.statePath, "utf8"));
|
|
2711
|
+
return this.decodeState(value);
|
|
2712
|
+
} catch (error) {
|
|
2713
|
+
if (errorCode3(error) !== "ENOENT") throw error;
|
|
2714
|
+
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
async writeState(state) {
|
|
2718
|
+
await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
|
|
2719
|
+
const temporary = `${this.statePath}.${process.pid}.new`;
|
|
2720
|
+
await writeFile(temporary, JSON.stringify(state), { mode: 384 });
|
|
2721
|
+
await rename2(temporary, this.statePath);
|
|
2722
|
+
}
|
|
2723
|
+
decodeState(value) {
|
|
2724
|
+
const label = this.host.producer_label;
|
|
2725
|
+
const fail = (code, message) => this.host.fail(code, message);
|
|
2726
|
+
if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
|
|
2727
|
+
throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
|
|
2728
|
+
{ name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
|
|
2729
|
+
{ name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
|
|
2730
|
+
])}).`);
|
|
2731
|
+
}
|
|
2732
|
+
if (Number(value.producer_epoch) > this.epoch) throw fail("storage", `${label} producer state epoch is ahead of this installation (${refusalFact("producer_epoch", value.producer_epoch, `at most ${this.epoch}`)}).`);
|
|
2733
|
+
const pending = {};
|
|
2734
|
+
const storedEpoch = Number(value.producer_epoch);
|
|
2735
|
+
for (const [submissionRef, frames] of Object.entries(value.pending)) {
|
|
2736
|
+
if (!isHostSafeRef(submissionRef) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
|
|
2737
|
+
pending[submissionRef] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
|
|
2738
|
+
}
|
|
2739
|
+
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
|
|
2740
|
+
}
|
|
2741
|
+
decodeStoredFrame(value, storedEpoch) {
|
|
2742
|
+
const label = this.host.producer_label;
|
|
2743
|
+
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
2744
|
+
const encoded = Buffer.allocUnsafe(json.length + 4);
|
|
2745
|
+
encoded.writeUInt32BE(json.length, 0);
|
|
2746
|
+
json.copy(encoded, 4);
|
|
2747
|
+
const frame = decodeHostProducerFrame(encoded);
|
|
2748
|
+
if (frame.producer_id !== this.host.producer_id || frame.producer_installation_ref !== this.installationRef || frame.producer_epoch > storedEpoch) throw this.host.fail("storage", `${label} producer pending frame identity is malformed (${refusalFacts([
|
|
2749
|
+
{ name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
|
|
2750
|
+
{ name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
|
|
2751
|
+
{ name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
|
|
2752
|
+
])}).`);
|
|
2753
|
+
return frame;
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
function isDrainStateHeader(value, schema, installationRef) {
|
|
2757
|
+
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);
|
|
2758
|
+
}
|
|
2759
|
+
function errorCode3(error) {
|
|
2760
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// host/relay/src/relay-quarantine-store.ts
|
|
2764
|
+
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2765
|
+
var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
|
|
2766
|
+
var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
|
|
2767
|
+
|
|
2768
|
+
// host/relay/src/relay-admission-store.ts
|
|
2769
|
+
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
2770
|
+
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2771
|
+
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2772
|
+
|
|
2773
|
+
// host/codex/src/codex-producer.ts
|
|
2774
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2775
|
+
|
|
2776
|
+
// host/codex/src/codex-outbox.ts
|
|
2777
|
+
import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
|
|
2778
|
+
import { lstat as lstat3, mkdir as mkdir3, open as open5, readdir, rename as rename3, unlink as unlink2 } from "node:fs/promises";
|
|
2779
|
+
import { dirname, isAbsolute as isAbsolute2, join as join3 } from "node:path";
|
|
2780
|
+
|
|
2781
|
+
// host/invocation-context.ts
|
|
2782
|
+
var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
|
|
2783
|
+
var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
|
|
2784
|
+
function invocationContextKey(identity) {
|
|
2785
|
+
return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
|
|
2786
|
+
}
|
|
2787
|
+
function hasHostInvocationIdentityKeys(value) {
|
|
2788
|
+
return exactKeys3(value, HOST_INVOCATION_IDENTITY_KEYS);
|
|
2789
|
+
}
|
|
2790
|
+
function hasHostInvocationContextKeys(value, optional = []) {
|
|
2791
|
+
return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
|
|
2792
|
+
}
|
|
2793
|
+
async function readLatestSessionProjection(file, hostSourceRef, hostSessionRef, readProjection) {
|
|
2794
|
+
const context = await readProjection(file);
|
|
2795
|
+
if (context === null || context.identity.host_source_ref !== hostSourceRef || context.identity.host_session_ref !== hostSessionRef) return null;
|
|
2796
|
+
return context;
|
|
2797
|
+
}
|
|
2798
|
+
function isLiveSessionProjection(context, now) {
|
|
2799
|
+
return context !== null && Date.parse(context.expires_at) > now.getTime();
|
|
2800
|
+
}
|
|
2801
|
+
function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
|
|
2802
|
+
const context = {
|
|
2803
|
+
schema_version: descriptor.schema_version,
|
|
2804
|
+
identity,
|
|
2805
|
+
prompt_ref: promptRef,
|
|
2806
|
+
expires_at: expiresAt,
|
|
2807
|
+
...descriptor.turn_generation_ref === void 0 ? {} : { turn_generation_ref: descriptor.turn_generation_ref },
|
|
2808
|
+
...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
|
|
2809
|
+
};
|
|
2810
|
+
descriptor.validate(context);
|
|
2811
|
+
return Object.freeze(context);
|
|
2812
|
+
}
|
|
2813
|
+
function isHostInvocationTurnSelection(value) {
|
|
2814
|
+
return isRecord2(value) && Object.keys(value).length === 5 && value.schema_version === "ceal.host_invocation_turn_selection.v1" && ["host_source_ref", "host_session_ref", "prompt_ref", "turn_generation_ref"].every((key) => safeRef2(value[key]));
|
|
2815
|
+
}
|
|
2816
|
+
function invocationTurnSelectionMatches(context, selection) {
|
|
2817
|
+
return selection === null || context !== null && selection.host_source_ref === context.identity.host_source_ref && selection.host_session_ref === context.identity.host_session_ref && selection.prompt_ref === context.prompt_ref && selection.turn_generation_ref === context.turn_generation_ref;
|
|
2818
|
+
}
|
|
2819
|
+
function exactKeys3(value, keys) {
|
|
2820
|
+
const actual = Object.keys(value);
|
|
2821
|
+
return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
|
|
2822
|
+
}
|
|
2823
|
+
function requiredKeys(value, required, optional) {
|
|
2824
|
+
const actual = Object.keys(value);
|
|
2825
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
2826
|
+
return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
|
|
2827
|
+
}
|
|
2828
|
+
function isRecord2(value) {
|
|
2829
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2830
|
+
}
|
|
2831
|
+
function safeRef2(value) {
|
|
2832
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
// host/codex/src/codex-types.ts
|
|
2836
|
+
var CODEX_OUTBOX_SCHEMA_VERSION = "ceal.codex_observation_outbox.v1";
|
|
2837
|
+
var CODEX_TURN_BINDING_SCHEMA_VERSION = "ceal.codex_turn_binding.v1";
|
|
2838
|
+
var CODEX_SESSION_SCHEMA_VERSION = "ceal.codex_session.v1";
|
|
2839
|
+
var CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.codex_invocation_context.v1";
|
|
2840
|
+
var CODEX_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.codex_transcript_registration.v1";
|
|
2841
|
+
var CODEX_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.codex_transcript_cursor.v1";
|
|
2842
|
+
var CODEX_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
2843
|
+
var CodexHostAdapterError = class extends Error {
|
|
2844
|
+
name = "CodexHostAdapterError";
|
|
2845
|
+
code;
|
|
2846
|
+
constructor(code, message) {
|
|
2847
|
+
super(message);
|
|
2848
|
+
this.code = code;
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
|
|
2852
|
+
// host/codex/src/codex-invocation-context.ts
|
|
2853
|
+
function codexInvocationContextFromTurn(identity, promptRef, expiresAt, turnGenerationRef) {
|
|
2854
|
+
return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateCodexHostInvocationContext, turn_generation_ref: turnGenerationRef });
|
|
2855
|
+
}
|
|
2856
|
+
function validateCodexHostInvocationContext(value) {
|
|
2857
|
+
if (!isContextShape(value)) {
|
|
2858
|
+
throw new CodexHostAdapterError("storage", "Codex Host invocation context is malformed.");
|
|
2859
|
+
}
|
|
2860
|
+
validateIdentity2(value.identity);
|
|
2861
|
+
validateContextReferences(value);
|
|
2862
|
+
validateContextTimes(value);
|
|
2863
|
+
}
|
|
2864
|
+
function invocationContextFromBinding(binding, turnGenerationRef) {
|
|
2865
|
+
const context = {
|
|
2866
|
+
schema_version: CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION,
|
|
2867
|
+
identity: binding.identity,
|
|
2868
|
+
prompt_ref: binding.prompt_ref,
|
|
2869
|
+
request_ref: binding.request_ref,
|
|
2870
|
+
bound_at: binding.bound_at,
|
|
2871
|
+
expires_at: binding.expires_at,
|
|
2872
|
+
...turnGenerationRef === void 0 ? {} : { turn_generation_ref: turnGenerationRef }
|
|
2873
|
+
};
|
|
2874
|
+
validateCodexHostInvocationContext(context);
|
|
2875
|
+
return Object.freeze(context);
|
|
2876
|
+
}
|
|
2877
|
+
function validateIdentity2(value) {
|
|
2878
|
+
if (!isRecord3(value) || !hasHostInvocationIdentityKeys(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef3)) {
|
|
2879
|
+
throw new CodexHostAdapterError("storage", "Codex Host invocation identity is malformed.");
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
function isContextShape(value) {
|
|
2883
|
+
return isRecord3(value) && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "turn_generation_ref"]) && value.schema_version === CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION;
|
|
2884
|
+
}
|
|
2885
|
+
function validateContextReferences(value) {
|
|
2886
|
+
if (!safeRef3(value.prompt_ref) || !optionalSafeRef(value.request_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new CodexHostAdapterError("storage", "Codex Host invocation context is malformed.");
|
|
2887
|
+
}
|
|
2888
|
+
function validateContextTimes(value) {
|
|
2889
|
+
const expiresAt = value.expires_at;
|
|
2890
|
+
const boundAt = value.bound_at;
|
|
2891
|
+
if (!timestamp2(expiresAt) || !optionalTimestamp(boundAt)) throw new CodexHostAdapterError("storage", "Codex Host invocation context is malformed.");
|
|
2260
2892
|
if (typeof boundAt === "string" && Date.parse(boundAt) > Date.parse(expiresAt)) throw new CodexHostAdapterError("storage", `Codex Host invocation context has invalid binding time (${refusalFact("bound_at", boundAt, `at or before ${expiresAt}`)}).`);
|
|
2261
2893
|
}
|
|
2262
2894
|
function optionalSafeRef(value) {
|
|
2263
|
-
return value === void 0 ||
|
|
2895
|
+
return value === void 0 || safeRef3(value);
|
|
2264
2896
|
}
|
|
2265
2897
|
function optionalTimestamp(value) {
|
|
2266
2898
|
return value === void 0 || timestamp2(value);
|
|
2267
2899
|
}
|
|
2268
|
-
function
|
|
2900
|
+
function safeRef3(value) {
|
|
2269
2901
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2270
2902
|
}
|
|
2271
2903
|
function timestamp2(value) {
|
|
2272
2904
|
return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
|
|
2273
2905
|
}
|
|
2274
|
-
function
|
|
2906
|
+
function isRecord3(value) {
|
|
2275
2907
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2276
2908
|
}
|
|
2277
2909
|
|
|
@@ -2284,20 +2916,22 @@ var CodexOutboxStore = class {
|
|
|
2284
2916
|
bindingsDirectory;
|
|
2285
2917
|
sessionsDirectory;
|
|
2286
2918
|
activeContextsDirectory;
|
|
2919
|
+
turnSelectionsDirectory;
|
|
2287
2920
|
registrationsDirectory;
|
|
2288
2921
|
cursorsDirectory;
|
|
2289
2922
|
constructor(directory) {
|
|
2290
2923
|
if (typeof directory !== "string" || directory.length === 0) throw new CodexHostAdapterError("configuration", "Codex outbox directory is required.");
|
|
2291
2924
|
this.directory = directory;
|
|
2292
|
-
this.entriesDirectory =
|
|
2293
|
-
this.bindingsDirectory =
|
|
2294
|
-
this.sessionsDirectory =
|
|
2295
|
-
this.activeContextsDirectory =
|
|
2296
|
-
this.
|
|
2297
|
-
this.
|
|
2925
|
+
this.entriesDirectory = join3(directory, "entries");
|
|
2926
|
+
this.bindingsDirectory = join3(directory, "bindings");
|
|
2927
|
+
this.sessionsDirectory = join3(directory, "sessions");
|
|
2928
|
+
this.activeContextsDirectory = join3(directory, "active-contexts");
|
|
2929
|
+
this.turnSelectionsDirectory = join3(directory, "turn-selections");
|
|
2930
|
+
this.registrationsDirectory = join3(directory, "transcript-registrations");
|
|
2931
|
+
this.cursorsDirectory = join3(directory, "transcript-cursors");
|
|
2298
2932
|
}
|
|
2299
2933
|
async prepare() {
|
|
2300
|
-
for (const path of [this.directory, this.entriesDirectory, this.bindingsDirectory, this.sessionsDirectory, this.activeContextsDirectory, this.registrationsDirectory, this.cursorsDirectory]) {
|
|
2934
|
+
for (const path of [this.directory, this.entriesDirectory, this.bindingsDirectory, this.sessionsDirectory, this.activeContextsDirectory, this.turnSelectionsDirectory, this.registrationsDirectory, this.cursorsDirectory]) {
|
|
2301
2935
|
await ensurePrivateOwnerDirectory(path);
|
|
2302
2936
|
}
|
|
2303
2937
|
}
|
|
@@ -2313,7 +2947,7 @@ var CodexOutboxStore = class {
|
|
|
2313
2947
|
for (const name of await readdir(this.entriesDirectory)) {
|
|
2314
2948
|
if (!name.endsWith(".json")) continue;
|
|
2315
2949
|
try {
|
|
2316
|
-
const value = JSON.parse(await readPrivateFile(
|
|
2950
|
+
const value = JSON.parse(await readPrivateFile(join3(this.entriesDirectory, name)));
|
|
2317
2951
|
validateEnvelope(value);
|
|
2318
2952
|
entries.push(value);
|
|
2319
2953
|
} catch {
|
|
@@ -2328,7 +2962,7 @@ var CodexOutboxStore = class {
|
|
|
2328
2962
|
try {
|
|
2329
2963
|
await unlink2(this.entryPath(entry.submission_ref));
|
|
2330
2964
|
} catch (error) {
|
|
2331
|
-
if (!
|
|
2965
|
+
if (!isNotFound2(error)) throw storageError("Unable to remove the Codex outbox entry.");
|
|
2332
2966
|
}
|
|
2333
2967
|
}
|
|
2334
2968
|
async writeBinding(binding) {
|
|
@@ -2341,8 +2975,8 @@ var CodexOutboxStore = class {
|
|
|
2341
2975
|
* prompt replaces this projection, so it cannot inherit the prior turn's
|
|
2342
2976
|
* Request by session alone.
|
|
2343
2977
|
*/
|
|
2344
|
-
async activateInvocationContext(identity, promptRef, expiresAt) {
|
|
2345
|
-
const context = codexInvocationContextFromTurn(identity, promptRef, expiresAt);
|
|
2978
|
+
async activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef) {
|
|
2979
|
+
const context = codexInvocationContextFromTurn(identity, promptRef, expiresAt, turnGenerationRef);
|
|
2346
2980
|
await this.prepare();
|
|
2347
2981
|
const path = this.activeContextPath(identity);
|
|
2348
2982
|
try {
|
|
@@ -2351,11 +2985,30 @@ var CodexOutboxStore = class {
|
|
|
2351
2985
|
try {
|
|
2352
2986
|
await unlink2(path);
|
|
2353
2987
|
} catch (unlinkError) {
|
|
2354
|
-
if (!
|
|
2988
|
+
if (!isNotFound2(unlinkError)) throw storageError("Unable to clear a failed Codex Host context replacement.");
|
|
2355
2989
|
}
|
|
2356
2990
|
throw error;
|
|
2357
2991
|
}
|
|
2358
2992
|
}
|
|
2993
|
+
async writeInvocationTurnSelection(selection) {
|
|
2994
|
+
if (!isHostInvocationTurnSelection(selection)) throw new CodexHostAdapterError("invalid_input", "Codex Host turn selection is invalid.");
|
|
2995
|
+
await this.prepare();
|
|
2996
|
+
await writeAtomicJson(this.turnSelectionPath(selection.host_source_ref, selection.host_session_ref), selection);
|
|
2997
|
+
}
|
|
2998
|
+
async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
|
|
2999
|
+
if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new CodexHostAdapterError("invalid_input", "Codex Host turn selection identity is invalid.");
|
|
3000
|
+
await this.prepare();
|
|
3001
|
+
return readOptionalJson(this.turnSelectionPath(hostSourceRef, hostSessionRef), validateTurnSelection);
|
|
3002
|
+
}
|
|
3003
|
+
async attachInvocationTurnGeneration(identity, promptRef, turnGenerationRef) {
|
|
3004
|
+
const selection = await this.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
3005
|
+
if (selection === null || selection.prompt_ref !== promptRef || selection.turn_generation_ref !== turnGenerationRef) return false;
|
|
3006
|
+
const path = this.activeContextPath(identity);
|
|
3007
|
+
const active = await readOptionalJson(path, validateCodexHostInvocationContext);
|
|
3008
|
+
if (active === null || active.prompt_ref !== promptRef || !sameIdentity(active.identity, identity)) return false;
|
|
3009
|
+
await writeAtomicJson(path, { ...active, turn_generation_ref: turnGenerationRef });
|
|
3010
|
+
return true;
|
|
3011
|
+
}
|
|
2359
3012
|
/**
|
|
2360
3013
|
* Publish a Request reference only while the acknowledged binding is still
|
|
2361
3014
|
* the active turn. A delayed relay response therefore cannot overwrite a
|
|
@@ -2365,17 +3018,25 @@ var CodexOutboxStore = class {
|
|
|
2365
3018
|
validateBinding(binding);
|
|
2366
3019
|
const active = await this.readInvocationContext(binding.identity.host_source_ref, binding.identity.host_session_ref, now);
|
|
2367
3020
|
if (active === null || !sameIdentity(active.identity, binding.identity)) return false;
|
|
2368
|
-
await writeAtomicJson(this.activeContextPath(binding.identity), invocationContextFromBinding(binding));
|
|
3021
|
+
await writeAtomicJson(this.activeContextPath(binding.identity), invocationContextFromBinding(binding, active.turn_generation_ref));
|
|
2369
3022
|
return true;
|
|
2370
3023
|
}
|
|
2371
|
-
/** Return only the
|
|
3024
|
+
/** Return only the live session projection; never fall back to another turn. */
|
|
2372
3025
|
async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
|
|
2373
|
-
if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new CodexHostAdapterError("invalid_input", "Codex Host session identity is invalid.");
|
|
2374
3026
|
if (!(now instanceof Date) || !Number.isFinite(now.getTime())) throw new CodexHostAdapterError("invalid_input", "Codex Host context clock is invalid.");
|
|
3027
|
+
const context = await this.readRawInvocationContext(hostSourceRef, hostSessionRef);
|
|
3028
|
+
return isLiveSessionProjection(context, now) && invocationTurnSelectionMatches(context, await this.readInvocationTurnSelection(hostSourceRef, hostSessionRef)) ? context : null;
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* The latest Codex session projection even when past expiry, so the
|
|
3032
|
+
* invocation authenticator can report an expired context distinctly from
|
|
3033
|
+
* a missing one. Every other caller keeps the fail-closed read above.
|
|
3034
|
+
*/
|
|
3035
|
+
async readRawInvocationContext(hostSourceRef, hostSessionRef) {
|
|
3036
|
+
if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new CodexHostAdapterError("invalid_input", "Codex Host session identity is invalid.");
|
|
2375
3037
|
await this.prepare();
|
|
2376
|
-
const context = await
|
|
2377
|
-
|
|
2378
|
-
return Date.parse(context.expires_at) > now.getTime() ? context : null;
|
|
3038
|
+
const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, (file) => readOptionalJson(file, validateCodexHostInvocationContext));
|
|
3039
|
+
return invocationTurnSelectionMatches(context, await this.readInvocationTurnSelection(hostSourceRef, hostSessionRef)) ? context : null;
|
|
2379
3040
|
}
|
|
2380
3041
|
/** Clear the projection only if the caller still owns its exact active turn. */
|
|
2381
3042
|
async clearInvocationContext(identity, now = /* @__PURE__ */ new Date()) {
|
|
@@ -2385,7 +3046,7 @@ var CodexOutboxStore = class {
|
|
|
2385
3046
|
try {
|
|
2386
3047
|
await unlink2(this.activeContextPath(identity));
|
|
2387
3048
|
} catch (error) {
|
|
2388
|
-
if (!
|
|
3049
|
+
if (!isNotFound2(error)) throw storageError("Unable to clear the Codex Host invocation context.");
|
|
2389
3050
|
}
|
|
2390
3051
|
return true;
|
|
2391
3052
|
}
|
|
@@ -2434,7 +3095,7 @@ var CodexOutboxStore = class {
|
|
|
2434
3095
|
for (const name of await readdir(this.registrationsDirectory)) {
|
|
2435
3096
|
if (!name.endsWith(".json")) continue;
|
|
2436
3097
|
try {
|
|
2437
|
-
const value = JSON.parse(await readPrivateFile(
|
|
3098
|
+
const value = JSON.parse(await readPrivateFile(join3(this.registrationsDirectory, name)));
|
|
2438
3099
|
validateRegistration(value);
|
|
2439
3100
|
registrations.push(value);
|
|
2440
3101
|
} catch {
|
|
@@ -2476,29 +3137,32 @@ var CodexOutboxStore = class {
|
|
|
2476
3137
|
}
|
|
2477
3138
|
entryPath(submissionRef) {
|
|
2478
3139
|
if (!SAFE_REF4.test(submissionRef)) throw storageError("Codex submission identity is malformed.");
|
|
2479
|
-
return
|
|
3140
|
+
return join3(this.entriesDirectory, `${cealRequestSha256(submissionRef)}.json`);
|
|
2480
3141
|
}
|
|
2481
3142
|
bindingPath(identity) {
|
|
2482
|
-
return
|
|
3143
|
+
return join3(this.bindingsDirectory, `${identityKey(identity)}.json`);
|
|
2483
3144
|
}
|
|
2484
3145
|
sessionPath(sourceRef, sessionRef) {
|
|
2485
|
-
return
|
|
3146
|
+
return join3(this.sessionsDirectory, `${identityKey({ host_source_ref: sourceRef, host_session_ref: sessionRef, host_turn_ref: "session" })}.json`);
|
|
2486
3147
|
}
|
|
2487
3148
|
activeContextPath(identity) {
|
|
2488
|
-
return
|
|
3149
|
+
return join3(this.activeContextsDirectory, `${invocationContextKey(identity)}.json`);
|
|
3150
|
+
}
|
|
3151
|
+
turnSelectionPath(sourceRef, sessionRef) {
|
|
3152
|
+
return join3(this.turnSelectionsDirectory, `${invocationContextKey({ host_source_ref: sourceRef, host_session_ref: sessionRef })}.json`);
|
|
2489
3153
|
}
|
|
2490
3154
|
registrationPath(sourceRef, sessionRef) {
|
|
2491
|
-
return
|
|
3155
|
+
return join3(this.registrationsDirectory, `${sessionStateKey(sourceRef, sessionRef)}.json`);
|
|
2492
3156
|
}
|
|
2493
3157
|
cursorPath(sourceRef, sessionRef) {
|
|
2494
|
-
return
|
|
3158
|
+
return join3(this.cursorsDirectory, `${sessionStateKey(sourceRef, sessionRef)}.json`);
|
|
2495
3159
|
}
|
|
2496
3160
|
};
|
|
2497
3161
|
async function removeOwnerState(path, what) {
|
|
2498
3162
|
try {
|
|
2499
3163
|
await unlink2(path);
|
|
2500
3164
|
} catch (error) {
|
|
2501
|
-
if (!
|
|
3165
|
+
if (!isNotFound2(error)) throw storageError(`Unable to retire the Codex transcript ${what}.`);
|
|
2502
3166
|
}
|
|
2503
3167
|
}
|
|
2504
3168
|
function sessionStateKey(sourceRef, sessionRef) {
|
|
@@ -2529,7 +3193,7 @@ async function findBindingByAlias(directory, sourceRef, sessionRef, alias) {
|
|
|
2529
3193
|
}
|
|
2530
3194
|
async function readBindingCandidate(directory, name) {
|
|
2531
3195
|
try {
|
|
2532
|
-
const value = JSON.parse(await readPrivateFile(
|
|
3196
|
+
const value = JSON.parse(await readPrivateFile(join3(directory, name)));
|
|
2533
3197
|
validateBinding(value);
|
|
2534
3198
|
return value;
|
|
2535
3199
|
} catch {
|
|
@@ -2548,14 +3212,14 @@ async function writeAtomicJson(path, value) {
|
|
|
2548
3212
|
if (text === void 0) throw storageError("Codex owner state cannot be serialized.");
|
|
2549
3213
|
const temp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
2550
3214
|
try {
|
|
2551
|
-
const handle = await
|
|
3215
|
+
const handle = await open5(temp, "wx", 384);
|
|
2552
3216
|
try {
|
|
2553
3217
|
await handle.writeFile(text, "utf8");
|
|
2554
3218
|
await handle.sync();
|
|
2555
3219
|
} finally {
|
|
2556
3220
|
await handle.close();
|
|
2557
3221
|
}
|
|
2558
|
-
await
|
|
3222
|
+
await rename3(temp, path);
|
|
2559
3223
|
await syncDirectory(dirname(path));
|
|
2560
3224
|
} catch (error) {
|
|
2561
3225
|
try {
|
|
@@ -2568,7 +3232,7 @@ async function writeAtomicJson(path, value) {
|
|
|
2568
3232
|
}
|
|
2569
3233
|
async function syncDirectory(path) {
|
|
2570
3234
|
try {
|
|
2571
|
-
const handle = await
|
|
3235
|
+
const handle = await open5(path, "r");
|
|
2572
3236
|
try {
|
|
2573
3237
|
await handle.sync();
|
|
2574
3238
|
} finally {
|
|
@@ -2583,7 +3247,7 @@ async function readOptionalJson(path, validate) {
|
|
|
2583
3247
|
validate(value);
|
|
2584
3248
|
return value;
|
|
2585
3249
|
} catch (error) {
|
|
2586
|
-
if (
|
|
3250
|
+
if (isNotFound2(error)) return null;
|
|
2587
3251
|
if (error instanceof CodexHostAdapterError) throw error;
|
|
2588
3252
|
throw storageError("Codex owner state is malformed.");
|
|
2589
3253
|
}
|
|
@@ -2592,8 +3256,8 @@ async function ensurePrivateOwnerDirectory(path) {
|
|
|
2592
3256
|
try {
|
|
2593
3257
|
await lstat3(path);
|
|
2594
3258
|
} catch (error) {
|
|
2595
|
-
if (!
|
|
2596
|
-
await
|
|
3259
|
+
if (!isNotFound2(error)) throw storageError("Codex owner state directory is unavailable.");
|
|
3260
|
+
await mkdir3(path, { recursive: true, mode: 448 });
|
|
2597
3261
|
}
|
|
2598
3262
|
const stats = await lstat3(path);
|
|
2599
3263
|
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
@@ -2604,7 +3268,7 @@ async function ensurePrivateOwnerDirectory(path) {
|
|
|
2604
3268
|
async function readPrivateFile(path) {
|
|
2605
3269
|
let handle;
|
|
2606
3270
|
try {
|
|
2607
|
-
handle = await
|
|
3271
|
+
handle = await open5(path, O_RDONLY3 | O_NOFOLLOW2);
|
|
2608
3272
|
const stats = await handle.stat();
|
|
2609
3273
|
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
2610
3274
|
if (!stats.isFile()) throw storageError(`Codex owner state path is not a regular file (${refusalFact("mode", `0o${stats.mode.toString(8)}`, "a regular-file mode (0o100xxx)")}).`);
|
|
@@ -2616,7 +3280,7 @@ async function readPrivateFile(path) {
|
|
|
2616
3280
|
}
|
|
2617
3281
|
}
|
|
2618
3282
|
function validateEnvelope(value) {
|
|
2619
|
-
if (!
|
|
3283
|
+
if (!isRecord4(value)) throw storageError("Codex outbox entry is malformed.");
|
|
2620
3284
|
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "input_kind", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"], ["request_ref", "codex_turn_id"])) throw storageError(`Codex outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
|
|
2621
3285
|
if (value.schema_version !== CODEX_OUTBOX_SCHEMA_VERSION) throw storageError(`Codex outbox entry is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_OUTBOX_SCHEMA_VERSION}`)}).`);
|
|
2622
3286
|
validateIdentity3(value.identity);
|
|
@@ -2634,7 +3298,7 @@ function validateEnvelopeReferences(value) {
|
|
|
2634
3298
|
if (typeof value.origin_input !== "boolean") throw storageError("Codex outbox origin disposition is malformed.");
|
|
2635
3299
|
}
|
|
2636
3300
|
function validateRegistration(value) {
|
|
2637
|
-
if (!
|
|
3301
|
+
if (!isRecord4(value)) throw storageError("Codex transcript registration is malformed.");
|
|
2638
3302
|
if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "codex_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) throw storageError(`Codex transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
|
|
2639
3303
|
if (value.schema_version !== CODEX_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw storageError(`Codex transcript registration is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION}`)}).`);
|
|
2640
3304
|
validateRegistrationFields(value);
|
|
@@ -2647,15 +3311,15 @@ function validateRegistrationFields(value) {
|
|
|
2647
3311
|
if (!isTimestamp(value.registered_at) || !isTimestamp(value.expires_at)) throw storageError(`Codex transcript registration timestamps are malformed (${refusalFacts([{ name: "registered_at", value: value.registered_at }, { name: "expires_at", value: value.expires_at }])}).`);
|
|
2648
3312
|
}
|
|
2649
3313
|
function validRegistrationCwd(value) {
|
|
2650
|
-
return value === void 0 || typeof value === "string" &&
|
|
3314
|
+
return value === void 0 || typeof value === "string" && isAbsolute2(value) && value.length <= CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS && !value.includes("\0");
|
|
2651
3315
|
}
|
|
2652
3316
|
function validateCursor(value) {
|
|
2653
|
-
if (!
|
|
3317
|
+
if (!isRecord4(value)) throw storageError("Codex transcript cursor is malformed.");
|
|
2654
3318
|
if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "transcript_path", "device", "inode", "offset", "last_ordinal", "anchor_start", "anchor_sha256", "header_validated", "status", "updated_at"], ["active_task", "reason"])) throw storageError(`Codex transcript cursor key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the cursor key set")}).`);
|
|
2655
3319
|
if (value.schema_version !== CODEX_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw storageError(`Codex transcript cursor is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_TRANSCRIPT_CURSOR_SCHEMA_VERSION}`)}).`);
|
|
2656
3320
|
if (!validCursorSource(value) || !validCursorPosition(value) || !validCursorStatus(value)) throw storageError("Codex transcript cursor fields are malformed.");
|
|
2657
3321
|
if (value.active_task !== void 0 && !validActiveTask(value.active_task)) throw storageError("Codex transcript task cursor is malformed.");
|
|
2658
|
-
if (value.reason !== void 0 && !
|
|
3322
|
+
if (value.reason !== void 0 && !isHostTranscriptCursorReason(value.reason)) throw storageError("Codex transcript cursor reason is malformed.");
|
|
2659
3323
|
}
|
|
2660
3324
|
function validCursorSource(value) {
|
|
2661
3325
|
return [value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate)) && typeof value.transcript_path === "string" && value.transcript_path.length > 0 && typeof value.device === "string" && typeof value.inode === "string";
|
|
@@ -2667,7 +3331,7 @@ function validCursorStatus(value) {
|
|
|
2667
3331
|
return (value.status === "active" || value.status === "quarantined") && isTimestamp(value.updated_at);
|
|
2668
3332
|
}
|
|
2669
3333
|
function validActiveTask(value) {
|
|
2670
|
-
if (!
|
|
3334
|
+
if (!isRecord4(value) || !sameKeys(value, ["codex_turn_id", "host_turn_ref", "started_at"], ["origin_host_input_ref", "prompt_ref", "model", "token_usage", "last_total_usage", "token_usage_gap_reason"])) return false;
|
|
2671
3335
|
return validActiveTaskIdentity(value) && validActiveTaskReferences(value) && validActiveTaskUsage(value);
|
|
2672
3336
|
}
|
|
2673
3337
|
function validActiveTaskIdentity(value) {
|
|
@@ -2683,7 +3347,7 @@ function validActiveTaskUsage(value) {
|
|
|
2683
3347
|
return (value.token_usage === void 0 || validTokenUsage(value.token_usage)) && (value.last_total_usage === void 0 || validTokenUsage(value.last_total_usage)) && (value.token_usage_gap_reason === void 0 || value.token_usage_gap_reason === "token_count_shape_not_attributable");
|
|
2684
3348
|
}
|
|
2685
3349
|
function validTokenUsage(value) {
|
|
2686
|
-
if (!
|
|
3350
|
+
if (!isRecord4(value) || !sameKeys(value, ["input_tokens", "cached_input_tokens", "cache_write_input_tokens", "output_tokens", "reasoning_output_tokens", "total_tokens"])) return false;
|
|
2687
3351
|
if (![value.input_tokens, value.cached_input_tokens, value.cache_write_input_tokens, value.output_tokens, value.reasoning_output_tokens, value.total_tokens].every(nonNegativeInteger)) return false;
|
|
2688
3352
|
return value.total_tokens === Number(value.input_tokens) + Number(value.output_tokens) && Number(value.cached_input_tokens) + Number(value.cache_write_input_tokens) <= Number(value.input_tokens) && Number(value.reasoning_output_tokens) <= Number(value.output_tokens);
|
|
2689
3353
|
}
|
|
@@ -2703,7 +3367,7 @@ function validateEnvelopeTimestamps(value) {
|
|
|
2703
3367
|
if (!isTimestamp(value.expires_at)) throw storageError("Codex outbox timestamps are malformed.");
|
|
2704
3368
|
}
|
|
2705
3369
|
function validateBinding(value) {
|
|
2706
|
-
if (!
|
|
3370
|
+
if (!isRecord4(value)) throw storageError("Codex turn binding is malformed.");
|
|
2707
3371
|
if (!sameKeys(value, ["schema_version", "identity", "prompt_ref", "request_ref", "codex_turn_ids", "bound_at", "expires_at"])) throw storageError(`Codex turn binding key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the turn binding key set")}).`);
|
|
2708
3372
|
if (value.schema_version !== CODEX_TURN_BINDING_SCHEMA_VERSION) throw storageError(`Codex turn binding is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_TURN_BINDING_SCHEMA_VERSION}`)}).`);
|
|
2709
3373
|
validateIdentity3(value.identity);
|
|
@@ -2711,19 +3375,22 @@ function validateBinding(value) {
|
|
|
2711
3375
|
if (!isStringList(value.codex_turn_ids) || !isTimestamp(value.bound_at) || !isTimestamp(value.expires_at)) throw storageError("Codex turn binding fields are malformed.");
|
|
2712
3376
|
}
|
|
2713
3377
|
function validateSession(value) {
|
|
2714
|
-
if (!
|
|
3378
|
+
if (!isRecord4(value)) throw storageError("Codex session snapshot is malformed.");
|
|
2715
3379
|
if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) throw storageError(`Codex session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
|
|
2716
3380
|
if (value.schema_version !== CODEX_SESSION_SCHEMA_VERSION) throw storageError(`Codex session snapshot is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_SESSION_SCHEMA_VERSION}`)}).`);
|
|
2717
3381
|
if (!SAFE_REF4.test(String(value.host_session_ref)) || !SAFE_REF4.test(String(value.host_source_ref)) || !isTimestamp(value.observed_at) || !isTimestamp(value.expires_at) || !isJsonValue(value.value)) throw storageError("Codex session snapshot fields are malformed.");
|
|
2718
3382
|
}
|
|
3383
|
+
function validateTurnSelection(value) {
|
|
3384
|
+
if (!isHostInvocationTurnSelection(value)) throw storageError("Codex Host turn selection is malformed.");
|
|
3385
|
+
}
|
|
2719
3386
|
function validateIdentity3(value) {
|
|
2720
|
-
if (!
|
|
3387
|
+
if (!isRecord4(value) || !hasHostInvocationIdentityKeys(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) throw storageError("Codex Host identity is malformed.");
|
|
2721
3388
|
}
|
|
2722
3389
|
function isJsonValue(value) {
|
|
2723
3390
|
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
2724
3391
|
if (typeof value === "number") return Number.isFinite(value);
|
|
2725
3392
|
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
2726
|
-
return
|
|
3393
|
+
return isRecord4(value) && Object.entries(value).every(([key, child]) => key.length > 0 && key.length <= 128 && isJsonValue(child));
|
|
2727
3394
|
}
|
|
2728
3395
|
function isStringList(value) {
|
|
2729
3396
|
return Array.isArray(value) && value.every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate));
|
|
@@ -2736,101 +3403,49 @@ function sameKeys(value, required, optional = []) {
|
|
|
2736
3403
|
const keys = Object.keys(value);
|
|
2737
3404
|
return keys.length >= required.length && keys.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => allowed.has(key));
|
|
2738
3405
|
}
|
|
2739
|
-
function
|
|
3406
|
+
function isRecord4(value) {
|
|
2740
3407
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2741
3408
|
}
|
|
2742
|
-
function
|
|
2743
|
-
return
|
|
3409
|
+
function isNotFound2(error) {
|
|
3410
|
+
return isRecord4(error) && error.code === "ENOENT";
|
|
2744
3411
|
}
|
|
2745
3412
|
function storageError(message) {
|
|
2746
3413
|
return new CodexHostAdapterError("storage", message);
|
|
2747
3414
|
}
|
|
2748
3415
|
|
|
2749
3416
|
// host/codex/src/codex-producer.ts
|
|
2750
|
-
var STATE_SCHEMA = "ceal.codex_producer_state.v1";
|
|
2751
3417
|
var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2752
3418
|
var CodexProducer = class {
|
|
3419
|
+
producer_id = "codex";
|
|
3420
|
+
producer_label = "Codex";
|
|
3421
|
+
outbox_directory;
|
|
2753
3422
|
store;
|
|
2754
|
-
|
|
2755
|
-
installationRef;
|
|
2756
|
-
epoch;
|
|
2757
|
-
relay;
|
|
2758
|
-
now;
|
|
2759
|
-
tail = Promise.resolve();
|
|
3423
|
+
core;
|
|
2760
3424
|
constructor(options) {
|
|
2761
|
-
if (!SAFE_REF5.test(options.producer_installation_ref)) throw new CodexHostAdapterError("configuration", "Codex producer installation identity is invalid.");
|
|
2762
|
-
const epoch = options.producer_epoch ?? 1;
|
|
2763
|
-
if (!Number.isSafeInteger(epoch) || epoch <= 0) throw new CodexHostAdapterError("configuration", "Codex producer epoch is invalid.");
|
|
2764
3425
|
this.store = new CodexOutboxStore(options.outbox_dir);
|
|
2765
|
-
this.
|
|
2766
|
-
this.
|
|
2767
|
-
this.epoch = epoch;
|
|
2768
|
-
this.relay = options.relay;
|
|
2769
|
-
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3426
|
+
this.outbox_directory = this.store.directory;
|
|
3427
|
+
this.core = new HostProducerDrainCore(this, options);
|
|
2770
3428
|
}
|
|
2771
3429
|
drainOutbox() {
|
|
2772
|
-
|
|
2773
|
-
this.tail = run.then(() => void 0, () => void 0);
|
|
2774
|
-
return run;
|
|
3430
|
+
return this.core.drainOutbox();
|
|
2775
3431
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
let admitted = 0;
|
|
2779
|
-
let expired = 0;
|
|
2780
|
-
let retained = 0;
|
|
2781
|
-
for (const envelope of read.entries) {
|
|
2782
|
-
const reserved = await this.hasReservation(envelope.submission_ref);
|
|
2783
|
-
if (!reserved && Date.parse(envelope.expires_at) <= this.now().getTime()) {
|
|
2784
|
-
await this.store.remove(envelope);
|
|
2785
|
-
expired += 1;
|
|
2786
|
-
continue;
|
|
2787
|
-
}
|
|
2788
|
-
const frames = await this.reserve(envelope);
|
|
2789
|
-
let complete = true;
|
|
2790
|
-
for (const frame of frames) {
|
|
2791
|
-
const outcome = await this.relay.admit(frame);
|
|
2792
|
-
if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
|
|
2793
|
-
complete = false;
|
|
2794
|
-
break;
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
if (!complete) {
|
|
2798
|
-
retained += 1;
|
|
2799
|
-
break;
|
|
2800
|
-
}
|
|
2801
|
-
await this.store.remove(envelope);
|
|
2802
|
-
await this.release(envelope.submission_ref);
|
|
2803
|
-
admitted += 1;
|
|
2804
|
-
}
|
|
2805
|
-
return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
|
|
3432
|
+
fail(code, message) {
|
|
3433
|
+
return new CodexHostAdapterError(code, message);
|
|
2806
3434
|
}
|
|
2807
|
-
|
|
2808
|
-
return
|
|
3435
|
+
listEnvelopes() {
|
|
3436
|
+
return this.store.list();
|
|
2809
3437
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
const existing = state.pending[envelope.submission_ref];
|
|
2813
|
-
if (existing !== void 0) return existing;
|
|
2814
|
-
const drafts = await this.mapEnvelope(envelope);
|
|
2815
|
-
const frames = drafts.map((draft, offset) => ({
|
|
2816
|
-
schema_version: "ceal.host_producer_frame.v1",
|
|
2817
|
-
producer_id: "codex",
|
|
2818
|
-
producer_installation_ref: this.installationRef,
|
|
2819
|
-
producer_epoch: this.epoch,
|
|
2820
|
-
sequence: state.next_sequence + offset,
|
|
2821
|
-
frame_kind: draft.frame_kind,
|
|
2822
|
-
occurred_at: envelope.created_at,
|
|
2823
|
-
payload: draft.payload
|
|
2824
|
-
}));
|
|
2825
|
-
await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [envelope.submission_ref]: frames } });
|
|
2826
|
-
return frames;
|
|
3438
|
+
removeEnvelope(envelope) {
|
|
3439
|
+
return this.store.remove(envelope);
|
|
2827
3440
|
}
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
3441
|
+
submissionRefOf(envelope) {
|
|
3442
|
+
return envelope.submission_ref;
|
|
3443
|
+
}
|
|
3444
|
+
expiresAtOf(envelope) {
|
|
3445
|
+
return envelope.expires_at;
|
|
3446
|
+
}
|
|
3447
|
+
createdAtOf(envelope) {
|
|
3448
|
+
return envelope.created_at;
|
|
2834
3449
|
}
|
|
2835
3450
|
async mapEnvelope(envelope) {
|
|
2836
3451
|
const prompt = observation(envelope, "prompt");
|
|
@@ -2846,7 +3461,7 @@ var CodexProducer = class {
|
|
|
2846
3461
|
const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
|
|
2847
3462
|
drafts.push({ frame_kind: "session_started", payload: compact({
|
|
2848
3463
|
local_session_ref: envelope.identity.host_session_ref,
|
|
2849
|
-
working_directory_ref: `workspace:${
|
|
3464
|
+
working_directory_ref: `workspace:${createHash4("sha256").update(directoryIdentity, "utf8").digest("hex")}`,
|
|
2850
3465
|
model: recordText(session.value, "model"),
|
|
2851
3466
|
host_version: nestedText(session.value, "host_version"),
|
|
2852
3467
|
observations: [session]
|
|
@@ -2871,21 +3486,6 @@ var CodexProducer = class {
|
|
|
2871
3486
|
}) });
|
|
2872
3487
|
return drafts;
|
|
2873
3488
|
}
|
|
2874
|
-
async readState() {
|
|
2875
|
-
try {
|
|
2876
|
-
const value = JSON.parse(await readFile(this.statePath, "utf8"));
|
|
2877
|
-
return decodeState(value, this.installationRef, this.epoch);
|
|
2878
|
-
} catch (error) {
|
|
2879
|
-
if (errorCode2(error) !== "ENOENT") throw error;
|
|
2880
|
-
return { schema_version: STATE_SCHEMA, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
async writeState(state) {
|
|
2884
|
-
await mkdir3(this.store.directory, { recursive: true, mode: 448 });
|
|
2885
|
-
const temporary = `${this.statePath}.${process.pid}.new`;
|
|
2886
|
-
await writeFile(temporary, JSON.stringify(state), { mode: 384 });
|
|
2887
|
-
await rename3(temporary, this.statePath);
|
|
2888
|
-
}
|
|
2889
3489
|
};
|
|
2890
3490
|
function mapTerminal(envelope) {
|
|
2891
3491
|
const timing = observation(envelope, "timing");
|
|
@@ -2913,7 +3513,7 @@ function compact(value) {
|
|
|
2913
3513
|
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
|
|
2914
3514
|
}
|
|
2915
3515
|
function asRecord(value) {
|
|
2916
|
-
return
|
|
3516
|
+
return isRecord5(value) ? value : void 0;
|
|
2917
3517
|
}
|
|
2918
3518
|
function recordText(value, key) {
|
|
2919
3519
|
const record3 = asRecord(value);
|
|
@@ -2927,55 +3527,16 @@ function nonNegativeNumber(value) {
|
|
|
2927
3527
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2928
3528
|
}
|
|
2929
3529
|
function safeReasonRef(value) {
|
|
2930
|
-
return SAFE_REF5.test(value) ? value : `reason:${
|
|
3530
|
+
return SAFE_REF5.test(value) ? value : `reason:${createHash4("sha256").update(value, "utf8").digest("hex")}`;
|
|
2931
3531
|
}
|
|
2932
|
-
function
|
|
2933
|
-
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2934
|
-
}
|
|
2935
|
-
function decodeState(value, installationRef, epoch) {
|
|
2936
|
-
if (!validStateHeader(value, installationRef)) {
|
|
2937
|
-
throw new CodexHostAdapterError("storage", `Codex producer state header is malformed or belongs to another installation (${refusalFacts([
|
|
2938
|
-
{ name: "schema_version", value: isRecord4(value) ? value.schema_version : void 0, expected: STATE_SCHEMA },
|
|
2939
|
-
{ name: "producer_installation_ref", value: isRecord4(value) ? value.producer_installation_ref : void 0, expected: installationRef }
|
|
2940
|
-
])}).`);
|
|
2941
|
-
}
|
|
2942
|
-
if (Number(value.producer_epoch) > epoch) throw new CodexHostAdapterError("storage", `Codex producer state epoch is ahead of this installation (${refusalFact("producer_epoch", value.producer_epoch, `at most ${epoch}`)}).`);
|
|
2943
|
-
const pending = {};
|
|
2944
|
-
for (const [submissionRef, frames] of Object.entries(value.pending)) {
|
|
2945
|
-
if (!SAFE_REF5.test(submissionRef) || !Array.isArray(frames) || frames.length === 0) throw new CodexHostAdapterError("storage", "Codex producer pending reservation is malformed.");
|
|
2946
|
-
pending[submissionRef] = frames.map((frame) => decodeStoredFrame(frame, installationRef, Number(value.producer_epoch)));
|
|
2947
|
-
}
|
|
2948
|
-
return { schema_version: STATE_SCHEMA, producer_installation_ref: installationRef, producer_epoch: epoch, next_sequence: Number(value.next_sequence), pending };
|
|
2949
|
-
}
|
|
2950
|
-
function validStateHeader(value, installationRef) {
|
|
2951
|
-
return isRecord4(value) && value.schema_version === STATE_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 && isRecord4(value.pending);
|
|
2952
|
-
}
|
|
2953
|
-
function decodeStoredFrame(value, installationRef, epoch) {
|
|
2954
|
-
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
2955
|
-
const encoded = Buffer.allocUnsafe(json.length + 4);
|
|
2956
|
-
encoded.writeUInt32BE(json.length, 0);
|
|
2957
|
-
json.copy(encoded, 4);
|
|
2958
|
-
const frame = decodeHostProducerFrame(encoded);
|
|
2959
|
-
if (frame.producer_id !== "codex" || frame.producer_installation_ref !== installationRef || frame.producer_epoch > epoch) throw new CodexHostAdapterError("storage", `Codex producer pending frame identity is malformed (${refusalFacts([
|
|
2960
|
-
{ name: "producer_id", value: frame.producer_id, expected: "codex" },
|
|
2961
|
-
{ name: "producer_installation_ref", value: frame.producer_installation_ref, expected: installationRef },
|
|
2962
|
-
{ name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${epoch}` }
|
|
2963
|
-
])}).`);
|
|
2964
|
-
return frame;
|
|
2965
|
-
}
|
|
2966
|
-
function isRecord4(value) {
|
|
3532
|
+
function isRecord5(value) {
|
|
2967
3533
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2968
3534
|
}
|
|
2969
3535
|
|
|
2970
|
-
// host/codex/src/codex-transcript-watcher.ts
|
|
2971
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
2972
|
-
import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
|
|
2973
|
-
import { open as open4 } from "node:fs/promises";
|
|
2974
|
-
|
|
2975
3536
|
// host/codex/src/codex-decoder.ts
|
|
2976
|
-
import { createHash as
|
|
3537
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
2977
3538
|
var SAFE_REF6 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2978
|
-
var MAX_PAYLOAD_BYTES =
|
|
3539
|
+
var MAX_PAYLOAD_BYTES = 512 * 1024;
|
|
2979
3540
|
function observationRef(event, identity, value) {
|
|
2980
3541
|
return cealHostObservationRef("codex", shortDigest({ event, identity, value }));
|
|
2981
3542
|
}
|
|
@@ -2996,14 +3557,14 @@ function turnObservationValue(sessionId, turnRef, status) {
|
|
|
2996
3557
|
return { status, host_session_id_sha256: sha256(sessionId), host_turn_ref: turnRef };
|
|
2997
3558
|
}
|
|
2998
3559
|
function sha256(value) {
|
|
2999
|
-
return
|
|
3560
|
+
return createHash5("sha256").update(value, "utf8").digest("hex");
|
|
3000
3561
|
}
|
|
3001
3562
|
function shortDigest(value) {
|
|
3002
3563
|
return cealRequestSha256(value);
|
|
3003
3564
|
}
|
|
3004
3565
|
|
|
3005
3566
|
// host/codex/src/codex-transcript-baseline.ts
|
|
3006
|
-
import { createHash as
|
|
3567
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
3007
3568
|
|
|
3008
3569
|
// host/codex/src/codex-transcript-decoder.ts
|
|
3009
3570
|
var SAFE_REF7 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
|
|
@@ -3015,40 +3576,40 @@ function decodeCodexTranscriptLine(line) {
|
|
|
3015
3576
|
} catch {
|
|
3016
3577
|
reject("Codex transcript record is not valid JSON.");
|
|
3017
3578
|
}
|
|
3018
|
-
if (!
|
|
3579
|
+
if (!isRecord6(value)) reject("Codex transcript record envelope is malformed.");
|
|
3019
3580
|
const ordinal = safeOrdinal(value.ordinal);
|
|
3020
|
-
const
|
|
3021
|
-
if (!
|
|
3022
|
-
if (value.type === "session_meta") return decodeSessionMeta(value.payload, ordinal,
|
|
3023
|
-
if (value.type === "turn_context") return decodeTurnContext(value.payload, ordinal,
|
|
3024
|
-
if (value.type === "event_msg") return decodeEventMessage(value.payload, ordinal,
|
|
3025
|
-
if (value.type === "response_item") return decodeResponseItem(value.payload, ordinal,
|
|
3026
|
-
return { kind: "ignored", ordinal, timestamp:
|
|
3027
|
-
}
|
|
3028
|
-
function decodeTurnContext(payload, ordinal,
|
|
3581
|
+
const timestamp4 = safeTimestamp(value.timestamp);
|
|
3582
|
+
if (!isRecord6(value.payload) || typeof value.type !== "string") reject("Codex transcript record payload is malformed.");
|
|
3583
|
+
if (value.type === "session_meta") return decodeSessionMeta(value.payload, ordinal, timestamp4);
|
|
3584
|
+
if (value.type === "turn_context") return decodeTurnContext(value.payload, ordinal, timestamp4);
|
|
3585
|
+
if (value.type === "event_msg") return decodeEventMessage(value.payload, ordinal, timestamp4);
|
|
3586
|
+
if (value.type === "response_item") return decodeResponseItem(value.payload, ordinal, timestamp4);
|
|
3587
|
+
return { kind: "ignored", ordinal, timestamp: timestamp4 };
|
|
3588
|
+
}
|
|
3589
|
+
function decodeTurnContext(payload, ordinal, timestamp4) {
|
|
3029
3590
|
return {
|
|
3030
3591
|
kind: "turn_context",
|
|
3031
3592
|
ordinal,
|
|
3032
|
-
timestamp:
|
|
3033
|
-
turn_id:
|
|
3593
|
+
timestamp: timestamp4,
|
|
3594
|
+
turn_id: safeRef4(payload.turn_id, "Codex turn context identity is invalid."),
|
|
3034
3595
|
model: boundedNonEmptyText(payload.model, 256, "Codex turn context model is invalid.")
|
|
3035
3596
|
};
|
|
3036
3597
|
}
|
|
3037
|
-
function decodeSessionMeta(payload, ordinal,
|
|
3038
|
-
const sessionId =
|
|
3598
|
+
function decodeSessionMeta(payload, ordinal, timestamp4) {
|
|
3599
|
+
const sessionId = safeRef4(payload.id, "Codex transcript session identity is invalid.");
|
|
3039
3600
|
const cliVersion = boundedNonEmptyText(payload.cli_version, 64, "Codex transcript version is invalid.");
|
|
3040
|
-
return { kind: "session_meta", ordinal, timestamp:
|
|
3601
|
+
return { kind: "session_meta", ordinal, timestamp: timestamp4, session_id: sessionId, cli_version: cliVersion };
|
|
3041
3602
|
}
|
|
3042
|
-
function decodeEventMessage(payload, ordinal,
|
|
3603
|
+
function decodeEventMessage(payload, ordinal, timestamp4) {
|
|
3043
3604
|
if (payload.type === "task_started") {
|
|
3044
|
-
return { kind: "task_started", ordinal, timestamp:
|
|
3605
|
+
return { kind: "task_started", ordinal, timestamp: timestamp4, turn_id: safeRef4(payload.turn_id, "Codex task identity is invalid."), started_at: safeNumber(payload.started_at) };
|
|
3045
3606
|
}
|
|
3046
3607
|
if (payload.type === "task_complete") {
|
|
3047
3608
|
return {
|
|
3048
3609
|
kind: "task_complete",
|
|
3049
3610
|
ordinal,
|
|
3050
|
-
timestamp:
|
|
3051
|
-
turn_id:
|
|
3611
|
+
timestamp: timestamp4,
|
|
3612
|
+
turn_id: safeRef4(payload.turn_id, "Codex task identity is invalid."),
|
|
3052
3613
|
started_at: safeNumber(payload.started_at),
|
|
3053
3614
|
completed_at: safeNumber(payload.completed_at),
|
|
3054
3615
|
duration_ms: safeNumber(payload.duration_ms),
|
|
@@ -3059,20 +3620,20 @@ function decodeEventMessage(payload, ordinal, timestamp5) {
|
|
|
3059
3620
|
return {
|
|
3060
3621
|
kind: "turn_aborted",
|
|
3061
3622
|
ordinal,
|
|
3062
|
-
timestamp:
|
|
3063
|
-
turn_id:
|
|
3623
|
+
timestamp: timestamp4,
|
|
3624
|
+
turn_id: safeRef4(payload.turn_id, "Codex task identity is invalid."),
|
|
3064
3625
|
started_at: safeNumber(payload.started_at),
|
|
3065
3626
|
completed_at: safeNumber(payload.completed_at),
|
|
3066
3627
|
duration_ms: safeNumber(payload.duration_ms),
|
|
3067
3628
|
reason: boundedText(payload.reason, 256, "Codex task abort reason is invalid.")
|
|
3068
3629
|
};
|
|
3069
3630
|
}
|
|
3070
|
-
if (payload.type === "token_count") return decodeTokenCount(payload, ordinal,
|
|
3071
|
-
return { kind: "ignored", ordinal, timestamp:
|
|
3631
|
+
if (payload.type === "token_count") return decodeTokenCount(payload, ordinal, timestamp4);
|
|
3632
|
+
return { kind: "ignored", ordinal, timestamp: timestamp4 };
|
|
3072
3633
|
}
|
|
3073
|
-
function decodeTokenCount(payload, ordinal,
|
|
3074
|
-
if (payload.info === null) return { kind: "token_count", ordinal, timestamp:
|
|
3075
|
-
if (!
|
|
3634
|
+
function decodeTokenCount(payload, ordinal, timestamp4) {
|
|
3635
|
+
if (payload.info === null) return { kind: "token_count", ordinal, timestamp: timestamp4 };
|
|
3636
|
+
if (!isRecord6(payload.info)) return { kind: "token_count", ordinal, timestamp: timestamp4, usage_gap_reason: "token_count_shape_not_attributable" };
|
|
3076
3637
|
const lastUsage = optionalTokenUsage(payload.info.last_token_usage);
|
|
3077
3638
|
const totalUsage = optionalTokenUsage(payload.info.total_token_usage);
|
|
3078
3639
|
const gap = lastUsage.status === "invalid" || totalUsage.status === "invalid";
|
|
@@ -3080,7 +3641,7 @@ function decodeTokenCount(payload, ordinal, timestamp5) {
|
|
|
3080
3641
|
return {
|
|
3081
3642
|
kind: "token_count",
|
|
3082
3643
|
ordinal,
|
|
3083
|
-
timestamp:
|
|
3644
|
+
timestamp: timestamp4,
|
|
3084
3645
|
...lastUsage.status === "valid" ? { last_usage: lastUsage.value } : {},
|
|
3085
3646
|
...totalUsage.status === "valid" ? { total_usage: totalUsage.value } : {},
|
|
3086
3647
|
...usageGapReason === void 0 ? {} : { usage_gap_reason: usageGapReason }
|
|
@@ -3096,7 +3657,7 @@ function optionalTokenUsage(value) {
|
|
|
3096
3657
|
}
|
|
3097
3658
|
}
|
|
3098
3659
|
function decodeTokenUsage(value) {
|
|
3099
|
-
if (!
|
|
3660
|
+
if (!isRecord6(value)) reject("Codex token usage is malformed.");
|
|
3100
3661
|
const usage = {
|
|
3101
3662
|
input_tokens: safeTokenCount(value.input_tokens),
|
|
3102
3663
|
cached_input_tokens: safeTokenCount(value.cached_input_tokens),
|
|
@@ -3108,20 +3669,20 @@ function decodeTokenUsage(value) {
|
|
|
3108
3669
|
if (usage.total_tokens !== usage.input_tokens + usage.output_tokens || usage.cached_input_tokens + usage.cache_write_input_tokens > usage.input_tokens || usage.reasoning_output_tokens > usage.output_tokens) reject("Codex token usage buckets are inconsistent.");
|
|
3109
3670
|
return usage;
|
|
3110
3671
|
}
|
|
3111
|
-
function decodeResponseItem(payload, ordinal,
|
|
3112
|
-
if (payload.type !== "message" || payload.role !== "user") return { kind: "ignored", ordinal, timestamp:
|
|
3672
|
+
function decodeResponseItem(payload, ordinal, timestamp4) {
|
|
3673
|
+
if (payload.type !== "message" || payload.role !== "user") return { kind: "ignored", ordinal, timestamp: timestamp4 };
|
|
3113
3674
|
const metadata = userMessageMetadata(payload.internal_chat_message_metadata_passthrough);
|
|
3114
3675
|
const inputKind = inputKindFromContentItems(metadata.content_item_kinds);
|
|
3115
|
-
if (inputKind === void 0) return { kind: "ignored", ordinal, timestamp:
|
|
3676
|
+
if (inputKind === void 0) return { kind: "ignored", ordinal, timestamp: timestamp4 };
|
|
3116
3677
|
const prompt = userTextContent(payload.content);
|
|
3117
3678
|
return {
|
|
3118
3679
|
kind: "user_text",
|
|
3119
3680
|
ordinal,
|
|
3120
|
-
timestamp:
|
|
3681
|
+
timestamp: timestamp4,
|
|
3121
3682
|
prompt,
|
|
3122
3683
|
input_kind: inputKind,
|
|
3123
|
-
message_id:
|
|
3124
|
-
turn_id:
|
|
3684
|
+
message_id: safeRef4(payload.id, "Codex user message identity is invalid."),
|
|
3685
|
+
turn_id: safeRef4(metadata.turn_id, "Codex user message task identity is invalid.")
|
|
3125
3686
|
};
|
|
3126
3687
|
}
|
|
3127
3688
|
function inputKindFromContentItems(kinds) {
|
|
@@ -3130,13 +3691,13 @@ function inputKindFromContentItems(kinds) {
|
|
|
3130
3691
|
return kinds[0] === "goal.internal_context" ? "host_automatic" : void 0;
|
|
3131
3692
|
}
|
|
3132
3693
|
function userMessageMetadata(value) {
|
|
3133
|
-
if (!
|
|
3694
|
+
if (!isRecord6(value) || !Array.isArray(value.content_item_kinds)) reject("Codex user message provenance is malformed.");
|
|
3134
3695
|
return { ...value, content_item_kinds: value.content_item_kinds };
|
|
3135
3696
|
}
|
|
3136
3697
|
function userTextContent(value) {
|
|
3137
3698
|
if (!Array.isArray(value)) reject(`Codex user text content is malformed (${refusalFact("kind", typeof value, "array")}).`);
|
|
3138
3699
|
if (value.length !== 1) reject(`Codex user text content is malformed (${refusalFact("items", value.length, "exactly one")}).`);
|
|
3139
|
-
if (!
|
|
3700
|
+
if (!isRecord6(value[0]) || value[0].type !== "input_text") reject(`Codex user text content is malformed (${refusalFact("item_type", isRecord6(value[0]) ? value[0].type : typeof value[0], "input_text")}).`);
|
|
3140
3701
|
return boundedText(value[0].text, MAX_PROMPT_BYTES, "Codex user text is invalid.");
|
|
3141
3702
|
}
|
|
3142
3703
|
function safeOrdinal(value) {
|
|
@@ -3157,7 +3718,7 @@ function safeTimestamp(value) {
|
|
|
3157
3718
|
if (!Number.isFinite(Date.parse(value))) reject(`Codex transcript timestamp is invalid (${refusalFact("parsed_ms", Date.parse(value), "a finite epoch milliseconds value")}).`);
|
|
3158
3719
|
return value;
|
|
3159
3720
|
}
|
|
3160
|
-
function
|
|
3721
|
+
function safeRef4(value, message) {
|
|
3161
3722
|
if (typeof value !== "string" || !SAFE_REF7.test(value)) reject(message);
|
|
3162
3723
|
return value;
|
|
3163
3724
|
}
|
|
@@ -3172,7 +3733,7 @@ function boundedNonEmptyText(value, maximum, message) {
|
|
|
3172
3733
|
if (result.length === 0) reject(message);
|
|
3173
3734
|
return result;
|
|
3174
3735
|
}
|
|
3175
|
-
function
|
|
3736
|
+
function isRecord6(value) {
|
|
3176
3737
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3177
3738
|
}
|
|
3178
3739
|
function reject(message) {
|
|
@@ -3216,7 +3777,7 @@ function assertCodexTranscriptHeader(registration, record3) {
|
|
|
3216
3777
|
])}).`);
|
|
3217
3778
|
}
|
|
3218
3779
|
function digest(value) {
|
|
3219
|
-
return
|
|
3780
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
3220
3781
|
}
|
|
3221
3782
|
function timestamp3(now) {
|
|
3222
3783
|
const value = now();
|
|
@@ -3232,78 +3793,53 @@ function codexSubmissionReference(hostInputRef, event, ordinal) {
|
|
|
3232
3793
|
}
|
|
3233
3794
|
|
|
3234
3795
|
// host/codex/src/codex-transcript-watcher.ts
|
|
3235
|
-
var
|
|
3236
|
-
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
3237
|
-
var CodexTranscriptWatcher = class {
|
|
3796
|
+
var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
3238
3797
|
store;
|
|
3239
|
-
outboxRetentionMs;
|
|
3240
|
-
now;
|
|
3241
|
-
log;
|
|
3242
|
-
producerInstallationRef;
|
|
3243
3798
|
registerCurrentPrompt;
|
|
3799
|
+
releaseCurrentPrompt;
|
|
3800
|
+
diagnostic;
|
|
3244
3801
|
constructor(options) {
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
this.log = options.log ?? ((line) => {
|
|
3250
|
-
process.stderr.write(`${line}
|
|
3251
|
-
`);
|
|
3802
|
+
super(options, {
|
|
3803
|
+
default_retention_ms: CODEX_DEFAULT_RETENTION_MS,
|
|
3804
|
+
invalid_outbox: () => new CodexHostAdapterError("configuration", "Codex transcript watcher outbox is invalid."),
|
|
3805
|
+
clock_failure: () => new CodexHostAdapterError("storage", "Codex watcher clock is invalid.")
|
|
3252
3806
|
});
|
|
3253
|
-
this.
|
|
3807
|
+
this.store = new CodexOutboxStore(options.outbox_dir);
|
|
3254
3808
|
this.registerCurrentPrompt = options.register_current_prompt;
|
|
3809
|
+
this.releaseCurrentPrompt = options.release_current_prompt;
|
|
3810
|
+
this.diagnostic = options.diagnostic ?? (() => void 0);
|
|
3255
3811
|
}
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
let records = 0;
|
|
3259
|
-
let enqueued = 0;
|
|
3260
|
-
let quarantined = 0;
|
|
3261
|
-
for (const registration of registrations) {
|
|
3262
|
-
const result = await this.scanRegistration(registration);
|
|
3263
|
-
records += result.records;
|
|
3264
|
-
enqueued += result.enqueued;
|
|
3265
|
-
if (result.quarantined) quarantined += 1;
|
|
3266
|
-
}
|
|
3267
|
-
return { registrations: registrations.length, records, enqueued, quarantined };
|
|
3812
|
+
transcriptPathOf(registration) {
|
|
3813
|
+
return registration.transcript_path;
|
|
3268
3814
|
}
|
|
3269
|
-
async
|
|
3270
|
-
|
|
3271
|
-
try {
|
|
3272
|
-
handle = await open4(registration.transcript_path, O_RDONLY3 | O_NOFOLLOW2);
|
|
3273
|
-
const { size, device, inode } = await ownedCodexTranscriptFile(handle);
|
|
3274
|
-
const preflight = await this.preflight(registration, handle, size, device, inode);
|
|
3275
|
-
if (preflight.cursor === null) return preflight.result;
|
|
3276
|
-
const complete = await readCompleteChunk(handle, preflight.cursor, size);
|
|
3277
|
-
if (complete === "empty" || complete === "partial") return { records: 0, enqueued: 0, quarantined: false };
|
|
3278
|
-
return await this.consumeCompleteChunk(registration, handle, preflight.cursor, complete, device, inode);
|
|
3279
|
-
} catch (error) {
|
|
3280
|
-
if (handle === void 0 && isMissingTranscript(error)) return await this.retireRegistration(registration);
|
|
3281
|
-
await this.quarantineFailure(registration, handle);
|
|
3282
|
-
return { records: 0, enqueued: 0, quarantined: true };
|
|
3283
|
-
} finally {
|
|
3284
|
-
await handle?.close();
|
|
3285
|
-
}
|
|
3815
|
+
async listRegistrations() {
|
|
3816
|
+
return this.store.listTranscriptRegistrations();
|
|
3286
3817
|
}
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3818
|
+
async scanOpened(handle, registration) {
|
|
3819
|
+
const { size, device, inode } = await ownedCodexTranscriptFile(handle);
|
|
3820
|
+
const preflight = await this.preflight(registration, handle, size, device, inode);
|
|
3821
|
+
if (preflight.cursor === null) return preflight.result;
|
|
3822
|
+
const complete = await this.readChunk(handle, preflight.cursor.offset, size);
|
|
3823
|
+
if (complete === "empty" || complete === "partial") return { records: 0, enqueued: 0, quarantined: false };
|
|
3824
|
+
return await this.consumeCompleteChunk(registration, handle, preflight.cursor, complete, device, inode);
|
|
3825
|
+
}
|
|
3826
|
+
async scanFailed(handle, registration, error) {
|
|
3827
|
+
this.diagnostic("watcher_scan_failed", codexTranscriptScanFailureKind(error));
|
|
3828
|
+
await this.quarantineFailure(registration, handle);
|
|
3829
|
+
return { records: 0, enqueued: 0, quarantined: true };
|
|
3830
|
+
}
|
|
3831
|
+
async retireMissing(registration) {
|
|
3832
|
+
await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
|
|
3833
|
+
return this.retireMissingTranscript(registration.transcript_path, "Codex", () => this.store.retireTranscriptRegistration(registration));
|
|
3301
3834
|
}
|
|
3302
3835
|
async preflight(registration, handle, size, device, inode) {
|
|
3303
3836
|
const existing = await this.store.readTranscriptCursor(registration);
|
|
3304
3837
|
let cursor = existing ?? codexTranscriptInitialCursor(registration, device, inode, this.now);
|
|
3305
|
-
if (cursor.status === "quarantined")
|
|
3306
|
-
|
|
3838
|
+
if (cursor.status === "quarantined") {
|
|
3839
|
+
if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
|
|
3840
|
+
return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
|
|
3841
|
+
}
|
|
3842
|
+
const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
|
|
3307
3843
|
if (reason === null) return { cursor, result: { records: 0, enqueued: 0, quarantined: false } };
|
|
3308
3844
|
await this.quarantine(registration, cursor, device, inode, reason);
|
|
3309
3845
|
return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
|
|
@@ -3329,39 +3865,53 @@ var CodexTranscriptWatcher = class {
|
|
|
3329
3865
|
async observeSessionMetadata(registration, record3) {
|
|
3330
3866
|
if (record3.kind !== "session_meta") return;
|
|
3331
3867
|
const session = await this.store.readSession(registration.host_source_ref, registration.host_session_ref);
|
|
3332
|
-
if (session !== null &&
|
|
3868
|
+
if (session !== null && this.isRecord(session.value)) await this.store.saveSession({ ...session, value: { ...session.value, host_version: record3.cli_version } });
|
|
3333
3869
|
}
|
|
3334
3870
|
async commitCursor(registration, handle, cursor, consumedBytes, device, inode, lastOrdinal, activeTask) {
|
|
3335
3871
|
const offset = cursor.offset + consumedBytes;
|
|
3336
|
-
const
|
|
3337
|
-
|
|
3338
|
-
if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, anchorStart);
|
|
3339
|
-
await this.store.writeTranscriptCursor({ schema_version: "ceal.codex_transcript_cursor.v1", host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, transcript_path: registration.transcript_path, device, inode, offset, last_ordinal: lastOrdinal, anchor_start: anchorStart, anchor_sha256: sha2562(anchor), header_validated: true, ...activeTask === void 0 ? {} : { active_task: activeTask }, status: "active", updated_at: timestamp4(this.now) });
|
|
3872
|
+
const anchor = await this.readAnchor(handle, offset, CODEX_TRANSCRIPT_ANCHOR_BYTES);
|
|
3873
|
+
await this.store.writeTranscriptCursor({ schema_version: "ceal.codex_transcript_cursor.v1", host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, transcript_path: registration.transcript_path, device, inode, offset, last_ordinal: lastOrdinal, anchor_start: anchor.start, anchor_sha256: anchor.sha256, header_validated: true, ...activeTask === void 0 ? {} : { active_task: activeTask }, status: "active", updated_at: this.stamp() });
|
|
3340
3874
|
}
|
|
3341
3875
|
async quarantineFailure(registration, handle) {
|
|
3342
3876
|
if (handle === void 0) return;
|
|
3343
3877
|
const stats = await handle.stat().catch(() => null);
|
|
3344
3878
|
if (stats === null) return;
|
|
3345
3879
|
const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
|
|
3346
|
-
|
|
3880
|
+
const quarantined = {
|
|
3881
|
+
...existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now),
|
|
3882
|
+
transcript_path: registration.transcript_path,
|
|
3883
|
+
device: String(stats.dev),
|
|
3884
|
+
inode: String(stats.ino),
|
|
3885
|
+
status: "quarantined",
|
|
3886
|
+
reason: "transcript_malformed",
|
|
3887
|
+
updated_at: this.stamp()
|
|
3888
|
+
};
|
|
3889
|
+
await this.store.writeTranscriptCursor(quarantined);
|
|
3347
3890
|
}
|
|
3348
3891
|
async processRecord(registration, activeTask, record3) {
|
|
3349
|
-
if (record3.kind === "task_started")
|
|
3350
|
-
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 };
|
|
3351
|
-
}
|
|
3892
|
+
if (record3.kind === "task_started") return this.processTaskStarted(registration, activeTask, record3);
|
|
3352
3893
|
if (record3.kind === "turn_context") return { ...activeTask === void 0 ? {} : { activeTask: record3.turn_id === activeTask.codex_turn_id ? { ...activeTask, model: record3.model } : activeTask }, enqueued: 0 };
|
|
3353
3894
|
if (record3.kind === "token_count") return { ...activeTask === void 0 ? {} : { activeTask: accumulateTokenCount(activeTask, record3) }, enqueued: 0 };
|
|
3354
3895
|
if (record3.kind === "user_text") return this.processUserInput(registration, activeTask, record3);
|
|
3355
3896
|
if (record3.kind === "task_complete" || record3.kind === "turn_aborted") return this.processTerminal(registration, activeTask, record3);
|
|
3356
3897
|
return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
3357
3898
|
}
|
|
3899
|
+
async processTaskStarted(registration, activeTask, record3) {
|
|
3900
|
+
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);
|
|
3901
|
+
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 };
|
|
3902
|
+
}
|
|
3358
3903
|
async processUserInput(registration, activeTask, record3) {
|
|
3359
|
-
if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id)
|
|
3904
|
+
if (activeTask === void 0 || activeTask.codex_turn_id !== record3.turn_id) {
|
|
3905
|
+
const adopted = await this.adoptHookCorrelatedTurn(registration, activeTask, record3);
|
|
3906
|
+
if (adopted === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
3907
|
+
activeTask = adopted;
|
|
3908
|
+
}
|
|
3360
3909
|
const hostInputRef = hostInputReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id, record3.message_id, record3.ordinal, record3.prompt);
|
|
3361
3910
|
const origin = activeTask.origin_host_input_ref === void 0;
|
|
3362
3911
|
const promptValue = promptObservationValue(record3.prompt);
|
|
3363
3912
|
const currentPromptRef = hostProducerPromptRef(promptValue.body_sha256);
|
|
3364
3913
|
const promptRef = activeTask.prompt_ref ?? currentPromptRef;
|
|
3914
|
+
const steeredPromptRef = await this.steeredPromptRef(registration, activeTask, origin, currentPromptRef);
|
|
3365
3915
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: activeTask.host_turn_ref };
|
|
3366
3916
|
const observations = [{
|
|
3367
3917
|
observation_ref: observationRef("transcript_user_text", hostInputRef, record3.prompt),
|
|
@@ -3385,16 +3935,90 @@ var CodexTranscriptWatcher = class {
|
|
|
3385
3935
|
origin_input: origin,
|
|
3386
3936
|
submission_ref: codexSubmissionReference(hostInputRef, "input", record3.ordinal),
|
|
3387
3937
|
created_at: record3.timestamp,
|
|
3388
|
-
expires_at:
|
|
3938
|
+
expires_at: this.expire(this.outboxRetentionMs)
|
|
3939
|
+
};
|
|
3940
|
+
const persisted = await this.persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope);
|
|
3941
|
+
if (steeredPromptRef !== null) await this.activateTranscriptTurnContext(identity, steeredPromptRef, envelope.expires_at);
|
|
3942
|
+
return persisted;
|
|
3943
|
+
}
|
|
3944
|
+
/**
|
|
3945
|
+
* The steered prompt to re-activate under the running task, or null when
|
|
3946
|
+
* there is nothing to repair. A steered message joins the running turn
|
|
3947
|
+
* with new prompt bytes but no task_started. When the steer hook already
|
|
3948
|
+
* replaced the turn selection (a turn id the hook did not recognize as
|
|
3949
|
+
* the running one, or a hook that ran before any context was live), the
|
|
3950
|
+
* running task's context no longer matches and the next invocation would
|
|
3951
|
+
* wait for a boundary that never comes. When the hook kept the live
|
|
3952
|
+
* selection, the context still matches and this stays null.
|
|
3953
|
+
*/
|
|
3954
|
+
async steeredPromptRef(registration, activeTask, origin, currentPromptRef) {
|
|
3955
|
+
if (origin || activeTask.prompt_ref === void 0 || activeTask.prompt_ref === currentPromptRef) return null;
|
|
3956
|
+
const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref).catch(() => null);
|
|
3957
|
+
return selection?.prompt_ref === currentPromptRef ? currentPromptRef : null;
|
|
3958
|
+
}
|
|
3959
|
+
/**
|
|
3960
|
+
* Adopt the live hooked turn when its transcript message arrives before
|
|
3961
|
+
* (or without) its task_started, as an interrupted turn's follow-up can.
|
|
3962
|
+
* The UserPromptSubmit hook names the exact hooked prompt, so only a
|
|
3963
|
+
* message carrying those bytes adopts; inherited history and other turns
|
|
3964
|
+
* stay ignored. A stale active task for another turn releases first, the
|
|
3965
|
+
* same as a task_started supersession.
|
|
3966
|
+
*/
|
|
3967
|
+
async adoptHookCorrelatedTurn(registration, activeTask, record3) {
|
|
3968
|
+
const currentPromptRef = hostProducerPromptRef(promptObservationValue(record3.prompt).body_sha256);
|
|
3969
|
+
const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref);
|
|
3970
|
+
if (selection?.prompt_ref !== currentPromptRef) return void 0;
|
|
3971
|
+
if (activeTask !== void 0 && activeTask.codex_turn_id !== record3.turn_id && activeTask.prompt_ref !== void 0) {
|
|
3972
|
+
await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
|
|
3973
|
+
}
|
|
3974
|
+
return {
|
|
3975
|
+
codex_turn_id: record3.turn_id,
|
|
3976
|
+
host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record3.turn_id),
|
|
3977
|
+
started_at: record3.timestamp
|
|
3389
3978
|
};
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3979
|
+
}
|
|
3980
|
+
async persistUserInputTurn(registration, activeTask, record3, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope) {
|
|
3981
|
+
const custodyRegistered = await this.registerPrompt(origin, registration, activeTask, currentPromptRef, record3.prompt, envelope.expires_at);
|
|
3982
|
+
if (custodyRegistered) this.diagnostic("watcher_turn_observed");
|
|
3983
|
+
try {
|
|
3984
|
+
await this.store.put(envelope);
|
|
3985
|
+
if (origin) await this.activateTranscriptTurnContext(identity, promptRef, envelope.expires_at);
|
|
3986
|
+
} catch (error) {
|
|
3987
|
+
if (custodyRegistered) await this.releasePrompt(registration.host_session_ref, activeTask, currentPromptRef).catch(() => void 0);
|
|
3988
|
+
throw error;
|
|
3989
|
+
}
|
|
3393
3990
|
return { activeTask: { ...activeTask, origin_host_input_ref: activeTask.origin_host_input_ref ?? hostInputRef, prompt_ref: activeTask.prompt_ref ?? currentPromptRef }, enqueued: 1 };
|
|
3394
3991
|
}
|
|
3395
3992
|
async registerPrompt(origin, registration, activeTask, promptRef, prompt, expiresAt) {
|
|
3396
|
-
if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
|
|
3397
|
-
|
|
3993
|
+
if (!origin || this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
|
|
3994
|
+
const register = this.registerCurrentPrompt;
|
|
3995
|
+
const installationRef = this.producerInstallationRef;
|
|
3996
|
+
return registerHostCurrentPromptDiagnosed(
|
|
3997
|
+
() => 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 }),
|
|
3998
|
+
(event) => this.diagnostic(event)
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
4001
|
+
async activateTranscriptTurnContext(identity, promptRef, expiresAt) {
|
|
4002
|
+
try {
|
|
4003
|
+
const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
4004
|
+
const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
|
|
4005
|
+
await this.store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
|
|
4006
|
+
this.diagnostic("activation_recorded");
|
|
4007
|
+
} catch (error) {
|
|
4008
|
+
this.diagnostic("activation_failed");
|
|
4009
|
+
throw error;
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
async releasePrompt(sessionRef, activeTask, promptRef) {
|
|
4013
|
+
if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
|
|
4014
|
+
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 });
|
|
4015
|
+
}
|
|
4016
|
+
async releaseStoredPromptBestEffort(sessionRef, cursor) {
|
|
4017
|
+
await this.releaseActiveTaskBestEffort(sessionRef, cursor?.active_task);
|
|
4018
|
+
}
|
|
4019
|
+
async releaseActiveTaskBestEffort(sessionRef, activeTask) {
|
|
4020
|
+
if (activeTask?.prompt_ref === void 0) return;
|
|
4021
|
+
await this.releasePrompt(sessionRef, activeTask, activeTask.prompt_ref).catch(() => void 0);
|
|
3398
4022
|
}
|
|
3399
4023
|
async processTerminal(registration, activeTask, record3) {
|
|
3400
4024
|
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 };
|
|
@@ -3412,16 +4036,26 @@ var CodexTranscriptWatcher = class {
|
|
|
3412
4036
|
origin_input: false,
|
|
3413
4037
|
submission_ref: codexSubmissionReference(activeTask.origin_host_input_ref, "terminal", record3.ordinal),
|
|
3414
4038
|
created_at: record3.timestamp,
|
|
3415
|
-
expires_at:
|
|
4039
|
+
expires_at: this.expire(this.outboxRetentionMs)
|
|
3416
4040
|
});
|
|
4041
|
+
await this.releasePrompt(registration.host_session_ref, activeTask, activeTask.prompt_ref);
|
|
3417
4042
|
await this.store.clearInvocationContext(identity, this.now());
|
|
3418
4043
|
return { enqueued: 1 };
|
|
3419
4044
|
}
|
|
3420
4045
|
async quarantine(registration, cursor, device, inode, reason) {
|
|
3421
|
-
|
|
4046
|
+
await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
|
|
4047
|
+
const quarantined = { ...cursor, transcript_path: registration.transcript_path, device, inode, status: "quarantined", reason, updated_at: this.stamp() };
|
|
3422
4048
|
await this.store.writeTranscriptCursor(quarantined);
|
|
3423
4049
|
}
|
|
3424
4050
|
};
|
|
4051
|
+
function codexTranscriptScanFailureKind(error) {
|
|
4052
|
+
return transcriptScanFailureKind(error, (candidate) => {
|
|
4053
|
+
if (!(candidate instanceof CodexHostAdapterError)) return void 0;
|
|
4054
|
+
if (candidate.code === "payload_rejected") return "payload_rejected";
|
|
4055
|
+
if (candidate.code === "storage") return "read_error";
|
|
4056
|
+
return void 0;
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
3425
4059
|
function accumulateTokenCount(activeTask, record3) {
|
|
3426
4060
|
if (record3.usage_gap_reason !== void 0) return { ...activeTask, token_usage_gap_reason: record3.usage_gap_reason };
|
|
3427
4061
|
const last = record3.last_usage;
|
|
@@ -3455,19 +4089,18 @@ function terminalCostValue() {
|
|
|
3455
4089
|
return notRecorded("provider_cost_not_observed");
|
|
3456
4090
|
}
|
|
3457
4091
|
function notRecorded(reason) {
|
|
3458
|
-
return
|
|
4092
|
+
return hostObservationNotRecorded(reason);
|
|
3459
4093
|
}
|
|
3460
4094
|
function tokenObservationValue(usage) {
|
|
3461
4095
|
return {
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
source: "codex_transcript.event_msg.token_count.info.last_token_usage",
|
|
4096
|
+
...createHostTokenObservationValue({
|
|
4097
|
+
input: usage.input_tokens,
|
|
4098
|
+
output: usage.output_tokens,
|
|
4099
|
+
total: usage.total_tokens,
|
|
4100
|
+
cached_input: usage.cached_input_tokens,
|
|
4101
|
+
cache_write_input: usage.cache_write_input_tokens,
|
|
4102
|
+
reasoning_output: usage.reasoning_output_tokens
|
|
4103
|
+
}, "codex_transcript.event_msg.token_count.info.last_token_usage"),
|
|
3471
4104
|
attribution: "sequential_active_task",
|
|
3472
4105
|
total_usage_role: "dedup_and_reset_detection_only"
|
|
3473
4106
|
};
|
|
@@ -3501,65 +4134,24 @@ function looksLikeStaleRegression(current, previous, last) {
|
|
|
3501
4134
|
if (previous.total_tokens <= 0 || current.total_tokens <= 0 || last.total_tokens <= 0) return false;
|
|
3502
4135
|
return current.total_tokens * 100 >= previous.total_tokens * 98 || current.total_tokens + last.total_tokens * 2 >= previous.total_tokens;
|
|
3503
4136
|
}
|
|
3504
|
-
function isMissingTranscript(error) {
|
|
3505
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3506
|
-
}
|
|
3507
|
-
async function cursorFailureReason(registration, handle, cursor, size, device, inode) {
|
|
3508
|
-
if (cursor.device !== device || cursor.inode !== inode || cursor.transcript_path !== registration.transcript_path) return "transcript_replaced";
|
|
3509
|
-
if (size < cursor.offset) return "transcript_truncated";
|
|
3510
|
-
if (cursor.offset > 0 && !await anchorMatches(handle, cursor)) return "transcript_rewritten";
|
|
3511
|
-
return null;
|
|
3512
|
-
}
|
|
3513
|
-
async function readCompleteChunk(handle, cursor, size) {
|
|
3514
|
-
const available = Math.max(0, size - cursor.offset);
|
|
3515
|
-
if (available === 0) return "empty";
|
|
3516
|
-
const chunks = [];
|
|
3517
|
-
let position = cursor.offset;
|
|
3518
|
-
let total = 0;
|
|
3519
|
-
let lastNewline = -1;
|
|
3520
|
-
while (position < size) {
|
|
3521
|
-
const buffer = Buffer.alloc(Math.min(READ_CHUNK_BYTES, size - position));
|
|
3522
|
-
const read = await handle.read(buffer, 0, buffer.length, position);
|
|
3523
|
-
if (read.bytesRead === 0) break;
|
|
3524
|
-
const chunk = buffer.subarray(0, read.bytesRead);
|
|
3525
|
-
chunks.push(chunk);
|
|
3526
|
-
const newline = chunk.lastIndexOf(10);
|
|
3527
|
-
if (newline >= 0) lastNewline = total + newline;
|
|
3528
|
-
total += chunk.length;
|
|
3529
|
-
position += chunk.length;
|
|
3530
|
-
if (lastNewline >= 0) break;
|
|
3531
|
-
}
|
|
3532
|
-
if (lastNewline < 0) return "partial";
|
|
3533
|
-
return Buffer.concat(chunks, total).subarray(0, lastNewline + 1);
|
|
3534
|
-
}
|
|
3535
4137
|
function assertRecordSequence(registration, cursor, records, lastOrdinal, record3) {
|
|
3536
4138
|
if ((cursor.header_validated || records > 0) && record3.ordinal <= lastOrdinal) throw new CodexHostAdapterError("payload_rejected", `Codex transcript ordinal is not monotonic (${refusalFact("ordinal", record3.ordinal, `greater than ${lastOrdinal}`)}).`);
|
|
3537
4139
|
if (!cursor.header_validated && records === 0) assertCodexTranscriptHeader(registration, record3);
|
|
3538
4140
|
}
|
|
3539
|
-
async function anchorMatches(handle, cursor) {
|
|
3540
|
-
const buffer = Buffer.alloc(cursor.offset - cursor.anchor_start);
|
|
3541
|
-
if (buffer.length > 0) await handle.read(buffer, 0, buffer.length, cursor.anchor_start);
|
|
3542
|
-
return sha2562(buffer) === cursor.anchor_sha256;
|
|
3543
|
-
}
|
|
3544
|
-
function sha2562(value) {
|
|
3545
|
-
return createHash6("sha256").update(value).digest("hex");
|
|
3546
|
-
}
|
|
3547
|
-
function isRecord6(value) {
|
|
3548
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3549
|
-
}
|
|
3550
|
-
function timestamp4(now) {
|
|
3551
|
-
const value = now();
|
|
3552
|
-
if (!Number.isFinite(value.getTime())) throw new CodexHostAdapterError("storage", "Codex watcher clock is invalid.");
|
|
3553
|
-
return value.toISOString();
|
|
3554
|
-
}
|
|
3555
|
-
function expiration(now, retentionMs) {
|
|
3556
|
-
return new Date(Date.parse(timestamp4(now)) + retentionMs).toISOString();
|
|
3557
|
-
}
|
|
3558
4141
|
|
|
3559
4142
|
// host/codex/src/codex-producer-runtime.ts
|
|
3560
4143
|
async function drainCodexProducerOnce(options) {
|
|
3561
4144
|
const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
|
|
3562
|
-
const scan = await new CodexTranscriptWatcher({
|
|
4145
|
+
const scan = await new CodexTranscriptWatcher({
|
|
4146
|
+
outbox_dir: options.outbox_dir,
|
|
4147
|
+
producer_installation_ref: options.producer_installation_ref,
|
|
4148
|
+
register_current_prompt: (input) => promptPort.register(input),
|
|
4149
|
+
release_current_prompt: (identity) => promptPort.release(identity),
|
|
4150
|
+
diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("codex", event, failure_kind)
|
|
4151
|
+
}).scanOnce().catch((error) => {
|
|
4152
|
+
writeHostServiceDiagnostic("codex", "watcher_scan_failed", codexTranscriptScanFailureKind(error));
|
|
4153
|
+
throw error;
|
|
4154
|
+
});
|
|
3563
4155
|
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 } });
|
|
3564
4156
|
return { scanned: scan.enqueued, ...await producer.drainOutbox() };
|
|
3565
4157
|
}
|
|
@@ -3585,53 +4177,6 @@ function runCodexProducerCli(args, signal, dependencies = {}) {
|
|
|
3585
4177
|
return runHostProducerCli(CODEX_PRODUCER_DESCRIPTOR, args, signal, dependencies);
|
|
3586
4178
|
}
|
|
3587
4179
|
|
|
3588
|
-
// host/service-log.ts
|
|
3589
|
-
import { copyFile, open as open5, stat, truncate } from "node:fs/promises";
|
|
3590
|
-
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
3591
|
-
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
3592
|
-
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
3593
|
-
var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
|
|
3594
|
-
function hostServiceLogPreviousPath(path) {
|
|
3595
|
-
return `${path}.1`;
|
|
3596
|
-
}
|
|
3597
|
-
async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
|
|
3598
|
-
let size;
|
|
3599
|
-
try {
|
|
3600
|
-
size = (await stat(path)).size;
|
|
3601
|
-
} catch (error) {
|
|
3602
|
-
if (isNotFound2(error)) return false;
|
|
3603
|
-
throw error;
|
|
3604
|
-
}
|
|
3605
|
-
if (size <= maxBytes) return false;
|
|
3606
|
-
await copyFile(path, hostServiceLogPreviousPath(path));
|
|
3607
|
-
await truncate(path, 0);
|
|
3608
|
-
return true;
|
|
3609
|
-
}
|
|
3610
|
-
function boundHostServiceLog(environment = process.env) {
|
|
3611
|
-
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
3612
|
-
if (path === void 0 || !isAbsolute2(path)) return () => void 0;
|
|
3613
|
-
let reported = false;
|
|
3614
|
-
const check = () => {
|
|
3615
|
-
rotateHostServiceLog(path).catch((error) => {
|
|
3616
|
-
if (reported) return;
|
|
3617
|
-
reported = true;
|
|
3618
|
-
process.stderr.write(`[host-service-log] rotation_failed code=${errorCode3(error)}
|
|
3619
|
-
`);
|
|
3620
|
-
});
|
|
3621
|
-
};
|
|
3622
|
-
check();
|
|
3623
|
-
const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
|
|
3624
|
-
timer.unref();
|
|
3625
|
-
return () => clearInterval(timer);
|
|
3626
|
-
}
|
|
3627
|
-
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
3628
|
-
function isNotFound2(error) {
|
|
3629
|
-
return errorCode3(error) === "ENOENT";
|
|
3630
|
-
}
|
|
3631
|
-
function errorCode3(error) {
|
|
3632
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
3633
|
-
}
|
|
3634
|
-
|
|
3635
4180
|
// host/producer-command.ts
|
|
3636
4181
|
async function runProducerCommand(schemaVersion, run) {
|
|
3637
4182
|
const controller = new AbortController();
|