@corca-org/ceal-host-win32-x64 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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"]);
@@ -950,6 +979,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
950
979
  var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
951
980
 
952
981
  // packages/ceal-client-protocol/src/resource-row.ts
982
+ var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
953
983
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
954
984
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
955
985
  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"]);
@@ -973,6 +1003,7 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
973
1003
  "resource_modified"
974
1004
  ]);
975
1005
  var CEAL_ACTIVITY_CONNECTOR_KINDS = vocabulary(["slack", "github", "google-calendar", "google-drive", "notion"]);
1006
+ var CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE = "unsupported_event_kind";
976
1007
  var CEAL_ACTIVITY_GAP_CODES = vocabulary([
977
1008
  "provider_page_budget",
978
1009
  "call_deadline",
@@ -980,7 +1011,7 @@ var CEAL_ACTIVITY_GAP_CODES = vocabulary([
980
1011
  "provider_cursor_unavailable",
981
1012
  "malformed_provider_page",
982
1013
  "visibility_incomplete",
983
- "unsupported_event_kind",
1014
+ CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE,
984
1015
  "direct_messages_excluded"
985
1016
  ]);
986
1017
  var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
@@ -1032,6 +1063,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1032
1063
  next: { required: true, timestamp: false },
1033
1064
  kind: { required: true, timestamp: false },
1034
1065
  matched_kinds: { required: false, timestamp: false },
1066
+ title_match: { required: false, timestamp: false },
1035
1067
  title: { required: true, timestamp: false },
1036
1068
  container_label: { required: true, timestamp: false },
1037
1069
  container_ref: { required: false, timestamp: false },
@@ -1045,6 +1077,8 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1045
1077
  modified_at: { required: false, timestamp: true },
1046
1078
  modified_at_local: { required: false, timestamp: true },
1047
1079
  viewed_by_me_at: { required: false, timestamp: true },
1080
+ own_ceal_write: { required: false, timestamp: false },
1081
+ owned_by_me: { required: false, timestamp: false },
1048
1082
  visibility: { required: false, timestamp: false },
1049
1083
  start: { required: false, timestamp: false },
1050
1084
  end: { required: false, timestamp: false },
@@ -1078,6 +1112,15 @@ function cealExcludedTargetCoverageJsonSchema() {
1078
1112
  };
1079
1113
  }
1080
1114
 
1115
+ // packages/ceal-client-protocol/src/gateway-write-identity.ts
1116
+ var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1117
+ var CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN = /^gateway-write-request:[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/u;
1118
+ var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1119
+ replay_identity: "idempotency_claim_sha256",
1120
+ lookup_handle: "write_request_sha256",
1121
+ collision_evidence: "normalized_mutation_sha256"
1122
+ });
1123
+
1081
1124
  // packages/ceal-client-protocol/src/write-safety-finding.ts
1082
1125
  var CEAL_WRITE_SAFETY_FINDING_STATUSES = vocabulary(["warning", "unchecked"]);
1083
1126
  var CEAL_WRITE_SAFETY_AUDIENCES = vocabulary(["workspace", "person", "members", "unknown"]);
@@ -1105,7 +1148,11 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
1105
1148
  "refresh_revoked",
1106
1149
  "authority_replaced",
1107
1150
  "refresh_recovery_unavailable",
1108
- "refresh_recovery_expired"
1151
+ "refresh_recovery_expired",
1152
+ // The serving gate answers this while the Gateway is still starting and the
1153
+ // refresh was never attempted; the same attempt stays retryable. It is never
1154
+ // stored by the session owner, only answered at the serving edge.
1155
+ "gateway_starting"
1109
1156
  ]);
1110
1157
 
1111
1158
  // packages/ceal-client-protocol/src/request-validation-primitives.ts
@@ -1138,7 +1185,7 @@ var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
1138
1185
  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"]);
1139
1186
  var CEAL_REQUEST_JSON_SCHEMA = {
1140
1187
  type: "object",
1141
- maxProperties: 10,
1188
+ maxProperties: 11,
1142
1189
  additionalProperties: false,
1143
1190
  properties: {
1144
1191
  schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
@@ -1162,7 +1209,8 @@ var CEAL_REQUEST_JSON_SCHEMA = {
1162
1209
  host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1163
1210
  host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1164
1211
  host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1165
- requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." }
1212
+ requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
1213
+ 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." }
1166
1214
  },
1167
1215
  required: CEAL_REQUEST_KEYS
1168
1216
  };
@@ -1221,6 +1269,7 @@ var REQUEST_EXECUTION_PREPARE_REFUSAL_PROPERTIES = {
1221
1269
  retryable: { type: "boolean" },
1222
1270
  source: { type: "string", enum: ["provider", "gateway"] },
1223
1271
  retry_after_ms: { type: "integer", minimum: 0, maximum: CEAL_RETRY_AFTER_MAX_MS },
1272
+ write_request_ref: { type: "string", pattern: CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN.source, maxLength: 128 },
1224
1273
  excluded_targets: cealExcludedTargetCoverageJsonSchema(),
1225
1274
  repaired_input: {},
1226
1275
  unrepaired_pointers: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 256 } },
@@ -1279,12 +1328,55 @@ function isCealHostInputKind(value) {
1279
1328
  return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
1280
1329
  }
1281
1330
  var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
1331
+ var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
1332
+ var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
1333
+ function cealHostObservationRef(producer, digest2) {
1334
+ if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(digest2)) invalid("Ceal Host observation reference parts are invalid.");
1335
+ return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${digest2}`;
1336
+ }
1337
+ var HOST_TOKEN_OBSERVATION_BREAKDOWN_KEYS = vocabulary(["cached_input", "cache_write_input", "reasoning_output"]);
1338
+ function createHostTokenObservationValue(counts, source) {
1339
+ const core = validTokenCore(counts);
1340
+ if (typeof source !== "string" || source.length === 0 || source.length > 256) invalid("Ceal Host token observation source is invalid.");
1341
+ const value = { status: "available", unit: "tokens", input: core.input, output: core.output, total: core.total, source };
1342
+ for (const breakdown of validTokenBreakdowns(counts)) value[breakdown.key] = breakdown.tokens;
1343
+ assertHostObservationValue(value, "token");
1344
+ return value;
1345
+ }
1346
+ function validTokenCore(counts) {
1347
+ if (!isOperationRecord(counts) || !safeCount(counts.input) || !safeCount(counts.output) || !safeCount(counts.total)) invalid("Ceal Host token observation counts are invalid.");
1348
+ if (counts.total !== counts.input + counts.output) invalid("Ceal Host token observation counts are invalid.");
1349
+ return { input: counts.input, output: counts.output, total: counts.total };
1350
+ }
1351
+ function validTokenBreakdowns(counts) {
1352
+ const breakdowns = [
1353
+ { key: "cached_input", tokens: counts.cached_input },
1354
+ { key: "cache_write_input", tokens: counts.cache_write_input },
1355
+ { key: "reasoning_output", tokens: counts.reasoning_output }
1356
+ ];
1357
+ const stated = breakdowns.filter((breakdown) => breakdown.tokens !== void 0).map((breakdown) => ({ key: breakdown.key, tokens: Number(breakdown.tokens) }));
1358
+ for (const breakdown of stated) {
1359
+ if (!safeCount(breakdown.tokens)) invalid("Ceal Host token observation counts are invalid.");
1360
+ }
1361
+ const cached = counts.cached_input ?? 0;
1362
+ const created = counts.cache_write_input ?? 0;
1363
+ const reasoning = counts.reasoning_output ?? 0;
1364
+ if (cached + created > counts.input || reasoning > counts.output) invalid("Ceal Host token observation counts are invalid.");
1365
+ return stated;
1366
+ }
1367
+ function hostObservationNotRecorded(reason) {
1368
+ if (typeof reason !== "string" || reason.length === 0 || reason.length > 256) invalid("Ceal Host observation gap reason is invalid.");
1369
+ return { status: "not_recorded", reason };
1370
+ }
1371
+ function safeCount(value) {
1372
+ return Number.isSafeInteger(value) && Number(value) >= 0;
1373
+ }
1282
1374
  function validateCealHostObservationDraft(value) {
1283
- const record6 = operationRecord(value, "Ceal Host observation draft is invalid.");
1284
- requirePrefixedRef(record6.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1285
- requireClosedValue(record6.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1286
- requireIsoTimestamp(record6.observed_at, "Ceal Host observation time is invalid.");
1287
- assertHostObservationValue(record6.value, record6.observation_kind);
1375
+ const record5 = operationRecord(value, "Ceal Host observation draft is invalid.");
1376
+ requirePrefixedRef(record5.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1377
+ requireClosedValue(record5.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1378
+ requireIsoTimestamp(record5.observed_at, "Ceal Host observation time is invalid.");
1379
+ assertHostObservationValue(record5.value, record5.observation_kind);
1288
1380
  }
1289
1381
  var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
1290
1382
  var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "cost", "timing"]);
@@ -1314,6 +1406,10 @@ function inspectHostKeys(value) {
1314
1406
  // packages/ceal-client-protocol/src/host-prompt-ref.ts
1315
1407
  var CEAL_HOST_PROMPT_DIGEST_PATTERN = "^[a-f0-9]{64}$";
1316
1408
  var PROMPT_DIGEST = new RegExp(CEAL_HOST_PROMPT_DIGEST_PATTERN, "u");
1409
+ function hostProducerPromptRef(promptSha256) {
1410
+ if (!PROMPT_DIGEST.test(promptSha256)) throw new TypeError("Canonical Host prompt digest is invalid.");
1411
+ return `ceal-host-prompt:${promptSha256}`;
1412
+ }
1317
1413
 
1318
1414
  // packages/ceal-client-protocol/src/request-effect-approval.ts
1319
1415
  var CEAL_EFFECT_APPROVAL_DECISION_CHECK_PHRASES = Object.freeze([
@@ -1427,6 +1523,55 @@ var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
1427
1523
  "host_input_mismatch",
1428
1524
  "host_prompt_mismatch"
1429
1525
  ]);
1526
+ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
1527
+ "invalid_arguments",
1528
+ "options_invalid",
1529
+ "session_unavailable",
1530
+ "context_unavailable",
1531
+ "context_replaced",
1532
+ "no_binding",
1533
+ "context_stale",
1534
+ "context_expired",
1535
+ "host_source_refused",
1536
+ "host_credential_refused",
1537
+ "gateway_unavailable",
1538
+ "gateway_rate_limited",
1539
+ "gateway_refused",
1540
+ "gateway_response_invalid",
1541
+ "producer_frame_invalid",
1542
+ "relay_permission_denied",
1543
+ "relay_unavailable",
1544
+ "transient_retryable",
1545
+ "mint_failed",
1546
+ "relay_failed",
1547
+ "host_refusal_invalid"
1548
+ ]);
1549
+ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1550
+ invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1551
+ options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1552
+ session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1553
+ context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "refresh_host_invocation_context", remedy: "the Host has no invocation context from the current transcript turn; send a new message to start a fresh Host turn, then retry this command on that turn (run `ceal-host install` only if the Host hook registration is missing or drifted)" }),
1554
+ context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1555
+ no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1556
+ context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1557
+ context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "refresh_host_invocation_context" }),
1558
+ host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
1559
+ host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
1560
+ gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
1561
+ gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
1562
+ gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
1563
+ gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
1564
+ 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" }),
1565
+ 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" }),
1566
+ relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
1567
+ transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
1568
+ mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
1569
+ relay_failed: Object.freeze({ classification: "host_relay_failed", next_action: "answer_with_gap" }),
1570
+ host_refusal_invalid: Object.freeze({ classification: "host_refusal_invalid", next_action: "answer_with_gap" })
1571
+ });
1572
+ function isCealHostAuthenticationCause(value) {
1573
+ return isVocabularyWord(CEAL_HOST_AUTHENTICATION_CAUSES, value);
1574
+ }
1430
1575
  function decodeCealHostInvocationAuthenticationRequest(value) {
1431
1576
  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)) {
1432
1577
  throw new TypeError("The Host invocation authentication request is invalid.");
@@ -1448,11 +1593,38 @@ function exactKeys(value, expected) {
1448
1593
  return keys.length === expected.length && expected.every((key) => Object.hasOwn(value, key));
1449
1594
  }
1450
1595
 
1596
+ // packages/ceal-client-protocol/src/host-service-diagnostic.ts
1597
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
1598
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
1599
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1600
+ "hook_invoked",
1601
+ "registration_written",
1602
+ "hook_failed",
1603
+ "watcher_turn_observed",
1604
+ "current_prompt_registered",
1605
+ "current_prompt_registration_failed",
1606
+ "current_prompt_capacity_exhausted",
1607
+ "activation_recorded",
1608
+ "activation_failed",
1609
+ "watcher_scan_failed",
1610
+ "activation_wait_skipped",
1611
+ "activation_wait_engaged",
1612
+ "activation_wait_completed",
1613
+ "activation_wait_exhausted"
1614
+ ]);
1615
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_FAILURE_KINDS = vocabulary([
1616
+ "malformed_line",
1617
+ "other",
1618
+ "payload_rejected",
1619
+ "read_error",
1620
+ "size_budget",
1621
+ "symlink"
1622
+ ]);
1623
+
1451
1624
  // packages/ceal-client-protocol/src/client-result-materialization.ts
1452
1625
  var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
1453
- var CEAL_MODEL_VISIBLE_COMMAND_OUTPUT_MAX_BYTES = 32 * 1024;
1454
- var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "read_argv"]);
1455
- var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "local_result_root", "continue_argv", "model_disclosure"]);
1626
+ var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
1627
+ var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
1456
1628
 
1457
1629
  // packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
1458
1630
  var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -1482,14 +1654,6 @@ var CEAL_GATEWAY_ANNOUNCEMENT_POLICY_NON_CLAIMS = vocabulary([
1482
1654
  "target_specific_scope_not_declared"
1483
1655
  ]);
1484
1656
 
1485
- // packages/ceal-client-protocol/src/gateway-write-identity.ts
1486
- var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1487
- var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1488
- replay_identity: "idempotency_claim_sha256",
1489
- lookup_handle: "write_request_sha256",
1490
- collision_evidence: "normalized_mutation_sha256"
1491
- });
1492
-
1493
1657
  // packages/ceal-client-protocol/src/gateway-scoped-identity-projection-validation.ts
1494
1658
  var PROJECTION_REVISION_KEYS = Object.freeze(["graph_revision", "subject_key_revision", "projection_revision"]);
1495
1659
 
@@ -1502,6 +1666,18 @@ var CEAL_SUPPORTED_GATEWAY_PROTOCOL_RANGE = Object.freeze({
1502
1666
  // packages/ceal-client-protocol/src/admin-projection-vocabulary.ts
1503
1667
  var ADMIN_PROJECTION_STATES = vocabulary(["ready", "empty", "partial", "unknown"]);
1504
1668
  var ADMIN_VIEW_KINDS = vocabulary(["request_detail", "resource_access_timeline", "weekly_usage"]);
1669
+ var ADMIN_RECORD_PROJECTION_KINDS = vocabulary([
1670
+ "request_list",
1671
+ "usage_series",
1672
+ "connector_health",
1673
+ "person_connections",
1674
+ "client_sessions",
1675
+ "knowledge_base",
1676
+ "change_history",
1677
+ "approval_queue",
1678
+ "access_group_list",
1679
+ "capability_catalogue"
1680
+ ]);
1505
1681
  var ADMIN_GAP_KINDS = vocabulary(["unknown", "unavailable", "partial"]);
1506
1682
  var ADMIN_UNAVAILABLE_REASONS = vocabulary([
1507
1683
  "not_recorded",
@@ -1525,7 +1701,11 @@ var ADMIN_SOURCE_OWNERS = vocabulary([
1525
1701
  "person",
1526
1702
  "resource",
1527
1703
  "prompt",
1528
- "usage"
1704
+ "usage",
1705
+ "connector",
1706
+ "session",
1707
+ "knowledge",
1708
+ "policy"
1529
1709
  ]);
1530
1710
  var ADMIN_COMPLETENESS_STATES = vocabulary(["complete", "partial", "unknown"]);
1531
1711
  var ADMIN_OPERATION_OUTCOMES = vocabulary(["completed", "failed", "denied", "cancelled", "unknown"]);
@@ -1554,12 +1734,12 @@ var FAILURE_RESPONSE_KEYS = Object.freeze(["error", "ok", "proof_ref_or_unavaila
1554
1734
  var MAX_DETAIL_CHARACTERS = 240;
1555
1735
  var HOST_OWNED_CODE = /^[a-z][a-z0-9]*(?:_[a-z0-9]+){0,7}$/u;
1556
1736
  function hostTerminalFailure(error, component) {
1557
- const code3 = errorCode(error);
1737
+ const code2 = errorCode(error);
1558
1738
  const detail = failureDetail(error);
1559
- 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." };
1560
- 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." };
1561
- 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." };
1562
- 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.` };
1739
+ 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." };
1740
+ 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." };
1741
+ 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." };
1742
+ 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.` };
1563
1743
  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.` };
