@corca-org/ceal-host-linux-arm64 1.0.17 → 1.0.19

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.
@@ -109,6 +109,9 @@ var client_wire_contract_default = {
109
109
  "document"
110
110
  ],
111
111
  optional_fields: [
112
+ "operation_id",
113
+ "visible_output_max_bytes",
114
+ "local_delivery",
112
115
  "verified_artifact"
113
116
  ],
114
117
  bare_document_fields: [
@@ -195,6 +198,7 @@ var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_CONTINUATION_DOCUMENT_FIELDS = Object.f
195
198
  var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
196
199
  var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = response.discovery_manifest_schema_version;
197
200
  var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
201
+ var CEAL_COMMAND_RESPONSE_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
198
202
 
199
203
  // packages/ceal-client-protocol/src/vocabulary.ts
200
204
  function vocabulary(words) {
@@ -225,6 +229,7 @@ var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verifie
225
229
  var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
226
230
  var CEAL_PROTOCOL_VERSION = "1.4.0";
227
231
  var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
232
+ var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
228
233
  var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
229
234
  "audited_call_pending",
230
235
  "continuation_read_pending",
@@ -248,6 +253,7 @@ var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
248
253
  // packages/ceal-client-protocol/src/operation-next-actions.ts
249
254
  var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
250
255
  var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
256
+ var CEAL_ANSWER_WITH_GAP_NEXT_ACTION = "answer_with_gap";
251
257
  var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
252
258
  "select_did_you_mean",
253
259
  "choose_new_output_path",
@@ -301,10 +307,20 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
301
307
  "retry_after",
302
308
  // The ref names a container of rows (a Notion database or data source), which
303
309
  // has no body of its own; the caller searches its rows instead of reading it.
304
- "search_container_rows"
310
+ "search_container_rows",
311
+ // A settled unknown write names the exact receipt Operation that can inspect
312
+ // its outcome without issuing another provider mutation.
313
+ "write_receipt.get",
314
+ // Command resolution carries the exact required Operation sequence beside
315
+ // this action, so the caller can execute each declared input in order.
316
+ "follow_required_action_sequence",
317
+ // The command needs caller-owned local materialization (for example an
318
+ // @path file) before the Gateway can receive the argument.
319
+ "materialize_local_argument"
305
320
  ];
306
321
  var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
307
322
  var NON_REMEDY_NEXT_ACTION_VALUES = [
323
+ CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
308
324
  // Only an administrator can take the named step -- approving a place for an
309
325
  // Operation, granting a withheld capability -- so a non-administrator asks
310
326
  // one. The refusal names the administrator step; this word is the ask.
@@ -352,7 +368,58 @@ var NON_REMEDY_NEXT_ACTION_VALUES = [
352
368
  "select_exact_approval"
353
369
  ];
354
370
  var CEAL_NON_REMEDY_NEXT_ACTIONS = Object.freeze(NON_REMEDY_NEXT_ACTION_VALUES);
355
- var DECLARED = /* @__PURE__ */ new Set([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
371
+ var CEAL_DECLARED_NEXT_ACTIONS = Object.freeze([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
372
+ var DECLARED = new Set(CEAL_DECLARED_NEXT_ACTIONS);
373
+ function defineCealNonModelNextActions(actions) {
374
+ return actions;
375
+ }
376
+ var NON_MODEL_NEXT_ACTION_VALUES = defineCealNonModelNextActions([
377
+ CEAL_INSPECT_RECEIPT_NEXT_ACTION,
378
+ "await_pending_approval",
379
+ "inspect_gateway",
380
+ "operator_action",
381
+ "none",
382
+ "check_gateway",
383
+ "inspect_context_store",
384
+ "inspect_local_cache",
385
+ "inspect_local_client",
386
+ "inspect_local_command",
387
+ "inspect_local_output",
388
+ "inspect_local_route",
389
+ "inspect_request",
390
+ "reconcile",
391
+ "reconcile_occurrence",
392
+ "retry_after_owner_recovery",
393
+ "select_exact_approval"
394
+ ]);
395
+ var NON_MODEL_NEXT_ACTIONS = new Set(NON_MODEL_NEXT_ACTION_VALUES);
396
+ function isCealModelActionableNonRemedyNextAction(action) {
397
+ return !NON_MODEL_NEXT_ACTIONS.has(action);
398
+ }
399
+ var CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS = Object.freeze([
400
+ ...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS,
401
+ ...CEAL_NON_REMEDY_NEXT_ACTIONS.filter(isCealModelActionableNonRemedyNextAction)
402
+ ]);
403
+ var MODEL_ACTIONABLE = new Set(CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS);
404
+ var NON_MODEL_ACTION_TO_MODEL_ACTION = Object.freeze({
405
+ [CEAL_INSPECT_RECEIPT_NEXT_ACTION]: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
406
+ await_pending_approval: "ask_admin",
407
+ inspect_gateway: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
408
+ operator_action: "ask_admin",
409
+ none: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
410
+ check_gateway: "ask_admin",
411
+ inspect_context_store: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
412
+ inspect_local_cache: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
413
+ inspect_local_client: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
414
+ inspect_local_command: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
415
+ inspect_local_output: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
416
+ inspect_local_route: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
417
+ inspect_request: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
418
+ reconcile: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
419
+ reconcile_occurrence: "ask_admin",
420
+ retry_after_owner_recovery: "ask_admin",
421
+ select_exact_approval: "ask_admin"
422
+ });
356
423
  var RETRY_PROMISE_NEXT_ACTIONS = ["retry", "retry_after"];
357
424
  var RETRY_PROMISE = new Set(RETRY_PROMISE_NEXT_ACTIONS);
358
425
  var RECOVERABLE = new Set(CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS);
@@ -409,9 +476,9 @@ var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
409
476
  var CealProtocolValidationError = class extends Error {
410
477
  name = "CealProtocolValidationError";
411
478
  code;
412
- constructor(code3) {
413
- super(code3 === "invalid_gateway_request" ? "Ceal Gateway request is invalid." : "Ceal client response is invalid.");
414
- this.code = code3;
479
+ constructor(code2) {
480
+ super(code2 === "invalid_gateway_request" ? "Ceal Gateway request is invalid." : "Ceal client response is invalid.");
481
+ this.code = code2;
415
482
  }
416
483
  };
417
484
  var SAFE_REF = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
@@ -444,10 +511,10 @@ function assertSafeJsonArray(value, options, depth, count) {
444
511
  if (value.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
445
512
  for (const item of value) assertSafeJsonValue(item, options, depth + 1, count);
446
513
  }
447
- function assertSafeJsonRecord(record6, options, depth, count) {
448
- const entries = Object.entries(record6);
514
+ function assertSafeJsonRecord(record5, options, depth, count) {
515
+ const entries = Object.entries(record5);
449
516
  if (entries.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
450
- const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record6.fields) : null;
517
+ const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record5.fields) : null;
451
518
  for (const [key, child] of entries) {
452
519
  if (key === "credential_material_included" && child !== false) invalidByContext(options);
453
520
  if (!isSafeNegativeMaterialAssertion(key, child)) assertSafeJsonKey(key, options);
@@ -622,21 +689,15 @@ var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
622
689
  // packages/ceal-client-protocol/src/domain-vocabulary.ts
623
690
  var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
624
691
  var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
625
- var CEAL_PROVIDER_BOUNDARY_KINDS = vocabulary([
626
- "github_account",
627
- "github_organization",
628
- "google_workspace_domain",
629
- "notion_workspace",
630
- "slack_workspace"
631
- ]);
632
- var CEAL_PROVIDER_BOUNDARY_EVIDENCE = vocabulary(["provider_readback", "operator_stated"]);
633
692
  var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
634
693
  var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
635
694
  var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
636
695
  var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
637
696
  var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
638
- var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
697
+ var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
698
+ var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
639
699
  var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
700
+ var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
640
701
  var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
641
702
 
642
703
  // packages/ceal-client-protocol/src/safe-json-budget.ts
@@ -812,7 +873,7 @@ var CEAL_SCHEMA_LITERALS = [
812
873
  "ceal.operation_receipt.v1",
813
874
  "ceal.operation_result.v1",
814
875
  "ceal.operation_result.compact.v1",
815
- "ceal.local_result_materialization.v1",
876
+ "ceal.local_result_file.v1",
816
877
  "ceal.local_result_root.v1",
817
878
  "ceal.owner_bootstrap_exchange_request.v1",
818
879
  "ceal.owner_bootstrap_exchange_result.v1",
@@ -842,6 +903,9 @@ var CEAL_BOUNDED_SERIES_CATALOG = Object.freeze({
842
903
  gateway_personal_client_access_audit: (root) => join(root, "access-audit.jsonl")
843
904
  });
844
905
 
906
+ // packages/ceal-client-protocol/src/canonical-http-boundary.ts
907
+ var CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES = CEAL_COMMAND_RESPONSE_MAX_BYTES;
908
+
845
909
  // packages/ceal-client-protocol/src/gateway-installation-activation.ts
846
910
  var CEAL_GATEWAY_INSTALLATION_ACTIVATION_CODE_DATA_CHARACTERS = 32;
847
911
  var CEAL_GATEWAY_INSTALLATION_ACTIVATION_CODE_CHECK_CHARACTERS = 8;
@@ -906,41 +970,6 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
906
970
  }
907
971
  });
908
972
 
909
- // packages/ceal-client-protocol/src/provider-operation-vocabulary.ts
910
- var CEAL_FILE_TYPE_FAMILIES = vocabulary(["all", "image", "pdf"]);
911
- var CEAL_GITHUB_ISSUE_STATES = vocabulary(["open", "closed", "all"]);
912
- var CEAL_NOTION_UPDATABLE_BLOCK_TYPES = vocabulary([
913
- "paragraph",
914
- "heading_1",
915
- "heading_2",
916
- "heading_3",
917
- "bulleted_list_item",
918
- "numbered_list_item",
919
- "to_do",
920
- "code"
921
- ]);
922
- var CEAL_NOTION_NEUTRAL_TYPES = vocabulary([
923
- "text",
924
- "number",
925
- "boolean",
926
- "date",
927
- "select",
928
- "multi_select",
929
- "people",
930
- "url",
931
- "email",
932
- "phone",
933
- "unsupported"
934
- ]);
935
- var CEAL_NOTION_IDENTITY_GAP_REASONS = vocabulary([
936
- "identity_unavailable",
937
- "identity_stale",
938
- "identity_ambiguous",
939
- "identity_revoked",
940
- "identity_unlinked"
941
- ]);
942
- var CEAL_NOTION_STRING_VALUE_TYPES = vocabulary(["text", "select", "url", "email", "phone"]);
943
-
944
973
  // packages/ceal-client-protocol/src/opaque-reference.ts
945
974
  var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
946
975
  var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
@@ -952,7 +981,7 @@ var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_o
952
981
  // packages/ceal-client-protocol/src/resource-row.ts
953
982
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
954
983
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
955
- var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
984
+ 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"]);
956
985
  var CEAL_RESOURCE_TIME_BASES = vocabulary(["created", "modified", "viewed"]);
957
986
  var CEAL_REPOSITORY_VISIBILITIES = vocabulary(["public", "private", "internal"]);
958
987
  var CEAL_ACTIVITY_V3_KINDS = vocabulary(["authored", "edited", "messaged", "responded"]);
@@ -973,6 +1002,7 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
973
1002
  "resource_modified"
974
1003
  ]);
975
1004
  var CEAL_ACTIVITY_CONNECTOR_KINDS = vocabulary(["slack", "github", "google-calendar", "google-drive", "notion"]);
1005
+ var CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE = "unsupported_event_kind";
976
1006
  var CEAL_ACTIVITY_GAP_CODES = vocabulary([
977
1007
  "provider_page_budget",
978
1008
  "call_deadline",
@@ -980,7 +1010,7 @@ var CEAL_ACTIVITY_GAP_CODES = vocabulary([
980
1010
  "provider_cursor_unavailable",
981
1011
  "malformed_provider_page",
982
1012
  "visibility_incomplete",
983
- "unsupported_event_kind",
1013
+ CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE,
984
1014
  "direct_messages_excluded"
985
1015
  ]);
986
1016
  var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
@@ -1031,6 +1061,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1031
1061
  ref: { required: true, timestamp: false },
1032
1062
  next: { required: true, timestamp: false },
1033
1063
  kind: { required: true, timestamp: false },
1064
+ matched_kinds: { required: false, timestamp: false },
1034
1065
  title: { required: true, timestamp: false },
1035
1066
  container_label: { required: true, timestamp: false },
1036
1067
  container_ref: { required: false, timestamp: false },
@@ -1044,6 +1075,8 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1044
1075
  modified_at: { required: false, timestamp: true },
1045
1076
  modified_at_local: { required: false, timestamp: true },
1046
1077
  viewed_by_me_at: { required: false, timestamp: true },
1078
+ own_ceal_write: { required: false, timestamp: false },
1079
+ owned_by_me: { required: false, timestamp: false },
1047
1080
  visibility: { required: false, timestamp: false },
1048
1081
  start: { required: false, timestamp: false },
1049
1082
  end: { required: false, timestamp: false },
@@ -1077,6 +1110,15 @@ function cealExcludedTargetCoverageJsonSchema() {
1077
1110
  };
1078
1111
  }
1079
1112
 
1113
+ // packages/ceal-client-protocol/src/gateway-write-identity.ts
1114
+ var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1115
+ var CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN = /^gateway-write-request:[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/u;
1116
+ var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1117
+ replay_identity: "idempotency_claim_sha256",
1118
+ lookup_handle: "write_request_sha256",
1119
+ collision_evidence: "normalized_mutation_sha256"
1120
+ });
1121
+
1080
1122
  // packages/ceal-client-protocol/src/write-safety-finding.ts
1081
1123
  var CEAL_WRITE_SAFETY_FINDING_STATUSES = vocabulary(["warning", "unchecked"]);
1082
1124
  var CEAL_WRITE_SAFETY_AUDIENCES = vocabulary(["workspace", "person", "members", "unknown"]);
@@ -1104,7 +1146,11 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
1104
1146
  "refresh_revoked",
1105
1147
  "authority_replaced",
1106
1148
  "refresh_recovery_unavailable",
1107
- "refresh_recovery_expired"
1149
+ "refresh_recovery_expired",
1150
+ // The serving gate answers this while the Gateway is still starting and the
1151
+ // refresh was never attempted; the same attempt stays retryable. It is never
1152
+ // stored by the session owner, only answered at the serving edge.
1153
+ "gateway_starting"
1108
1154
  ]);
1109
1155
 
1110
1156
  // packages/ceal-client-protocol/src/request-validation-primitives.ts
@@ -1137,7 +1183,7 @@ var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
1137
1183
  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"]);
1138
1184
  var CEAL_REQUEST_JSON_SCHEMA = {
1139
1185
  type: "object",
1140
- maxProperties: 10,
1186
+ maxProperties: 11,
1141
1187
  additionalProperties: false,
1142
1188
  properties: {
1143
1189
  schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
@@ -1161,7 +1207,8 @@ var CEAL_REQUEST_JSON_SCHEMA = {
1161
1207
  host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1162
1208
  host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1163
1209
  host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1164
- requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." }
1210
+ requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
1211
+ 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." }
1165
1212
  },
1166
1213
  required: CEAL_REQUEST_KEYS
1167
1214
  };
@@ -1220,6 +1267,7 @@ var REQUEST_EXECUTION_PREPARE_REFUSAL_PROPERTIES = {
1220
1267
  retryable: { type: "boolean" },
1221
1268
  source: { type: "string", enum: ["provider", "gateway"] },
1222
1269
  retry_after_ms: { type: "integer", minimum: 0, maximum: CEAL_RETRY_AFTER_MAX_MS },
1270
+ write_request_ref: { type: "string", pattern: CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN.source, maxLength: 128 },
1223
1271
  excluded_targets: cealExcludedTargetCoverageJsonSchema(),
1224
1272
  repaired_input: {},
1225
1273
  unrepaired_pointers: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 256 } },
@@ -1278,12 +1326,55 @@ function isCealHostInputKind(value) {
1278
1326
  return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
1279
1327
  }
1280
1328
  var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
1329
+ var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
1330
+ var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
1331
+ function cealHostObservationRef(producer, digest2) {
1332
+ if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(digest2)) invalid("Ceal Host observation reference parts are invalid.");
1333
+ return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${digest2}`;
1334
+ }
1335
+ var HOST_TOKEN_OBSERVATION_BREAKDOWN_KEYS = vocabulary(["cached_input", "cache_write_input", "reasoning_output"]);
1336
+ function createHostTokenObservationValue(counts, source) {
1337
+ const core = validTokenCore(counts);
1338
+ if (typeof source !== "string" || source.length === 0 || source.length > 256) invalid("Ceal Host token observation source is invalid.");
1339
+ const value = { status: "available", unit: "tokens", input: core.input, output: core.output, total: core.total, source };
1340
+ for (const breakdown of validTokenBreakdowns(counts)) value[breakdown.key] = breakdown.tokens;
1341
+ assertHostObservationValue(value, "token");
1342
+ return value;
1343
+ }
1344
+ function validTokenCore(counts) {
1345
+ if (!isOperationRecord(counts) || !safeCount(counts.input) || !safeCount(counts.output) || !safeCount(counts.total)) invalid("Ceal Host token observation counts are invalid.");
1346
+ if (counts.total !== counts.input + counts.output) invalid("Ceal Host token observation counts are invalid.");
1347
+ return { input: counts.input, output: counts.output, total: counts.total };
1348
+ }
1349
+ function validTokenBreakdowns(counts) {
1350
+ const breakdowns = [
1351
+ { key: "cached_input", tokens: counts.cached_input },
1352
+ { key: "cache_write_input", tokens: counts.cache_write_input },
1353
+ { key: "reasoning_output", tokens: counts.reasoning_output }
1354
+ ];
1355
+ const stated = breakdowns.filter((breakdown) => breakdown.tokens !== void 0).map((breakdown) => ({ key: breakdown.key, tokens: Number(breakdown.tokens) }));
1356
+ for (const breakdown of stated) {
1357
+ if (!safeCount(breakdown.tokens)) invalid("Ceal Host token observation counts are invalid.");
1358
+ }
1359
+ const cached = counts.cached_input ?? 0;
1360
+ const created = counts.cache_write_input ?? 0;
1361
+ const reasoning = counts.reasoning_output ?? 0;
1362
+ if (cached + created > counts.input || reasoning > counts.output) invalid("Ceal Host token observation counts are invalid.");
1363
+ return stated;
1364
+ }
1365
+ function hostObservationNotRecorded(reason) {
1366
+ if (typeof reason !== "string" || reason.length === 0 || reason.length > 256) invalid("Ceal Host observation gap reason is invalid.");
1367
+ return { status: "not_recorded", reason };
1368
+ }
1369
+ function safeCount(value) {
1370
+ return Number.isSafeInteger(value) && Number(value) >= 0;
1371
+ }
1281
1372
  function validateCealHostObservationDraft(value) {
1282
- const record6 = operationRecord(value, "Ceal Host observation draft is invalid.");
1283
- requirePrefixedRef(record6.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1284
- requireClosedValue(record6.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1285
- requireIsoTimestamp(record6.observed_at, "Ceal Host observation time is invalid.");
1286
- assertHostObservationValue(record6.value, record6.observation_kind);
1373
+ const record5 = operationRecord(value, "Ceal Host observation draft is invalid.");
1374
+ requirePrefixedRef(record5.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1375
+ requireClosedValue(record5.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1376
+ requireIsoTimestamp(record5.observed_at, "Ceal Host observation time is invalid.");
1377
+ assertHostObservationValue(record5.value, record5.observation_kind);
1287
1378
  }
1288
1379
  var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
1289
1380
  var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "cost", "timing"]);
@@ -1313,6 +1404,10 @@ function inspectHostKeys(value) {
1313
1404
  // packages/ceal-client-protocol/src/host-prompt-ref.ts
1314
1405
  var CEAL_HOST_PROMPT_DIGEST_PATTERN = "^[a-f0-9]{64}$";
1315
1406
  var PROMPT_DIGEST = new RegExp(CEAL_HOST_PROMPT_DIGEST_PATTERN, "u");
1407
+ function hostProducerPromptRef(promptSha256) {
1408
+ if (!PROMPT_DIGEST.test(promptSha256)) throw new TypeError("Canonical Host prompt digest is invalid.");
1409
+ return `ceal-host-prompt:${promptSha256}`;
1410
+ }
1316
1411
 
1317
1412
  // packages/ceal-client-protocol/src/request-effect-approval.ts
1318
1413
  var CEAL_EFFECT_APPROVAL_DECISION_CHECK_PHRASES = Object.freeze([
@@ -1416,6 +1511,65 @@ var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = new TextEncoder().enc
1416
1511
  expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
1417
1512
  })}
1418
1513
  `).byteLength;
1514
+ var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
1515
+ "request_authority_unavailable",
1516
+ "request_missing",
1517
+ "authority_mismatch",
1518
+ "host_source_mismatch",
1519
+ "host_session_mismatch",
1520
+ "host_turn_mismatch",
1521
+ "host_input_mismatch",
1522
+ "host_prompt_mismatch"
1523
+ ]);
1524
+ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
1525
+ "invalid_arguments",
1526
+ "options_invalid",
1527
+ "session_unavailable",
1528
+ "context_unavailable",
1529
+ "context_replaced",
1530
+ "no_binding",
1531
+ "context_stale",
1532
+ "context_expired",
1533
+ "host_source_refused",
1534
+ "host_credential_refused",
1535
+ "gateway_unavailable",
1536
+ "gateway_rate_limited",
1537
+ "gateway_refused",
1538
+ "gateway_response_invalid",
1539
+ "producer_frame_invalid",
1540
+ "relay_permission_denied",
1541
+ "relay_unavailable",
1542
+ "transient_retryable",
1543
+ "mint_failed",
1544
+ "relay_failed",
1545
+ "host_refusal_invalid"
1546
+ ]);
1547
+ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1548
+ invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1549
+ options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1550
+ session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1551
+ context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "refresh_host_invocation_context", remedy: "the Host has no invocation context from the current transcript turn; run `ceal-host install` to repair the Host hook registration, then retry this command on a fresh Host turn" }),
1552
+ context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1553
+ no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1554
+ context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1555
+ context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "refresh_host_invocation_context" }),
1556
+ host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
1557
+ host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
1558
+ gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
1559
+ gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
1560
+ gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
1561
+ gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
1562
+ 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" }),
1563
+ 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" }),
1564
+ relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
1565
+ transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
1566
+ mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
1567
+ relay_failed: Object.freeze({ classification: "host_relay_failed", next_action: "answer_with_gap" }),
1568
+ host_refusal_invalid: Object.freeze({ classification: "host_refusal_invalid", next_action: "answer_with_gap" })
1569
+ });
1570
+ function isCealHostAuthenticationCause(value) {
1571
+ return isVocabularyWord(CEAL_HOST_AUTHENTICATION_CAUSES, value);
1572
+ }
1419
1573
  function decodeCealHostInvocationAuthenticationRequest(value) {
1420
1574
  if (!isCealJsonRecord(value) || !exactKeys(value, ["schema_version", "client_nonce"]) || value.schema_version !== CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA || typeof value.client_nonce !== "string" || !CEAL_HOST_CLIENT_NONCE_PATTERN.test(value.client_nonce)) {
1421
1575
  throw new TypeError("The Host invocation authentication request is invalid.");
@@ -1437,11 +1591,37 @@ function exactKeys(value, expected) {
1437
1591
  return keys.length === expected.length && expected.every((key) => Object.hasOwn(value, key));
1438
1592
  }
1439
1593
 
1594
+ // packages/ceal-client-protocol/src/host-service-diagnostic.ts
1595
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
1596
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
1597
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1598
+ "hook_invoked",
1599
+ "registration_written",
1600
+ "hook_failed",
1601
+ "watcher_turn_observed",
1602
+ "current_prompt_registered",
1603
+ "current_prompt_registration_failed",
1604
+ "activation_recorded",
1605
+ "activation_failed",
1606
+ "watcher_scan_failed",
1607
+ "activation_wait_skipped",
1608
+ "activation_wait_engaged",
1609
+ "activation_wait_completed",
1610
+ "activation_wait_exhausted"
1611
+ ]);
1612
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_FAILURE_KINDS = vocabulary([
1613
+ "malformed_line",
1614
+ "other",
1615
+ "payload_rejected",
1616
+ "read_error",
1617
+ "size_budget",
1618
+ "symlink"
1619
+ ]);
1620
+
1440
1621
  // packages/ceal-client-protocol/src/client-result-materialization.ts