1564
1744
  }
1565
1745
  var DOCTOR_FLAGS = Object.freeze({
@@ -1632,6 +1812,9 @@ function hostClientProbeRefusal(probe, subject) {
1632
1812
  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.` };
1633
1813
  }
1634
1814
 
1815
+ // host/invocation-authentication-command.ts
1816
+ import { performance } from "node:perf_hooks";
1817
+
1635
1818
  // host/relay/src/producer-frame.ts
1636
1819
  var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
1637
1820
  var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
@@ -1643,11 +1826,20 @@ var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
1643
1826
  function isHostProducerFrameKind(value) {
1644
1827
  return typeof value === "string" && FRAME_KINDS.has(value);
1645
1828
  }
1829
+ var HOST_PRODUCER_FRAME_ERROR_CODES = [
1830
+ "invalid_frame_encoding",
1831
+ "invalid_frame_length",
1832
+ "frame_truncated",
1833
+ "unsupported_frame_schema",
1834
+ "invalid_frame_shape",
1835
+ "invalid_producer_identity",
1836
+ "frame_too_large"
1837
+ ];
1646
1838
  var HostProducerFrameError = class extends Error {
1647
1839
  code;
1648
- constructor(code3, message = code3) {
1840
+ constructor(code2, message = code2) {
1649
1841
  super(message);
1650
- this.code = code3;
1842
+ this.code = code2;
1651
1843
  }
1652
1844
  };
1653
1845
  function encodeHostProducerFrame(frame) {
@@ -1677,6 +1869,9 @@ function decodeHostProducerFrame(input) {
1677
1869
  validateFrame(value);
1678
1870
  return value;
1679
1871
  }
1872
+ function isHostSafeRef(value) {
1873
+ return safeRef(value);
1874
+ }
1680
1875
  function validateFrame(value) {
1681
1876
  if (!record(value)) throw new HostProducerFrameError("invalid_frame_shape");
1682
1877
  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)}`);
@@ -1764,6 +1959,15 @@ function exactOptional(value, required, optional) {
1764
1959
  return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
1765
1960
  }
1766
1961
 
1962
+ // host/relay/src/host-producer-core.ts
1963
+ import { createHash as createHash2 } from "node:crypto";
1964
+ import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
1965
+ import { mkdir as mkdir2, open as open2, readFile, rename as rename2, writeFile } from "node:fs/promises";
1966
+ import { join as join2 } from "node:path";
1967
+
1968
+ // host/relay/src/relay-ipc.ts
1969
+ import { createConnection, createServer } from "node:net";
1970
+
1767
1971
  // host/relay/src/durable-atomic-replacement.ts
1768
1972
  import { O_RDONLY } from "node:constants";
1769
1973
  import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
@@ -1852,20 +2056,24 @@ function errorCode2(error) {
1852
2056
  // host/relay/src/relay-request-binding-store.ts
1853
2057
  var MAX_STATE_BYTES = 8 * 1024 * 1024;
1854
2058
 
1855
- // host/relay/src/relay-quarantine-store.ts
1856
- var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
1857
-
1858
- // host/relay/src/relay-admission-store.ts
1859
- var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
1860
- var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
1861
- var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
1862
-
1863
- // host/relay/src/relay-ipc.ts
1864
- import { createConnection, createServer } from "node:net";
1865
-
1866
2059
  // host/relay/src/current-prompt-custody.ts
1867
2060
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
1868
2061
 
2062
+ // host/service-log.ts
2063
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
2064
+ const line = `${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {}, ...event === "watcher_scan_failed" && cause !== void 0 ? { cause } : {} })}
2065
+ `;
2066
+ try {
2067
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
2068
+ } catch {
2069
+ }
2070
+ }
2071
+ function defaultHostServiceDiagnosticSink(line) {
2072
+ process.stderr.write(line);
2073
+ }
2074
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2075
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2076
+
1869
2077
  // host/relay/src/relay-ipc.ts
1870
2078
  var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
1871
2079
  "schema_version",
@@ -1885,6 +2093,9 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
1885
2093
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
1886
2094
  var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v1";
1887
2095
  var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v1";
2096
+ var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2097
+ var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
2098
+ var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
1888
2099
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
1889
2100
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
1890
2101
  var HOST_RELAY_QUERY_RESPONSE_MAX_BYTES = 8192;
@@ -1896,6 +2107,8 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
1896
2107
  "gateway_unavailable",
1897
2108
  "gateway_refused",
1898
2109
  "gateway_response_invalid",
2110
+ "gateway_rate_limited",
2111
+ "producer_frame_invalid",
1899
2112
  "deadline",
1900
2113
  "cancelled",
1901
2114
  "mint_failed",
@@ -1909,6 +2122,17 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
1909
2122
  "host_input_mismatch",
1910
2123
  "host_prompt_mismatch"
1911
2124
  ]);
2125
+ var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
2126
+ "context_stale",
2127
+ "context_expired",
2128
+ "host_source_refused",
2129
+ "host_credential_refused",
2130
+ "gateway_unavailable",
2131
+ "gateway_rate_limited",
2132
+ "gateway_refused",
2133
+ "gateway_response_invalid",
2134
+ "producer_frame_invalid"
2135
+ ]);
1912
2136
  var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
1913
2137
  var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
1914
2138
  "permission_denied",
@@ -1924,12 +2148,18 @@ var HostRelayIpcError = class extends Error {
1924
2148
  code;
1925
2149
  /** Set only when the client-side socket exchange itself failed. */
1926
2150
  failure;
1927
- constructor(code3, message = code3, failure2) {
2151
+ constructor(code2, message = code2, failure2) {
1928
2152
  super(message);
1929
- this.code = code3;
2153
+ this.code = code2;
1930
2154
  this.failure = failure2;
1931
2155
  }
1932
2156
  };
2157
+ var HostRelayCurrentPromptCapacityRefusal = class extends Error {
2158
+ constructor() {
2159
+ super("current_prompt_capacity_exhausted");
2160
+ this.name = "HostRelayCurrentPromptCapacityRefusal";
2161
+ }
2162
+ };
1933
2163
  function hostRelayExchangeFailure(error) {
1934
2164
  return error instanceof HostRelayIpcError ? error.failure : void 0;
1935
2165
  }
@@ -1968,6 +2198,25 @@ var HostRelayIpcAdmissionPort = class {
1968
2198
  return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
1969
2199
  }
1970
2200
  };
2201
+ var HostRelayCurrentPromptPort = class {
2202
+ endpoint;
2203
+ timeout_ms;
2204
+ constructor(endpoint, timeoutMs = 5e3) {
2205
+ this.endpoint = endpoint;
2206
+ this.timeout_ms = timeoutMs;
2207
+ }
2208
+ async register(input) {
2209
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2210
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2211
+ if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
2212
+ if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2213
+ }
2214
+ async release(identity) {
2215
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
2216
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2217
+ if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
2218
+ }
2219
+ };
1971
2220
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
1972
2221
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
1973
2222
  }
@@ -2055,17 +2304,26 @@ function decodeInvocationContextResponseBuffer(buffer) {
2055
2304
  if (!validInvocationContextResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay invocation context response is invalid.");
2056
2305
  return value;
2057
2306
  }
2307
+ function decodeCurrentPromptResponseBuffer(buffer) {
2308
+ const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2309
+ if (value === null) return null;
2310
+ if (!record2(value) || !exactKeys3(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "capacity_exhausted" && value.status !== "current_prompt_unavailable" && value.status !== "released") throw new HostRelayIpcError("relay_unavailable", "Host Relay current-prompt response is invalid.", "response_invalid");
2311
+ return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2312
+ }
2058
2313
  function encodeInvocationContextQuery(query) {
2059
2314
  const value = { schema_version: HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA, ...query };
2060
2315
  if (!validInvocationContextQueryRequest(value)) throw new HostRelayIpcError("invalid_frame_shape");
2061
2316
  return encodeLengthPrefixed(value);
2062
2317
  }
2063
2318
  function validInvocationContextQueryRequest(value) {
2064
- 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);
2319
+ 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));
2065
2320
  }
2066
2321
  function validQueryIdentity(value, schema) {
2067
2322
  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);
2068
2323
  }
2324
+ function validVisibleOutputBudget(value) {
2325
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES;
2326
+ }
2069
2327
  function validInvocationContextResponse(value) {
2070
2328
  if (!record2(value) || value.schema_version !== HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA) return false;
2071
2329
  if (value.status === "no_binding") return Object.keys(value).length === 2;
@@ -2097,8 +2355,9 @@ function encodeLengthPrefixed(value) {
2097
2355
  body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
2098
2356
  return output;
2099
2357
  }
2100
- function exactKeys3(value, keys) {
2101
- return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
2358
+ function exactKeys3(value, required, optional = []) {
2359
+ const keys = Object.keys(value);
2360
+ return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2102
2361
  }
2103
2362
  function safeRef2(value) {
2104
2363
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
@@ -2107,15 +2366,376 @@ function errorCode3(error) {
2107
2366
  return record2(error) && typeof error.code === "string" ? error.code : null;
2108
2367
  }
2109
2368
 
2369
+ // host/relay/src/host-producer-core.ts
2370
+ var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
2371
+ function isHostTranscriptCursorReason(value) {
2372
+ return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2373
+ }
2374
+ var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2375
+ var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
2376
+ function transcriptScanFailureKind(error, adapterFailureKind) {
2377
+ if (transcriptErrorCode(error) === "ELOOP") return "symlink";
2378
+ const adapter = adapterFailureKind(error);
2379
+ if (adapter !== void 0) return adapter;
2380
+ const code2 = transcriptErrorCode(error);
2381
+ if (code2 === "EFBIG") return "size_budget";
2382
+ if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
2383
+ return "other";
2384
+ }
2385
+ async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
2386
+ try {
2387
+ await register();
2388
+ diagnostic("current_prompt_registered");
2389
+ return true;
2390
+ } catch (error) {
2391
+ if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
2392
+ throw error;
2393
+ }
2394
+ }
2395
+ function transcriptErrorCode(error) {
2396
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2397
+ }
2398
+ var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2399
+ async function readTranscriptCompleteChunk(handle, offset, size) {
2400
+ const available = Math.max(0, size - offset);
2401
+ if (available === 0) return "empty";
2402
+ const chunks = [];
2403
+ let position = offset;
2404
+ let total = 0;
2405
+ let lastNewline = -1;
2406
+ while (position < size) {
2407
+ const buffer = Buffer.alloc(Math.min(HOST_TRANSCRIPT_READ_CHUNK_BYTES, size - position));
2408
+ const read = await handle.read(buffer, 0, buffer.length, position);
2409
+ if (read.bytesRead === 0) break;
2410
+ const chunk = buffer.subarray(0, read.bytesRead);
2411
+ chunks.push(chunk);
2412
+ const newline = chunk.lastIndexOf(10);
2413
+ if (newline >= 0) lastNewline = total + newline;
2414
+ total += chunk.length;
2415
+ position += chunk.length;
2416
+ if (lastNewline >= 0) break;
2417
+ }
2418
+ if (lastNewline < 0) return "partial";
2419
+ return Buffer.concat(chunks, total).subarray(0, lastNewline + 1);
2420
+ }
2421
+ async function readTranscriptAnchor(handle, offset, anchorBytes) {
2422
+ const start = Math.max(0, offset - anchorBytes);
2423
+ const anchor = Buffer.alloc(offset - start);
2424
+ if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, start);
2425
+ return { start, sha256: transcriptSha256(anchor) };
2426
+ }
2427
+ async function transcriptAnchorMatches(handle, cursor) {
2428
+ const buffer = Buffer.alloc(cursor.offset - cursor.anchor_start);
2429
+ if (buffer.length > 0) await handle.read(buffer, 0, buffer.length, cursor.anchor_start);
2430
+ return transcriptSha256(buffer) === cursor.anchor_sha256;
2431
+ }
2432
+ function transcriptCursorIdentityReason(cursor, transcriptPath, device, inode) {
2433
+ if (cursor.device !== device || cursor.inode !== inode || cursor.transcript_path !== transcriptPath) return "transcript_replaced";
2434
+ return null;
2435
+ }
2436
+ async function transcriptCursorBoundsReason(handle, cursor, size) {
2437
+ if (size < cursor.offset) return "transcript_truncated";
2438
+ if (cursor.offset > 0 && !await transcriptAnchorMatches(handle, cursor)) return "transcript_rewritten";
2439
+ return null;
2440
+ }
2441
+ function transcriptSha256(value) {
2442
+ return createHash2("sha256").update(value).digest("hex");
2443
+ }
2444
+ function transcriptObjectSha256(value) {
2445
+ return createHash2("sha256").update(JSON.stringify(value), "utf8").digest("hex");
2446
+ }
2447
+ function isTranscriptRecord(value) {
2448
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2449
+ }
2450
+ function isMissingTranscriptError(error) {
2451
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2452
+ }
2453
+ function transcriptTimestamp(now, fail) {
2454
+ const value = now();
2455
+ if (!Number.isFinite(value.getTime())) throw fail();
2456
+ return value.toISOString();
2457
+ }
2458
+ function transcriptExpiration(now, retentionMs, fail) {
2459
+ return new Date(Date.parse(transcriptTimestamp(now, fail)) + retentionMs).toISOString();
2460
+ }
2461
+ function createTranscriptWatcherBase(options, owner) {
2462
+ if (typeof options.outbox_dir !== "string" || options.outbox_dir.length === 0) throw owner.invalid_outbox();
2463
+ return {
2464
+ outboxRetentionMs: options.retention_ms ?? owner.default_retention_ms,
2465
+ now: options.now ?? (() => /* @__PURE__ */ new Date()),
2466
+ log: options.log ?? ((line) => {
2467
+ process.stderr.write(`${line}
2468
+ `);
2469
+ }),
2470
+ producerInstallationRef: options.producer_installation_ref
2471
+ };
2472
+ }
2473
+ async function scanTranscriptRegistrations(registrations, scanOne) {
2474
+ let records = 0;
2475
+ let enqueued = 0;
2476
+ let quarantined = 0;
2477
+ for (const registration of registrations) {
2478
+ const result = await scanOne(registration);
2479
+ records += result.records;
2480
+ enqueued += result.enqueued;
2481
+ if (result.quarantined) quarantined += 1;
2482
+ }
2483
+ return { registrations: registrations.length, records, enqueued, quarantined };
2484
+ }
2485
+ function formatRetiredTranscriptLog(producerLabel, transcriptPath) {
2486
+ return `${producerLabel} retired a transcript registration: transcript_path=${JSON.stringify(transcriptPath)}, reason=${TRANSCRIPT_MISSING_REASON}.`;
2487
+ }
2488
+ var HostTranscriptWatcher = class {
2489
+ outboxRetentionMs;
2490
+ now;
2491
+ log;
2492
+ producerInstallationRef;
2493
+ clockFailure;
2494
+ constructor(options, owner) {
2495
+ const base = createTranscriptWatcherBase(options, owner);
2496
+ this.outboxRetentionMs = base.outboxRetentionMs;
2497
+ this.now = base.now;
2498
+ this.log = base.log;
2499
+ this.producerInstallationRef = base.producerInstallationRef;
2500
+ this.clockFailure = owner.clock_failure;
2501
+ }
2502
+ async scanOnce() {
2503
+ return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2504
+ }
2505
+ async scanRegistration(registration) {
2506
+ try {
2507
+ return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2508
+ missing: () => this.retireMissing(registration),
2509
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2510
+ });
2511
+ } catch (error) {
2512
+ if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
2513
+ throw error;
2514
+ }
2515
+ }
2516
+ async openTranscript(registration, scan, handlers) {
2517
+ let handle;
2518
+ try {
2519
+ handle = await open2(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2520
+ return await scan(handle);
2521
+ } catch (error) {
2522
+ if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2523
+ if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
2524
+ return handlers.failed(handle, error);
2525
+ } finally {
2526
+ await handle?.close();
2527
+ }
2528
+ }
2529
+ /**
2530
+ * A transcript the Host deleted never comes back, so retrying its
2531
+ * registration once per reconcile interval is a failure with no end.
2532
+ * Retire it at the scan that finds it missing and say so once. Only
2533
+ * ENOENT means gone; a permission or busy failure is about this moment,
2534
+ * not about the file, and keeps the registration for the next scan.
2535
+ */
2536
+ async retireMissingTranscript(transcriptPath, producerLabel, retire) {
2537
+ await retire();
2538
+ this.log(formatRetiredTranscriptLog(producerLabel, transcriptPath));
2539
+ return { records: 0, enqueued: 0, quarantined: false };
2540
+ }
2541
+ stamp() {
2542
+ return transcriptTimestamp(this.now, this.clockFailure);
2543
+ }
2544
+ expire(retentionMs) {
2545
+ return transcriptExpiration(this.now, retentionMs, this.clockFailure);
2546
+ }
2547
+ sha(value) {
2548
+ return transcriptSha256(value);
2549
+ }
2550
+ objectSha(value) {
2551
+ return transcriptObjectSha256(value);
2552
+ }
2553
+ isRecord(value) {
2554
+ return isTranscriptRecord(value);
2555
+ }
2556
+ readChunk(handle, offset, size) {
2557
+ return readTranscriptCompleteChunk(handle, offset, size);
2558
+ }
2559
+ readAnchor(handle, offset, anchorBytes) {
2560
+ return readTranscriptAnchor(handle, offset, anchorBytes);
2561
+ }
2562
+ cursorIdentityReason(cursor, transcriptPath, device, inode) {
2563
+ return transcriptCursorIdentityReason(cursor, transcriptPath, device, inode);
2564
+ }
2565
+ cursorBoundsReason(handle, cursor, size) {
2566
+ return transcriptCursorBoundsReason(handle, cursor, size);
2567
+ }
2568
+ };
2569
+ var HOST_PRODUCER_STATE_IDENTITY = {
2570
+ codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
2571
+ claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
2572
+ };
2573
+ var HostProducerDrainCore = class {
2574
+ host;
2575
+ relay;
2576
+ statePath;
2577
+ stateSchema;
2578
+ installationRef;
2579
+ epoch;
2580
+ now;
2581
+ tail = Promise.resolve();
2582
+ constructor(host, runtime) {
2583
+ if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
2584
+ const epoch = runtime.producer_epoch ?? 1;
2585
+ if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
2586
+ this.host = host;
2587
+ this.relay = runtime.relay;
2588
+ this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
2589
+ this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
2590
+ this.installationRef = runtime.producer_installation_ref;
2591
+ this.epoch = epoch;
2592
+ this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
2593
+ }
2594
+ drainOutbox() {
2595
+ const run = this.tail.then(() => this.drainLocked());
2596
+ this.tail = run.then(() => void 0, () => void 0);
2597
+ return run;
2598
+ }
2599
+ async drainLocked() {
2600
+ const read = await this.host.listEnvelopes();
2601
+ let admitted = 0;
2602
+ let expired = 0;
2603
+ let retained = 0;
2604
+ for (const envelope of read.entries) {
2605
+ const reserved = await this.hasReservation(this.host.submissionRefOf(envelope));
2606
+ if (!reserved && Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) {
2607
+ await this.host.removeEnvelope(envelope);
2608
+ expired += 1;
2609
+ continue;
2610
+ }
2611
+ const frames = await this.reserve(envelope);
2612
+ let complete = true;
2613
+ for (const frame of frames) {
2614
+ const outcome = await this.relay.admit(frame);
2615
+ if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2616
+ complete = false;
2617
+ break;
2618
+ }
2619
+ }
2620
+ if (!complete) {
2621
+ retained += 1;
2622
+ break;
2623
+ }
2624
+ await this.host.removeEnvelope(envelope);
2625
+ await this.release(this.host.submissionRefOf(envelope));
2626
+ admitted += 1;
2627
+ }
2628
+ return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
2629
+ }
2630
+ async hasReservation(submissionRef2) {
2631
+ return (await this.readState()).pending[submissionRef2] !== void 0;
2632
+ }
2633
+ async reserve(envelope) {
2634
+ const state = await this.readState();
2635
+ const submissionRef2 = this.host.submissionRefOf(envelope);
2636
+ const existing = state.pending[submissionRef2];
2637
+ if (existing !== void 0) return existing;
2638
+ const drafts = await this.host.mapEnvelope(envelope);
2639
+ const frames = drafts.map((draft, offset) => ({
2640
+ schema_version: "ceal.host_producer_frame.v1",
2641
+ producer_id: this.host.producer_id,
2642
+ producer_installation_ref: this.installationRef,
2643
+ producer_epoch: this.epoch,
2644
+ sequence: state.next_sequence + offset,
2645
+ frame_kind: draft.frame_kind,
2646
+ occurred_at: this.host.createdAtOf(envelope),
2647
+ payload: draft.payload
2648
+ }));
2649
+ await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef2]: frames } });
2650
+ return frames;
2651
+ }
2652
+ async release(submissionRef2) {
2653
+ const state = await this.readState();
2654
+ if (state.pending[submissionRef2] === void 0) return;
2655
+ const pending = { ...state.pending };
2656
+ delete pending[submissionRef2];
2657
+ await this.writeState({ ...state, pending });
2658
+ }
2659
+ async readState() {
2660
+ try {
2661
+ const value = JSON.parse(await readFile(this.statePath, "utf8"));
2662
+ return this.decodeState(value);
2663
+ } catch (error) {
2664
+ if (errorCode4(error) !== "ENOENT") throw error;
2665
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2666
+ }
2667
+ }
2668
+ async writeState(state) {
2669
+ await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
2670
+ const temporary = `${this.statePath}.${process.pid}.new`;
2671
+ await writeFile(temporary, JSON.stringify(state), { mode: 384 });
2672
+ await rename2(temporary, this.statePath);
2673
+ }
2674
+ decodeState(value) {
2675
+ const label = this.host.producer_label;
2676
+ const fail = (code2, message) => this.host.fail(code2, message);
2677
+ if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
2678
+ throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
2679
+ { name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
2680
+ { name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
2681
+ ])}).`);
2682
+ }
2683
+ 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}`)}).`);
2684
+ const pending = {};
2685
+ const storedEpoch = Number(value.producer_epoch);
2686
+ for (const [submissionRef2, frames] of Object.entries(value.pending)) {
2687
+ if (!isHostSafeRef(submissionRef2) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
2688
+ pending[submissionRef2] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
2689
+ }
2690
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
2691
+ }
2692
+ decodeStoredFrame(value, storedEpoch) {
2693
+ const label = this.host.producer_label;
2694
+ const json = Buffer.from(JSON.stringify(value), "utf8");
2695
+ const encoded = Buffer.allocUnsafe(json.length + 4);
2696
+ encoded.writeUInt32BE(json.length, 0);
2697
+ json.copy(encoded, 4);
2698
+ const frame = decodeHostProducerFrame(encoded);
2699
+ 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([
2700
+ { name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
2701
+ { name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
2702
+ { name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
2703
+ ])}).`);
2704
+ return frame;
2705
+ }
2706
+ };
2707
+ function isDrainStateHeader(value, schema, installationRef) {
2708
+ 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);
2709
+ }
2710
+ function errorCode4(error) {
2711
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2712
+ }
2713
+
2714
+ // host/relay/src/relay-quarantine-store.ts
2715
+ var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
2716
+ var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
2717
+ var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
2718
+
2719
+ // host/relay/src/relay-admission-store.ts
2720
+ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
2721
+ var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
2722
+ var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
2723
+
2110
2724
  // host/invocation-authentication-command.ts
2111
2725
  var HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX = ["authenticate", "--client-nonce"];
2112
- var REFUSAL_REASON_PATTERN = /^[a-z][a-z0-9_ ]{0,63}$/u;
2113
- function installedHostInvocationAuthenticationCommandOptions(config) {
2726
+ var HOST_AUTHENTICATION_BUDGET_ENVIRONMENT = "CEAL_HOST_AUTHENTICATION_BUDGET_MS";
2727
+ var HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS = 2e4;
2728
+ var CLAUDE_CONTEXT_ACTIVATION_WAIT_MAX_MS = HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS;
2729
+ var HOST_AUTHENTICATION_COMPLETION_RESERVE_MS = 5e3;
2730
+ var INVOCATION_CONTEXT_POLL_INTERVAL_MS = 250;
2731
+ var REFUSAL_REASON_PATTERN = /^[a-z][a-z0-9_]{0,63}$/u;
2732
+ function installedHostInvocationAuthenticationCommandOptions(config, contextBudgetMs, maxContextWaitMs) {
2114
2733
  const port = new HostRelayInvocationContextPort(config.relay_endpoint);
2115
2734
  return {
2116
2735
  host_source_ref: config.host_source_ref,
2117
2736
  outbox_dir: config.outbox_dir,
2118
2737
  producer_installation_ref: config.producer_installation_ref,
2738
+ context_activation_wait_ms: boundedContextActivationWait(contextBudgetMs, config.ipc_timeout_ms, maxContextWaitMs),
2119
2739
  mint_invocation_context: (input) => port.mintInvocationContext({ ...input, producer_id: input.producer_id })
2120
2740
  };
2121
2741
  }
@@ -2133,65 +2753,150 @@ async function runHostInvocationAuthenticationCommand(descriptor, args, options)
2133
2753
  }
2134
2754
  function authenticationRefusalReason(error) {
2135
2755
  const message = error instanceof Error ? error.message : "";
2136
- return REFUSAL_REASON_PATTERN.test(message) ? message : "unclassified";
2756
+ if (!REFUSAL_REASON_PATTERN.test(message)) return "host_refusal_invalid";
2757
+ return isCealHostAuthenticationCause(message) ? message : "host_refusal_invalid";
2137
2758
  }
2138
2759
  async function authenticateHostInvocation(descriptor, input, options) {
2139
- 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");
2760
+ 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");
2140
2761
  const request2 = decodeCealHostInvocationAuthenticationRequest({ schema_version: CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA, client_nonce: input[2] });
2141
2762
  const config = validateOptions(options);
2142
- const now = (config.now ?? (() => /* @__PURE__ */ new Date()))();
2763
+ const now = config.now ?? (() => /* @__PURE__ */ new Date());
2143
2764
  const { store, current } = await currentInvocation(descriptor, config, now);
2144
- if (config.mint_invocation_context === void 0) throw new TypeError("mint unavailable");
2765
+ if (config.mint_invocation_context === void 0) throw new TypeError("relay_failed");
2145
2766
  const query = {
2146
2767
  producer_id: descriptor.producer_id,
2147
2768
  producer_installation_ref: config.producer_installation_ref,
2148
2769
  local_session_ref: current.identity.host_session_ref,
2149
2770
  local_turn_ref: current.identity.host_turn_ref,
2150
- client_nonce: request2.client_nonce
2771
+ client_nonce: request2.client_nonce,
2772
+ ...await visibleOutputBudget(descriptor)
2151
2773
  };
2152
2774
  const response2 = await mintedResult(config.mint_invocation_context, query);
2153
- const rechecked = await store.readInvocationContext(current.identity.host_source_ref, current.identity.host_session_ref, (config.now ?? (() => /* @__PURE__ */ new Date()))());
2154
- if (rechecked === null || !sameTurn(current, rechecked)) throw new TypeError("context replaced");
2155
- return decodeCealHostInvocationAuthenticationResult(response2.result, now);
2775
+ const completedAt = now();
2776
+ const rechecked = await store.readInvocationContext(current.identity.host_source_ref, current.identity.host_session_ref, completedAt);
2777
+ if (rechecked === null || !sameTurn(current, rechecked)) throw new TypeError("context_replaced");
2778
+ return decodeCealHostInvocationAuthenticationResult(response2.result, completedAt);
2779
+ }
2780
+ async function visibleOutputBudget(descriptor) {
2781
+ const bytes = await descriptor.resolve_visible_output_max_bytes?.();
2782
+ return bytes === void 0 ? {} : { visible_output_max_bytes: bytes };
2156
2783
  }
2157
2784
  async function mintedResult(mint, query) {
2158
2785
  let response2;
2159
2786
  try {
2160
2787
  response2 = await mint(query);
2161
2788
  } catch (error) {
2162
- throw new TypeError(`relay ${hostRelayExchangeFailure(error) ?? "unavailable"}`);
2789
+ throw new TypeError(exchangeFailureCause(hostRelayExchangeFailure(error) ?? "unavailable"));
2163
2790
  }
2164
- if (response2.status === "refused") throw new TypeError(`mint ${response2.reason}`);
2165
- if (response2.status !== "ready") throw new TypeError(response2.status);
2791
+ if (response2.status === "refused") throw new TypeError(mintRefusalCause(response2.reason));
2792
+ if (response2.status !== "ready") throw new TypeError("no_binding");
2166
2793
  return response2;
2167
2794
  }
2795
+ function mintRefusalCause(reason) {
2796
+ if (isDirectMintRefusalCause(reason)) return reason;
2797
+ if (reason === "deadline" || reason === "cancelled") return "transient_retryable";
2798
+ if (reason === "mint_failed" || reason === "relay_failed") return reason;
2799
+ return isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, reason) ? "context_stale" : "host_refusal_invalid";
2800
+ }
2801
+ function isDirectMintRefusalCause(reason) {
2802
+ return isVocabularyWord(HOST_RELAY_DIRECT_WIRE_MINT_REASONS, reason);
2803
+ }
2804
+ function exchangeFailureCause(failure2) {
2805
+ if (failure2 === "permission_denied") return "relay_permission_denied";
2806
+ if (failure2 === "timed_out") return "transient_retryable";
2807
+ return "relay_unavailable";
2808
+ }
2168
2809
  async function currentInvocation(descriptor, config, now) {
2169
2810
  const store = descriptor.create_store(config.outbox_dir);
2170
2811
  const identity = resolveIdentity(descriptor, config);
2171
2812
  if (identity.status !== "ready") throw new TypeError(identity.status);
2172
- const current = await store.readInvocationContext(identity.identity.host_source_ref, identity.identity.host_session_ref, now);
2173
- if (current === null || Date.parse(current.expires_at) <= now.getTime()) throw new TypeError("context unavailable");
2174
- return { store, current };
2813
+ const waitBudgetMs = config.context_activation_wait_ms ?? 0;
2814
+ const monotonicNow = config.monotonic_now ?? (() => performance.now());
2815
+ const deadline = monotonicNow() + waitBudgetMs;
2816
+ let firstCheck = true;
2817
+ let waitEngaged = false;
2818
+ while (true) {
2819
+ if (!firstCheck && monotonicNow() >= deadline) {
2820
+ recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
2821
+ throw new TypeError("context_unavailable");
2822
+ }
2823
+ firstCheck = false;
2824
+ const observedAt = now();
2825
+ const current = await readLiveSessionContext(store, identity.identity.host_source_ref, identity.identity.host_session_ref, observedAt);
2826
+ if (current !== null) {
2827
+ recordActivationWaitCompletion(descriptor.producer_id, waitEngaged, config.diagnostic_sink);
2828
+ return { store, current };
2829
+ }
2830
+ const remainingMs = deadline - monotonicNow();
2831
+ if (remainingMs <= 0) {
2832
+ recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
2833
+ throw new TypeError("context_unavailable");
2834
+ }
2835
+ recordActivationWaitEngagement(descriptor.producer_id, waitEngaged, config.diagnostic_sink);
2836
+ waitEngaged = true;
2837
+ const intervalMs = Math.min(INVOCATION_CONTEXT_POLL_INTERVAL_MS, remainingMs);
2838
+ await (config.wait ?? waitForContextActivation)(intervalMs);
2839
+ }
2840
+ }
2841
+ function recordActivationWaitExhaustion(producer, engaged, budgetMs, sink) {
2842
+ if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted", void 0, void 0, sink);
2843
+ else writeHostServiceDiagnostic(producer, "activation_wait_skipped", void 0, void 0, sink);
2844
+ }
2845
+ function recordActivationWaitCompletion(producer, engaged, sink) {
2846
+ if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed", void 0, void 0, sink);
2847
+ }
2848
+ function recordActivationWaitEngagement(producer, engaged, sink) {
2849
+ if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged", void 0, void 0, sink);
2850
+ }
2851
+ async function readLiveSessionContext(store, hostSourceRef, hostSessionRef, now) {
2852
+ const current = await store.readInvocationContext(hostSourceRef, hostSessionRef, now);
2853
+ if (current !== null) {
2854
+ if (Date.parse(current.expires_at) <= now.getTime()) throw new TypeError("context_expired");
2855
+ return current;
2856
+ }
2857
+ const raw = await store.readRawInvocationContext?.(hostSourceRef, hostSessionRef);
2858
+ if (raw !== null && raw !== void 0 && Date.parse(raw.expires_at) <= now.getTime()) throw new TypeError("context_expired");
2859
+ return null;
2175
2860
  }