1441
1622
  var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
1442
- var CEAL_MODEL_VISIBLE_COMMAND_OUTPUT_MAX_BYTES = 32 * 1024;
1443
- var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id"]);
1444
- var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "read_argv", "local_result_root", "continue_argv", "model_disclosure", "item_read_argv"]);
1623
+ var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
1624
+ var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
1445
1625
 
1446
1626
  // packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
1447
1627
  var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -1471,14 +1651,6 @@ var CEAL_GATEWAY_ANNOUNCEMENT_POLICY_NON_CLAIMS = vocabulary([
1471
1651
  "target_specific_scope_not_declared"
1472
1652
  ]);
1473
1653
 
1474
- // packages/ceal-client-protocol/src/gateway-write-identity.ts
1475
- var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1476
- var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1477
- replay_identity: "idempotency_claim_sha256",
1478
- lookup_handle: "write_request_sha256",
1479
- collision_evidence: "normalized_mutation_sha256"
1480
- });
1481
-
1482
1654
  // packages/ceal-client-protocol/src/gateway-scoped-identity-projection-validation.ts
1483
1655
  var PROJECTION_REVISION_KEYS = Object.freeze(["graph_revision", "subject_key_revision", "projection_revision"]);
1484
1656
 
@@ -1491,6 +1663,18 @@ var CEAL_SUPPORTED_GATEWAY_PROTOCOL_RANGE = Object.freeze({
1491
1663
  // packages/ceal-client-protocol/src/admin-projection-vocabulary.ts
1492
1664
  var ADMIN_PROJECTION_STATES = vocabulary(["ready", "empty", "partial", "unknown"]);
1493
1665
  var ADMIN_VIEW_KINDS = vocabulary(["request_detail", "resource_access_timeline", "weekly_usage"]);
1666
+ var ADMIN_RECORD_PROJECTION_KINDS = vocabulary([
1667
+ "request_list",
1668
+ "usage_series",
1669
+ "connector_health",
1670
+ "person_connections",
1671
+ "client_sessions",
1672
+ "knowledge_base",
1673
+ "change_history",
1674
+ "approval_queue",
1675
+ "access_group_list",
1676
+ "capability_catalogue"
1677
+ ]);
1494
1678
  var ADMIN_GAP_KINDS = vocabulary(["unknown", "unavailable", "partial"]);
1495
1679
  var ADMIN_UNAVAILABLE_REASONS = vocabulary([
1496
1680
  "not_recorded",
@@ -1514,7 +1698,11 @@ var ADMIN_SOURCE_OWNERS = vocabulary([
1514
1698
  "person",
1515
1699
  "resource",
1516
1700
  "prompt",
1517
- "usage"
1701
+ "usage",
1702
+ "connector",
1703
+ "session",
1704
+ "knowledge",
1705
+ "policy"
1518
1706
  ]);
1519
1707
  var ADMIN_COMPLETENESS_STATES = vocabulary(["complete", "partial", "unknown"]);
1520
1708
  var ADMIN_OPERATION_OUTCOMES = vocabulary(["completed", "failed", "denied", "cancelled", "unknown"]);
@@ -1543,12 +1731,12 @@ var FAILURE_RESPONSE_KEYS = Object.freeze(["error", "ok", "proof_ref_or_unavaila
1543
1731
  var MAX_DETAIL_CHARACTERS = 240;
1544
1732
  var HOST_OWNED_CODE = /^[a-z][a-z0-9]*(?:_[a-z0-9]+){0,7}$/u;
1545
1733
  function hostTerminalFailure(error, component) {
1546
- const code3 = errorCode(error);
1734
+ const code2 = errorCode(error);
1547
1735
  const detail = failureDetail(error);
1548
- if (code3 === "ENOENT") return { code: "host_dependency_unavailable", message: `A command or file the ${component} Host needs is unavailable: ${detail}`, next_action: "Install the missing dependency or restore the missing file, then rerun this command." };
1549
- if (code3 === "EACCES" || code3 === "EPERM") return { code: "host_permission_denied", message: `The ${component} Host was refused access to a local path: ${detail}`, next_action: "Repair the ownership or mode of the named path, then rerun this command." };
1550
- if (code3 === "ENOSPC") return { code: "host_storage_unavailable", message: `The ${component} Host could not write local state: ${detail}`, next_action: "Free local disk space, then rerun this command." };
1551
- if (code3 !== null && HOST_OWNED_CODE.test(code3)) return { code: code3, message: `The ${component} Host command failed: ${detail}`, next_action: `Resolve the named failure above; run \`ceal-host doctor\` only if it reports installed ${component} Host drift.` };
1736
+ if (code2 === "ENOENT") return { code: "host_dependency_unavailable", message: `A command or file the ${component} Host needs is unavailable: ${detail}`, next_action: "Install the missing dependency or restore the missing file, then rerun this command." };
1737
+ if (code2 === "EACCES" || code2 === "EPERM") return { code: "host_permission_denied", message: `The ${component} Host was refused access to a local path: ${detail}`, next_action: "Repair the ownership or mode of the named path, then rerun this command." };
1738
+ if (code2 === "ENOSPC") return { code: "host_storage_unavailable", message: `The ${component} Host could not write local state: ${detail}`, next_action: "Free local disk space, then rerun this command." };
1739
+ if (code2 !== null && HOST_OWNED_CODE.test(code2)) return { code: code2, message: `The ${component} Host command failed: ${detail}`, next_action: `Resolve the named failure above; run \`ceal-host doctor\` only if it reports installed ${component} Host drift.` };
1552
1740
  return { code: "host_failure_unclassified", message: `The ${component} Host command failed for a cause it cannot classify: ${detail}`, next_action: `Run \`ceal-host doctor\`; if it reports no drift then the ${component} Host configuration is not the cause, and the message above is the only evidence of what is.` };
1553
1741
  }
1554
1742
  var DOCTOR_FLAGS = Object.freeze({
@@ -1621,6 +1809,9 @@ function hostClientProbeRefusal(probe, subject) {
1621
1809
  return { code: "ceal_output_unreadable", message: `The ${invocation} command returned output the ${subject.component} Host cannot read.`, next_action: `Verify the installed Ceal client matches the protocol this ${subject.component} Host expects, then run this doctor again.` };
1622
1810
  }
1623
1811
 
1812
+ // host/invocation-authentication-command.ts
1813
+ import { performance } from "node:perf_hooks";
1814
+
1624
1815
  // host/relay/src/producer-frame.ts
1625
1816
  var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
1626
1817
  var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
@@ -1632,11 +1823,20 @@ var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
1632
1823
  function isHostProducerFrameKind(value) {
1633
1824
  return typeof value === "string" && FRAME_KINDS.has(value);
1634
1825
  }
1826
+ var HOST_PRODUCER_FRAME_ERROR_CODES = [
1827
+ "invalid_frame_encoding",
1828
+ "invalid_frame_length",
1829
+ "frame_truncated",
1830
+ "unsupported_frame_schema",
1831
+ "invalid_frame_shape",
1832
+ "invalid_producer_identity",
1833
+ "frame_too_large"
1834
+ ];
1635
1835
  var HostProducerFrameError = class extends Error {
1636
1836
  code;
1637
- constructor(code3, message = code3) {
1837
+ constructor(code2, message = code2) {
1638
1838
  super(message);
1639
- this.code = code3;
1839
+ this.code = code2;
1640
1840
  }
1641
1841
  };
1642
1842
  function encodeHostProducerFrame(frame) {
@@ -1666,6 +1866,9 @@ function decodeHostProducerFrame(input) {
1666
1866
  validateFrame(value);
1667
1867
  return value;
1668
1868
  }
1869
+ function isHostSafeRef(value) {
1870
+ return safeRef(value);
1871
+ }
1669
1872
  function validateFrame(value) {
1670
1873
  if (!record(value)) throw new HostProducerFrameError("invalid_frame_shape");
1671
1874
  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)}`);
@@ -1753,6 +1956,15 @@ function exactOptional(value, required, optional) {
1753
1956
  return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
1754
1957
  }
1755
1958
 
1959
+ // host/relay/src/host-producer-core.ts
1960
+ import { createHash as createHash2 } from "node:crypto";
1961
+ import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
1962
+ import { mkdir as mkdir2, open as open2, readFile, rename as rename2, writeFile } from "node:fs/promises";
1963
+ import { join as join2 } from "node:path";
1964
+
1965
+ // host/relay/src/relay-ipc.ts
1966
+ import { createConnection, createServer } from "node:net";
1967
+
1756
1968
  // host/relay/src/durable-atomic-replacement.ts
1757
1969
  import { O_RDONLY } from "node:constants";
1758
1970
  import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
@@ -1841,17 +2053,6 @@ function errorCode2(error) {
1841
2053
  // host/relay/src/relay-request-binding-store.ts
1842
2054
  var MAX_STATE_BYTES = 8 * 1024 * 1024;
1843
2055
 
1844
- // host/relay/src/relay-quarantine-store.ts
1845
- var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
1846
-
1847
- // host/relay/src/relay-admission-store.ts
1848
- var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
1849
- var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
1850
- var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
1851
-
1852
- // host/relay/src/relay-ipc.ts
1853
- import { createConnection, createServer } from "node:net";
1854
-
1855
2056
  // host/relay/src/current-prompt-custody.ts
1856
2057
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
1857
2058
 
@@ -1874,6 +2075,8 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
1874
2075
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
1875
2076
  var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v1";
1876
2077
  var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v1";
2078
+ var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2079
+ var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
1877
2080
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
1878
2081
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
1879
2082
  var HOST_RELAY_QUERY_RESPONSE_MAX_BYTES = 8192;
@@ -1885,10 +2088,31 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
1885
2088
  "gateway_unavailable",
1886
2089
  "gateway_refused",
1887
2090
  "gateway_response_invalid",
2091
+ "gateway_rate_limited",
2092
+ "producer_frame_invalid",
1888
2093
  "deadline",
1889
2094
  "cancelled",
1890
2095
  "mint_failed",
1891
- "relay_failed"
2096
+ "relay_failed",
2097
+ "request_authority_unavailable",
2098
+ "request_missing",
2099
+ "authority_mismatch",
2100
+ "host_source_mismatch",
2101
+ "host_session_mismatch",
2102
+ "host_turn_mismatch",
2103
+ "host_input_mismatch",
2104
+ "host_prompt_mismatch"
2105
+ ]);
2106
+ var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
2107
+ "context_stale",
2108
+ "context_expired",
2109
+ "host_source_refused",
2110
+ "host_credential_refused",
2111
+ "gateway_unavailable",
2112
+ "gateway_rate_limited",
2113
+ "gateway_refused",
2114
+ "gateway_response_invalid",
2115
+ "producer_frame_invalid"
1892
2116
  ]);
1893
2117
  var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
1894
2118
  var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
@@ -1905,9 +2129,9 @@ var HostRelayIpcError = class extends Error {
1905
2129
  code;
1906
2130
  /** Set only when the client-side socket exchange itself failed. */
1907
2131
  failure;
1908
- constructor(code3, message = code3, failure2) {
2132
+ constructor(code2, message = code2, failure2) {
1909
2133
  super(message);
1910
- this.code = code3;
2134
+ this.code = code2;
1911
2135
  this.failure = failure2;
1912
2136
  }
1913
2137
  };
@@ -1949,6 +2173,19 @@ var HostRelayIpcAdmissionPort = class {
1949
2173
  return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
1950
2174
  }
1951
2175
  };
2176
+ var HostRelayCurrentPromptPort = class {
2177
+ endpoint;
2178
+ timeout_ms;
2179
+ constructor(endpoint, timeoutMs = 5e3) {
2180
+ this.endpoint = endpoint;
2181
+ this.timeout_ms = timeoutMs;
2182
+ }
2183
+ async register(input) {
2184
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2185
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2186
+ if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2187
+ }
2188
+ };
1952
2189
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
1953
2190
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
1954
2191
  }
@@ -2036,17 +2273,26 @@ function decodeInvocationContextResponseBuffer(buffer) {
2036
2273
  if (!validInvocationContextResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay invocation context response is invalid.");
2037
2274
  return value;
2038
2275
  }
2276
+ function decodeCurrentPromptResponseBuffer(buffer) {
2277
+ const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2278
+ if (value === null) return null;
2279
+ if (!record2(value) || !exactKeys3(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "current_prompt_unavailable") throw new HostRelayIpcError("relay_unavailable");
2280
+ return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2281
+ }
2039
2282
  function encodeInvocationContextQuery(query) {
2040
2283
  const value = { schema_version: HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA, ...query };
2041
2284
  if (!validInvocationContextQueryRequest(value)) throw new HostRelayIpcError("invalid_frame_shape");
2042
2285
  return encodeLengthPrefixed(value);
2043
2286
  }
2044
2287
  function validInvocationContextQueryRequest(value) {
2045
- return validQueryIdentity(value, HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) && exactKeys3(value, ["schema_version", "producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref", "client_nonce"]) && typeof value.client_nonce === "string" && CEAL_HOST_CLIENT_NONCE_PATTERN.test(value.client_nonce);
2288
+ return validQueryIdentity(value, HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) && exactKeys3(value, ["schema_version", "producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref", "client_nonce"], ["visible_output_max_bytes"]) && typeof value.client_nonce === "string" && CEAL_HOST_CLIENT_NONCE_PATTERN.test(value.client_nonce) && (value.visible_output_max_bytes === void 0 || validVisibleOutputBudget(value.visible_output_max_bytes));
2046
2289
  }
2047
2290
  function validQueryIdentity(value, schema) {
2048
2291
  return record2(value) && value.schema_version === schema && (value.producer_id === "codex" || value.producer_id === "claude") && safeRef2(value.producer_installation_ref) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref);
2049
2292
  }
2293
+ function validVisibleOutputBudget(value) {
2294
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES;
2295
+ }
2050
2296
  function validInvocationContextResponse(value) {
2051
2297
  if (!record2(value) || value.schema_version !== HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA) return false;
2052
2298
  if (value.status === "no_binding") return Object.keys(value).length === 2;
@@ -2078,8 +2324,9 @@ function encodeLengthPrefixed(value) {
2078
2324
  body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
2079
2325
  return output;
2080
2326
  }
2081
- function exactKeys3(value, keys) {
2082
- return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
2327
+ function exactKeys3(value, required, optional = []) {
2328
+ const keys = Object.keys(value);
2329
+ return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2083
2330
  }
2084
2331
  function safeRef2(value) {
2085
2332
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
@@ -2088,15 +2335,359 @@ function errorCode3(error) {
2088
2335
  return record2(error) && typeof error.code === "string" ? error.code : null;
2089
2336
  }
2090
2337
 
2338
+ // host/relay/src/host-producer-core.ts
2339
+ var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
2340
+ function isHostTranscriptCursorReason(value) {
2341
+ return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2342
+ }
2343
+ var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2344
+ var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2345
+ async function readTranscriptCompleteChunk(handle, offset, size) {
2346
+ const available = Math.max(0, size - offset);
2347
+ if (available === 0) return "empty";
2348
+ const chunks = [];
2349
+ let position = offset;
2350
+ let total = 0;
2351
+ let lastNewline = -1;
2352
+ while (position < size) {
2353
+ const buffer = Buffer.alloc(Math.min(HOST_TRANSCRIPT_READ_CHUNK_BYTES, size - position));
2354
+ const read = await handle.read(buffer, 0, buffer.length, position);
2355
+ if (read.bytesRead === 0) break;
2356
+ const chunk = buffer.subarray(0, read.bytesRead);
2357
+ chunks.push(chunk);
2358
+ const newline = chunk.lastIndexOf(10);
2359
+ if (newline >= 0) lastNewline = total + newline;
2360
+ total += chunk.length;
2361
+ position += chunk.length;
2362
+ if (lastNewline >= 0) break;
2363
+ }
2364
+ if (lastNewline < 0) return "partial";
2365
+ return Buffer.concat(chunks, total).subarray(0, lastNewline + 1);
2366
+ }
2367
+ async function readTranscriptAnchor(handle, offset, anchorBytes) {
2368
+ const start = Math.max(0, offset - anchorBytes);
2369
+ const anchor = Buffer.alloc(offset - start);
2370
+ if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, start);
2371
+ return { start, sha256: transcriptSha256(anchor) };
2372
+ }
2373
+ async function transcriptAnchorMatches(handle, cursor) {
2374
+ const buffer = Buffer.alloc(cursor.offset - cursor.anchor_start);
2375
+ if (buffer.length > 0) await handle.read(buffer, 0, buffer.length, cursor.anchor_start);
2376
+ return transcriptSha256(buffer) === cursor.anchor_sha256;
2377
+ }
2378
+ function transcriptCursorIdentityReason(cursor, transcriptPath, device, inode) {
2379
+ if (cursor.device !== device || cursor.inode !== inode || cursor.transcript_path !== transcriptPath) return "transcript_replaced";
2380
+ return null;
2381
+ }
2382
+ async function transcriptCursorBoundsReason(handle, cursor, size) {
2383
+ if (size < cursor.offset) return "transcript_truncated";
2384
+ if (cursor.offset > 0 && !await transcriptAnchorMatches(handle, cursor)) return "transcript_rewritten";
2385
+ return null;
2386
+ }
2387
+ function transcriptSha256(value) {
2388
+ return createHash2("sha256").update(value).digest("hex");
2389
+ }
2390
+ function transcriptObjectSha256(value) {
2391
+ return createHash2("sha256").update(JSON.stringify(value), "utf8").digest("hex");
2392
+ }
2393
+ function isTranscriptRecord(value) {
2394
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2395
+ }
2396
+ function isMissingTranscriptError(error) {
2397
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2398
+ }
2399
+ function transcriptTimestamp(now, fail) {
2400
+ const value = now();
2401
+ if (!Number.isFinite(value.getTime())) throw fail();
2402
+ return value.toISOString();
2403
+ }
2404
+ function transcriptExpiration(now, retentionMs, fail) {
2405
+ return new Date(Date.parse(transcriptTimestamp(now, fail)) + retentionMs).toISOString();
2406
+ }
2407
+ function createTranscriptWatcherBase(options, owner) {
2408
+ if (typeof options.outbox_dir !== "string" || options.outbox_dir.length === 0) throw owner.invalid_outbox();
2409
+ return {
2410
+ outboxRetentionMs: options.retention_ms ?? owner.default_retention_ms,
2411
+ now: options.now ?? (() => /* @__PURE__ */ new Date()),
2412
+ log: options.log ?? ((line) => {
2413
+ process.stderr.write(`${line}
2414
+ `);
2415
+ }),
2416
+ producerInstallationRef: options.producer_installation_ref
2417
+ };
2418
+ }
2419
+ async function scanTranscriptRegistrations(registrations, scanOne) {
2420
+ let records = 0;
2421
+ let enqueued = 0;
2422
+ let quarantined = 0;
2423
+ for (const registration of registrations) {
2424
+ const result = await scanOne(registration);
2425
+ records += result.records;
2426
+ enqueued += result.enqueued;
2427
+ if (result.quarantined) quarantined += 1;
2428
+ }
2429
+ return { registrations: registrations.length, records, enqueued, quarantined };
2430
+ }
2431
+ function formatRetiredTranscriptLog(producerLabel, transcriptPath) {
2432
+ return `${producerLabel} retired a transcript registration: transcript_path=${JSON.stringify(transcriptPath)}, reason=${TRANSCRIPT_MISSING_REASON}.`;
2433
+ }
2434
+ var HostTranscriptWatcher = class {
2435
+ outboxRetentionMs;
2436
+ now;
2437
+ log;
2438
+ producerInstallationRef;
2439
+ clockFailure;
2440
+ constructor(options, owner) {
2441
+ const base = createTranscriptWatcherBase(options, owner);
2442
+ this.outboxRetentionMs = base.outboxRetentionMs;
2443
+ this.now = base.now;
2444
+ this.log = base.log;
2445
+ this.producerInstallationRef = base.producerInstallationRef;
2446
+ this.clockFailure = owner.clock_failure;
2447
+ }
2448
+ async scanOnce() {
2449
+ return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2450
+ }
2451
+ async scanRegistration(registration) {
2452
+ return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2453
+ missing: () => this.retireMissing(registration),
2454
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2455
+ });
2456
+ }
2457
+ async openTranscript(registration, scan, handlers) {
2458
+ let handle;
2459
+ try {
2460
+ handle = await open2(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2461
+ return await scan(handle);
2462
+ } catch (error) {
2463
+ if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2464
+ if (hostRelayExchangeFailure(error) !== void 0) throw error;
2465
+ return handlers.failed(handle, error);
2466
+ } finally {
2467
+ await handle?.close();
2468
+ }
2469
+ }
2470
+ /**
2471
+ * A transcript the Host deleted never comes back, so retrying its
2472
+ * registration once per reconcile interval is a failure with no end.
2473
+ * Retire it at the scan that finds it missing and say so once. Only
2474
+ * ENOENT means gone; a permission or busy failure is about this moment,
2475
+ * not about the file, and keeps the registration for the next scan.
2476
+ */
2477
+ async retireMissingTranscript(transcriptPath, producerLabel, retire) {
2478
+ await retire();
2479
+ this.log(formatRetiredTranscriptLog(producerLabel, transcriptPath));
2480
+ return { records: 0, enqueued: 0, quarantined: false };
2481
+ }
2482
+ stamp() {
2483
+ return transcriptTimestamp(this.now, this.clockFailure);
2484
+ }
2485
+ expire(retentionMs) {
2486
+ return transcriptExpiration(this.now, retentionMs, this.clockFailure);
2487
+ }
2488
+ sha(value) {
2489
+ return transcriptSha256(value);
2490
+ }
2491
+ objectSha(value) {
2492
+ return transcriptObjectSha256(value);
2493
+ }
2494
+ isRecord(value) {
2495
+ return isTranscriptRecord(value);
2496
+ }
2497
+ readChunk(handle, offset, size) {
2498
+ return readTranscriptCompleteChunk(handle, offset, size);
2499
+ }
2500
+ readAnchor(handle, offset, anchorBytes) {
2501
+ return readTranscriptAnchor(handle, offset, anchorBytes);
2502
+ }
2503
+ cursorIdentityReason(cursor, transcriptPath, device, inode) {
2504
+ return transcriptCursorIdentityReason(cursor, transcriptPath, device, inode);
2505
+ }
2506
+ cursorBoundsReason(handle, cursor, size) {
2507
+ return transcriptCursorBoundsReason(handle, cursor, size);
2508
+ }
2509
+ };
2510
+ var HOST_PRODUCER_STATE_IDENTITY = {
2511
+ codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
2512
+ claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
2513
+ };
2514
+ var HostProducerDrainCore = class {
2515
+ host;
2516
+ relay;
2517
+ statePath;
2518
+ stateSchema;
2519
+ installationRef;
2520
+ epoch;
2521
+ now;
2522
+ tail = Promise.resolve();
2523
+ constructor(host, runtime) {
2524
+ if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
2525
+ const epoch = runtime.producer_epoch ?? 1;
2526
+ if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
2527
+ this.host = host;
2528
+ this.relay = runtime.relay;
2529
+ this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
2530
+ this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
2531
+ this.installationRef = runtime.producer_installation_ref;
2532
+ this.epoch = epoch;
2533
+ this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
2534
+ }
2535
+ drainOutbox() {
2536
+ const run = this.tail.then(() => this.drainLocked());
2537
+ this.tail = run.then(() => void 0, () => void 0);
2538
+ return run;
2539
+ }
2540
+ async drainLocked() {
2541
+ const read = await this.host.listEnvelopes();
2542
+ let admitted = 0;
2543
+ let expired = 0;
2544
+ let retained = 0;
2545
+ for (const envelope of read.entries) {
2546
+ const reserved = await this.hasReservation(this.host.submissionRefOf(envelope));
2547
+ if (!reserved && Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) {
2548
+ await this.host.removeEnvelope(envelope);
2549
+ expired += 1;
2550
+ continue;
2551
+ }
2552
+ const frames = await this.reserve(envelope);
2553
+ let complete = true;
2554
+ for (const frame of frames) {
2555
+ const outcome = await this.relay.admit(frame);
2556
+ if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2557
+ complete = false;
2558
+ break;
2559
+ }
2560
+ }
2561
+ if (!complete) {
2562
+ retained += 1;
2563
+ break;
2564
+ }
2565
+ await this.host.removeEnvelope(envelope);
2566
+ await this.release(this.host.submissionRefOf(envelope));
2567
+ admitted += 1;
2568
+ }
2569
+ return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
2570
+ }
2571
+ async hasReservation(submissionRef2) {
2572
+ return (await this.readState()).pending[submissionRef2] !== void 0;
2573
+ }
2574
+ async reserve(envelope) {
2575
+ const state = await this.readState();
2576
+ const submissionRef2 = this.host.submissionRefOf(envelope);
2577
+ const existing = state.pending[submissionRef2];
2578
+ if (existing !== void 0) return existing;
2579
+ const drafts = await this.host.mapEnvelope(envelope);
2580
+ const frames = drafts.map((draft, offset) => ({
2581
+ schema_version: "ceal.host_producer_frame.v1",
2582
+ producer_id: this.host.producer_id,
2583
+ producer_installation_ref: this.installationRef,
2584
+ producer_epoch: this.epoch,
2585
+ sequence: state.next_sequence + offset,
2586
+ frame_kind: draft.frame_kind,
2587
+ occurred_at: this.host.createdAtOf(envelope),
2588
+ payload: draft.payload
2589
+ }));
2590
+ await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef2]: frames } });
2591
+ return frames;
2592
+ }
2593
+ async release(submissionRef2) {
2594
+ const state = await this.readState();
2595
+ if (state.pending[submissionRef2] === void 0) return;
2596
+ const pending = { ...state.pending };
2597
+ delete pending[submissionRef2];
2598
+ await this.writeState({ ...state, pending });
2599
+ }
2600
+ async readState() {
2601
+ try {
2602
+ const value = JSON.parse(await readFile(this.statePath, "utf8"));
2603
+ return this.decodeState(value);
2604
+ } catch (error) {
2605
+ if (errorCode4(error) !== "ENOENT") throw error;
2606
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2607
+ }
2608
+ }
2609
+ async writeState(state) {
2610
+ await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
2611
+ const temporary = `${this.statePath}.${process.pid}.new`;
2612
+ await writeFile(temporary, JSON.stringify(state), { mode: 384 });
2613
+ await rename2(temporary, this.statePath);
2614
+ }
2615
+ decodeState(value) {
2616
+ const label = this.host.producer_label;
2617
+ const fail = (code2, message) => this.host.fail(code2, message);
2618
+ if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
2619
+ throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
2620
+ { name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
2621
+ { name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
2622
+ ])}).`);
2623
+ }
2624
+ 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}`)}).`);
2625
+ const pending = {};
2626
+ const storedEpoch = Number(value.producer_epoch);
2627
+ for (const [submissionRef2, frames] of Object.entries(value.pending)) {
2628
+ if (!isHostSafeRef(submissionRef2) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
2629
+ pending[submissionRef2] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
2630
+ }
2631
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
2632
+ }
2633
+ decodeStoredFrame(value, storedEpoch) {
2634
+ const label = this.host.producer_label;
2635
+ const json = Buffer.from(JSON.stringify(value), "utf8");
2636
+ const encoded = Buffer.allocUnsafe(json.length + 4);
2637
+ encoded.writeUInt32BE(json.length, 0);
2638
+ json.copy(encoded, 4);
2639
+ const frame = decodeHostProducerFrame(encoded);
2640
+ 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([
2641
+ { name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
2642
+ { name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
2643
+ { name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
2644
+ ])}).`);
2645
+ return frame;
2646
+ }
2647
+ };
2648
+ function isDrainStateHeader(value, schema, installationRef) {
2649
+ 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);
2650
+ }
2651
+ function errorCode4(error) {
2652
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2653
+ }
2654
+
2655
+ // host/relay/src/relay-quarantine-store.ts
2656
+ var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
2657
+ var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
2658
+ var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
2659
+
2660
+ // host/relay/src/relay-admission-store.ts
2661
+ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
2662
+ var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
2663
+ var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
2664
+
2665
+ // host/service-log.ts
2666
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind) {
2667
+ try {
2668
+ process.stderr.write(`${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {} })}
2669
+ `);
2670
+ } catch {
2671
+ }
2672
+ }
2673
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2674
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2675
+
2091
2676
  // host/invocation-authentication-command.ts
2092
2677
  var HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX = ["authenticate", "--client-nonce"];
2093
- var REFUSAL_REASON_PATTERN = /^[a-z][a-z0-9_ ]{0,63}$/u;
2094
- function installedHostInvocationAuthenticationCommandOptions(config) {
2678
+ var HOST_AUTHENTICATION_BUDGET_ENVIRONMENT = "CEAL_HOST_AUTHENTICATION_BUDGET_MS";
2679
+ var HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS = 2e4;
2680
+ var CLAUDE_CONTEXT_ACTIVATION_WAIT_MAX_MS = HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS;
2681
+ var HOST_AUTHENTICATION_COMPLETION_RESERVE_MS = 5e3;
2682
+ var INVOCATION_CONTEXT_POLL_INTERVAL_MS = 250;
2683
+ var REFUSAL_REASON_PATTERN = /^[a-z][a-z0-9_]{0,63}$/u;
2684
+ function installedHostInvocationAuthenticationCommandOptions(config, contextBudgetMs, maxContextWaitMs) {
2095
2685
  const port = new HostRelayInvocationContextPort(config.relay_endpoint);
2096
2686
  return {
2097
2687
  host_source_ref: config.host_source_ref,
2098
2688
  outbox_dir: config.outbox_dir,
2099
2689
  producer_installation_ref: config.producer_installation_ref,
2690
+ context_activation_wait_ms: boundedContextActivationWait(contextBudgetMs, config.ipc_timeout_ms, maxContextWaitMs),
2100
2691
  mint_invocation_context: (input) => port.mintInvocationContext({ ...input, producer_id: input.producer_id })
2101
2692
  };
2102
2693
  }
@@ -2114,65 +2705,150 @@ async function runHostInvocationAuthenticationCommand(descriptor, args, options)
2114
2705
  }
2115
2706
  function authenticationRefusalReason(error) {
2116
2707
  const message = error instanceof Error ? error.message : "";
2117
- return REFUSAL_REASON_PATTERN.test(message) ? message : "unclassified";
2708
+ if (!REFUSAL_REASON_PATTERN.test(message)) return "host_refusal_invalid";
2709
+ return isCealHostAuthenticationCause(message) ? message : "host_refusal_invalid";
2118
2710
  }
2119
2711
  async function authenticateHostInvocation(descriptor, input, options) {
2120
- if (input.length !== HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX.length + 1 || input[0] !== HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX[0] || input[1] !== HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX[1]) throw new TypeError("invalid arguments");
2712
+ if (input.length !== HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX.length + 1 || input[0] !== HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX[0] || input[1] !== HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX[1]) throw new TypeError("invalid_arguments");
2121
2713
  const request2 = decodeCealHostInvocationAuthenticationRequest({ schema_version: CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA, client_nonce: input[2] });
2122
2714
  const config = validateOptions(options);
2123
- const now = (config.now ?? (() => /* @__PURE__ */ new Date()))();
2715
+ const now = config.now ?? (() => /* @__PURE__ */ new Date());
2124
2716
  const { store, current } = await currentInvocation(descriptor, config, now);
2125
- if (config.mint_invocation_context === void 0) throw new TypeError("mint unavailable");
2717
+ if (config.mint_invocation_context === void 0) throw new TypeError("relay_failed");
2126
2718
  const query = {
2127
2719
  producer_id: descriptor.producer_id,
2128
2720
  producer_installation_ref: config.producer_installation_ref,
2129
2721
  local_session_ref: current.identity.host_session_ref,
2130
2722
  local_turn_ref: current.identity.host_turn_ref,
2131
- client_nonce: request2.client_nonce
2723
+ client_nonce: request2.client_nonce,
2724
+ ...await visibleOutputBudget(descriptor)
2132
2725
  };
2133
2726
  const response2 = await mintedResult(config.mint_invocation_context, query);
2134
- const rechecked = await store.readInvocationContext(current.identity.host_source_ref, current.identity.host_session_ref, (config.now ?? (() => /* @__PURE__ */ new Date()))());
2135
- if (rechecked === null || !sameTurn(current, rechecked)) throw new TypeError("context replaced");
2136
- return decodeCealHostInvocationAuthenticationResult(response2.result, now);
2727
+ const completedAt = now();
2728
+ const rechecked = await store.readInvocationContext(current.identity.host_source_ref, current.identity.host_session_ref, completedAt);
2729
+ if (rechecked === null || !sameTurn(current, rechecked)) throw new TypeError("context_replaced");
2730
+ return decodeCealHostInvocationAuthenticationResult(response2.result, completedAt);
2731
+ }
2732
+ async function visibleOutputBudget(descriptor) {
2733
+ const bytes = await descriptor.resolve_visible_output_max_bytes?.();
2734
+ return bytes === void 0 ? {} : { visible_output_max_bytes: bytes };
2137
2735
  }
2138
2736
  async function mintedResult(mint, query) {
2139
2737
  let response2;
2140
2738
  try {
2141
2739
  response2 = await mint(query);
2142
2740
  } catch (error) {
2143
- throw new TypeError(`relay ${hostRelayExchangeFailure(error) ?? "unavailable"}`);
2741
+ throw new TypeError(exchangeFailureCause(hostRelayExchangeFailure(error) ?? "unavailable"));
2144
2742
  }
2145
- if (response2.status === "refused") throw new TypeError(`mint ${response2.reason}`);
2146
- if (response2.status !== "ready") throw new TypeError(response2.status);
2743
+ if (response2.status === "refused") throw new TypeError(mintRefusalCause(response2.reason));
2744
+ if (response2.status !== "ready") throw new TypeError("no_binding");
2147
2745
  return response2;
2148
2746
  }
2747
+ function mintRefusalCause(reason) {
2748
+ if (isDirectMintRefusalCause(reason)) return reason;
2749
+ if (reason === "deadline" || reason === "cancelled") return "transient_retryable";
2750
+ if (reason === "mint_failed" || reason === "relay_failed") return reason;
2751
+ return isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, reason) ? "context_stale" : "host_refusal_invalid";
2752
+ }
2753
+ function isDirectMintRefusalCause(reason) {
2754
+ return isVocabularyWord(HOST_RELAY_DIRECT_WIRE_MINT_REASONS, reason);
2755
+ }
2756
+ function exchangeFailureCause(failure2) {
2757
+ if (failure2 === "permission_denied") return "relay_permission_denied";
2758
+ if (failure2 === "timed_out") return "transient_retryable";
2759
+ return "relay_unavailable";
2760
+ }
2149
2761
  async function currentInvocation(descriptor, config, now) {
2150
2762
  const store = descriptor.create_store(config.outbox_dir);
2151
2763
  const identity = resolveIdentity(descriptor, config);
2152
2764
  if (identity.status !== "ready") throw new TypeError(identity.status);
2153
- const current = await store.readInvocationContext(identity.identity.host_source_ref, identity.identity.host_session_ref, now);
2154
- if (current === null || Date.parse(current.expires_at) <= now.getTime()) throw new TypeError("context unavailable");
2155
- return { store, current };
2765
+ const waitBudgetMs = config.context_activation_wait_ms ?? 0;
2766
+ const monotonicNow = config.monotonic_now ?? (() => performance.now());
2767
+ const deadline = monotonicNow() + waitBudgetMs;
2768
+ let firstCheck = true;
2769
+ let waitEngaged = false;
2770
+ while (true) {
2771
+ if (!firstCheck && monotonicNow() >= deadline) {
2772
+ recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs);
2773
+ throw new TypeError("context_unavailable");
2774
+ }
2775
+ firstCheck = false;
2776
+ const observedAt = now();
2777
+ const current = await readLiveSessionContext(store, identity.identity.host_source_ref, identity.identity.host_session_ref, observedAt);
2778
+ if (current !== null) {
2779
+ recordActivationWaitCompletion(descriptor.producer_id, waitEngaged);
2780
+ return { store, current };
2781
+ }
2782
+ const remainingMs = deadline - monotonicNow();
2783
+ if (remainingMs <= 0) {
2784
+ recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs);
2785
+ throw new TypeError("context_unavailable");
2786
+ }
2787
+ recordActivationWaitEngagement(descriptor.producer_id, waitEngaged);
2788
+ waitEngaged = true;
2789
+ const intervalMs = Math.min(INVOCATION_CONTEXT_POLL_INTERVAL_MS, remainingMs);
2790
+ await (config.wait ?? waitForContextActivation)(intervalMs);
2791
+ }
2792
+ }
2793
+ function recordActivationWaitExhaustion(producer, engaged, budgetMs) {
2794
+ if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted");
2795
+ else writeHostServiceDiagnostic(producer, "activation_wait_skipped");
2796
+ }
2797
+ function recordActivationWaitCompletion(producer, engaged) {
2798
+ if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed");
2799
+ }
2800
+ function recordActivationWaitEngagement(producer, engaged) {
2801
+ if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged");
2802
+ }
2803
+ async function readLiveSessionContext(store, hostSourceRef, hostSessionRef, now) {
2804
+ const current = await store.readInvocationContext(hostSourceRef, hostSessionRef, now);
2805
+ if (current !== null) {
2806
+ if (Date.parse(current.expires_at) <= now.getTime()) throw new TypeError("context_expired");
2807
+ return current;
2808
+ }
2809
+ const raw = await store.readRawInvocationContext?.(hostSourceRef, hostSessionRef);
2810
+ if (raw !== null && raw !== void 0 && Date.parse(raw.expires_at) <= now.getTime()) throw new TypeError("context_expired");
2811
+ return null;
2156
2812
  }
2157
2813
  function resolveIdentity(descriptor, options) {
2158
2814
  const sessionId = options.session_id === null ? void 0 : options.session_id ?? process.env[descriptor.session_id_env];
2159
2815
  if (isHostSessionId(sessionId)) return { status: "ready", identity: { host_source_ref: options.host_source_ref, host_session_ref: descriptor.session_ref(options.host_source_ref, sessionId) } };
2160
- return { status: "unavailable" };
2816
+ return { status: "session_unavailable" };
2161
2817
  }
2162
2818
  async function resolveOptions(descriptor, args, options) {
2163
2819
  if (options !== void 0) return { input: [...args], options };
2164
2820
  const configPath = descriptor.parse_config_flag(args);
2821
+ const config = await descriptor.read_producer_config(configPath);
2165
2822
  return {
2166
2823
  input: args.filter((argument) => !argument.startsWith(`${descriptor.config_flag}=`)),
2167
- options: installedHostInvocationAuthenticationCommandOptions(await descriptor.read_producer_config(configPath))
2824
+ options: installedHostInvocationAuthenticationCommandOptions(config, authenticationBudgetMsFromEnvironment(), descriptor.context_activation_wait_max_ms)
2168
2825
  };
2169
2826
  }
2827
+ function authenticationBudgetMsFromEnvironment() {
2828
+ const value = process.env[HOST_AUTHENTICATION_BUDGET_ENVIRONMENT];
2829
+ if (value === void 0) return 0;
2830
+ if (!/^(?:0|[1-9][0-9]{0,8})$/u.test(value)) throw new TypeError("invalid_arguments");
2831
+ return Number(value);
2832
+ }
2833
+ function boundedContextActivationWait(contextBudgetMs, relayTimeoutMs, maxContextWaitMs) {
2834
+ if (maxContextWaitMs === void 0 || contextBudgetMs <= 0) return 0;
2835
+ const availableWaitMs = contextBudgetMs - relayTimeoutMs - HOST_AUTHENTICATION_COMPLETION_RESERVE_MS;
2836
+ return Math.max(0, Math.min(maxContextWaitMs, availableWaitMs));
2837
+ }
2838
+ function waitForContextActivation(milliseconds) {
2839
+ return new Promise((resolve) => {
2840
+ setTimeout(resolve, milliseconds);
2841
+ });
2842
+ }
2170
2843
  function validateOptions(options) {
2171
- if (typeof options.outbox_dir !== "string" || options.outbox_dir.length === 0 || typeof options.host_source_ref !== "string" || !SAFE_REF.test(options.host_source_ref) || typeof options.producer_installation_ref !== "string" || !SAFE_REF.test(options.producer_installation_ref) || !validOptionalSession(options.session_id)) {
2172
- throw new TypeError("options invalid");
2844
+ if (typeof options.outbox_dir !== "string" || options.outbox_dir.length === 0 || typeof options.host_source_ref !== "string" || !SAFE_REF.test(options.host_source_ref) || typeof options.producer_installation_ref !== "string" || !SAFE_REF.test(options.producer_installation_ref) || !validContextActivationWait(options.context_activation_wait_ms) || !validOptionalSession(options.session_id)) {
2845
+ throw new TypeError("options_invalid");
2173
2846
  }
2174
2847
  return options;
2175
2848
  }
2849
+ function validContextActivationWait(value) {
2850
+ return value === void 0 || Number.isSafeInteger(value) && value >= 0 && value <= HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS;
2851
+ }
2176
2852
  function isHostSessionId(value) {
2177
2853
  return typeof value === "string" && value.length > 0 && value.length <= 256 && !value.includes("\0");
2178
2854
  }
@@ -2191,7 +2867,7 @@ function writeResult(value, sink) {
2191
2867
  import { posix as posix3, win32 as win323 } from "node:path";
2192
2868
 
2193
2869
  // host/runtime-source-paths.ts
2194
- import { createHash as createHash2 } from "node:crypto";
2870
+ import { createHash as createHash3 } from "node:crypto";
2195
2871
  import { posix as posix2, win32 as win322 } from "node:path";
2196
2872
 
2197
2873
  // host/absolute-path.ts
@@ -2213,7 +2889,7 @@ function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platfor
2213
2889
  const sourcesRoot = path.join(stateRoot, "sources");
2214
2890
  const sourceRoot = path.join(sourcesRoot, sourceId);
2215
2891
  const runtimeRoot = path.join(stateRoot, "run");
2216
- const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash2("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
2892
+ const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash3("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
2217
2893
  assertHostRuntimeIpcLength(endpoint, platform);
2218
2894
  return { sourceId, sourcesRoot, sourceRoot, relayRoot: path.join(sourceRoot, "relay"), codexOutbox: path.join(sourceRoot, "producers", "codex", "outbox"), claudeOutbox: path.join(sourceRoot, "producers", "claude", "outbox"), endpoint, runtimeRoot };
2219
2895
  }
@@ -2229,19 +2905,24 @@ function assertHostProducerSourcePaths(stateRoot, config, producer, platform = p
2229
2905
 
2230
2906
  // host/producer-service.ts
2231
2907
  import { constants } from "node:fs";
2232
- import { lstat as lstat2, open as open2 } from "node:fs/promises";
2908
+ import { lstat as lstat2, open as open3 } from "node:fs/promises";
2233
2909
  var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2234
2910
  var HOST_PRODUCER_CONFIG_FLAG = "--ceal-host-config";
2235
2911
  var HostProducerServiceError = class extends Error {
2236
2912
  code;
2237
- constructor(code3, message = code3) {
2238
- super(message === code3 ? code3 : `${code3}: ${message}`);
2913
+ constructor(code2, message = code2) {
2914
+ super(message === code2 ? code2 : `${code2}: ${message}`);
2239
2915
  this.name = "HostProducerServiceError";
2240
- this.code = code3;
2916
+ this.code = code2;
2241
2917
  }
2242
2918
  };
2243
2919
  var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
2244
2920
  var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
2921
+ function hostProducerExtraPath(extras, key) {
2922
+ const value = extras[key];
2923
+ if (value === void 0) throw hostProducerError("invalid_config");
2924
+ return value;
2925
+ }
2245
2926
  function parseHostProducerConfigFlag(args, platform = process.platform) {
2246
2927
  return parseProducerHostConfigFlag(args, HOST_PRODUCER_CONFIG_FLAG, platform, hostProducerError);
2247
2928
  }
@@ -2262,8 +2943,8 @@ function decodeHostProducerConfigText(descriptor, text, platform = process.platf
2262
2943
  const { extra_paths: extras, ...core } = decoded;
2263
2944
  return Object.freeze(descriptor.config({ schema_version: descriptor.schema, ...core }, extras));
2264
2945
  }
2265
- function hostProducerError(code3, message = code3) {
2266
- return new HostProducerServiceError(code3, message);
2946
+ function hostProducerError(code2, message = code2) {
2947
+ return new HostProducerServiceError(code2, message);
2267
2948
  }
2268
2949
  function parseProducerHostConfigFlag(args, flag, platform, error) {
2269
2950
  const prefix = `${flag}=`;
@@ -2335,7 +3016,7 @@ function validateConfigFile(value, platform, error) {
2335
3016
  }
2336
3017
  async function openConfigFile(configPath, flags, error) {
2337
3018
  try {
2338
- return await open2(configPath, flags);
3019
+ return await open3(configPath, flags);
2339
3020
  } catch {
2340
3021
  throw error("config_unavailable");
2341
3022
  }
@@ -2396,39 +3077,56 @@ function boundedMilliseconds(value) {
2396
3077
  }
2397
3078
 
2398
3079
  // host/claude/src/claude-decoder.ts
2399
- import { createHash as createHash3 } from "node:crypto";
3080
+ import { createHash as createHash4 } from "node:crypto";
2400
3081
 
2401
3082
  // host/claude/src/claude-types.ts
2402
3083
  var CLAUDE_SESSION_ID_ENVIRONMENT = "CLAUDE_CODE_SESSION_ID";
2403
- var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "storage", "configuration"];
3084
+ var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "payload_rejected", "storage", "configuration"];
2404
3085
  function isClaudeHostErrorCode(value) {
2405
- return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code3) => code3 === value);
3086
+ return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code2) => code2 === value);
2406
3087
  }
2407
- var CLAUDE_OUTBOX_SCHEMA = "ceal.claude_hook_outbox.v1";
2408
3088
  var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
3089
+ var CLAUDE_OUTBOX_SCHEMA_VERSION = "ceal.claude_observation_outbox.v1";
3090
+ var CLAUDE_SESSION_SCHEMA_VERSION = "ceal.claude_session.v1";
3091
+ var CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.claude_transcript_registration.v1";
3092
+ var CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.claude_transcript_cursor.v1";
3093
+ var CLAUDE_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
3094
+ var CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES = 1024;
3095
+ var CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES = 64;
3096
+ var CLAUDE_TRANSCRIPT_ANCHOR_BYTES = 256;
2409
3097
  var ClaudeHostError = class extends Error {
2410
3098
  name = "ClaudeHostError";
2411
3099
  code;
2412
- constructor(code3, message) {
3100
+ diagnostic_kind;
3101
+ constructor(code2, message, diagnostic_kind) {
2413
3102
  super(message);
2414
- if (!isClaudeHostErrorCode(code3)) throw new TypeError("Claude Host error code is invalid.");
2415
- this.code = code3;
3103
+ if (!isClaudeHostErrorCode(code2)) throw new TypeError("Claude Host error code is invalid.");
3104
+ this.code = code2;
3105
+ this.diagnostic_kind = diagnostic_kind;
2416
3106
  }
2417
3107
  };
2418
3108
 
2419
3109
  // host/claude/src/claude-decoder.ts
2420
3110
  var MAX_HOOK_BYTES = 256 * 1024;
2421
- var MAX_TEXT = 128 * 1024;
2422
3111
  function claudeLocalSessionRef(sessionId) {
2423
3112
  return `claude-session:${digest(sessionId)}`;
2424
3113
  }
3114
+ function claudeLocalTurnRef(sessionId, turnId) {
3115
+ return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
3116
+ }
3117
+ function claudeLocalInputRef(sessionId, turnId) {
3118
+ return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
3119
+ }
3120
+ function claudeWorkspaceRef(cwd) {
3121
+ return `workspace:${digest(cwd)}`;
3122
+ }
2425
3123
  function digest(value) {
2426
- return createHash3("sha256").update(value, "utf8").digest("hex");
3124
+ return createHash4("sha256").update(value, "utf8").digest("hex");
2427
3125
  }
2428
3126
 
2429
3127
  // host/claude/src/claude-outbox.ts
2430
- import { open as open3, mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
2431
- import { join as join2 } from "node:path";
3128
+ import { mkdir as mkdir3, readdir, readFile as readFile2, unlink as unlink2 } from "node:fs/promises";
3129
+ import { isAbsolute, join as join3 } from "node:path";
2432
3130
 
2433
3131
  // host/invocation-context.ts
2434
3132
  var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
@@ -2442,17 +3140,32 @@ function hasHostInvocationIdentityKeys(value) {
2442
3140
  function hasHostInvocationContextKeys(value, optional = []) {
2443
3141
  return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
2444
3142
  }
3143
+ async function readLatestSessionProjection(file, hostSourceRef, hostSessionRef, readProjection) {
3144
+ const context = await readProjection(file);
3145
+ if (context === null || context.identity.host_source_ref !== hostSourceRef || context.identity.host_session_ref !== hostSessionRef) return null;
3146
+ return context;
3147
+ }
3148
+ function isLiveSessionProjection(context, now) {
3149
+ return context !== null && Date.parse(context.expires_at) > now.getTime();
3150
+ }
2445
3151
  function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
2446
3152
  const context = {
2447
3153
  schema_version: descriptor.schema_version,
2448
3154
  identity,
2449
3155
  prompt_ref: promptRef,
2450
3156
  expires_at: expiresAt,
3157
+ ...descriptor.turn_generation_ref === void 0 ? {} : { turn_generation_ref: descriptor.turn_generation_ref },
2451
3158
  ...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
2452
3159
  };
2453
3160
  descriptor.validate(context);
2454
3161
  return Object.freeze(context);
2455
3162
  }
3163
+ function isHostInvocationTurnSelection(value) {
3164
+ 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) => safeRef3(value[key]));
3165
+ }
3166
+ function invocationTurnSelectionMatches(context, selection) {
3167
+ 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;
3168
+ }
2456
3169
  function exactKeys4(value, keys) {
2457
3170
  const actual = Object.keys(value);
2458
3171
  return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
@@ -2462,10 +3175,16 @@ function requiredKeys(value, required, optional) {
2462
3175
  const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
2463
3176
  return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
2464
3177
  }
3178
+ function isRecord2(value) {
3179
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3180
+ }
3181
+ function safeRef3(value) {
3182
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
3183
+ }
2465
3184
 
2466
3185
  // host/claude/src/claude-invocation-context.ts
2467
- function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory) {
2468
- return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory });
3186
+ function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
3187
+ return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory, turn_generation_ref: turnGenerationRef });
2469
3188
  }
2470
3189
  function validateClaudeHostInvocationContext(value) {
2471
3190
  if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
@@ -2474,23 +3193,23 @@ function validateClaudeHostInvocationContext(value) {
2474
3193
  validateContextTimes(value);
2475
3194
  }
2476
3195
  function contextShape(value) {
2477
- return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory"]);
3196
+ return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory", "turn_generation_ref"]);
2478
3197
  }
2479
3198
  function validateIdentity2(value) {
2480
3199
  if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2481
3200
  }
2482
3201
  function validateContextReferences(value) {
2483
- if (!safeRef3(value.prompt_ref) || !optionalSafeRef(value.request_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
3202
+ if (!safeRef4(value.prompt_ref) || !optionalSafeRef(value.request_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2484
3203
  }
2485
3204
  function validateContextTimes(value) {
2486
3205
  if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2487
3206
  if (typeof value.bound_at === "string" && Date.parse(value.bound_at) > Date.parse(String(value.expires_at))) throw new ClaudeHostError("storage", `Claude Host invocation context has invalid binding time (${refusalFact("bound_at", value.bound_at, `at or before ${String(value.expires_at)}`)}).`);
2488
3207
  }
2489
3208
  function validIdentity(value) {
2490
- return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef3);
3209
+ return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef4);
2491
3210
  }
2492
3211
  function optionalSafeRef(value) {
2493
- return value === void 0 || safeRef3(value);
3212
+ return value === void 0 || safeRef4(value);
2494
3213
  }
2495
3214
  function optionalTimestamp(value) {
2496
3215
  return value === void 0 || timestamp2(value);
@@ -2498,7 +3217,7 @@ function optionalTimestamp(value) {
2498
3217
  function optionalDirectory(value) {
2499
3218
  return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
2500
3219
  }
2501
- function safeRef3(value) {
3220
+ function safeRef4(value) {
2502
3221
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2503
3222
  }
2504
3223
  function timestamp2(value) {
@@ -2510,29 +3229,67 @@ function record3(value) {
2510
3229
 
2511
3230
  // host/claude/src/claude-outbox.ts
2512
3231
  var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
3232
+ var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
3233
+ var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
2513
3234
  var ClaudeOutboxStore = class {
2514
- entries;
3235
+ envelopes;
3236
+ sessions;
3237
+ registrations;
3238
+ cursors;
2515
3239
  directory;
2516
3240
  activeContexts;
3241
+ turnSelections;
2517
3242
  constructor(directory) {
2518
3243
  if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2519
3244
  this.directory = directory;
2520
- this.entries = join2(directory, "entries");
2521
- this.activeContexts = join2(directory, "active-contexts");
3245
+ this.envelopes = join3(directory, "envelopes");
3246
+ this.sessions = join3(directory, "sessions");
3247
+ this.registrations = join3(directory, "transcript-registrations");
3248
+ this.cursors = join3(directory, "transcript-cursors");
3249
+ this.activeContexts = join3(directory, "active-contexts");
3250
+ this.turnSelections = join3(directory, "turn-selections");
2522
3251
  }
2523
3252
  async prepare() {
2524
- await mkdir2(this.entries, { recursive: true, mode: 448 });
2525
- await mkdir2(this.activeContexts, { recursive: true, mode: 448 });
3253
+ for (const path of [this.envelopes, this.sessions, this.registrations, this.cursors, this.activeContexts, this.turnSelections]) {
3254
+ await mkdir3(path, { recursive: true, mode: 448 });
3255
+ }
2526
3256
  }
2527
- async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory) {
2528
- const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory);
3257
+ async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
3258
+ const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef);
2529
3259
  await this.prepare();
2530
3260
  await durableAtomicReplace(this.activeContextPath(identity), JSON.stringify(context));
2531
3261
  }
2532
3262
  async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
2533
3263
  validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now);
3264
+ const context = await this.readRawInvocationContext(hostSourceRef, hostSessionRef);
3265
+ return isLiveSessionProjection(context, now) ? context : null;
3266
+ }
3267
+ async writeInvocationTurnSelection(selection) {
3268
+ if (!isHostInvocationTurnSelection(selection)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection is invalid.");
3269
+ await this.prepare();
3270
+ await durableAtomicReplace(this.turnSelectionPath(selection.host_source_ref, selection.host_session_ref), JSON.stringify(selection));
3271
+ }
3272
+ async attachInvocationTurnGeneration(identity, promptRef, turnGenerationRef) {
3273
+ const selection = await this.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3274
+ if (selection === null || selection.prompt_ref !== promptRef || selection.turn_generation_ref !== turnGenerationRef) return false;
3275
+ const path = this.activeContextPath(identity);
3276
+ const active = await readProjectionFile(path);
3277
+ if (active === null || active.prompt_ref !== promptRef || active.identity.host_turn_ref !== identity.host_turn_ref) return false;
3278
+ const updated = { ...active, turn_generation_ref: turnGenerationRef };
3279
+ validateClaudeHostInvocationContext(updated);
3280
+ await durableAtomicReplace(path, JSON.stringify(updated));
3281
+ return true;
3282
+ }
3283
+ /**
3284
+ * The latest Claude session projection even when past expiry, so the
3285
+ * invocation authenticator can report an expired context distinctly from
3286
+ * a missing one. Every other caller keeps the fail-closed read above.
3287
+ */
3288
+ async readRawInvocationContext(hostSourceRef, hostSessionRef) {
3289
+ if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2534
3290
  await this.prepare();
2535
- return readInvocationContextFile(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, now);
3291
+ const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
3292
+ return invocationTurnSelectionMatches(context, await this.readInvocationTurnSelection(hostSourceRef, hostSessionRef)) ? context : null;
2536
3293
  }
2537
3294
  async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
2538
3295
  await this.prepare();
@@ -2540,109 +3297,175 @@ var ClaudeOutboxStore = class {
2540
3297
  for (const name of await readdir(this.activeContexts)) {
2541
3298
  if (!name.endsWith(".json")) continue;
2542
3299
  try {
2543
- const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
3300
+ const value = JSON.parse(await readFile2(join3(this.activeContexts, name), "utf8"));
2544
3301
  validateClaudeHostInvocationContext(value);
2545
- if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
3302
+ if (Date.parse(value.expires_at) > now.getTime() && invocationTurnSelectionMatches(value, await this.readInvocationTurnSelection(value.identity.host_source_ref, value.identity.host_session_ref))) contexts.push(value);
2546
3303
  } catch {
2547
3304
  }
2548
3305
  }
2549
3306
  return contexts;
2550
3307
  }
2551
- async put(batch) {
2552
- validateBatch(batch);
3308
+ async putEnvelope(entry) {
3309
+ validateEnvelope(entry);
2553
3310
  await this.prepare();
2554
- const path = this.path(batch.batch_ref);
2555
- try {
2556
- const handle = await open3(path, "wx", 384);
3311
+ await durableAtomicReplace(this.envelopePath(entry.submission_ref), JSON.stringify(entry));
3312
+ }
3313
+ async listEnvelopes() {
3314
+ await this.prepare();
3315
+ const entries = [];
3316
+ let malformed = 0;
3317
+ for (const name of await readdir(this.envelopes)) {
3318
+ if (!name.endsWith(".json")) continue;
2557
3319
  try {
2558
- await handle.writeFile(JSON.stringify(batch));
2559
- await handle.sync();
2560
- } finally {
2561
- await handle.close();
3320
+ const value = JSON.parse(await readFile2(join3(this.envelopes, name), "utf8"));
3321
+ validateEnvelope(value);
3322
+ entries.push(value);
3323
+ } catch {
3324
+ malformed += 1;
2562
3325
  }
3326
+ }
3327
+ entries.sort((left, right) => left.created_at.localeCompare(right.created_at));
3328
+ return { entries, malformed };
3329
+ }
3330
+ async removeEnvelope(entry) {
3331
+ validateEnvelope(entry);
3332
+ try {
3333
+ await unlink2(this.envelopePath(entry.submission_ref));
2563
3334
  } catch (error) {
2564
- if (code(error) !== "EEXIST") throw new ClaudeHostError("storage", `Unable to persist Claude hook batch (${refusalFact("error_code", code(error), "EEXIST for an idempotent retry")}).`);
2565
- const existing = JSON.parse(await readFile(path, "utf8"));
2566
- validateBatch(existing);
2567
- if (JSON.stringify(existing) !== JSON.stringify(batch)) throw new ClaudeHostError("storage", "Claude hook batch identity conflicts with different bytes.");
3335
+ if (code(error) !== "ENOENT") throw error;
2568
3336
  }
2569
3337
  }
2570
- async list() {
3338
+ async saveSession(snapshot) {
3339
+ validateSession(snapshot);
2571
3340
  await this.prepare();
2572
- const batches = [];
2573
- let malformed = 0;
2574
- for (const name of await readdir(this.entries)) {
3341
+ await durableAtomicReplace(join3(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
3342
+ }
3343
+ async readSession(sourceRef, sessionRef) {
3344
+ if (!SAFE_REF4.test(sourceRef) || !SAFE_REF4.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
3345
+ await this.prepare();
3346
+ try {
3347
+ const value = JSON.parse(await readFile2(join3(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
3348
+ validateSession(value);
3349
+ return value;
3350
+ } catch (error) {
3351
+ if (code(error) === "ENOENT") return null;
3352
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3353
+ }
3354
+ }
3355
+ async registerTranscript(registration) {
3356
+ validateRegistration(registration);
3357
+ await this.prepare();
3358
+ const path = this.registrationPath(registration.host_source_ref, registration.host_session_ref);
3359
+ const existing = await this.readRegistrationFile(path);
3360
+ if (existing !== null && (existing.claude_session_id !== registration.claude_session_id || existing.transcript_path !== registration.transcript_path)) {
3361
+ throw new ClaudeHostError("storage", "Claude transcript registration conflicts with the source-bound session.");
3362
+ }
3363
+ await durableAtomicReplace(path, JSON.stringify(registration));
3364
+ }
3365
+ /**
3366
+ * End a registration whose transcript the Host has deleted. The cursor is
3367
+ * keyed by the same session and describes byte offsets of a file that no
3368
+ * longer exists, so it leaves with the registration.
3369
+ */
3370
+ async retireTranscriptRegistration(registration) {
3371
+ validateRegistration(registration);
3372
+ await this.prepare();
3373
+ await this.removeStateFile(this.registrationPath(registration.host_source_ref, registration.host_session_ref));
3374
+ await this.removeStateFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref));
3375
+ }
3376
+ async listTranscriptRegistrations() {
3377
+ await this.prepare();
3378
+ const registrations = [];
3379
+ for (const name of await readdir(this.registrations)) {
2575
3380
  if (!name.endsWith(".json")) continue;
2576
3381
  try {
2577
- const value = JSON.parse(await readFile(join2(this.entries, name), "utf8"));
2578
- validateBatch(value);
2579
- batches.push(value);
3382
+ const value = JSON.parse(await readFile2(join3(this.registrations, name), "utf8"));
3383
+ validateRegistration(value);
3384
+ registrations.push(value);
2580
3385
  } catch {
2581
- malformed += 1;
2582
3386
  }
2583
3387
  }
2584
- batches.sort(compareBatch);
2585
- return { entries: batches, malformed };
3388
+ return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
2586
3389
  }
2587
- async remove(batch) {
3390
+ async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
3391
+ if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
3392
+ await this.prepare();
2588
3393
  try {
2589
- await unlink2(this.path(batch.batch_ref));
3394
+ const value = JSON.parse(await readFile2(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
3395
+ if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
3396
+ return value;
2590
3397
  } catch (error) {
2591
- if (code(error) !== "ENOENT") throw error;
3398
+ if (code(error) === "ENOENT") return null;
3399
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Unable to read the Claude Host turn selection.");
3400
+ }
3401
+ }
3402
+ async readTranscriptCursor(registration) {
3403
+ validateRegistration(registration);
3404
+ await this.prepare();
3405
+ try {
3406
+ const value = JSON.parse(await readFile2(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
3407
+ validateCursor(value);
3408
+ return value;
3409
+ } catch (error) {
3410
+ if (code(error) === "ENOENT") return null;
3411
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3412
+ }
3413
+ }
3414
+ async writeTranscriptCursor(cursor) {
3415
+ validateCursor(cursor);
3416
+ await this.prepare();
3417
+ await durableAtomicReplace(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), JSON.stringify(cursor));
3418
+ }
3419
+ envelopePath(ref) {
3420
+ return join3(this.envelopes, `${cealRequestSha256(ref)}.json`);
3421
+ }
3422
+ registrationPath(sourceRef, sessionRef) {
3423
+ return join3(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3424
+ }
3425
+ turnSelectionPath(sourceRef, sessionRef) {
3426
+ return join3(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3427
+ }
3428
+ cursorPath(sourceRef, sessionRef) {
3429
+ return join3(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3430
+ }
3431
+ async readRegistrationFile(path) {
3432
+ try {
3433
+ const value = JSON.parse(await readFile2(path, "utf8"));
3434
+ validateRegistration(value);
3435
+ return value;
3436
+ } catch (error) {
3437
+ if (code(error) === "ENOENT") return null;
3438
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript registration is malformed.");
2592
3439
  }
2593
3440
  }
2594
- path(ref) {
2595
- return join2(this.entries, `${cealRequestSha256(ref)}.json`);
3441
+ async removeStateFile(path) {
3442
+ try {
3443
+ await unlink2(path);
3444
+ } catch (error) {
3445
+ if (code(error) !== "ENOENT") throw error;
3446
+ }
2596
3447
  }
2597
3448
  activeContextPath(identity) {
2598
- return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
3449
+ return join3(this.activeContexts, `${invocationContextKey(identity)}.json`);
2599
3450
  }
2600
3451
  };
2601
3452
  function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
2602
3453
  if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef) || !(now instanceof Date) || !Number.isFinite(now.getTime())) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2603
3454
  }
2604
- async function readInvocationContextFile(file, hostSourceRef, hostSessionRef, now) {
3455
+ async function readProjectionFile(file) {
2605
3456
  try {
2606
- const context = JSON.parse(await readFile(file, "utf8"));
3457
+ const context = JSON.parse(await readFile2(file, "utf8"));
2607
3458
  validateClaudeHostInvocationContext(context);
2608
- return contextMatches(context, hostSourceRef, hostSessionRef) && Date.parse(context.expires_at) > now.getTime() ? context : null;
3459
+ return context;
2609
3460
  } catch (error) {
2610
3461
  if (code(error) === "ENOENT") return null;
2611
3462
  if (error instanceof ClaudeHostError) throw error;
2612
3463
  throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
2613
3464
  }
2614
3465
  }
2615
- function contextMatches(context, hostSourceRef, hostSessionRef) {
2616
- return context.identity.host_source_ref === hostSourceRef && context.identity.host_session_ref === hostSessionRef;
2617
- }
2618
- function compareBatch(left, right) {
2619
- return rank(left.hook_event) - rank(right.hook_event) || left.occurred_at.localeCompare(right.occurred_at) || left.batch_ref.localeCompare(right.batch_ref);
2620
- }
2621
- function rank(event) {
2622
- return event === "SessionStart" ? 0 : event === "UserPromptSubmit" ? 1 : 2;
2623
- }
2624
- function validateBatch(value) {
2625
- if (!validBatchHeader(value)) throw new ClaudeHostError("storage", "Claude hook batch is malformed.");
2626
- for (const [index, draft] of value.frames.entries()) {
2627
- if (!record4(draft) || !hostFrameKind(draft.frame_kind) || !record4(draft.payload)) throw new ClaudeHostError("storage", "Claude frame draft is malformed.");
2628
- encodeHostProducerFrame({ schema_version: "ceal.host_producer_frame.v1", producer_id: "claude", producer_installation_ref: "validation", producer_epoch: 1, sequence: index + 1, frame_kind: draft.frame_kind, occurred_at: value.occurred_at, payload: draft.payload });
2629
- }
2630
- }
2631
- function validBatchHeader(value) {
2632
- return record4(value) && value.schema_version === CLAUDE_OUTBOX_SCHEMA && safeRef4(value.batch_ref) && claudeEvent(value.hook_event) && safeRef4(value.local_session_ref) && optionalRef(value.local_turn_ref) && timestamp3(value.occurred_at) && Array.isArray(value.frames) && value.frames.length > 0 && value.frames.length <= 3;
2633
- }
2634
- function claudeEvent(value) {
2635
- return typeof value === "string" && ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure"].includes(value);
2636
- }
2637
- function hostFrameKind(value) {
2638
- return isHostProducerFrameKind(value);
2639
- }
2640
- function safeRef4(value) {
3466
+ function safeRef5(value) {
2641
3467
  return typeof value === "string" && SAFE_REF4.test(value);
2642
3468
  }
2643
- function optionalRef(value) {
2644
- return value === void 0 || typeof value === "string" && SAFE_REF4.test(value);
2645
- }
2646
3469
  function timestamp3(value) {
2647
3470
  return typeof value === "string" && Number.isFinite(Date.parse(value));
2648
3471
  }
@@ -2652,133 +3475,766 @@ function record4(value) {
2652
3475
  function code(error) {
2653
3476
  return record4(error) && typeof error.code === "string" ? error.code : null;
2654
3477
  }
3478
+ function validateEnvelope(value) {
3479
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
3480
+ if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "input_kind", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"], ["request_ref"])) {
3481
+ throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
3482
+ }
3483
+ if (value.schema_version !== CLAUDE_OUTBOX_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude outbox entry is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_OUTBOX_SCHEMA_VERSION}`)}).`);
3484
+ validateEnvelopeIdentity(value);
3485
+ validateEnvelopeObservations(value.observations);
3486
+ if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
3487
+ if (!timestamp3(value.created_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
3488
+ }
3489
+ function validateEnvelopeIdentity(value) {
3490
+ validateIdentity3(value.identity);
3491
+ if (!safeRef5(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
3492
+ if (!isCealHostInputKind(value.input_kind)) throw new ClaudeHostError("storage", "Claude outbox input provenance is malformed.");
3493
+ if (!safeRef5(value.prompt_ref) || !safeRef5(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
3494
+ if (value.request_ref !== void 0 && !safeRef5(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
3495
+ }
3496
+ function validateEnvelopeObservations(value) {
3497
+ if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3498
+ try {
3499
+ for (const observation2 of value) validateCealHostObservationDraft(observation2);
3500
+ } catch {
3501
+ throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3502
+ }
3503
+ }
3504
+ function validateSession(value) {
3505
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3506
+ if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) {
3507
+ throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
3508
+ }
3509
+ if (value.schema_version !== CLAUDE_SESSION_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude session snapshot is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_SESSION_SCHEMA_VERSION}`)}).`);
3510
+ if (!safeRef5(value.host_session_ref) || !safeRef5(value.host_source_ref) || !timestamp3(value.observed_at) || !timestamp3(value.expires_at)) {
3511
+ throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
3512
+ }
3513
+ }
3514
+ function validateRegistration(value) {
3515
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3516
+ if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "claude_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) {
3517
+ throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
3518
+ }
3519
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3520
+ if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) {
3521
+ throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
3522
+ }
3523
+ validateRegistrationPaths(value.cwd, value.transcript_path);
3524
+ if (!timestamp3(value.registered_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
3525
+ }
3526
+ function validateRegistrationPaths(cwd, transcriptPath) {
3527
+ if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
3528
+ throw new ClaudeHostError("storage", "Claude transcript registration working directory is malformed.");
3529
+ }
3530
+ if (typeof transcriptPath !== "string" || transcriptPath.length === 0 || transcriptPath.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS) {
3531
+ throw new ClaudeHostError("storage", "Claude transcript registration path is malformed.");
3532
+ }
3533
+ }
3534
+ function validateCursor(value) {
3535
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3536
+ if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "transcript_path", "offset", "anchor_start", "anchor_sha256", "subagents", "status", "updated_at"], ["device", "inode", "open_turn", "unattributed", "session_observed", "reason"])) {
3537
+ throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
3538
+ }
3539
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3540
+ if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) throw new ClaudeHostError("storage", "Claude transcript cursor references are malformed.");
3541
+ if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
3542
+ validateCursorPosition(value);
3543
+ validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
3544
+ validateSubagentCursors(value.subagents);
3545
+ if (value.reason !== void 0 && !isHostTranscriptCursorReason(value.reason)) throw new ClaudeHostError("storage", "Claude transcript cursor reason is malformed.");
3546
+ }
3547
+ function validateCursorPosition(value) {
3548
+ if (!validCursorPosition(value)) throw new ClaudeHostError("storage", "Claude transcript cursor position is malformed.");
3549
+ if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
3550
+ if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
3551
+ if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
3552
+ if (value.status !== "active" && value.status !== "quarantined" || !timestamp3(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
3553
+ }
3554
+ function validateCursorTurn(openTurn, unattributed, sessionObserved) {
3555
+ if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
3556
+ if (unattributed !== void 0 && !validUnattributed(unattributed)) throw new ClaudeHostError("storage", "Claude transcript unattributed cursor is malformed.");
3557
+ if (sessionObserved !== void 0 && typeof sessionObserved !== "boolean") throw new ClaudeHostError("storage", "Claude transcript cursor session marker is malformed.");
3558
+ }
3559
+ function validateSubagentCursors(value) {
3560
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
3561
+ for (const [name, entry] of Object.entries(value)) validateSubagentEntry(name, entry);
3562
+ }
3563
+ function validateSubagentEntry(name, entry) {
3564
+ if (name.length === 0 || name.length > CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS || name.includes("/")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3565
+ if (name.includes("\0")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3566
+ if (!record4(entry) || !sameKeys(entry, ["offset", "anchor_start", "anchor_sha256"], ["device", "inode"]) || !nonNegativeInteger(entry.offset)) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3567
+ validateSubagentIdentity(entry);
3568
+ }
3569
+ function validateSubagentIdentity(entry) {
3570
+ if (entry.device !== void 0 && typeof entry.device !== "string" || entry.inode !== void 0 && typeof entry.inode !== "string") {
3571
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3572
+ }
3573
+ if (!nonNegativeInteger(entry.anchor_start) || typeof entry.anchor_sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(entry.anchor_sha256)) {
3574
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3575
+ }
3576
+ if (entry.device === void 0 && Number(entry.offset) !== 0) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is unpinned past its start.");
3577
+ }
3578
+ function validCursorPosition(value) {
3579
+ return nonNegativeInteger(value.offset) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256);
3580
+ }
3581
+ function validOpenTurn(value) {
3582
+ if (!record4(value) || !sameKeys(value, ["turn_key", "user_uuid", "prompt_ref", "host_input_ref", "started_at", "last_observed_at", "message_ids", "counts"], ["last_terminal_digest"])) return false;
3583
+ if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) return false;
3584
+ if (!timestamp3(value.started_at) || !timestamp3(value.last_observed_at)) return false;
3585
+ if (!validMessageIds(value.message_ids)) return false;
3586
+ if (!validTurnCounts(value.counts)) return false;
3587
+ return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
3588
+ }
3589
+ function validMessageIds(value) {
3590
+ if (!Array.isArray(value) || value.length > CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES) return false;
3591
+ if (!value.every((candidate) => typeof candidate === "string" && candidate.length > 0 && candidate.length <= 256)) return false;
3592
+ return new Set(value).size === value.length;
3593
+ }
3594
+ function validUnattributed(value) {
3595
+ if (!record4(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
3596
+ if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
3597
+ if (!timestamp3(value.last_observed_at)) return false;
3598
+ return validTurnCounts(value);
3599
+ }
3600
+ function validTurnCounts(value) {
3601
+ if (!record4(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
3602
+ return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
3603
+ }
3604
+ function validateIdentity3(value) {
3605
+ if (!record4(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) {
3606
+ throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
3607
+ }
3608
+ }
3609
+ function nonNegativeInteger(value) {
3610
+ return Number.isSafeInteger(value) && Number(value) >= 0;
3611
+ }
3612
+ function sameKeys(value, required, optional = []) {
3613
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
3614
+ }
2655
3615
 
2656
3616
  // host/claude/src/claude-producer.ts
2657
- import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile } from "node:fs/promises";
2658
- import { join as join3 } from "node:path";
2659
- var STATE_SCHEMA = "ceal.claude_producer_state.v1";
2660
- var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2661
3617
  var ClaudeProducer = class {
3618
+ producer_id = "claude";
3619
+ producer_label = "Claude";
3620
+ outbox_directory;
2662
3621
  store;
2663
- statePath;
2664
- installation;
2665
- epoch;
2666
- options;
2667
- tail = Promise.resolve();
3622
+ core;
2668
3623
  constructor(options) {
2669
- if (!SAFE_REF5.test(options.producer_installation_ref)) throw new ClaudeHostError("configuration", "Claude producer installation identity is invalid.");
2670
- this.options = options;
2671
- this.epoch = options.producer_epoch ?? 1;
2672
- if (!Number.isSafeInteger(this.epoch) || this.epoch <= 0) throw new ClaudeHostError("configuration", "Claude producer epoch is invalid.");
2673
3624
  this.store = new ClaudeOutboxStore(options.outbox_dir);
2674
- this.statePath = join3(options.outbox_dir, "claude-producer-state.json");
2675
- this.installation = options.producer_installation_ref;
3625
+ this.outbox_directory = this.store.directory;
3626
+ this.core = new HostProducerDrainCore(this, options);
2676
3627
  }
2677
3628
  drainOutbox() {
2678
- const run = this.tail.then(() => this.drainLocked());
2679
- this.tail = run.then(() => void 0, () => void 0);
2680
- return run;
3629
+ return this.core.drainOutbox();
2681
3630
  }
2682
- async drainLocked() {
2683
- const read = await this.store.list();
2684
- let admitted = 0;
2685
- let retained = 0;
2686
- for (const batch of read.entries) {
2687
- const frames = await this.reserve(batch);
2688
- let complete = true;
2689
- for (const frame of frames) {
2690
- const outcome = await this.options.relay.admit(frame);
2691
- if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2692
- complete = false;
2693
- break;
2694
- }
3631
+ fail(code2, message) {
3632
+ return new ClaudeHostError(code2, message);
3633
+ }
3634
+ listEnvelopes() {
3635
+ return this.store.listEnvelopes();
3636
+ }
3637
+ removeEnvelope(envelope) {
3638
+ return this.store.removeEnvelope(envelope);
3639
+ }
3640
+ submissionRefOf(envelope) {
3641
+ return envelope.submission_ref;
3642
+ }
3643
+ expiresAtOf(envelope) {
3644
+ return envelope.expires_at;
3645
+ }
3646
+ createdAtOf(envelope) {
3647
+ return envelope.created_at;
3648
+ }
3649
+ async mapEnvelope(envelope) {
3650
+ const prompt = observation(envelope, "prompt");
3651
+ if (prompt !== void 0) return this.mapInput(envelope, prompt);
3652
+ return [mapTerminal(envelope)];
3653
+ }
3654
+ async mapInput(envelope, prompt) {
3655
+ const drafts = [];
3656
+ const session = observation(envelope, "session");
3657
+ if (session !== void 0) {
3658
+ const registrations = await this.store.listTranscriptRegistrations();
3659
+ const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
3660
+ const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
3661
+ drafts.push({
3662
+ frame_kind: "session_started",
3663
+ payload: compact({
3664
+ local_session_ref: envelope.identity.host_session_ref,
3665
+ working_directory_ref: claudeWorkspaceRef(directoryIdentity),
3666
+ model: recordText(session.value, "model"),
3667
+ observations: [session]
3668
+ })
3669
+ });
3670
+ }
3671
+ const turn = observation(envelope, "turn");
3672
+ if (turn !== void 0) drafts.push({
3673
+ frame_kind: "turn_started",
3674
+ payload: compact({
3675
+ local_session_ref: envelope.identity.host_session_ref,
3676
+ local_turn_ref: localTurnRef(envelope),
3677
+ observations: [turn]
3678
+ })
3679
+ });
3680
+ if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
3681
+ drafts.push({
3682
+ frame_kind: "user_input",
3683
+ payload: compact({
3684
+ local_session_ref: envelope.identity.host_session_ref,
3685
+ local_turn_ref: localTurnRef(envelope),
3686
+ local_input_ref: envelope.host_input_ref,
3687
+ input_kind: envelope.input_kind,
3688
+ origin_input: envelope.origin_input,
3689
+ observations: [prompt],
3690
+ context_expires_at: envelope.expires_at
3691
+ })
3692
+ });
3693
+ return drafts;
3694
+ }
3695
+ };
3696
+ function mapTerminal(envelope) {
3697
+ const usage = envelope.observations.filter((item) => ["token", "cost"].includes(item.observation_kind));
3698
+ return {
3699
+ frame_kind: "turn_terminal",
3700
+ payload: compact({
3701
+ local_session_ref: envelope.identity.host_session_ref,
3702
+ local_turn_ref: localTurnRef(envelope),
3703
+ terminal_state: "completed",
3704
+ usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
3705
+ observations: envelope.observations
3706
+ })
3707
+ };
3708
+ }
3709
+ function observation(envelope, kind) {
3710
+ return envelope.observations.find((item) => item.observation_kind === kind);
3711
+ }
3712
+ function localTurnRef(envelope) {
3713
+ return envelope.identity.host_turn_ref;
3714
+ }
3715
+ function compact(value) {
3716
+ return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
3717
+ }
3718
+ function asRecord(value) {
3719
+ return isRecord3(value) ? value : void 0;
3720
+ }
3721
+ function recordText(value, key) {
3722
+ const record5 = asRecord(value);
3723
+ return typeof record5?.[key] === "string" ? record5[key] : void 0;
3724
+ }
3725
+ function isRecord3(value) {
3726
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3727
+ }
3728
+
3729
+ // host/claude/src/claude-transcript-watcher.ts
3730
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
3731
+ import { open as open4, readdir as readdir2 } from "node:fs/promises";
3732
+ import { join as join4 } from "node:path";
3733
+ var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
3734
+ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3735
+ store;
3736
+ registerCurrentPrompt;
3737
+ diagnostic;
3738
+ constructor(options) {
3739
+ super(options, {
3740
+ default_retention_ms: CLAUDE_DEFAULT_RETENTION_MS,
3741
+ invalid_outbox: () => new ClaudeHostError("configuration", "Claude transcript watcher outbox is invalid."),
3742
+ clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
3743
+ });
3744
+ this.store = new ClaudeOutboxStore(options.outbox_dir);
3745
+ this.registerCurrentPrompt = options.register_current_prompt;
3746
+ this.diagnostic = options.diagnostic ?? (() => void 0);
3747
+ }
3748
+ transcriptPathOf(registration) {
3749
+ return registration.transcript_path;
3750
+ }
3751
+ async listRegistrations() {
3752
+ return this.store.listTranscriptRegistrations();
3753
+ }
3754
+ async scanOpened(handle, registration) {
3755
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3756
+ const cursor = await this.preflight(registration, handle, size, device, inode);
3757
+ if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
3758
+ if (cursor.status === "quarantined") return { records: 0, enqueued: 0, quarantined: true };
3759
+ const draft = mutableCursor(cursor);
3760
+ await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
3761
+ return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
3762
+ }
3763
+ async scanFailed(handle, registration, error) {
3764
+ this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
3765
+ if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
3766
+ if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
3767
+ await this.quarantineFailure(registration, handle);
3768
+ return { records: 0, enqueued: 0, quarantined: true };
3769
+ }
3770
+ async retireMissing(registration) {
3771
+ return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
3772
+ }
3773
+ initialCursor(registration) {
3774
+ return {
3775
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3776
+ host_source_ref: registration.host_source_ref,
3777
+ host_session_ref: registration.host_session_ref,
3778
+ transcript_path: registration.transcript_path,
3779
+ offset: 0,
3780
+ anchor_start: 0,
3781
+ anchor_sha256: this.sha(Buffer.alloc(0)),
3782
+ subagents: {},
3783
+ status: "active",
3784
+ updated_at: this.stamp()
3785
+ };
3786
+ }
3787
+ async scanOpenedTranscript(registration, handle, draft, size, device, inode) {
3788
+ const main2 = await this.readChunk(handle, draft.offset, size);
3789
+ if (main2 !== "empty" && main2 !== "partial") {
3790
+ await this.consumeMainChunk(registration, main2, draft);
3791
+ draft.offset += main2.length;
3792
+ }
3793
+ draft.consumed += await this.consumeSubagents(registration, draft);
3794
+ await this.emitOpenTerminal(registration, draft);
3795
+ await this.commitCursor(registration, handle, draft, device, inode);
3796
+ }
3797
+ async preflight(registration, handle, size, device, inode) {
3798
+ const existing = await this.store.readTranscriptCursor(registration);
3799
+ if (existing?.status === "quarantined") return existing;
3800
+ const cursor = existing ?? this.initialCursor(registration);
3801
+ if (cursor.device === void 0) {
3802
+ if (cursor.offset !== 0) return await this.quarantineStored(cursor, device, inode, "transcript_replaced");
3803
+ return { ...cursor, device, inode };
3804
+ }
3805
+ const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
3806
+ if (reason !== null) return await this.quarantineStored(cursor, device, inode, reason);
3807
+ return cursor;
3808
+ }
3809
+ async consumeMainChunk(registration, complete, draft) {
3810
+ for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3811
+ draft.consumed += 1;
3812
+ const record5 = parseTranscriptLine(line);
3813
+ if (record5 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
3814
+ if (isUserTurnLine(record5)) await this.openTurn(registration, draft, record5);
3815
+ else if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
3816
+ else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3817
+ }
3818
+ }
3819
+ async openTurn(registration, draft, record5) {
3820
+ this.diagnostic("watcher_turn_observed");
3821
+ await this.closeTurn(registration, draft, record5.timestamp);
3822
+ const sessionRef = registration.host_session_ref;
3823
+ const turnRef = claudeLocalTurnRef(registration.claude_session_id, record5.uuid);
3824
+ const inputRef = claudeLocalInputRef(registration.claude_session_id, record5.uuid);
3825
+ const promptRef = hostProducerPromptRef(this.sha(record5.message.content));
3826
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
3827
+ const observations = [
3828
+ { observation_ref: observationRef("transcript_user_text", inputRef, record5.message.content), observation_kind: "prompt", observed_at: record5.timestamp, value: { body_sha256: this.sha(record5.message.content) } },
3829
+ { observation_ref: observationRef("transcript_turn_started", turnRef, record5.uuid), observation_kind: "turn", observed_at: record5.timestamp, value: { status: "started", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turnRef } }
3830
+ ];
3831
+ if (!draft.session_observed) {
3832
+ const session = await this.store.readSession(registration.host_source_ref, sessionRef);
3833
+ if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record5.timestamp, value: session.value });
3834
+ draft.session_observed = true;
3835
+ }
3836
+ await this.store.putEnvelope({
3837
+ schema_version: "ceal.claude_observation_outbox.v1",
3838
+ identity,
3839
+ host_input_ref: inputRef,
3840
+ input_kind: "human",
3841
+ prompt_ref: promptRef,
3842
+ observations,
3843
+ origin_input: true,
3844
+ submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3845
+ created_at: record5.timestamp,
3846
+ expires_at: this.expire(this.outboxRetentionMs)
3847
+ });
3848
+ draft.enqueued += 1;
3849
+ await this.registerPrompt(sessionRef, turnRef, promptRef, record5.message.content);
3850
+ const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3851
+ const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3852
+ await this.activateContext(identity, promptRef, turnGenerationRef);
3853
+ const folded = foldUnattributed(draft.unattributed);
3854
+ draft.open_turn = {
3855
+ turn_key: turnRef,
3856
+ user_uuid: record5.uuid,
3857
+ prompt_ref: promptRef,
3858
+ host_input_ref: inputRef,
3859
+ started_at: record5.timestamp,
3860
+ last_observed_at: folded?.last_observed_at ?? record5.timestamp,
3861
+ message_ids: folded?.message_ids ?? [],
3862
+ counts: folded?.counts ?? zeroCounts()
3863
+ };
3864
+ draft.unattributed = void 0;
3865
+ }
3866
+ async closeTurn(registration, draft, at) {
3867
+ const turn = draft.open_turn;
3868
+ draft.open_turn = void 0;
3869
+ if (turn === void 0) return;
3870
+ const digest2 = terminalDigest(turn);
3871
+ if (digest2 === turn.last_terminal_digest) return;
3872
+ await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3873
+ draft.enqueued += 1;
3874
+ }
3875
+ accumulateUsage(draft, usage) {
3876
+ const turn = draft.open_turn;
3877
+ if (turn === void 0) {
3878
+ draft.unattributed = accumulateUnattributed(draft.unattributed, usage);
3879
+ return;
3880
+ }
3881
+ if (turn.message_ids.includes(usage.uuid)) return;
3882
+ if (turn.message_ids.length >= CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES) throw new ClaudeHostError("payload_rejected", "Claude transcript turn exceeds its usage message bound.", "size_budget");
3883
+ draft.open_turn = {
3884
+ ...turn,
3885
+ last_observed_at: usage.observed_at > turn.last_observed_at ? usage.observed_at : turn.last_observed_at,
3886
+ message_ids: [...turn.message_ids, usage.uuid],
3887
+ counts: addCounts(turn.counts, usage)
3888
+ };
3889
+ }
3890
+ /**
3891
+ * Subagent transcripts live beside the session transcript at
3892
+ * `<session-id>/subagents/*.jsonl` with a `.meta.json` sibling the scan
3893
+ * ignores. Their usage belongs to the turn open when the scan runs: a
3894
+ * subagent only runs while its parent turn is in flight. A straggler line
3895
+ * that lands after the next user message therefore attributes to the open
3896
+ * turn, never to a closed one.
3897
+ */
3898
+ async consumeSubagents(registration, draft) {
3899
+ const directory = subagentsDirectory(registration.transcript_path);
3900
+ let names;
3901
+ try {
3902
+ names = (await readdir2(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
3903
+ } catch (error) {
3904
+ if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
3905
+ throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
3906
+ }
3907
+ if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
3908
+ this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
3909
+ names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
3910
+ }
3911
+ let consumed = 0;
3912
+ for (const name of names) {
3913
+ consumed += await this.consumeSubagentFile(draft, directory, name);
3914
+ }
3915
+ return consumed;
3916
+ }
3917
+ async consumeSubagentFile(draft, directory, name) {
3918
+ const path = join4(directory, name);
3919
+ let handle;
3920
+ try {
3921
+ handle = await open4(path, O_RDONLY3 | O_NOFOLLOW2);
3922
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3923
+ const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
3924
+ const pinned = await this.preflightSubagent(entry, size, device, inode);
3925
+ const chunk = await this.readChunk(handle, pinned.offset, size);
3926
+ if (chunk === "empty" || chunk === "partial") {
3927
+ draft.subagents[name] = pinned;
3928
+ return 0;
2695
3929
  }
2696
- if (!complete) {
2697
- retained += 1;
2698
- break;
3930
+ const consumed = this.consumeSubagentLines(draft, chunk);
3931
+ const offset = pinned.offset + chunk.length;
3932
+ const anchor = await this.readAnchor(handle, offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
3933
+ draft.subagents[name] = { device, inode, offset, anchor_start: anchor.start, anchor_sha256: anchor.sha256 };
3934
+ return consumed;
3935
+ } catch (error) {
3936
+ if (isMissingTranscriptError(error)) {
3937
+ delete draft.subagents[name];
3938
+ return 0;
2699
3939
  }
2700
- await this.store.remove(batch);
2701
- await this.release(batch.batch_ref);
2702
- admitted += 1;
3940
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("payload_rejected", "Claude subagent transcript is unavailable.");
3941
+ } finally {
3942
+ await handle?.close();
2703
3943
  }
2704
- return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - retained), malformed: read.malformed };
2705
3944
  }
2706
- async reserve(batch) {
2707
- const state = await this.readState();
2708
- const existing = state.pending[batch.batch_ref];
2709
- if (existing !== void 0) return existing;
2710
- const frames = batch.frames.map((draft, offset) => ({ schema_version: "ceal.host_producer_frame.v1", producer_id: "claude", producer_installation_ref: this.installation, producer_epoch: this.epoch, sequence: state.next_sequence + offset, frame_kind: draft.frame_kind, occurred_at: batch.occurred_at, payload: draft.payload }));
2711
- await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [batch.batch_ref]: frames } });
2712
- return frames;
3945
+ consumeSubagentLines(draft, chunk) {
3946
+ let consumed = 0;
3947
+ for (const line of chunk.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3948
+ consumed += 1;
3949
+ const record5 = parseTranscriptLine(line);
3950
+ if (record5 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
3951
+ if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
3952
+ else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
3953
+ }
3954
+ return consumed;
2713
3955
  }
2714
- async release(ref) {
2715
- const state = await this.readState();
2716
- if (state.pending[ref] === void 0) return;
2717
- const pending = { ...state.pending };
2718
- delete pending[ref];
2719
- await this.writeState({ ...state, pending });
3956
+ async preflightSubagent(entry, size, device, inode) {
3957
+ if (entry.device === void 0) {
3958
+ if (entry.offset !== 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript cursor is unpinned past its start.");
3959
+ return { device, inode, offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
3960
+ }
3961
+ if (entry.device !== device || entry.inode !== inode) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was replaced.");
3962
+ if (size < entry.offset) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was truncated.");
3963
+ return { device, inode, offset: entry.offset, anchor_start: entry.anchor_start, anchor_sha256: entry.anchor_sha256 };
2720
3964
  }
2721
- async readState() {
3965
+ /**
3966
+ * The open turn's terminal is emitted optimistically on every scan whose
3967
+ * new lines changed its totals, so usage is visible before the next user
3968
+ * message closes the turn. The emission digest changes with the content,
3969
+ * so a repeated scan over unchanged state emits nothing.
3970
+ */
3971
+ async emitOpenTerminal(registration, draft) {
3972
+ const turn = draft.open_turn;
3973
+ if (turn === void 0 || turn.message_ids.length === 0) return;
3974
+ const digest2 = terminalDigest(turn);
3975
+ if (digest2 === turn.last_terminal_digest) return;
3976
+ await this.putTerminalEnvelope(registration, turn, digest2, void 0);
3977
+ draft.enqueued += 1;
3978
+ draft.open_turn = { ...turn, last_terminal_digest: digest2 };
3979
+ }
3980
+ async putTerminalEnvelope(registration, turn, digest2, gapAt) {
3981
+ const observedAt = gapAt ?? turn.last_observed_at;
3982
+ const tokenValue = turn.message_ids.length === 0 ? hostObservationNotRecorded("claude_turn_usage_not_observed") : availableTokenValue(turn.counts);
3983
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: turn.turn_key };
3984
+ await this.store.putEnvelope({
3985
+ schema_version: "ceal.claude_observation_outbox.v1",
3986
+ identity,
3987
+ host_input_ref: turn.host_input_ref,
3988
+ input_kind: "host_automatic",
3989
+ prompt_ref: turn.prompt_ref,
3990
+ observations: [
3991
+ { observation_ref: observationRef("transcript_terminal", turn.turn_key, digest2), observation_kind: "turn", observed_at: observedAt, value: { status: "stopped", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turn.turn_key } },
3992
+ { observation_ref: observationRef("transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue },
3993
+ { observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
3994
+ ],
3995
+ origin_input: false,
3996
+ submission_ref: submissionRef("terminal", turn.turn_key, digest2, turn.prompt_ref),
3997
+ created_at: observedAt,
3998
+ expires_at: this.expire(this.outboxRetentionMs)
3999
+ });
4000
+ }
4001
+ async commitCursor(registration, handle, draft, device, inode) {
4002
+ const anchor = await this.readAnchor(handle, draft.offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
4003
+ await this.store.writeTranscriptCursor({
4004
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
4005
+ host_source_ref: registration.host_source_ref,
4006
+ host_session_ref: registration.host_session_ref,
4007
+ transcript_path: registration.transcript_path,
4008
+ device,
4009
+ inode,
4010
+ offset: draft.offset,
4011
+ anchor_start: anchor.start,
4012
+ anchor_sha256: anchor.sha256,
4013
+ subagents: draft.subagents,
4014
+ ...draft.open_turn === void 0 ? {} : { open_turn: draft.open_turn },
4015
+ ...draft.unattributed === void 0 ? {} : { unattributed: draft.unattributed },
4016
+ ...draft.session_observed ? { session_observed: true } : {},
4017
+ status: "active",
4018
+ updated_at: this.stamp()
4019
+ });
4020
+ }
4021
+ async registerPrompt(sessionRef, turnRef, promptRef, prompt) {
4022
+ if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
2722
4023
  try {
2723
- return decodeState(JSON.parse(await readFile2(this.statePath, "utf8")), this.installation, this.epoch);
4024
+ await this.registerCurrentPrompt({
4025
+ producer_id: "claude",
4026
+ producer_installation_ref: this.producerInstallationRef,
4027
+ local_session_ref: sessionRef,
4028
+ local_turn_ref: turnRef,
4029
+ prompt_ref: promptRef,
4030
+ current_prompt: prompt,
4031
+ context_expires_at: this.expire(this.outboxRetentionMs)
4032
+ });
4033
+ this.diagnostic("current_prompt_registered");
2724
4034
  } catch (error) {
2725
- if (code2(error) !== "ENOENT") throw error;
2726
- return { schema_version: STATE_SCHEMA, producer_installation_ref: this.installation, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
4035
+ this.diagnostic("current_prompt_registration_failed");
4036
+ throw error;
2727
4037
  }
2728
4038
  }
2729
- async writeState(state) {
2730
- await mkdir3(this.store.directory, { recursive: true, mode: 448 });
2731
- const temp = `${this.statePath}.${process.pid}.new`;
2732
- await writeFile(temp, JSON.stringify(state), { mode: 384 });
2733
- await rename2(temp, this.statePath);
4039
+ async activateContext(identity, promptRef, turnGenerationRef) {
4040
+ try {
4041
+ await this.store.activateInvocationContext(identity, promptRef, this.expire(this.outboxRetentionMs), void 0, turnGenerationRef);
4042
+ this.diagnostic("activation_recorded");
4043
+ } catch (error) {
4044
+ this.diagnostic("activation_failed");
4045
+ throw error;
4046
+ }
4047
+ }
4048
+ async quarantineStored(cursor, device, inode, reason) {
4049
+ await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
4050
+ return null;
4051
+ }
4052
+ async quarantineUnopened(registration, reason) {
4053
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
4054
+ await this.store.writeTranscriptCursor({ ...existing ?? this.initialCursor(registration), transcript_path: registration.transcript_path, status: "quarantined", reason, updated_at: this.stamp() });
4055
+ return { records: 0, enqueued: 0, quarantined: true };
4056
+ }
4057
+ async quarantineDraft(cursor) {
4058
+ await this.store.writeTranscriptCursor({ ...cursor, status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
4059
+ return { records: 0, enqueued: 0, quarantined: true };
4060
+ }
4061
+ async quarantineFailure(registration, handle) {
4062
+ if (handle === void 0) return;
4063
+ const stats = await handle.stat().catch(() => null);
4064
+ if (stats === null) return;
4065
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
4066
+ const cursor = existing ?? this.initialCursor(registration);
4067
+ await this.store.writeTranscriptCursor({ ...cursor, device: String(stats.dev), inode: String(stats.ino), status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
4068
+ }
4069
+ async preservedCursor(registration) {
4070
+ return await this.store.readTranscriptCursor(registration).catch(() => null) ?? this.initialCursor(registration);
2734
4071
  }
2735
4072
  };
2736
- function decodeState(value, installation, epoch) {
2737
- if (!validStateHeader(value, installation)) throw new ClaudeHostError("storage", `Claude producer state header is malformed or belongs to another installation (${refusalFacts([
2738
- { name: "schema_version", value: record5(value) ? value.schema_version : void 0, expected: STATE_SCHEMA },
2739
- { name: "producer_installation_ref", value: record5(value) ? value.producer_installation_ref : void 0, expected: installation }
2740
- ])}).`);
2741
- if (Number(value.producer_epoch) > epoch) throw new ClaudeHostError("storage", `Claude producer state epoch is ahead of this installation (${refusalFact("producer_epoch", value.producer_epoch, `at most ${epoch}`)}).`);
2742
- const pending = {};
2743
- for (const [ref, frames] of Object.entries(value.pending)) {
2744
- if (!SAFE_REF5.test(ref) || !Array.isArray(frames) || frames.length === 0) throw new ClaudeHostError("storage", "Claude producer reservation is malformed.");
2745
- pending[ref] = frames.map((frame) => decodeFrame(frame, installation, Number(value.producer_epoch)));
2746
- }
2747
- return { schema_version: STATE_SCHEMA, producer_installation_ref: installation, producer_epoch: epoch, next_sequence: Number(value.next_sequence), pending };
2748
- }
2749
- function validStateHeader(value, installation) {
2750
- return record5(value) && value.schema_version === STATE_SCHEMA && value.producer_installation_ref === installation && positiveInteger(value.producer_epoch) && positiveInteger(value.next_sequence) && record5(value.pending);
4073
+ function mutableCursor(cursor) {
4074
+ return {
4075
+ offset: cursor.offset,
4076
+ ...cursor.open_turn === void 0 ? {} : { open_turn: cursor.open_turn },
4077
+ ...cursor.unattributed === void 0 ? {} : { unattributed: cursor.unattributed },
4078
+ subagents: Object.fromEntries(Object.entries(cursor.subagents).map(([name, entry]) => [name, { ...entry }])),
4079
+ session_observed: cursor.session_observed ?? false,
4080
+ consumed: 0,
4081
+ enqueued: 0
4082
+ };
2751
4083
  }
2752
- function positiveInteger(value) {
2753
- return Number.isSafeInteger(value) && Number(value) > 0;
4084
+ function zeroCounts() {
4085
+ return { input: 0, cached_input: 0, cache_write_input: 0, output: 0 };
4086
+ }
4087
+ function terminalDigest(turn) {
4088
+ return transcriptObjectSha256({ turn: turn.turn_key, messages: turn.message_ids, counts: turn.counts });
4089
+ }
4090
+ function availableTokenValue(counts) {
4091
+ const input = counts.input;
4092
+ const output = counts.output;
4093
+ if (!Number.isSafeInteger(input + output)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4094
+ return createHostTokenObservationValue({
4095
+ input,
4096
+ output,
4097
+ total: input + output,
4098
+ cached_input: counts.cached_input,
4099
+ cache_write_input: counts.cache_write_input
4100
+ }, TOKEN_OBSERVATION_SOURCE);
4101
+ }
4102
+ function addCounts(current, next) {
4103
+ const counts = {
4104
+ input: current.input + next.input,
4105
+ cached_input: current.cached_input + next.cached_input,
4106
+ cache_write_input: current.cache_write_input + next.cache_write_input,
4107
+ output: current.output + next.output
4108
+ };
4109
+ if (![counts.input, counts.cached_input, counts.cache_write_input, counts.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
4110
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4111
+ }
4112
+ return counts;
2754
4113
  }
2755
- function decodeFrame(value, installation, epoch) {
2756
- const json = Buffer.from(JSON.stringify(value));
2757
- const encoded = Buffer.allocUnsafe(json.length + 4);
2758
- encoded.writeUInt32BE(json.length, 0);
2759
- json.copy(encoded, 4);
2760
- const frame = decodeHostProducerFrame(encoded);
2761
- if (frame.producer_id !== "claude" || frame.producer_installation_ref !== installation || frame.producer_epoch > epoch) throw new ClaudeHostError("storage", `Claude producer frame identity is malformed (${refusalFacts([{ name: "producer_id", value: frame.producer_id, expected: "claude" }, { name: "producer_installation_ref", value: frame.producer_installation_ref, expected: installation }, { name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${epoch}` }])}).`);
2762
- return frame;
4114
+ function accumulateUnattributed(current, usage) {
4115
+ const base = current ?? { input: 0, cached_input: 0, cache_write_input: 0, output: 0, message_ids: [], last_observed_at: usage.observed_at };
4116
+ if (base.message_ids.includes(usage.uuid)) return base;
4117
+ if (base.message_ids.length >= CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES) throw new ClaudeHostError("payload_rejected", "Claude transcript turn exceeds its usage message bound.", "size_budget");
4118
+ const counts = addCounts(base, usage);
4119
+ return {
4120
+ ...counts,
4121
+ message_ids: [...base.message_ids, usage.uuid],
4122
+ last_observed_at: usage.observed_at > base.last_observed_at ? usage.observed_at : base.last_observed_at
4123
+ };
2763
4124
  }
2764
- function record5(value) {
2765
- return value !== null && typeof value === "object" && !Array.isArray(value);
4125
+ function foldUnattributed(bucket) {
4126
+ if (bucket === void 0) return void 0;
4127
+ if (![bucket.input, bucket.cached_input, bucket.cache_write_input, bucket.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
4128
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4129
+ }
4130
+ return { message_ids: [...bucket.message_ids], counts: { input: bucket.input, cached_input: bucket.cached_input, cache_write_input: bucket.cache_write_input, output: bucket.output }, last_observed_at: bucket.last_observed_at };
4131
+ }
4132
+ function toUsage(uuid, observedAt, usage) {
4133
+ const input = usageNumber(usage.input_tokens);
4134
+ const cached = usageNumber(usage.cache_read_input_tokens ?? 0);
4135
+ const created = usageNumber(usage.cache_creation_input_tokens ?? 0);
4136
+ const output = usageNumber(usage.output_tokens);
4137
+ if (input === null || cached === null || created === null || output === null) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
4138
+ const total = input + cached + created;
4139
+ if (![total, output].every((value) => Number.isSafeInteger(value) && value >= 0)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4140
+ return { uuid, observed_at: observedAt, input: total, cached_input: cached, cache_write_input: created, output };
4141
+ }
4142
+ function usageNumber(value) {
4143
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
4144
+ }
4145
+ function parseTranscriptLine(line) {
4146
+ try {
4147
+ return JSON.parse(line);
4148
+ } catch {
4149
+ return null;
4150
+ }
4151
+ }
4152
+ function lineIdentity(record5) {
4153
+ if (typeof record5.uuid !== "string" || record5.uuid.length === 0 || record5.uuid.length > 256) return null;
4154
+ if (typeof record5.timestamp !== "string" || !Number.isFinite(Date.parse(record5.timestamp))) return null;
4155
+ return { uuid: record5.uuid, timestamp: record5.timestamp };
4156
+ }
4157
+ function isUserTurnLine(record5) {
4158
+ if (!isTranscriptRecord(record5) || record5.type !== "user") return false;
4159
+ if (!isTranscriptRecord(record5.message) || typeof record5.message.content !== "string") return false;
4160
+ if (record5.isMeta === true && (!isTranscriptRecord(record5.origin) || typeof record5.origin.kind !== "string")) return false;
4161
+ const identity = lineIdentity(record5);
4162
+ if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
4163
+ return true;
4164
+ }
4165
+ function claudeTranscriptScanFailureKind(error) {
4166
+ if (isSymlinkTranscript(error)) return "symlink";
4167
+ if (error instanceof ClaudeHostError) {
4168
+ if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
4169
+ if (error.code === "payload_rejected") return "payload_rejected";
4170
+ if (error.code === "storage") return "read_error";
4171
+ }
4172
+ const code2 = transcriptErrorCode(error);
4173
+ if (code2 === "EFBIG") return "size_budget";
4174
+ if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
4175
+ return "other";
4176
+ }
4177
+ function isAssistantLine(record5) {
4178
+ return isTranscriptRecord(record5) && record5.type === "assistant" && isTranscriptRecord(record5.message);
4179
+ }
4180
+ function isAssistantUsageLine(record5) {
4181
+ if (!isTranscriptRecord(record5)) return false;
4182
+ if (!isAssistantLine(record5) || record5.message.usage === void 0) return false;
4183
+ const identity = lineIdentity(record5);
4184
+ if (identity === null || !isTranscriptRecord(record5.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
4185
+ return true;
4186
+ }
4187
+ function subagentsDirectory(transcriptPath) {
4188
+ const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
4189
+ return join4(stem, "subagents");
4190
+ }
4191
+ function observationRef(event, identity, value) {
4192
+ return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
4193
+ }
4194
+ function submissionRef(kind, turn, digest2, prompt) {
4195
+ return `claude-submission:${transcriptObjectSha256({ kind, turn, digest: digest2, prompt })}`;
4196
+ }
4197
+ async function ownedClaudeTranscriptFile(handle) {
4198
+ const stats = await handle.stat();
4199
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
4200
+ if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
4201
+ if (stats.uid !== uid) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
4202
+ return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
4203
+ }
4204
+ function isNotDirectory(error) {
4205
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
2766
4206
  }
2767
- function code2(error) {
2768
- return record5(error) && typeof error.code === "string" ? error.code : null;
4207
+ function isSymlinkTranscript(error) {
4208
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
4209
+ }
4210
+ function transcriptErrorCode(error) {
4211
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2769
4212
  }
2770
4213
 
2771
4214
  // host/claude/src/claude-producer-runtime.ts
2772
4215
  async function drainClaudeProducerOnce(options) {
2773
- return new ClaudeProducer({ outbox_dir: options.outbox_dir, producer_installation_ref: options.producer_installation_ref, relay: new HostRelayIpcAdmissionPort(options.relay_endpoint, options.timeout_ms), ...options.producer_epoch === void 0 ? {} : { producer_epoch: options.producer_epoch } }).drainOutbox();
4216
+ const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
4217
+ const scan = await new ClaudeTranscriptWatcher({
4218
+ outbox_dir: options.outbox_dir,
4219
+ producer_installation_ref: options.producer_installation_ref,
4220
+ register_current_prompt: (input) => promptPort.register(input),
4221
+ diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("claude", event, failure_kind)
4222
+ }).scanOnce().catch((error) => {
4223
+ writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
4224
+ throw error;
4225
+ });
4226
+ const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir, producer_installation_ref: options.producer_installation_ref, relay: new HostRelayIpcAdmissionPort(options.relay_endpoint, options.timeout_ms), ...options.producer_epoch === void 0 ? {} : { producer_epoch: options.producer_epoch }, ...options.now === void 0 ? {} : { now: options.now } });
4227
+ return { scanned: scan.enqueued, ...await producer.drainOutbox() };
2774
4228
  }
2775
4229
 
2776
4230
  // host/claude/src/claude-producer-service.ts
2777
4231
  var CLAUDE_PRODUCER_CONFIG_SCHEMA = "ceal.claude_producer_config.v1";
4232
+ var CLAUDE_TRANSCRIPTS_ROOT = "claude_transcripts_root";
2778
4233
  var CLAUDE_PRODUCER_DESCRIPTOR = {
2779
4234
  schema: CLAUDE_PRODUCER_CONFIG_SCHEMA,
2780
4235
  producer_id: "claude",
2781
- config: (shared) => shared,
4236
+ extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
4237
+ config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
2782
4238
  runtime_options: (config) => Object.freeze({
2783
4239
  outbox_dir: config.outbox_dir,
2784
4240
  producer_installation_ref: config.producer_installation_ref,
@@ -2789,30 +4245,70 @@ var CLAUDE_PRODUCER_DESCRIPTOR = {
2789
4245
  drain: drainClaudeProducerOnce
2790
4246
  };
2791
4247
 
4248
+ // host/claude/src/claude-visible-output-budget.ts
4249
+ import { readFile as readFile3 } from "node:fs/promises";
4250
+ import { homedir } from "node:os";
4251
+ import { join as join5 } from "node:path";
4252
+ var CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS = 3e4;
4253
+ var CLAUDE_MAX_INLINE_OUTPUT_MAX_CHARS = 128e3;
4254
+ async function resolveClaudeVisibleOutputMaxBytes(environment = process.env, workingDirectory = process.cwd()) {
4255
+ const configured = await projectOrUserInlineLimit(environment, workingDirectory);
4256
+ if (configured !== void 0) return configured;
4257
+ const bashReadback = positiveInteger(environment.BASH_MAX_OUTPUT_LENGTH);
4258
+ return Math.min(bashReadback ?? CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS, CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS);
4259
+ }
4260
+ async function projectOrUserInlineLimit(environment, workingDirectory) {
4261
+ const configHome = environment.CLAUDE_CONFIG_DIR ?? join5(homedir(), ".claude");
4262
+ const userLimit = await settingsInlineLimit(join5(configHome, "settings.json"));
4263
+ const projectLimit = await settingsInlineLimit(join5(workingDirectory, ".claude", "settings.json"));
4264
+ const localLimit = await settingsInlineLimit(join5(workingDirectory, ".claude", "settings.local.json"));
4265
+ const candidate = localLimit ?? projectLimit ?? userLimit;
4266
+ return candidate === void 0 ? void 0 : Math.min(candidate, CLAUDE_MAX_INLINE_OUTPUT_MAX_CHARS);
4267
+ }
4268
+ async function settingsInlineLimit(path) {
4269
+ try {
4270
+ const parsed = JSON.parse(await readFile3(path, "utf8"));
4271
+ if (!isRecord4(parsed)) return void 0;
4272
+ return positiveInteger(parsed.bashOutputMaxChars);
4273
+ } catch {
4274
+ return void 0;
4275
+ }
4276
+ }
4277
+ function positiveInteger(value) {
4278
+ const normalized = typeof value === "string" ? value.trim().replaceAll("_", "") : value;
4279
+ const parsed = typeof normalized === "string" && normalized.length > 0 ? Number(normalized) : normalized;
4280
+ return typeof parsed === "number" && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
4281
+ }
4282
+ function isRecord4(value) {
4283
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4284
+ }
4285
+
2792
4286
  // host/claude/src/claude-invocation-authentication-command.ts
2793
4287
  var CLAUDE_INVOCATION_AUTHENTICATION_DESCRIPTOR = {
2794
4288
  producer_id: "claude",
4289
+ context_activation_wait_max_ms: CLAUDE_CONTEXT_ACTIVATION_WAIT_MAX_MS,
2795
4290
  create_store: (directory) => new ClaudeOutboxStore(directory),
2796
4291
  session_id_env: CLAUDE_SESSION_ID_ENVIRONMENT,
2797
4292
  session_ref: (_hostSourceRef, sessionId) => claudeLocalSessionRef(sessionId),
2798
4293
  config_flag: HOST_PRODUCER_CONFIG_FLAG,
2799
4294
  parse_config_flag: parseHostProducerConfigFlag,
2800
- read_producer_config: (configPath) => readHostProducerConfig(CLAUDE_PRODUCER_DESCRIPTOR, configPath)
4295
+ read_producer_config: (configPath) => readHostProducerConfig(CLAUDE_PRODUCER_DESCRIPTOR, configPath),
4296
+ resolve_visible_output_max_bytes: () => resolveClaudeVisibleOutputMaxBytes()
2801
4297
  };
2802
4298
  function runClaudeInvocationAuthenticationCommand(args, options) {
2803
4299
  return runHostInvocationAuthenticationCommand(CLAUDE_INVOCATION_AUTHENTICATION_DESCRIPTOR, args, options);
2804
4300
  }
2805
4301
 
2806
4302
  // host/client-doctor.ts
2807
- import { createHash as createHash4 } from "node:crypto";
4303
+ import { createHash as createHash5 } from "node:crypto";
2808
4304
  import { execFile } from "node:child_process";
2809
- import { access, open as open4, readFile as readFile3, realpath, stat } from "node:fs/promises";
2810
- import { delimiter, isAbsolute, join as join4 } from "node:path";
4305
+ import { access, open as open5, readFile as readFile4, realpath, stat } from "node:fs/promises";
4306
+ import { delimiter, isAbsolute as isAbsolute2, join as join6 } from "node:path";
2811
4307
  import { promisify } from "node:util";
2812
4308
  var execFileAsync = promisify(execFile);
2813
4309
  var GIT_OID = /^[a-f0-9]{40}$/u;
2814
4310
  var SHA2562 = /^[a-f0-9]{64}$/u;
2815
- var SAFE_REF6 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
4311
+ var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
2816
4312
  var CEAL_ENVIRONMENT_OVERRIDES = ["CEAL_GATEWAY_ORIGIN", "CEAL_GATEWAY_TRANSPORT_ORIGIN", "CEAL_ACCESS_TOKEN", "CEAL_CONTEXT_STORE_PATH"];
2817
4313
  async function doctorClient(options = {}, component) {
2818
4314
  const environment = canonicalClientEnvironment(options.environment ?? process.env);
@@ -2862,8 +4358,8 @@ function probeFailure(probe, command, component, options) {
2862
4358
  const refusal = hostClientProbeRefusal(probe, { component, command, executable_identity: executableIdentity(options) });
2863
4359
  return inspectedFailure(probeCode(refusal.code), refusal.message, refusal.next_action);
2864
4360
  }
2865
- function probeCode(code3) {
2866
- if (code3 === "ceal_execution_failed" || code3 === "ceal_command_refused") return code3;
4361
+ function probeCode(code2) {
4362
+ if (code2 === "ceal_execution_failed" || code2 === "ceal_command_refused") return code2;
2867
4363
  return "ceal_output_unreadable";
2868
4364
  }
2869
4365
  async function inspectCanonicalContext(options, executable, environment, run, component) {
@@ -2872,8 +4368,8 @@ async function inspectCanonicalContext(options, executable, environment, run, co
2872
4368
  if (probe.kind !== "value" || options.expected_instance_ref !== void 0 && probe.value.instance_ref !== options.expected_instance_ref) return inspectedFailure("canonical_context_unavailable", `The ${component} Host cannot select the intended authenticated context from Ceal's canonical user configuration (${refusalFacts([{ name: "probe_kind", value: probe.kind, expected: "value" }, { name: "instance_ref", value: probe.kind === "value" ? probe.value.instance_ref : "unavailable", expected: options.expected_instance_ref ?? "a selected context" }])}).`, `Run \`ceal session adopt --email <email>\` without CEAL context overrides, then restart ${component}.`);
2873
4369
  return { ok: true, value: probe.value };
2874
4370
  }
2875
- function inspectedFailure(code3, message, nextAction) {
2876
- return { ok: false, failure: failure(code3, message, nextAction) };
4371
+ function inspectedFailure(code2, message, nextAction) {
4372
+ return { ok: false, failure: failure(code2, message, nextAction) };
2877
4373
  }
2878
4374
  function canonicalClientEnvironment(input) {
2879
4375
  const environment = { ...input };
@@ -2881,7 +4377,7 @@ function canonicalClientEnvironment(input) {
2881
4377
  return environment;
2882
4378
  }
2883
4379
  async function resolveCealExecutable(command, environment) {
2884
- const candidates = isAbsolute(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0).map((entry) => join4(entry, command));
4380
+ const candidates = isAbsolute2(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0).map((entry) => join6(entry, command));
2885
4381
  for (const candidate of candidates) {
2886
4382
  try {
2887
4383
  await access(candidate);
@@ -2889,7 +4385,7 @@ async function resolveCealExecutable(command, environment) {
2889
4385
  const metadata = await stat(path);
2890
4386
  if (!metadata.isFile()) continue;
2891
4387
  if (await isLauncher(path)) return resolveLauncherBinary(path, environment);
2892
- return { path, sha256: createHash4("sha256").update(await readFile3(path)).digest("hex") };
4388
+ return { path, sha256: createHash5("sha256").update(await readFile4(path)).digest("hex") };
2893
4389
  } catch {
2894
4390
  }
2895
4391
  }
@@ -2897,7 +4393,7 @@ async function resolveCealExecutable(command, environment) {
2897
4393
  }
2898
4394
  async function isLauncher(path) {
2899
4395
  if (/\.(?:bat|cjs|cmd|js|mjs|ps1)$/u.test(path)) return true;
2900
- const handle = await open4(path, "r");
4396
+ const handle = await open5(path, "r");
2901
4397
  try {
2902
4398
  const buffer = Buffer.alloc(128);
2903
4399
  const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
@@ -2915,18 +4411,18 @@ async function resolveLauncherBinary(launcherPath, environment) {
2915
4411
  } catch {
2916
4412
  throw new Error("launcher doctor returned unreadable output");
2917
4413
  }
2918
- if (!isRecord2(parsed) || parsed.schema_version !== "ceal.doctor.v1" || parsed.ok !== true || typeof parsed.binary_path !== "string" || parsed.binary_path.length === 0) throw new Error("launcher doctor did not report a binary path");
4414
+ if (!isRecord5(parsed) || parsed.schema_version !== "ceal.doctor.v1" || parsed.ok !== true || typeof parsed.binary_path !== "string" || parsed.binary_path.length === 0) throw new Error("launcher doctor did not report a binary path");
2919
4415
  const binaryPath = await realpath(parsed.binary_path);
2920
4416
  const metadata = await stat(binaryPath);
2921
4417
  if (!metadata.isFile()) throw new Error("launcher doctor reported a non-file binary path");
2922
- return { path: binaryPath, sha256: createHash4("sha256").update(await readFile3(binaryPath)).digest("hex") };
4418
+ return { path: binaryPath, sha256: createHash5("sha256").update(await readFile4(binaryPath)).digest("hex") };
2923
4419
  }
2924
4420
  async function executeCealCommand(executable, args, environment) {
2925
4421
  try {
2926
4422
  const result = await execFileAsync(executable, [...args], { env: environment, timeout: 15e3, encoding: "utf8" });
2927
4423
  return { exit_code: 0, stdout: result.stdout, stderr: result.stderr };
2928
4424
  } catch (error) {
2929
- if (isRecord2(error)) return { exit_code: typeof error.code === "number" ? error.code : 1, stdout: typeof error.stdout === "string" ? error.stdout : "", stderr: typeof error.stderr === "string" ? error.stderr : "" };
4425
+ if (isRecord5(error)) return { exit_code: typeof error.code === "number" ? error.code : 1, stdout: typeof error.stdout === "string" ? error.stdout : "", stderr: typeof error.stderr === "string" ? error.stderr : "" };
2930
4426
  throw error;
2931
4427
  }
2932
4428
  }
@@ -2937,20 +4433,20 @@ function commandDocument(result) {
2937
4433
  if (result === null) return null;
2938
4434
  try {
2939
4435
  const value = JSON.parse(result.stdout);
2940
- return isRecord2(value) ? value : null;
4436
+ return isRecord5(value) ? value : null;
2941
4437
  } catch {
2942
4438
  return null;
2943
4439
  }
2944
4440
  }
2945
4441
  function selectedContext(value) {
2946
4442
  if (value?.schema_version !== "ceal.access-group.v1" || value.ok !== true || !Array.isArray(value.contexts)) return null;
2947
- const selected = value.contexts.filter((item) => isRecord2(item) && item.selected === true);
4443
+ const selected = value.contexts.filter((item) => isRecord5(item) && item.selected === true);
2948
4444
  if (selected.length !== 1) return null;
2949
4445
  return selectedContextRecord(selected[0]);
2950
4446
  }
2951
4447
  function selectedContextRecord(item) {
2952
- if (!isRecord2(item) || typeof item.name !== "string" || typeof item.instance_ref !== "string") return null;
2953
- if (!SAFE_REF6.test(item.instance_ref)) return null;
4448
+ if (!isRecord5(item) || typeof item.name !== "string" || typeof item.instance_ref !== "string") return null;
4449
+ if (!SAFE_REF5.test(item.instance_ref)) return null;
2954
4450
  return { name: item.name, instance_ref: item.instance_ref };
2955
4451
  }
2956
4452
  function versionIdentity(value) {
@@ -2959,12 +4455,12 @@ function versionIdentity(value) {
2959
4455
  return { version: value.version, source_commit: value.source_commit, source_tree: value.source_tree };
2960
4456
  }
2961
4457
  function validExpectations(options) {
2962
- return (options.expected_sha256 === void 0 || SHA2562.test(options.expected_sha256)) && (options.expected_source_commit === void 0 || GIT_OID.test(options.expected_source_commit)) && (options.expected_instance_ref === void 0 || SAFE_REF6.test(options.expected_instance_ref));
4458
+ return (options.expected_sha256 === void 0 || SHA2562.test(options.expected_sha256)) && (options.expected_source_commit === void 0 || GIT_OID.test(options.expected_source_commit)) && (options.expected_instance_ref === void 0 || SAFE_REF5.test(options.expected_instance_ref));
2963
4459
  }
2964
- function failure(code3, message, next_action) {
2965
- return { ok: false, status: "failure", error: { code: code3, message, next_action } };
4460
+ function failure(code2, message, next_action) {
4461
+ return { ok: false, status: "failure", error: { code: code2, message, next_action } };
2966
4462
  }
2967
- function isRecord2(value) {
4463
+ function isRecord5(value) {
2968
4464
  return value !== null && typeof value === "object" && !Array.isArray(value);
2969
4465
  }
2970
4466