2176
2861
  function resolveIdentity(descriptor, options) {
2177
2862
  const sessionId = options.session_id === null ? void 0 : options.session_id ?? process.env[descriptor.session_id_env];
2178
2863
  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) } };
2179
- return { status: "unavailable" };
2864
+ return { status: "session_unavailable" };
2180
2865
  }
2181
2866
  async function resolveOptions(descriptor, args, options) {
2182
2867
  if (options !== void 0) return { input: [...args], options };
2183
2868
  const configPath = descriptor.parse_config_flag(args);
2869
+ const config = await descriptor.read_producer_config(configPath);
2184
2870
  return {
2185
2871
  input: args.filter((argument) => !argument.startsWith(`${descriptor.config_flag}=`)),
2186
- options: installedHostInvocationAuthenticationCommandOptions(await descriptor.read_producer_config(configPath))
2872
+ options: installedHostInvocationAuthenticationCommandOptions(config, authenticationBudgetMsFromEnvironment(), descriptor.context_activation_wait_max_ms)
2187
2873
  };
2188
2874
  }
2875
+ function authenticationBudgetMsFromEnvironment() {
2876
+ const value = process.env[HOST_AUTHENTICATION_BUDGET_ENVIRONMENT];
2877
+ if (value === void 0) return 0;
2878
+ if (!/^(?:0|[1-9][0-9]{0,8})$/u.test(value)) throw new TypeError("invalid_arguments");
2879
+ return Number(value);
2880
+ }
2881
+ function boundedContextActivationWait(contextBudgetMs, relayTimeoutMs, maxContextWaitMs) {
2882
+ if (maxContextWaitMs === void 0 || contextBudgetMs <= 0) return 0;
2883
+ const availableWaitMs = contextBudgetMs - relayTimeoutMs - HOST_AUTHENTICATION_COMPLETION_RESERVE_MS;
2884
+ return Math.max(0, Math.min(maxContextWaitMs, availableWaitMs));
2885
+ }
2886
+ function waitForContextActivation(milliseconds) {
2887
+ return new Promise((resolve) => {
2888
+ setTimeout(resolve, milliseconds);
2889
+ });
2890
+ }
2189
2891
  function validateOptions(options) {
2190
- 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)) {
2191
- throw new TypeError("options invalid");
2892
+ 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)) {
2893
+ throw new TypeError("options_invalid");
2192
2894
  }
2193
2895
  return options;
2194
2896
  }
2897
+ function validContextActivationWait(value) {
2898
+ return value === void 0 || Number.isSafeInteger(value) && value >= 0 && value <= HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS;
2899
+ }
2195
2900
  function isHostSessionId(value) {
2196
2901
  return typeof value === "string" && value.length > 0 && value.length <= 256 && !value.includes("\0");
2197
2902
  }
@@ -2210,7 +2915,7 @@ function writeResult(value, sink) {
2210
2915
  import { posix as posix3, win32 as win323 } from "node:path";
2211
2916
 
2212
2917
  // host/runtime-source-paths.ts
2213
- import { createHash as createHash2 } from "node:crypto";
2918
+ import { createHash as createHash3 } from "node:crypto";
2214
2919
  import { posix as posix2, win32 as win322 } from "node:path";
2215
2920
 
2216
2921
  // host/absolute-path.ts
@@ -2232,7 +2937,7 @@ function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platfor
2232
2937
  const sourcesRoot = path.join(stateRoot, "sources");
2233
2938
  const sourceRoot = path.join(sourcesRoot, sourceId);
2234
2939
  const runtimeRoot = path.join(stateRoot, "run");
2235
- const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash2("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
2940
+ const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash3("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
2236
2941
  assertHostRuntimeIpcLength(endpoint, platform);
2237
2942
  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 };
2238
2943
  }
@@ -2248,19 +2953,24 @@ function assertHostProducerSourcePaths(stateRoot, config, producer, platform = p
2248
2953
 
2249
2954
  // host/producer-service.ts
2250
2955
  import { constants } from "node:fs";
2251
- import { lstat as lstat2, open as open2 } from "node:fs/promises";
2956
+ import { lstat as lstat2, open as open3 } from "node:fs/promises";
2252
2957
  var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2253
2958
  var HOST_PRODUCER_CONFIG_FLAG = "--ceal-host-config";
2254
2959
  var HostProducerServiceError = class extends Error {
2255
2960
  code;
2256
- constructor(code3, message = code3) {
2257
- super(message === code3 ? code3 : `${code3}: ${message}`);
2961
+ constructor(code2, message = code2) {
2962
+ super(message === code2 ? code2 : `${code2}: ${message}`);
2258
2963
  this.name = "HostProducerServiceError";
2259
- this.code = code3;
2964
+ this.code = code2;
2260
2965
  }
2261
2966
  };
2262
2967
  var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
2263
2968
  var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
2969
+ function hostProducerExtraPath(extras, key) {
2970
+ const value = extras[key];
2971
+ if (value === void 0) throw hostProducerError("invalid_config");
2972
+ return value;
2973
+ }
2264
2974
  function parseHostProducerConfigFlag(args, platform = process.platform) {
2265
2975
  return parseProducerHostConfigFlag(args, HOST_PRODUCER_CONFIG_FLAG, platform, hostProducerError);
2266
2976
  }
@@ -2281,8 +2991,8 @@ function decodeHostProducerConfigText(descriptor, text, platform = process.platf
2281
2991
  const { extra_paths: extras, ...core } = decoded;
2282
2992
  return Object.freeze(descriptor.config({ schema_version: descriptor.schema, ...core }, extras));
2283
2993
  }
2284
- function hostProducerError(code3, message = code3) {
2285
- return new HostProducerServiceError(code3, message);
2994
+ function hostProducerError(code2, message = code2) {
2995
+ return new HostProducerServiceError(code2, message);
2286
2996
  }
2287
2997
  function parseProducerHostConfigFlag(args, flag, platform, error) {
2288
2998
  const prefix = `${flag}=`;
@@ -2354,7 +3064,7 @@ function validateConfigFile(value, platform, error) {
2354
3064
  }
2355
3065
  async function openConfigFile(configPath, flags, error) {
2356
3066
  try {
2357
- return await open2(configPath, flags);
3067
+ return await open3(configPath, flags);
2358
3068
  } catch {
2359
3069
  throw error("config_unavailable");
2360
3070
  }
@@ -2415,43 +3125,56 @@ function boundedMilliseconds(value) {
2415
3125
  }
2416
3126
 
2417
3127
  // host/claude/src/claude-decoder.ts
2418
- import { createHash as createHash3 } from "node:crypto";
3128
+ import { createHash as createHash4 } from "node:crypto";
2419
3129
 
2420
3130
  // host/claude/src/claude-types.ts
2421
- var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure"];
2422
3131
  var CLAUDE_SESSION_ID_ENVIRONMENT = "CLAUDE_CODE_SESSION_ID";
2423
- var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "storage", "configuration"];
3132
+ var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "payload_rejected", "storage", "configuration"];
2424
3133
  function isClaudeHostErrorCode(value) {
2425
- return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code3) => code3 === value);
3134
+ return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code2) => code2 === value);
2426
3135
  }
2427
- var CLAUDE_OUTBOX_SCHEMA = "ceal.claude_hook_outbox.v1";
2428
3136
  var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
3137
+ var CLAUDE_OUTBOX_SCHEMA_VERSION = "ceal.claude_observation_outbox.v1";
3138
+ var CLAUDE_SESSION_SCHEMA_VERSION = "ceal.claude_session.v1";
3139
+ var CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.claude_transcript_registration.v1";
3140
+ var CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.claude_transcript_cursor.v1";
3141
+ var CLAUDE_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
3142
+ var CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES = 1024;
3143
+ var CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES = 64;
3144
+ var CLAUDE_TRANSCRIPT_ANCHOR_BYTES = 256;
2429
3145
  var ClaudeHostError = class extends Error {
2430
3146
  name = "ClaudeHostError";
2431
3147
  code;
2432
- constructor(code3, message) {
3148
+ diagnostic_kind;
3149
+ constructor(code2, message, diagnostic_kind) {
2433
3150
  super(message);
2434
- if (!isClaudeHostErrorCode(code3)) throw new TypeError("Claude Host error code is invalid.");
2435
- this.code = code3;
3151
+ if (!isClaudeHostErrorCode(code2)) throw new TypeError("Claude Host error code is invalid.");
3152
+ this.code = code2;
3153
+ this.diagnostic_kind = diagnostic_kind;
2436
3154
  }
2437
3155
  };
2438
- function isClaudeHookEvent(value) {
2439
- return typeof value === "string" && CLAUDE_HOOK_EVENTS.some((event) => event === value);
2440
- }
2441
3156
 
2442
3157
  // host/claude/src/claude-decoder.ts
2443
3158
  var MAX_HOOK_BYTES = 256 * 1024;
2444
- var MAX_TEXT = 128 * 1024;
2445
3159
  function claudeLocalSessionRef(sessionId) {
2446
3160
  return `claude-session:${digest(sessionId)}`;
2447
3161
  }
3162
+ function claudeLocalTurnRef(sessionId, turnId) {
3163
+ return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
3164
+ }
3165
+ function claudeLocalInputRef(sessionId, turnId) {
3166
+ return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
3167
+ }
3168
+ function claudeWorkspaceRef(cwd) {
3169
+ return `workspace:${digest(cwd)}`;
3170
+ }
2448
3171
  function digest(value) {
2449
- return createHash3("sha256").update(value, "utf8").digest("hex");
3172
+ return createHash4("sha256").update(value, "utf8").digest("hex");
2450
3173
  }
2451
3174
 
2452
3175
  // host/claude/src/claude-outbox.ts
2453
- import { open as open3, mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
2454
- import { join as join2 } from "node:path";
3176
+ import { mkdir as mkdir3, readdir, readFile as readFile2, unlink as unlink2 } from "node:fs/promises";
3177
+ import { isAbsolute, join as join3 } from "node:path";
2455
3178
 
2456
3179
  // host/invocation-context.ts
2457
3180
  var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
@@ -2465,17 +3188,32 @@ function hasHostInvocationIdentityKeys(value) {
2465
3188
  function hasHostInvocationContextKeys(value, optional = []) {
2466
3189
  return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
2467
3190
  }
3191
+ async function readLatestSessionProjection(file, hostSourceRef, hostSessionRef, readProjection) {
3192
+ const context = await readProjection(file);
3193
+ if (context === null || context.identity.host_source_ref !== hostSourceRef || context.identity.host_session_ref !== hostSessionRef) return null;
3194
+ return context;
3195
+ }
3196
+ function isLiveSessionProjection(context, now) {
3197
+ return context !== null && Date.parse(context.expires_at) > now.getTime();
3198
+ }
2468
3199
  function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
2469
3200
  const context = {
2470
3201
  schema_version: descriptor.schema_version,
2471
3202
  identity,
2472
3203
  prompt_ref: promptRef,
2473
3204
  expires_at: expiresAt,
3205
+ ...descriptor.turn_generation_ref === void 0 ? {} : { turn_generation_ref: descriptor.turn_generation_ref },
2474
3206
  ...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
2475
3207
  };
2476
3208
  descriptor.validate(context);
2477
3209
  return Object.freeze(context);
2478
3210
  }
3211
+ function isHostInvocationTurnSelection(value) {
3212
+ 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]));
3213
+ }
3214
+ function invocationTurnSelectionMatches(context, selection) {
3215
+ 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;
3216
+ }
2479
3217
  function exactKeys4(value, keys) {
2480
3218
  const actual = Object.keys(value);
2481
3219
  return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
@@ -2485,10 +3223,16 @@ function requiredKeys(value, required, optional) {
2485
3223
  const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
2486
3224
  return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
2487
3225
  }
3226
+ function isRecord2(value) {
3227
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3228
+ }
3229
+ function safeRef3(value) {
3230
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
3231
+ }
2488
3232
 
2489
3233
  // host/claude/src/claude-invocation-context.ts
2490
- function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory) {
2491
- return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory });
3234
+ function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
3235
+ return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory, turn_generation_ref: turnGenerationRef });
2492
3236
  }
2493
3237
  function validateClaudeHostInvocationContext(value) {
2494
3238
  if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
@@ -2497,23 +3241,23 @@ function validateClaudeHostInvocationContext(value) {
2497
3241
  validateContextTimes(value);
2498
3242
  }
2499
3243
  function contextShape(value) {
2500
- return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory"]);
3244
+ return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory", "turn_generation_ref"]);
2501
3245
  }
2502
3246
  function validateIdentity2(value) {
2503
3247
  if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2504
3248
  }
2505
3249
  function validateContextReferences(value) {
2506
- if (!safeRef3(value.prompt_ref) || !optionalSafeRef(value.request_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
3250
+ if (!safeRef4(value.prompt_ref) || !optionalSafeRef(value.request_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2507
3251
  }
2508
3252
  function validateContextTimes(value) {
2509
3253
  if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2510
3254
  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)}`)}).`);
2511
3255
  }
2512
3256
  function validIdentity(value) {
2513
- return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef3);
3257
+ return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef4);
2514
3258
  }
2515
3259
  function optionalSafeRef(value) {
2516
- return value === void 0 || safeRef3(value);
3260
+ return value === void 0 || safeRef4(value);
2517
3261
  }
2518
3262
  function optionalTimestamp(value) {
2519
3263
  return value === void 0 || timestamp2(value);
@@ -2521,7 +3265,7 @@ function optionalTimestamp(value) {
2521
3265
  function optionalDirectory(value) {
2522
3266
  return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
2523
3267
  }
2524
- function safeRef3(value) {
3268
+ function safeRef4(value) {
2525
3269
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2526
3270
  }
2527
3271
  function timestamp2(value) {
@@ -2533,29 +3277,67 @@ function record3(value) {
2533
3277
 
2534
3278
  // host/claude/src/claude-outbox.ts
2535
3279
  var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
3280
+ var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
3281
+ var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
2536
3282
  var ClaudeOutboxStore = class {
2537
- entries;
3283
+ envelopes;
3284
+ sessions;
3285
+ registrations;
3286
+ cursors;
2538
3287
  directory;
2539
3288
  activeContexts;
3289
+ turnSelections;
2540
3290
  constructor(directory) {
2541
3291
  if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2542
3292
  this.directory = directory;
2543
- this.entries = join2(directory, "entries");
2544
- this.activeContexts = join2(directory, "active-contexts");
3293
+ this.envelopes = join3(directory, "envelopes");
3294
+ this.sessions = join3(directory, "sessions");
3295
+ this.registrations = join3(directory, "transcript-registrations");
3296
+ this.cursors = join3(directory, "transcript-cursors");
3297
+ this.activeContexts = join3(directory, "active-contexts");
3298
+ this.turnSelections = join3(directory, "turn-selections");
2545
3299
  }
2546
3300
  async prepare() {
2547
- await mkdir2(this.entries, { recursive: true, mode: 448 });
2548
- await mkdir2(this.activeContexts, { recursive: true, mode: 448 });
3301
+ for (const path of [this.envelopes, this.sessions, this.registrations, this.cursors, this.activeContexts, this.turnSelections]) {
3302
+ await mkdir3(path, { recursive: true, mode: 448 });
3303
+ }
2549
3304
  }
2550
- async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory) {
2551
- const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory);
3305
+ async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
3306
+ const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef);
2552
3307
  await this.prepare();
2553
3308
  await durableAtomicReplace(this.activeContextPath(identity), JSON.stringify(context));
2554
3309
  }
2555
3310
  async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
2556
3311
  validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now);
3312
+ const context = await this.readRawInvocationContext(hostSourceRef, hostSessionRef);
3313
+ return isLiveSessionProjection(context, now) ? context : null;
3314
+ }
3315
+ async writeInvocationTurnSelection(selection) {
3316
+ if (!isHostInvocationTurnSelection(selection)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection is invalid.");
3317
+ await this.prepare();
3318
+ await durableAtomicReplace(this.turnSelectionPath(selection.host_source_ref, selection.host_session_ref), JSON.stringify(selection));
3319
+ }
3320
+ async attachInvocationTurnGeneration(identity, promptRef, turnGenerationRef) {
3321
+ const selection = await this.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3322
+ if (selection === null || selection.prompt_ref !== promptRef || selection.turn_generation_ref !== turnGenerationRef) return false;
3323
+ const path = this.activeContextPath(identity);
3324
+ const active = await readProjectionFile(path);
3325
+ if (active === null || active.prompt_ref !== promptRef || active.identity.host_turn_ref !== identity.host_turn_ref) return false;
3326
+ const updated = { ...active, turn_generation_ref: turnGenerationRef };
3327
+ validateClaudeHostInvocationContext(updated);
3328
+ await durableAtomicReplace(path, JSON.stringify(updated));
3329
+ return true;
3330
+ }
3331
+ /**
3332
+ * The latest Claude session projection even when past expiry, so the
3333
+ * invocation authenticator can report an expired context distinctly from
3334
+ * a missing one. Every other caller keeps the fail-closed read above.
3335
+ */
3336
+ async readRawInvocationContext(hostSourceRef, hostSessionRef) {
3337
+ if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2557
3338
  await this.prepare();
2558
- return readInvocationContextFile(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, now);
3339
+ const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
3340
+ return invocationTurnSelectionMatches(context, await this.readInvocationTurnSelection(hostSourceRef, hostSessionRef)) ? context : null;
2559
3341
  }
2560
3342
  async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
2561
3343
  await this.prepare();
@@ -2563,106 +3345,175 @@ var ClaudeOutboxStore = class {
2563
3345
  for (const name of await readdir(this.activeContexts)) {
2564
3346
  if (!name.endsWith(".json")) continue;
2565
3347
  try {
2566
- const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
3348
+ const value = JSON.parse(await readFile2(join3(this.activeContexts, name), "utf8"));
2567
3349
  validateClaudeHostInvocationContext(value);
2568
- if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
3350
+ 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);
2569
3351
  } catch {
2570
3352
  }
2571
3353
  }
2572
3354
  return contexts;
2573
3355
  }
2574
- async put(batch) {
2575
- validateBatch(batch);
3356
+ async putEnvelope(entry) {
3357
+ validateEnvelope(entry);
2576
3358
  await this.prepare();
2577
- const path = this.path(batch.batch_ref);
2578
- try {
2579
- const handle = await open3(path, "wx", 384);
3359
+ await durableAtomicReplace(this.envelopePath(entry.submission_ref), JSON.stringify(entry));
3360
+ }
3361
+ async listEnvelopes() {
3362
+ await this.prepare();
3363
+ const entries = [];
3364
+ let malformed = 0;
3365
+ for (const name of await readdir(this.envelopes)) {
3366
+ if (!name.endsWith(".json")) continue;
2580
3367
  try {
2581
- await handle.writeFile(JSON.stringify(batch));
2582
- await handle.sync();
2583
- } finally {
2584
- await handle.close();
3368
+ const value = JSON.parse(await readFile2(join3(this.envelopes, name), "utf8"));
3369
+ validateEnvelope(value);
3370
+ entries.push(value);
3371
+ } catch {
3372
+ malformed += 1;
2585
3373
  }
3374
+ }
3375
+ entries.sort((left, right) => left.created_at.localeCompare(right.created_at));
3376
+ return { entries, malformed };
3377
+ }
3378
+ async removeEnvelope(entry) {
3379
+ validateEnvelope(entry);
3380
+ try {
3381
+ await unlink2(this.envelopePath(entry.submission_ref));
2586
3382
  } catch (error) {
2587
- if (code(error) !== "EEXIST") throw new ClaudeHostError("storage", `Unable to persist Claude hook batch (${refusalFact("error_code", code(error), "EEXIST for an idempotent retry")}).`);
2588
- const existing = JSON.parse(await readFile(path, "utf8"));
2589
- validateBatch(existing);
2590
- if (JSON.stringify(existing) !== JSON.stringify(batch)) throw new ClaudeHostError("storage", "Claude hook batch identity conflicts with different bytes.");
3383
+ if (code(error) !== "ENOENT") throw error;
2591
3384
  }
2592
3385
  }
2593
- async list() {
3386
+ async saveSession(snapshot) {
3387
+ validateSession(snapshot);
2594
3388
  await this.prepare();
2595
- const batches = [];
2596
- let malformed = 0;
2597
- for (const name of await readdir(this.entries)) {
3389
+ await durableAtomicReplace(join3(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
3390
+ }
3391
+ async readSession(sourceRef, sessionRef) {
3392
+ if (!SAFE_REF4.test(sourceRef) || !SAFE_REF4.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
3393
+ await this.prepare();
3394
+ try {
3395
+ const value = JSON.parse(await readFile2(join3(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
3396
+ validateSession(value);
3397
+ return value;
3398
+ } catch (error) {
3399
+ if (code(error) === "ENOENT") return null;
3400
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3401
+ }
3402
+ }
3403
+ async registerTranscript(registration) {
3404
+ validateRegistration(registration);
3405
+ await this.prepare();
3406
+ const path = this.registrationPath(registration.host_source_ref, registration.host_session_ref);
3407
+ const existing = await this.readRegistrationFile(path);
3408
+ if (existing !== null && (existing.claude_session_id !== registration.claude_session_id || existing.transcript_path !== registration.transcript_path)) {
3409
+ throw new ClaudeHostError("storage", "Claude transcript registration conflicts with the source-bound session.");
3410
+ }
3411
+ await durableAtomicReplace(path, JSON.stringify(registration));
3412
+ }
3413
+ /**
3414
+ * End a registration whose transcript the Host has deleted. The cursor is
3415
+ * keyed by the same session and describes byte offsets of a file that no
3416
+ * longer exists, so it leaves with the registration.
3417
+ */
3418
+ async retireTranscriptRegistration(registration) {
3419
+ validateRegistration(registration);
3420
+ await this.prepare();
3421
+ await this.removeStateFile(this.registrationPath(registration.host_source_ref, registration.host_session_ref));
3422
+ await this.removeStateFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref));
3423
+ }
3424
+ async listTranscriptRegistrations() {
3425
+ await this.prepare();
3426
+ const registrations = [];
3427
+ for (const name of await readdir(this.registrations)) {
2598
3428
  if (!name.endsWith(".json")) continue;
2599
3429
  try {
2600
- const value = JSON.parse(await readFile(join2(this.entries, name), "utf8"));
2601
- validateBatch(value);
2602
- batches.push(value);
3430
+ const value = JSON.parse(await readFile2(join3(this.registrations, name), "utf8"));
3431
+ validateRegistration(value);
3432
+ registrations.push(value);
2603
3433
  } catch {
2604
- malformed += 1;
2605
3434
  }
2606
3435
  }
2607
- batches.sort(compareBatch);
2608
- return { entries: batches, malformed };
3436
+ return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
2609
3437
  }
2610
- async remove(batch) {
3438
+ async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
3439
+ if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
3440
+ await this.prepare();
2611
3441
  try {
2612
- await unlink2(this.path(batch.batch_ref));
3442
+ const value = JSON.parse(await readFile2(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
3443
+ if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
3444
+ return value;
2613
3445
  } catch (error) {
2614
- if (code(error) !== "ENOENT") throw error;
3446
+ if (code(error) === "ENOENT") return null;
3447
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Unable to read the Claude Host turn selection.");
2615
3448
  }
2616
3449
  }
2617
- path(ref) {
2618
- return join2(this.entries, `${cealRequestSha256(ref)}.json`);
3450
+ async readTranscriptCursor(registration) {
3451
+ validateRegistration(registration);
3452
+ await this.prepare();
3453
+ try {
3454
+ const value = JSON.parse(await readFile2(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
3455
+ validateCursor(value);
3456
+ return value;
3457
+ } catch (error) {
3458
+ if (code(error) === "ENOENT") return null;
3459
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3460
+ }
3461
+ }
3462
+ async writeTranscriptCursor(cursor) {
3463
+ validateCursor(cursor);
3464
+ await this.prepare();
3465
+ await durableAtomicReplace(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), JSON.stringify(cursor));
3466
+ }
3467
+ envelopePath(ref) {
3468
+ return join3(this.envelopes, `${cealRequestSha256(ref)}.json`);
3469
+ }
3470
+ registrationPath(sourceRef, sessionRef) {
3471
+ return join3(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3472
+ }
3473
+ turnSelectionPath(sourceRef, sessionRef) {
3474
+ return join3(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3475
+ }
3476
+ cursorPath(sourceRef, sessionRef) {
3477
+ return join3(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3478
+ }
3479
+ async readRegistrationFile(path) {
3480
+ try {
3481
+ const value = JSON.parse(await readFile2(path, "utf8"));
3482
+ validateRegistration(value);
3483
+ return value;
3484
+ } catch (error) {
3485
+ if (code(error) === "ENOENT") return null;
3486
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3487
+ }
3488
+ }
3489
+ async removeStateFile(path) {
3490
+ try {
3491
+ await unlink2(path);
3492
+ } catch (error) {
3493
+ if (code(error) !== "ENOENT") throw error;
3494
+ }
2619
3495
  }
2620
3496
  activeContextPath(identity) {
2621
- return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
3497
+ return join3(this.activeContexts, `${invocationContextKey(identity)}.json`);
2622
3498
  }
2623
3499
  };
2624
3500
  function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
2625
3501
  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.");
2626
3502
  }
2627
- async function readInvocationContextFile(file, hostSourceRef, hostSessionRef, now) {
3503
+ async function readProjectionFile(file) {
2628
3504
  try {
2629
- const context = JSON.parse(await readFile(file, "utf8"));
3505
+ const context = JSON.parse(await readFile2(file, "utf8"));
2630
3506
  validateClaudeHostInvocationContext(context);
2631
- return contextMatches(context, hostSourceRef, hostSessionRef) && Date.parse(context.expires_at) > now.getTime() ? context : null;
3507
+ return context;
2632
3508
  } catch (error) {
2633
3509
  if (code(error) === "ENOENT") return null;
2634
3510
  if (error instanceof ClaudeHostError) throw error;
2635
3511
  throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
2636
3512
  }
2637
3513
  }
2638
- function contextMatches(context, hostSourceRef, hostSessionRef) {
2639
- return context.identity.host_source_ref === hostSourceRef && context.identity.host_session_ref === hostSessionRef;
2640
- }
2641
- function compareBatch(left, right) {
2642
- return rank(left.hook_event) - rank(right.hook_event) || left.occurred_at.localeCompare(right.occurred_at) || left.batch_ref.localeCompare(right.batch_ref);
2643
- }
2644
- function rank(event) {
2645
- return event === "SessionStart" ? 0 : event === "UserPromptSubmit" ? 1 : 2;
2646
- }
2647
- function validateBatch(value) {
2648
- if (!validBatchHeader(value)) throw new ClaudeHostError("storage", "Claude hook batch is malformed.");
2649
- for (const [index, draft] of value.frames.entries()) {
2650
- if (!record4(draft) || !hostFrameKind(draft.frame_kind) || !record4(draft.payload)) throw new ClaudeHostError("storage", "Claude frame draft is malformed.");
2651
- 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 });
2652
- }
2653
- }
2654
- function validBatchHeader(value) {
2655
- return record4(value) && value.schema_version === CLAUDE_OUTBOX_SCHEMA && safeRef4(value.batch_ref) && isClaudeHookEvent(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;
2656
- }
2657
- function hostFrameKind(value) {
2658
- return isHostProducerFrameKind(value);
2659
- }
2660
- function safeRef4(value) {
3514
+ function safeRef5(value) {
2661
3515
  return typeof value === "string" && SAFE_REF4.test(value);
2662
3516
  }
2663
- function optionalRef(value) {
2664
- return value === void 0 || typeof value === "string" && SAFE_REF4.test(value);
2665
- }
2666
3517
  function timestamp3(value) {
2667
3518
  return typeof value === "string" && Number.isFinite(Date.parse(value));
2668
3519
  }
@@ -2672,133 +3523,811 @@ function record4(value) {
2672
3523
  function code(error) {
2673
3524
  return record4(error) && typeof error.code === "string" ? error.code : null;
2674
3525
  }
3526
+ function validateEnvelope(value) {
3527
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
3528
+ if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "input_kind", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"], ["request_ref"])) {
3529
+ throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
3530
+ }
3531
+ 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}`)}).`);
3532
+ validateEnvelopeIdentity(value);
3533
+ validateEnvelopeObservations(value.observations);
3534
+ if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
3535
+ if (!timestamp3(value.created_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
3536
+ }
3537
+ function validateEnvelopeIdentity(value) {
3538
+ validateIdentity3(value.identity);
3539
+ if (!safeRef5(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
3540
+ if (!isCealHostInputKind(value.input_kind)) throw new ClaudeHostError("storage", "Claude outbox input provenance is malformed.");
3541
+ if (!safeRef5(value.prompt_ref) || !safeRef5(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
3542
+ if (value.request_ref !== void 0 && !safeRef5(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
3543
+ }
3544
+ function validateEnvelopeObservations(value) {
3545
+ if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3546
+ try {
3547
+ for (const observation2 of value) validateCealHostObservationDraft(observation2);
3548
+ } catch {
3549
+ throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3550
+ }
3551
+ }
3552
+ function validateSession(value) {
3553
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3554
+ if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) {
3555
+ throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
3556
+ }
3557
+ 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}`)}).`);
3558
+ if (!safeRef5(value.host_session_ref) || !safeRef5(value.host_source_ref) || !timestamp3(value.observed_at) || !timestamp3(value.expires_at)) {
3559
+ throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
3560
+ }
3561
+ }
3562
+ function validateRegistration(value) {
3563
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3564
+ if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "claude_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) {
3565
+ throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
3566
+ }
3567
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3568
+ if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) {
3569
+ throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
3570
+ }
3571
+ validateRegistrationPaths(value.cwd, value.transcript_path);
3572
+ if (!timestamp3(value.registered_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
3573
+ }
3574
+ function validateRegistrationPaths(cwd, transcriptPath) {
3575
+ if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
3576
+ throw new ClaudeHostError("storage", "Claude transcript registration working directory is malformed.");
3577
+ }
3578
+ if (typeof transcriptPath !== "string" || transcriptPath.length === 0 || transcriptPath.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS) {
3579
+ throw new ClaudeHostError("storage", "Claude transcript registration path is malformed.");
3580
+ }
3581
+ }
3582
+ function validateCursor(value) {
3583
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3584
+ 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"])) {
3585
+ throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
3586
+ }
3587
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3588
+ 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.");
3589
+ if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
3590
+ validateCursorPosition(value);
3591
+ validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
3592
+ validateSubagentCursors(value.subagents);
3593
+ if (value.reason !== void 0 && !isHostTranscriptCursorReason(value.reason)) throw new ClaudeHostError("storage", "Claude transcript cursor reason is malformed.");
3594
+ }
3595
+ function validateCursorPosition(value) {
3596
+ if (!validCursorPosition(value)) throw new ClaudeHostError("storage", "Claude transcript cursor position is malformed.");
3597
+ if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
3598
+ if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
3599
+ if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
3600
+ if (value.status !== "active" && value.status !== "quarantined" || !timestamp3(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
3601
+ }
3602
+ function validateCursorTurn(openTurn, unattributed, sessionObserved) {
3603
+ if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
3604
+ if (unattributed !== void 0 && !validUnattributed(unattributed)) throw new ClaudeHostError("storage", "Claude transcript unattributed cursor is malformed.");
3605
+ if (sessionObserved !== void 0 && typeof sessionObserved !== "boolean") throw new ClaudeHostError("storage", "Claude transcript cursor session marker is malformed.");
3606
+ }
3607
+ function validateSubagentCursors(value) {
3608
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
3609
+ for (const [name, entry] of Object.entries(value)) validateSubagentEntry(name, entry);
3610
+ }
3611
+ function validateSubagentEntry(name, entry) {
3612
+ if (name.length === 0 || name.length > CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS || name.includes("/")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3613
+ if (name.includes("\0")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3614
+ 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.");
3615
+ validateSubagentIdentity(entry);
3616
+ }
3617
+ function validateSubagentIdentity(entry) {
3618
+ if (entry.device !== void 0 && typeof entry.device !== "string" || entry.inode !== void 0 && typeof entry.inode !== "string") {
3619
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3620
+ }
3621
+ if (!nonNegativeInteger(entry.anchor_start) || typeof entry.anchor_sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(entry.anchor_sha256)) {
3622
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3623
+ }
3624
+ if (entry.device === void 0 && Number(entry.offset) !== 0) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is unpinned past its start.");
3625
+ }
3626
+ function validCursorPosition(value) {
3627
+ return nonNegativeInteger(value.offset) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256);
3628
+ }
3629
+ function validOpenTurn(value) {
3630
+ 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;
3631
+ 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;
3632
+ if (!timestamp3(value.started_at) || !timestamp3(value.last_observed_at)) return false;
3633
+ if (!validMessageIds(value.message_ids)) return false;
3634
+ if (!validTurnCounts(value.counts)) return false;
3635
+ return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
3636
+ }
3637
+ function validMessageIds(value) {
3638
+ if (!Array.isArray(value) || value.length > CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES) return false;
3639
+ if (!value.every((candidate) => typeof candidate === "string" && candidate.length > 0 && candidate.length <= 256)) return false;
3640
+ return new Set(value).size === value.length;
3641
+ }
3642
+ function validUnattributed(value) {
3643
+ if (!record4(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
3644
+ if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
3645
+ if (!timestamp3(value.last_observed_at)) return false;
3646
+ return validTurnCounts(value);
3647
+ }
3648
+ function validTurnCounts(value) {
3649
+ if (!record4(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
3650
+ return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
3651
+ }
3652
+ function validateIdentity3(value) {
3653
+ if (!record4(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) {
3654
+ throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
3655
+ }
3656
+ }
3657
+ function nonNegativeInteger(value) {
3658
+ return Number.isSafeInteger(value) && Number(value) >= 0;
3659
+ }
3660
+ function sameKeys(value, required, optional = []) {
3661
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
3662
+ }
2675
3663
 
2676
3664
  // host/claude/src/claude-producer.ts
2677
- import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile } from "node:fs/promises";
2678
- import { join as join3 } from "node:path";
2679
- var STATE_SCHEMA = "ceal.claude_producer_state.v1";
2680
- var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2681
3665
  var ClaudeProducer = class {
3666
+ producer_id = "claude";
3667
+ producer_label = "Claude";
3668
+ outbox_directory;
2682
3669
  store;
2683
- statePath;
2684
- installation;
2685
- epoch;
2686
- options;
2687
- tail = Promise.resolve();
3670
+ core;
2688
3671
  constructor(options) {
2689
- if (!SAFE_REF5.test(options.producer_installation_ref)) throw new ClaudeHostError("configuration", "Claude producer installation identity is invalid.");
2690
- this.options = options;
2691
- this.epoch = options.producer_epoch ?? 1;
2692
- if (!Number.isSafeInteger(this.epoch) || this.epoch <= 0) throw new ClaudeHostError("configuration", "Claude producer epoch is invalid.");
2693
3672
  this.store = new ClaudeOutboxStore(options.outbox_dir);
2694
- this.statePath = join3(options.outbox_dir, "claude-producer-state.json");
2695
- this.installation = options.producer_installation_ref;
3673
+ this.outbox_directory = this.store.directory;
3674
+ this.core = new HostProducerDrainCore(this, options);
2696
3675
  }
2697
3676
  drainOutbox() {
2698
- const run = this.tail.then(() => this.drainLocked());
2699
- this.tail = run.then(() => void 0, () => void 0);
2700
- return run;
3677
+ return this.core.drainOutbox();
2701
3678
  }
2702
- async drainLocked() {
2703
- const read = await this.store.list();
2704
- let admitted = 0;
2705
- let retained = 0;
2706
- for (const batch of read.entries) {
2707
- const frames = await this.reserve(batch);
2708
- let complete = true;
2709
- for (const frame of frames) {
2710
- const outcome = await this.options.relay.admit(frame);
2711
- if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2712
- complete = false;
2713
- break;
2714
- }
3679
+ fail(code2, message) {
3680
+ return new ClaudeHostError(code2, message);
3681
+ }
3682
+ listEnvelopes() {
3683
+ return this.store.listEnvelopes();
3684
+ }
3685
+ removeEnvelope(envelope) {
3686
+ return this.store.removeEnvelope(envelope);
3687
+ }
3688
+ submissionRefOf(envelope) {
3689
+ return envelope.submission_ref;
3690
+ }
3691
+ expiresAtOf(envelope) {
3692
+ return envelope.expires_at;
3693
+ }
3694
+ createdAtOf(envelope) {
3695
+ return envelope.created_at;
3696
+ }
3697
+ async mapEnvelope(envelope) {
3698
+ const prompt = observation(envelope, "prompt");
3699
+ if (prompt !== void 0) return this.mapInput(envelope, prompt);
3700
+ return [mapTerminal(envelope)];
3701
+ }
3702
+ async mapInput(envelope, prompt) {
3703
+ const drafts = [];
3704
+ const session = observation(envelope, "session");
3705
+ if (session !== void 0) {
3706
+ const registrations = await this.store.listTranscriptRegistrations();
3707
+ const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
3708
+ const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
3709
+ drafts.push({
3710
+ frame_kind: "session_started",
3711
+ payload: compact({
3712
+ local_session_ref: envelope.identity.host_session_ref,
3713
+ working_directory_ref: claudeWorkspaceRef(directoryIdentity),
3714
+ model: recordText(session.value, "model"),
3715
+ observations: [session]
3716
+ })
3717
+ });
3718
+ }
3719
+ const turn = observation(envelope, "turn");
3720
+ if (turn !== void 0) drafts.push({
3721
+ frame_kind: "turn_started",
3722
+ payload: compact({
3723
+ local_session_ref: envelope.identity.host_session_ref,
3724
+ local_turn_ref: localTurnRef(envelope),
3725
+ observations: [turn]
3726
+ })
3727
+ });
3728
+ if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
3729
+ drafts.push({
3730
+ frame_kind: "user_input",
3731
+ payload: compact({
3732
+ local_session_ref: envelope.identity.host_session_ref,
3733
+ local_turn_ref: localTurnRef(envelope),
3734
+ local_input_ref: envelope.host_input_ref,
3735
+ input_kind: envelope.input_kind,
3736
+ origin_input: envelope.origin_input,
3737
+ observations: [prompt],
3738
+ context_expires_at: envelope.expires_at
3739
+ })
3740
+ });
3741
+ return drafts;
3742
+ }
3743
+ };
3744
+ function mapTerminal(envelope) {
3745
+ const usage = envelope.observations.filter((item) => ["token", "cost"].includes(item.observation_kind));
3746
+ return {
3747
+ frame_kind: "turn_terminal",
3748
+ payload: compact({
3749
+ local_session_ref: envelope.identity.host_session_ref,
3750
+ local_turn_ref: localTurnRef(envelope),
3751
+ terminal_state: "completed",
3752
+ usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
3753
+ observations: envelope.observations
3754
+ })
3755
+ };
3756
+ }
3757
+ function observation(envelope, kind) {
3758
+ return envelope.observations.find((item) => item.observation_kind === kind);
3759
+ }
3760
+ function localTurnRef(envelope) {
3761
+ return envelope.identity.host_turn_ref;
3762
+ }
3763
+ function compact(value) {
3764
+ return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
3765
+ }
3766
+ function asRecord(value) {
3767
+ return isRecord3(value) ? value : void 0;
3768
+ }
3769
+ function recordText(value, key) {
3770
+ const record5 = asRecord(value);
3771
+ return typeof record5?.[key] === "string" ? record5[key] : void 0;
3772
+ }
3773
+ function isRecord3(value) {
3774
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3775
+ }
3776
+
3777
+ // host/claude/src/claude-transcript-watcher.ts
3778
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
3779
+ import { open as open4, readdir as readdir2 } from "node:fs/promises";
3780
+ import { join as join4 } from "node:path";
3781
+ var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
3782
+ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3783
+ store;
3784
+ registerCurrentPrompt;
3785
+ releaseCurrentPrompt;
3786
+ diagnostic;
3787
+ constructor(options) {
3788
+ super(options, {
3789
+ default_retention_ms: CLAUDE_DEFAULT_RETENTION_MS,
3790
+ invalid_outbox: () => new ClaudeHostError("configuration", "Claude transcript watcher outbox is invalid."),
3791
+ clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
3792
+ });
3793
+ this.store = new ClaudeOutboxStore(options.outbox_dir);
3794
+ this.registerCurrentPrompt = options.register_current_prompt;
3795
+ this.releaseCurrentPrompt = options.release_current_prompt;
3796
+ this.diagnostic = options.diagnostic ?? (() => void 0);
3797
+ }
3798
+ transcriptPathOf(registration) {
3799
+ return registration.transcript_path;
3800
+ }
3801
+ async listRegistrations() {
3802
+ return this.store.listTranscriptRegistrations();
3803
+ }
3804
+ async scanOpened(handle, registration) {
3805
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3806
+ const cursor = await this.preflight(registration, handle, size, device, inode);
3807
+ if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
3808
+ if (cursor.status === "quarantined") {
3809
+ if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
3810
+ return { records: 0, enqueued: 0, quarantined: true };
3811
+ }
3812
+ const draft = mutableCursor(cursor);
3813
+ await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
3814
+ return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
3815
+ }
3816
+ async scanFailed(handle, registration, error) {
3817
+ this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
3818
+ if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
3819
+ if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
3820
+ await this.quarantineFailure(registration, handle);
3821
+ return { records: 0, enqueued: 0, quarantined: true };
3822
+ }
3823
+ async retireMissing(registration) {
3824
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
3825
+ return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
3826
+ }
3827
+ initialCursor(registration) {
3828
+ return {
3829
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3830
+ host_source_ref: registration.host_source_ref,
3831
+ host_session_ref: registration.host_session_ref,
3832
+ transcript_path: registration.transcript_path,
3833
+ offset: 0,
3834
+ anchor_start: 0,
3835
+ anchor_sha256: this.sha(Buffer.alloc(0)),
3836
+ subagents: {},
3837
+ status: "active",
3838
+ updated_at: this.stamp()
3839
+ };
3840
+ }
3841
+ async scanOpenedTranscript(registration, handle, draft, size, device, inode) {
3842
+ const main2 = await this.readChunk(handle, draft.offset, size);
3843
+ if (main2 !== "empty" && main2 !== "partial") {
3844
+ await this.consumeMainChunk(registration, main2, draft);
3845
+ draft.offset += main2.length;
3846
+ }
3847
+ draft.consumed += await this.consumeSubagents(registration, draft);
3848
+ await this.emitOpenTerminal(registration, draft);
3849
+ await this.commitCursor(registration, handle, draft, device, inode);
3850
+ }
3851
+ async preflight(registration, handle, size, device, inode) {
3852
+ const existing = await this.store.readTranscriptCursor(registration);
3853
+ if (existing?.status === "quarantined") return existing;
3854
+ const cursor = existing ?? this.initialCursor(registration);
3855
+ if (cursor.device === void 0) {
3856
+ if (cursor.offset !== 0) return await this.quarantineStored(cursor, device, inode, "transcript_replaced");
3857
+ return { ...cursor, device, inode };
3858
+ }
3859
+ const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
3860
+ if (reason !== null) return await this.quarantineStored(cursor, device, inode, reason);
3861
+ return cursor;
3862
+ }
3863
+ async consumeMainChunk(registration, complete, draft) {
3864
+ for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3865
+ draft.consumed += 1;
3866
+ const record5 = parseTranscriptLine(line);
3867
+ if (record5 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
3868
+ if (isUserTurnLine(record5)) await this.openTurn(registration, draft, record5);
3869
+ else if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
3870
+ else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3871
+ if (isAssistantTerminalLine(record5)) await this.releaseOpenTurn(registration, draft);
3872
+ }
3873
+ }
3874
+ async openTurn(registration, draft, record5) {
3875
+ await this.closeTurn(registration, draft, record5.timestamp);
3876
+ const sessionRef = registration.host_session_ref;
3877
+ const turnRef = claudeLocalTurnRef(registration.claude_session_id, record5.uuid);
3878
+ const inputRef = claudeLocalInputRef(registration.claude_session_id, record5.uuid);
3879
+ const promptRef = hostProducerPromptRef(this.sha(record5.message.content));
3880
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
3881
+ const observations = [
3882
+ { 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) } },
3883
+ { 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 } }
3884
+ ];
3885
+ if (!draft.session_observed) {
3886
+ const session = await this.store.readSession(registration.host_source_ref, sessionRef);
3887
+ if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record5.timestamp, value: session.value });
3888
+ draft.session_observed = true;
3889
+ }
3890
+ await this.persistOpenedTurnContext(registration, draft, record5, inputRef, turnRef, promptRef, identity, observations);
3891
+ }
3892
+ async persistOpenedTurnContext(registration, draft, record5, inputRef, turnRef, promptRef, identity, observations) {
3893
+ const sessionRef = registration.host_session_ref;
3894
+ const expiresAt = this.expire(this.outboxRetentionMs);
3895
+ const custodyRegistered = await this.registerPrompt(sessionRef, turnRef, promptRef, record5.message.content, expiresAt);
3896
+ if (custodyRegistered) this.diagnostic("watcher_turn_observed");
3897
+ try {
3898
+ await this.store.putEnvelope({
3899
+ schema_version: "ceal.claude_observation_outbox.v1",
3900
+ identity,
3901
+ host_input_ref: inputRef,
3902
+ input_kind: "human",
3903
+ prompt_ref: promptRef,
3904
+ observations,
3905
+ origin_input: true,
3906
+ submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3907
+ created_at: record5.timestamp,
3908
+ expires_at: expiresAt
3909
+ });
3910
+ draft.enqueued += 1;
3911
+ await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
3912
+ this.setOpenedTurn(draft, record5, inputRef, turnRef, promptRef);
3913
+ } catch (error) {
3914
+ if (custodyRegistered) await this.releasePrompt(sessionRef, { turn_key: turnRef, prompt_ref: promptRef }).catch(() => void 0);
3915
+ throw error;
3916
+ }
3917
+ }
3918
+ async activateOpenedTurnContext(identity, promptRef, expiresAt) {
3919
+ const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3920
+ const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3921
+ await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
3922
+ }
3923
+ setOpenedTurn(draft, record5, inputRef, turnRef, promptRef) {
3924
+ const folded = foldUnattributed(draft.unattributed);
3925
+ draft.open_turn = {
3926
+ turn_key: turnRef,
3927
+ user_uuid: record5.uuid,
3928
+ prompt_ref: promptRef,
3929
+ host_input_ref: inputRef,
3930
+ started_at: record5.timestamp,
3931
+ last_observed_at: folded?.last_observed_at ?? record5.timestamp,
3932
+ message_ids: folded?.message_ids ?? [],
3933
+ counts: folded?.counts ?? zeroCounts()
3934
+ };
3935
+ draft.unattributed = void 0;
3936
+ }
3937
+ async closeTurn(registration, draft, at) {
3938
+ const turn = draft.open_turn;
3939
+ draft.open_turn = void 0;
3940
+ if (turn === void 0) return;
3941
+ const digest2 = terminalDigest(turn);
3942
+ if (digest2 !== turn.last_terminal_digest) {
3943
+ await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3944
+ draft.enqueued += 1;
3945
+ }
3946
+ await this.releasePrompt(registration.host_session_ref, turn);
3947
+ }
3948
+ async releaseOpenTurn(registration, draft) {
3949
+ if (draft.open_turn !== void 0) await this.releasePrompt(registration.host_session_ref, draft.open_turn);
3950
+ }
3951
+ async releasePrompt(sessionRef, turn) {
3952
+ if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
3953
+ await this.releaseCurrentPrompt({ producer_id: "claude", producer_installation_ref: this.producerInstallationRef, local_session_ref: sessionRef, local_turn_ref: turn.turn_key, prompt_ref: turn.prompt_ref });
3954
+ }
3955
+ accumulateUsage(draft, usage) {
3956
+ const turn = draft.open_turn;
3957
+ if (turn === void 0) {
3958
+ draft.unattributed = accumulateUnattributed(draft.unattributed, usage);
3959
+ return;
3960
+ }
3961
+ if (turn.message_ids.includes(usage.uuid)) return;
3962
+ 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");
3963
+ draft.open_turn = {
3964
+ ...turn,
3965
+ last_observed_at: usage.observed_at > turn.last_observed_at ? usage.observed_at : turn.last_observed_at,
3966
+ message_ids: [...turn.message_ids, usage.uuid],
3967
+ counts: addCounts(turn.counts, usage)
3968
+ };
3969
+ }
3970
+ /**
3971
+ * Subagent transcripts live beside the session transcript at
3972
+ * `<session-id>/subagents/*.jsonl` with a `.meta.json` sibling the scan
3973
+ * ignores. Their usage belongs to the turn open when the scan runs: a
3974
+ * subagent only runs while its parent turn is in flight. A straggler line
3975
+ * that lands after the next user message therefore attributes to the open
3976
+ * turn, never to a closed one.
3977
+ */
3978
+ async consumeSubagents(registration, draft) {
3979
+ const directory = subagentsDirectory(registration.transcript_path);
3980
+ let names;
3981
+ try {
3982
+ names = (await readdir2(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
3983
+ } catch (error) {
3984
+ if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
3985
+ throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
3986
+ }
3987
+ const listed = new Set(names);
3988
+ for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
3989
+ if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
3990
+ this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
3991
+ names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
3992
+ }
3993
+ let consumed = 0;
3994
+ for (const name of names) {
3995
+ consumed += await this.consumeSubagentFile(draft, directory, name);
3996
+ }
3997
+ return consumed;
3998
+ }
3999
+ async consumeSubagentFile(draft, directory, name) {
4000
+ const path = join4(directory, name);
4001
+ let handle;
4002
+ try {
4003
+ handle = await open4(path, O_RDONLY3 | O_NOFOLLOW2);
4004
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
4005
+ const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
4006
+ const pinned = await this.preflightSubagent(entry, size, device, inode);
4007
+ const chunk = await this.readChunk(handle, pinned.offset, size);
4008
+ if (chunk === "empty" || chunk === "partial") {
4009
+ draft.subagents[name] = pinned;
4010
+ return 0;
2715
4011
  }
2716
- if (!complete) {
2717
- retained += 1;
2718
- break;
4012
+ const consumed = this.consumeSubagentLines(draft, chunk);
4013
+ const offset = pinned.offset + chunk.length;
4014
+ const anchor = await this.readAnchor(handle, offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
4015
+ draft.subagents[name] = { device, inode, offset, anchor_start: anchor.start, anchor_sha256: anchor.sha256 };
4016
+ return consumed;
4017
+ } catch (error) {
4018
+ if (isMissingTranscriptError(error)) {
4019
+ delete draft.subagents[name];
4020
+ return 0;
2719
4021
  }
2720
- await this.store.remove(batch);
2721
- await this.release(batch.batch_ref);
2722
- admitted += 1;
4022
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("payload_rejected", "Claude subagent transcript is unavailable.");
4023
+ } finally {
4024
+ await handle?.close();
2723
4025
  }
2724
- return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - retained), malformed: read.malformed };
2725
4026
  }
2726
- async reserve(batch) {
2727
- const state = await this.readState();
2728
- const existing = state.pending[batch.batch_ref];
2729
- if (existing !== void 0) return existing;
2730
- 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 }));
2731
- await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [batch.batch_ref]: frames } });
2732
- return frames;
4027
+ consumeSubagentLines(draft, chunk) {
4028
+ let consumed = 0;
4029
+ for (const line of chunk.toString("utf8").split("\n").filter((value) => value.length > 0)) {
4030
+ consumed += 1;
4031
+ const record5 = parseTranscriptLine(line);
4032
+ if (record5 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
4033
+ if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
4034
+ else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
4035
+ }
4036
+ return consumed;
2733
4037
  }
2734
- async release(ref) {
2735
- const state = await this.readState();
2736
- if (state.pending[ref] === void 0) return;
2737
- const pending = { ...state.pending };
2738
- delete pending[ref];
2739
- await this.writeState({ ...state, pending });
4038
+ async preflightSubagent(entry, size, device, inode) {
4039
+ if (entry.device === void 0) {
4040
+ if (entry.offset !== 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript cursor is unpinned past its start.");
4041
+ return { device, inode, offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
4042
+ }
4043
+ if (entry.device !== device || entry.inode !== inode) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was replaced.");
4044
+ if (size < entry.offset) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was truncated.");
4045
+ return { device, inode, offset: entry.offset, anchor_start: entry.anchor_start, anchor_sha256: entry.anchor_sha256 };
2740
4046
  }
2741
- async readState() {
4047
+ /**
4048
+ * The open turn's terminal is emitted optimistically on every scan whose
4049
+ * new lines changed its totals, so usage is visible before the next user
4050
+ * message closes the turn. The emission digest changes with the content,
4051
+ * so a repeated scan over unchanged state emits nothing.
4052
+ */
4053
+ async emitOpenTerminal(registration, draft) {
4054
+ const turn = draft.open_turn;
4055
+ if (turn === void 0 || turn.message_ids.length === 0) return;
4056
+ const digest2 = terminalDigest(turn);
4057
+ if (digest2 === turn.last_terminal_digest) return;
4058
+ await this.putTerminalEnvelope(registration, turn, digest2, void 0);
4059
+ draft.enqueued += 1;
4060
+ draft.open_turn = { ...turn, last_terminal_digest: digest2 };
4061
+ }
4062
+ async putTerminalEnvelope(registration, turn, digest2, gapAt) {
4063
+ const observedAt = gapAt ?? turn.last_observed_at;
4064
+ const tokenValue = turn.message_ids.length === 0 ? hostObservationNotRecorded("claude_turn_usage_not_observed") : availableTokenValue(turn.counts);
4065
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: turn.turn_key };
4066
+ await this.store.putEnvelope({
4067
+ schema_version: "ceal.claude_observation_outbox.v1",
4068
+ identity,
4069
+ host_input_ref: turn.host_input_ref,
4070
+ input_kind: "host_automatic",
4071
+ prompt_ref: turn.prompt_ref,
4072
+ observations: [
4073
+ { 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 } },
4074
+ { observation_ref: observationRef("transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue },
4075
+ { observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
4076
+ ],
4077
+ origin_input: false,
4078
+ submission_ref: submissionRef("terminal", turn.turn_key, digest2, turn.prompt_ref),
4079
+ created_at: observedAt,
4080
+ expires_at: this.expire(this.outboxRetentionMs)
4081
+ });
4082
+ }
4083
+ async commitCursor(registration, handle, draft, device, inode) {
4084
+ const anchor = await this.readAnchor(handle, draft.offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
4085
+ await this.store.writeTranscriptCursor({
4086
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
4087
+ host_source_ref: registration.host_source_ref,
4088
+ host_session_ref: registration.host_session_ref,
4089
+ transcript_path: registration.transcript_path,
4090
+ device,
4091
+ inode,
4092
+ offset: draft.offset,
4093
+ anchor_start: anchor.start,
4094
+ anchor_sha256: anchor.sha256,
4095
+ subagents: draft.subagents,
4096
+ ...draft.open_turn === void 0 ? {} : { open_turn: draft.open_turn },
4097
+ ...draft.unattributed === void 0 ? {} : { unattributed: draft.unattributed },
4098
+ ...draft.session_observed ? { session_observed: true } : {},
4099
+ status: "active",
4100
+ updated_at: this.stamp()
4101
+ });
4102
+ }
4103
+ async registerPrompt(sessionRef, turnRef, promptRef, prompt, expiresAt) {
4104
+ if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
4105
+ const register = this.registerCurrentPrompt;
4106
+ const installationRef = this.producerInstallationRef;
4107
+ return registerHostCurrentPromptDiagnosed(
4108
+ () => register({
4109
+ producer_id: "claude",
4110
+ producer_installation_ref: installationRef,
4111
+ local_session_ref: sessionRef,
4112
+ local_turn_ref: turnRef,
4113
+ prompt_ref: promptRef,
4114
+ current_prompt: prompt,
4115
+ context_expires_at: expiresAt
4116
+ }),
4117
+ (event) => this.diagnostic(event)
4118
+ );
4119
+ }
4120
+ async releaseStoredPromptBestEffort(sessionRef, cursor) {
4121
+ await this.releaseOpenTurnBestEffort(sessionRef, cursor?.open_turn);
4122
+ }
4123
+ async releaseOpenTurnBestEffort(sessionRef, turn) {
4124
+ if (turn === void 0) return;
4125
+ await this.releasePrompt(sessionRef, turn).catch(() => void 0);
4126
+ }
4127
+ async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
2742
4128
  try {
2743
- return decodeState(JSON.parse(await readFile2(this.statePath, "utf8")), this.installation, this.epoch);
4129
+ await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
4130
+ this.diagnostic("activation_recorded");
2744
4131
  } catch (error) {
2745
- if (code2(error) !== "ENOENT") throw error;
2746
- return { schema_version: STATE_SCHEMA, producer_installation_ref: this.installation, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
4132
+ this.diagnostic("activation_failed");
4133
+ throw error;
2747
4134
  }
2748
4135
  }
2749
- async writeState(state) {
2750
- await mkdir3(this.store.directory, { recursive: true, mode: 448 });
2751
- const temp = `${this.statePath}.${process.pid}.new`;
2752
- await writeFile(temp, JSON.stringify(state), { mode: 384 });
2753
- await rename2(temp, this.statePath);
4136
+ async quarantineStored(cursor, device, inode, reason) {
4137
+ await this.releaseStoredPromptBestEffort(cursor.host_session_ref, cursor);
4138
+ await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
4139
+ return null;
4140
+ }
4141
+ async quarantineUnopened(registration, reason) {
4142
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
4143
+ await this.store.writeTranscriptCursor({ ...existing ?? this.initialCursor(registration), transcript_path: registration.transcript_path, status: "quarantined", reason, updated_at: this.stamp() });
4144
+ return { records: 0, enqueued: 0, quarantined: true };
4145
+ }
4146
+ async quarantineDraft(cursor) {
4147
+ await this.store.writeTranscriptCursor({ ...cursor, status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
4148
+ return { records: 0, enqueued: 0, quarantined: true };
4149
+ }
4150
+ async quarantineFailure(registration, handle) {
4151
+ if (handle === void 0) return;
4152
+ const stats = await handle.stat().catch(() => null);
4153
+ if (stats === null) return;
4154
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
4155
+ const cursor = existing ?? this.initialCursor(registration);
4156
+ await this.store.writeTranscriptCursor({ ...cursor, device: String(stats.dev), inode: String(stats.ino), status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
4157
+ }
4158
+ async preservedCursor(registration) {
4159
+ return await this.store.readTranscriptCursor(registration).catch(() => null) ?? this.initialCursor(registration);
2754
4160
  }
2755
4161
  };
2756
- function decodeState(value, installation, epoch) {
2757
- if (!validStateHeader(value, installation)) throw new ClaudeHostError("storage", `Claude producer state header is malformed or belongs to another installation (${refusalFacts([
2758
- { name: "schema_version", value: record5(value) ? value.schema_version : void 0, expected: STATE_SCHEMA },
2759
- { name: "producer_installation_ref", value: record5(value) ? value.producer_installation_ref : void 0, expected: installation }
2760
- ])}).`);
2761
- 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}`)}).`);
2762
- const pending = {};
2763
- for (const [ref, frames] of Object.entries(value.pending)) {
2764
- if (!SAFE_REF5.test(ref) || !Array.isArray(frames) || frames.length === 0) throw new ClaudeHostError("storage", "Claude producer reservation is malformed.");
2765
- pending[ref] = frames.map((frame) => decodeFrame(frame, installation, Number(value.producer_epoch)));
2766
- }
2767
- return { schema_version: STATE_SCHEMA, producer_installation_ref: installation, producer_epoch: epoch, next_sequence: Number(value.next_sequence), pending };
2768
- }
2769
- function validStateHeader(value, installation) {
2770
- return record5(value) && value.schema_version === STATE_SCHEMA && value.producer_installation_ref === installation && positiveInteger(value.producer_epoch) && positiveInteger(value.next_sequence) && record5(value.pending);
4162
+ function mutableCursor(cursor) {
4163
+ return {
4164
+ offset: cursor.offset,
4165
+ ...cursor.open_turn === void 0 ? {} : { open_turn: cursor.open_turn },
4166
+ ...cursor.unattributed === void 0 ? {} : { unattributed: cursor.unattributed },
4167
+ subagents: Object.fromEntries(Object.entries(cursor.subagents).map(([name, entry]) => [name, { ...entry }])),
4168
+ session_observed: cursor.session_observed ?? false,
4169
+ consumed: 0,
4170
+ enqueued: 0
4171
+ };
2771
4172
  }
2772
- function positiveInteger(value) {
2773
- return Number.isSafeInteger(value) && Number(value) > 0;
4173
+ function zeroCounts() {
4174
+ return { input: 0, cached_input: 0, cache_write_input: 0, output: 0 };
4175
+ }
4176
+ function terminalDigest(turn) {
4177
+ return transcriptObjectSha256({ turn: turn.turn_key, messages: turn.message_ids, counts: turn.counts });
4178
+ }
4179
+ function availableTokenValue(counts) {
4180
+ const input = counts.input;
4181
+ const output = counts.output;
4182
+ if (!Number.isSafeInteger(input + output)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4183
+ return createHostTokenObservationValue({
4184
+ input,
4185
+ output,
4186
+ total: input + output,
4187
+ cached_input: counts.cached_input,
4188
+ cache_write_input: counts.cache_write_input
4189
+ }, TOKEN_OBSERVATION_SOURCE);
4190
+ }
4191
+ function addCounts(current, next) {
4192
+ const counts = {
4193
+ input: current.input + next.input,
4194
+ cached_input: current.cached_input + next.cached_input,
4195
+ cache_write_input: current.cache_write_input + next.cache_write_input,
4196
+ output: current.output + next.output
4197
+ };
4198
+ if (![counts.input, counts.cached_input, counts.cache_write_input, counts.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
4199
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4200
+ }
4201
+ return counts;
4202
+ }
4203
+ function accumulateUnattributed(current, usage) {
4204
+ const base = current ?? { input: 0, cached_input: 0, cache_write_input: 0, output: 0, message_ids: [], last_observed_at: usage.observed_at };
4205
+ if (base.message_ids.includes(usage.uuid)) return base;
4206
+ 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");
4207
+ const counts = addCounts(base, usage);
4208
+ return {
4209
+ ...counts,
4210
+ message_ids: [...base.message_ids, usage.uuid],
4211
+ last_observed_at: usage.observed_at > base.last_observed_at ? usage.observed_at : base.last_observed_at
4212
+ };
2774
4213
  }
2775
- function decodeFrame(value, installation, epoch) {
2776
- const json = Buffer.from(JSON.stringify(value));
2777
- const encoded = Buffer.allocUnsafe(json.length + 4);
2778
- encoded.writeUInt32BE(json.length, 0);
2779
- json.copy(encoded, 4);
2780
- const frame = decodeHostProducerFrame(encoded);
2781
- 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}` }])}).`);
2782
- return frame;
4214
+ function foldUnattributed(bucket) {
4215
+ if (bucket === void 0) return void 0;
4216
+ if (![bucket.input, bucket.cached_input, bucket.cache_write_input, bucket.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
4217
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4218
+ }
4219
+ 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 };
4220
+ }
4221
+ function toUsage(uuid, observedAt, usage) {
4222
+ const input = usageNumber(usage.input_tokens);
4223
+ const cached = usageNumber(usage.cache_read_input_tokens ?? 0);
4224
+ const created = usageNumber(usage.cache_creation_input_tokens ?? 0);
4225
+ const output = usageNumber(usage.output_tokens);
4226
+ if (input === null || cached === null || created === null || output === null) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
4227
+ const total = input + cached + created;
4228
+ if (![total, output].every((value) => Number.isSafeInteger(value) && value >= 0)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
4229
+ return { uuid, observed_at: observedAt, input: total, cached_input: cached, cache_write_input: created, output };
4230
+ }
4231
+ function usageNumber(value) {
4232
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
4233
+ }
4234
+ function parseTranscriptLine(line) {
4235
+ try {
4236
+ return JSON.parse(line);
4237
+ } catch {
4238
+ return null;
4239
+ }
2783
4240
  }
2784
- function record5(value) {
2785
- return value !== null && typeof value === "object" && !Array.isArray(value);
4241
+ function lineIdentity(record5) {
4242
+ if (typeof record5.uuid !== "string" || record5.uuid.length === 0 || record5.uuid.length > 256) return null;
4243
+ if (typeof record5.timestamp !== "string" || !Number.isFinite(Date.parse(record5.timestamp))) return null;
4244
+ return { uuid: record5.uuid, timestamp: record5.timestamp };
2786
4245
  }
2787
- function code2(error) {
2788
- return record5(error) && typeof error.code === "string" ? error.code : null;
4246
+ function isUserTurnLine(record5) {
4247
+ if (!isTranscriptRecord(record5) || record5.type !== "user") return false;
4248
+ if (!isTranscriptRecord(record5.message) || typeof record5.message.content !== "string") return false;
4249
+ if (record5.isMeta === true && (!isTranscriptRecord(record5.origin) || typeof record5.origin.kind !== "string")) return false;
4250
+ const identity = lineIdentity(record5);
4251
+ if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
4252
+ return true;
4253
+ }
4254
+ function isAssistantLine(record5) {
4255
+ return isTranscriptRecord(record5) && record5.type === "assistant" && isTranscriptRecord(record5.message);
4256
+ }
4257
+ function isAssistantTerminalLine(record5) {
4258
+ return isAssistantLine(record5) && (record5.message.stop_reason === "end_turn" || record5.message.stop_reason === "stop_sequence");
4259
+ }
4260
+ function isAssistantUsageLine(record5) {
4261
+ if (!isTranscriptRecord(record5)) return false;
4262
+ if (!isAssistantLine(record5) || record5.message.usage === void 0) return false;
4263
+ const identity = lineIdentity(record5);
4264
+ if (identity === null || !isTranscriptRecord(record5.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
4265
+ return true;
4266
+ }
4267
+ function subagentsDirectory(transcriptPath) {
4268
+ const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
4269
+ return join4(stem, "subagents");
4270
+ }
4271
+ function observationRef(event, identity, value) {
4272
+ return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
4273
+ }
4274
+ function submissionRef(kind, turn, digest2, prompt) {
4275
+ return `claude-submission:${transcriptObjectSha256({ kind, turn, digest: digest2, prompt })}`;
4276
+ }
4277
+ async function ownedClaudeTranscriptFile(handle) {
4278
+ const stats = await handle.stat();
4279
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
4280
+ if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
4281
+ if (stats.uid !== uid) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
4282
+ return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
4283
+ }
4284
+ function isNotDirectory(error) {
4285
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
4286
+ }
4287
+ function claudeTranscriptScanCause(handle, error) {
4288
+ if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
4289
+ const code2 = transcriptErrorCode(error);
4290
+ if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
4291
+ return `${handle === void 0 ? "open" : "scan"}:unknown`;
4292
+ }
4293
+ function claudeTranscriptScanFailureKind(error) {
4294
+ return transcriptScanFailureKind(error, (candidate) => {
4295
+ if (!(candidate instanceof ClaudeHostError)) return void 0;
4296
+ if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
4297
+ if (candidate.code === "payload_rejected") return "payload_rejected";
4298
+ if (candidate.code === "storage") return "read_error";
4299
+ return void 0;
4300
+ });
4301
+ }
4302
+ function isSymlinkTranscript(error) {
4303
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
2789
4304
  }
2790
4305
 
2791
4306
  // host/claude/src/claude-producer-runtime.ts
2792
4307
  async function drainClaudeProducerOnce(options) {
2793
- 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();
4308
+ const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
4309
+ const scan = await new ClaudeTranscriptWatcher({
4310
+ outbox_dir: options.outbox_dir,
4311
+ producer_installation_ref: options.producer_installation_ref,
4312
+ register_current_prompt: (input) => promptPort.register(input),
4313
+ release_current_prompt: (identity) => promptPort.release(identity),
4314
+ diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
4315
+ }).scanOnce().catch((error) => {
4316
+ writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
4317
+ throw error;
4318
+ });
4319
+ 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 } });
4320
+ return { scanned: scan.enqueued, ...await producer.drainOutbox() };
2794
4321
  }
2795
4322
 
2796
4323
  // host/claude/src/claude-producer-service.ts
2797
4324
  var CLAUDE_PRODUCER_CONFIG_SCHEMA = "ceal.claude_producer_config.v1";
4325
+ var CLAUDE_TRANSCRIPTS_ROOT = "claude_transcripts_root";
2798
4326
  var CLAUDE_PRODUCER_DESCRIPTOR = {
2799
4327
  schema: CLAUDE_PRODUCER_CONFIG_SCHEMA,
2800
4328
  producer_id: "claude",
2801
- config: (shared) => shared,
4329
+ extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
4330
+ config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
2802
4331
  runtime_options: (config) => Object.freeze({
2803
4332
  outbox_dir: config.outbox_dir,
2804
4333
  producer_installation_ref: config.producer_installation_ref,
@@ -2809,30 +4338,70 @@ var CLAUDE_PRODUCER_DESCRIPTOR = {
2809
4338
  drain: drainClaudeProducerOnce
2810
4339
  };
2811
4340
 
4341
+ // host/claude/src/claude-visible-output-budget.ts
4342
+ import { readFile as readFile3 } from "node:fs/promises";
4343
+ import { homedir } from "node:os";
4344
+ import { join as join5 } from "node:path";
4345
+ var CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS = 3e4;
4346
+ var CLAUDE_MAX_INLINE_OUTPUT_MAX_CHARS = 128e3;
4347
+ async function resolveClaudeVisibleOutputMaxBytes(environment = process.env, workingDirectory = process.cwd()) {
4348
+ const configured = await projectOrUserInlineLimit(environment, workingDirectory);
4349
+ if (configured !== void 0) return configured;
4350
+ const bashReadback = positiveInteger(environment.BASH_MAX_OUTPUT_LENGTH);
4351
+ return Math.min(bashReadback ?? CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS, CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS);
4352
+ }
4353
+ async function projectOrUserInlineLimit(environment, workingDirectory) {
4354
+ const configHome = environment.CLAUDE_CONFIG_DIR ?? join5(homedir(), ".claude");
4355
+ const userLimit = await settingsInlineLimit(join5(configHome, "settings.json"));
4356
+ const projectLimit = await settingsInlineLimit(join5(workingDirectory, ".claude", "settings.json"));
4357
+ const localLimit = await settingsInlineLimit(join5(workingDirectory, ".claude", "settings.local.json"));
4358
+ const candidate = localLimit ?? projectLimit ?? userLimit;
4359
+ return candidate === void 0 ? void 0 : Math.min(candidate, CLAUDE_MAX_INLINE_OUTPUT_MAX_CHARS);
4360
+ }
4361
+ async function settingsInlineLimit(path) {
4362
+ try {
4363
+ const parsed = JSON.parse(await readFile3(path, "utf8"));
4364
+ if (!isRecord4(parsed)) return void 0;
4365
+ return positiveInteger(parsed.bashOutputMaxChars);
4366
+ } catch {
4367
+ return void 0;
4368
+ }
4369
+ }
4370
+ function positiveInteger(value) {
4371
+ const normalized = typeof value === "string" ? value.trim().replaceAll("_", "") : value;
4372
+ const parsed = typeof normalized === "string" && normalized.length > 0 ? Number(normalized) : normalized;
4373
+ return typeof parsed === "number" && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
4374
+ }
4375
+ function isRecord4(value) {
4376
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4377
+ }
4378
+
2812
4379
  // host/claude/src/claude-invocation-authentication-command.ts
2813
4380
  var CLAUDE_INVOCATION_AUTHENTICATION_DESCRIPTOR = {
2814
4381
  producer_id: "claude",
4382
+ context_activation_wait_max_ms: CLAUDE_CONTEXT_ACTIVATION_WAIT_MAX_MS,
2815
4383
  create_store: (directory) => new ClaudeOutboxStore(directory),
2816
4384
  session_id_env: CLAUDE_SESSION_ID_ENVIRONMENT,
2817
4385
  session_ref: (_hostSourceRef, sessionId) => claudeLocalSessionRef(sessionId),
2818
4386
  config_flag: HOST_PRODUCER_CONFIG_FLAG,
2819
4387
  parse_config_flag: parseHostProducerConfigFlag,
2820
- read_producer_config: (configPath) => readHostProducerConfig(CLAUDE_PRODUCER_DESCRIPTOR, configPath)
4388
+ read_producer_config: (configPath) => readHostProducerConfig(CLAUDE_PRODUCER_DESCRIPTOR, configPath),
4389
+ resolve_visible_output_max_bytes: () => resolveClaudeVisibleOutputMaxBytes()
2821
4390
  };
2822
4391
  function runClaudeInvocationAuthenticationCommand(args, options) {
2823
4392
  return runHostInvocationAuthenticationCommand(CLAUDE_INVOCATION_AUTHENTICATION_DESCRIPTOR, args, options);
2824
4393
  }
2825
4394
 
2826
4395
  // host/client-doctor.ts
2827
- import { createHash as createHash4 } from "node:crypto";
4396
+ import { createHash as createHash5 } from "node:crypto";
2828
4397
  import { execFile } from "node:child_process";
2829
- import { access, open as open4, readFile as readFile3, realpath, stat } from "node:fs/promises";
2830
- import { delimiter, isAbsolute, join as join4 } from "node:path";
4398
+ import { access, open as open5, readFile as readFile4, realpath, stat } from "node:fs/promises";
4399
+ import { delimiter, isAbsolute as isAbsolute2, join as join6 } from "node:path";
2831
4400
  import { promisify } from "node:util";
2832
4401
  var execFileAsync = promisify(execFile);
2833
4402
  var GIT_OID = /^[a-f0-9]{40}$/u;
2834
4403
  var SHA2562 = /^[a-f0-9]{64}$/u;
2835
- var SAFE_REF6 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
4404
+ var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
2836
4405
  var CEAL_ENVIRONMENT_OVERRIDES = ["CEAL_GATEWAY_ORIGIN", "CEAL_GATEWAY_TRANSPORT_ORIGIN", "CEAL_ACCESS_TOKEN", "CEAL_CONTEXT_STORE_PATH"];
2837
4406
  async function doctorClient(options = {}, component) {
2838
4407
  const environment = canonicalClientEnvironment(options.environment ?? process.env);
@@ -2882,8 +4451,8 @@ function probeFailure(probe, command, component, options) {
2882
4451
  const refusal = hostClientProbeRefusal(probe, { component, command, executable_identity: executableIdentity(options) });
2883
4452
  return inspectedFailure(probeCode(refusal.code), refusal.message, refusal.next_action);
2884
4453
  }
2885
- function probeCode(code3) {
2886
- if (code3 === "ceal_execution_failed" || code3 === "ceal_command_refused") return code3;
4454
+ function probeCode(code2) {
4455
+ if (code2 === "ceal_execution_failed" || code2 === "ceal_command_refused") return code2;
2887
4456
  return "ceal_output_unreadable";
2888
4457
  }
2889
4458
  async function inspectCanonicalContext(options, executable, environment, run, component) {
@@ -2892,8 +4461,8 @@ async function inspectCanonicalContext(options, executable, environment, run, co
2892
4461
  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}.`);
2893
4462
  return { ok: true, value: probe.value };
2894
4463
  }
2895
- function inspectedFailure(code3, message, nextAction) {
2896
- return { ok: false, failure: failure(code3, message, nextAction) };
4464
+ function inspectedFailure(code2, message, nextAction) {
4465
+ return { ok: false, failure: failure(code2, message, nextAction) };
2897
4466
  }
2898
4467
  function canonicalClientEnvironment(input) {
2899
4468
  const environment = { ...input };
@@ -2901,7 +4470,7 @@ function canonicalClientEnvironment(input) {
2901
4470
  return environment;
2902
4471
  }
2903
4472
  async function resolveCealExecutable(command, environment) {
2904
- const candidates = isAbsolute(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0).map((entry) => join4(entry, command));
4473
+ const candidates = isAbsolute2(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0).map((entry) => join6(entry, command));
2905
4474
  for (const candidate of candidates) {
2906
4475
  try {
2907
4476
  await access(candidate);
@@ -2909,7 +4478,7 @@ async function resolveCealExecutable(command, environment) {
2909
4478
  const metadata = await stat(path);
2910
4479
  if (!metadata.isFile()) continue;
2911
4480
  if (await isLauncher(path)) return resolveLauncherBinary(path, environment);
2912
- return { path, sha256: createHash4("sha256").update(await readFile3(path)).digest("hex") };
4481
+ return { path, sha256: createHash5("sha256").update(await readFile4(path)).digest("hex") };
2913
4482
  } catch {
2914
4483
  }
2915
4484
  }
@@ -2917,7 +4486,7 @@ async function resolveCealExecutable(command, environment) {
2917
4486
  }
2918
4487
  async function isLauncher(path) {
2919
4488
  if (/\.(?:bat|cjs|cmd|js|mjs|ps1)$/u.test(path)) return true;
2920
- const handle = await open4(path, "r");
4489
+ const handle = await open5(path, "r");
2921
4490
  try {
2922
4491
  const buffer = Buffer.alloc(128);
2923
4492
  const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
@@ -2935,18 +4504,18 @@ async function resolveLauncherBinary(launcherPath, environment) {
2935
4504
  } catch {
2936
4505
  throw new Error("launcher doctor returned unreadable output");
2937
4506
  }
2938
- 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");
4507
+ 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");
2939
4508
  const binaryPath = await realpath(parsed.binary_path);
2940
4509
  const metadata = await stat(binaryPath);
2941
4510
  if (!metadata.isFile()) throw new Error("launcher doctor reported a non-file binary path");
2942
- return { path: binaryPath, sha256: createHash4("sha256").update(await readFile3(binaryPath)).digest("hex") };
4511
+ return { path: binaryPath, sha256: createHash5("sha256").update(await readFile4(binaryPath)).digest("hex") };
2943
4512
  }
2944
4513
  async function executeCealCommand(executable, args, environment) {
2945
4514
  try {
2946
4515
  const result = await execFileAsync(executable, [...args], { env: environment, timeout: 15e3, encoding: "utf8" });
2947
4516
  return { exit_code: 0, stdout: result.stdout, stderr: result.stderr };
2948
4517
  } catch (error) {
2949
- 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 : "" };
4518
+ 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 : "" };
2950
4519
  throw error;
2951
4520
  }
2952
4521
  }
@@ -2957,20 +4526,20 @@ function commandDocument(result) {
2957
4526
  if (result === null) return null;
2958
4527
  try {
2959
4528
  const value = JSON.parse(result.stdout);
2960
- return isRecord2(value) ? value : null;
4529
+ return isRecord5(value) ? value : null;
2961
4530
  } catch {
2962
4531
  return null;
2963
4532
  }
2964
4533
  }
2965
4534
  function selectedContext(value) {
2966
4535
  if (value?.schema_version !== "ceal.access-group.v1" || value.ok !== true || !Array.isArray(value.contexts)) return null;
2967
- const selected = value.contexts.filter((item) => isRecord2(item) && item.selected === true);
4536
+ const selected = value.contexts.filter((item) => isRecord5(item) && item.selected === true);
2968
4537
  if (selected.length !== 1) return null;
2969
4538
  return selectedContextRecord(selected[0]);
2970
4539
  }
2971
4540
  function selectedContextRecord(item) {
2972
- if (!isRecord2(item) || typeof item.name !== "string" || typeof item.instance_ref !== "string") return null;
2973
- if (!SAFE_REF6.test(item.instance_ref)) return null;
4541
+ if (!isRecord5(item) || typeof item.name !== "string" || typeof item.instance_ref !== "string") return null;
4542
+ if (!SAFE_REF5.test(item.instance_ref)) return null;
2974
4543
  return { name: item.name, instance_ref: item.instance_ref };
2975
4544
  }
2976
4545
  function versionIdentity(value) {
@@ -2979,12 +4548,12 @@ function versionIdentity(value) {
2979
4548
  return { version: value.version, source_commit: value.source_commit, source_tree: value.source_tree };
2980
4549
  }
2981
4550
  function validExpectations(options) {
2982
- 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));
4551
+ 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));
2983
4552
  }
2984
- function failure(code3, message, next_action) {
2985
- return { ok: false, status: "failure", error: { code: code3, message, next_action } };
4553
+ function failure(code2, message, next_action) {
4554
+ return { ok: false, status: "failure", error: { code: code2, message, next_action } };
2986
4555
  }
2987
- function isRecord2(value) {
4556
+ function isRecord5(value) {
2988
4557
  return value !== null && typeof value === "object" && !Array.isArray(value);
2989
4558
  }
2990
4559