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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -153,6 +153,9 @@ var client_wire_contract_default = {
153
153
  "document"
154
154
  ],
155
155
  optional_fields: [
156
+ "operation_id",
157
+ "visible_output_max_bytes",
158
+ "local_delivery",
156
159
  "verified_artifact"
157
160
  ],
158
161
  bare_document_fields: [
@@ -266,6 +269,7 @@ var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verifie
266
269
  var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
267
270
  var CEAL_PROTOCOL_VERSION = "1.4.0";
268
271
  var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
272
+ var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
269
273
  var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
270
274
  "audited_call_pending",
271
275
  "continuation_read_pending",
@@ -289,6 +293,7 @@ var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
289
293
  // packages/ceal-client-protocol/src/operation-next-actions.ts
290
294
  var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
291
295
  var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
296
+ var CEAL_ANSWER_WITH_GAP_NEXT_ACTION = "answer_with_gap";
292
297
  var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
293
298
  "select_did_you_mean",
294
299
  "choose_new_output_path",
@@ -342,10 +347,20 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
342
347
  "retry_after",
343
348
  // The ref names a container of rows (a Notion database or data source), which
344
349
  // has no body of its own; the caller searches its rows instead of reading it.
345
- "search_container_rows"
350
+ "search_container_rows",
351
+ // A settled unknown write names the exact receipt Operation that can inspect
352
+ // its outcome without issuing another provider mutation.
353
+ "write_receipt.get",
354
+ // Command resolution carries the exact required Operation sequence beside
355
+ // this action, so the caller can execute each declared input in order.
356
+ "follow_required_action_sequence",
357
+ // The command needs caller-owned local materialization (for example an
358
+ // @path file) before the Gateway can receive the argument.
359
+ "materialize_local_argument"
346
360
  ];
347
361
  var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
348
362
  var NON_REMEDY_NEXT_ACTION_VALUES = [
363
+ CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
349
364
  // Only an administrator can take the named step -- approving a place for an
350
365
  // Operation, granting a withheld capability -- so a non-administrator asks
351
366
  // one. The refusal names the administrator step; this word is the ask.
@@ -393,7 +408,58 @@ var NON_REMEDY_NEXT_ACTION_VALUES = [
393
408
  "select_exact_approval"
394
409
  ];
395
410
  var CEAL_NON_REMEDY_NEXT_ACTIONS = Object.freeze(NON_REMEDY_NEXT_ACTION_VALUES);
396
- var DECLARED = /* @__PURE__ */ new Set([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
411
+ var CEAL_DECLARED_NEXT_ACTIONS = Object.freeze([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
412
+ var DECLARED = new Set(CEAL_DECLARED_NEXT_ACTIONS);
413
+ function defineCealNonModelNextActions(actions) {
414
+ return actions;
415
+ }
416
+ var NON_MODEL_NEXT_ACTION_VALUES = defineCealNonModelNextActions([
417
+ CEAL_INSPECT_RECEIPT_NEXT_ACTION,
418
+ "await_pending_approval",
419
+ "inspect_gateway",
420
+ "operator_action",
421
+ "none",
422
+ "check_gateway",
423
+ "inspect_context_store",
424
+ "inspect_local_cache",
425
+ "inspect_local_client",
426
+ "inspect_local_command",
427
+ "inspect_local_output",
428
+ "inspect_local_route",
429
+ "inspect_request",
430
+ "reconcile",
431
+ "reconcile_occurrence",
432
+ "retry_after_owner_recovery",
433
+ "select_exact_approval"
434
+ ]);
435
+ var NON_MODEL_NEXT_ACTIONS = new Set(NON_MODEL_NEXT_ACTION_VALUES);
436
+ function isCealModelActionableNonRemedyNextAction(action) {
437
+ return !NON_MODEL_NEXT_ACTIONS.has(action);
438
+ }
439
+ var CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS = Object.freeze([
440
+ ...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS,
441
+ ...CEAL_NON_REMEDY_NEXT_ACTIONS.filter(isCealModelActionableNonRemedyNextAction)
442
+ ]);
443
+ var MODEL_ACTIONABLE = new Set(CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS);
444
+ var NON_MODEL_ACTION_TO_MODEL_ACTION = Object.freeze({
445
+ [CEAL_INSPECT_RECEIPT_NEXT_ACTION]: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
446
+ await_pending_approval: "ask_admin",
447
+ inspect_gateway: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
448
+ operator_action: "ask_admin",
449
+ none: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
450
+ check_gateway: "ask_admin",
451
+ inspect_context_store: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
452
+ inspect_local_cache: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
453
+ inspect_local_client: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
454
+ inspect_local_command: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
455
+ inspect_local_output: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
456
+ inspect_local_route: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
457
+ inspect_request: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
458
+ reconcile: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
459
+ reconcile_occurrence: "ask_admin",
460
+ retry_after_owner_recovery: "ask_admin",
461
+ select_exact_approval: "ask_admin"
462
+ });
397
463
  var RETRY_PROMISE_NEXT_ACTIONS = ["retry", "retry_after"];
398
464
  var RETRY_PROMISE = new Set(RETRY_PROMISE_NEXT_ACTIONS);
399
465
  var RECOVERABLE = new Set(CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS);
@@ -450,9 +516,9 @@ var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
450
516
  var CealProtocolValidationError = class extends Error {
451
517
  name = "CealProtocolValidationError";
452
518
  code;
453
- constructor(code3) {
454
- super(code3 === "invalid_gateway_request" ? "Ceal Gateway request is invalid." : "Ceal client response is invalid.");
455
- this.code = code3;
519
+ constructor(code2) {
520
+ super(code2 === "invalid_gateway_request" ? "Ceal Gateway request is invalid." : "Ceal client response is invalid.");
521
+ this.code = code2;
456
522
  }
457
523
  };
458
524
  var SAFE_REF = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
@@ -485,10 +551,10 @@ function assertSafeJsonArray(value, options, depth, count) {
485
551
  if (value.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
486
552
  for (const item of value) assertSafeJsonValue(item, options, depth + 1, count);
487
553
  }
488
- function assertSafeJsonRecord(record6, options, depth, count) {
489
- const entries = Object.entries(record6);
554
+ function assertSafeJsonRecord(record5, options, depth, count) {
555
+ const entries = Object.entries(record5);
490
556
  if (entries.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
491
- const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record6.fields) : null;
557
+ const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record5.fields) : null;
492
558
  for (const [key, child] of entries) {
493
559
  if (key === "credential_material_included" && child !== false) invalidByContext(options);
494
560
  if (!isSafeNegativeMaterialAssertion(key, child)) assertSafeJsonKey(key, options);
@@ -663,21 +729,15 @@ var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
663
729
  // packages/ceal-client-protocol/src/domain-vocabulary.ts
664
730
  var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
665
731
  var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
666
- var CEAL_PROVIDER_BOUNDARY_KINDS = vocabulary([
667
- "github_account",
668
- "github_organization",
669
- "google_workspace_domain",
670
- "notion_workspace",
671
- "slack_workspace"
672
- ]);
673
- var CEAL_PROVIDER_BOUNDARY_EVIDENCE = vocabulary(["provider_readback", "operator_stated"]);
674
732
  var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
675
733
  var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
676
734
  var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
677
735
  var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
678
736
  var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
679
- var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
737
+ var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
738
+ var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
680
739
  var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
740
+ var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
681
741
  var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
682
742
 
683
743
  // packages/ceal-client-protocol/src/safe-json-budget.ts
@@ -853,7 +913,7 @@ var CEAL_SCHEMA_LITERALS = [
853
913
  "ceal.operation_receipt.v1",
854
914
  "ceal.operation_result.v1",
855
915
  "ceal.operation_result.compact.v1",
856
- "ceal.local_result_materialization.v1",
916
+ "ceal.local_result_file.v1",
857
917
  "ceal.local_result_root.v1",
858
918
  "ceal.owner_bootstrap_exchange_request.v1",
859
919
  "ceal.owner_bootstrap_exchange_result.v1",
@@ -947,41 +1007,6 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
947
1007
  }
948
1008
  });
949
1009
 
950
- // packages/ceal-client-protocol/src/provider-operation-vocabulary.ts
951
- var CEAL_FILE_TYPE_FAMILIES = vocabulary(["all", "image", "pdf"]);
952
- var CEAL_GITHUB_ISSUE_STATES = vocabulary(["open", "closed", "all"]);
953
- var CEAL_NOTION_UPDATABLE_BLOCK_TYPES = vocabulary([
954
- "paragraph",
955
- "heading_1",
956
- "heading_2",
957
- "heading_3",
958
- "bulleted_list_item",
959
- "numbered_list_item",
960
- "to_do",
961
- "code"
962
- ]);
963
- var CEAL_NOTION_NEUTRAL_TYPES = vocabulary([
964
- "text",
965
- "number",
966
- "boolean",
967
- "date",
968
- "select",
969
- "multi_select",
970
- "people",
971
- "url",
972
- "email",
973
- "phone",
974
- "unsupported"
975
- ]);
976
- var CEAL_NOTION_IDENTITY_GAP_REASONS = vocabulary([
977
- "identity_unavailable",
978
- "identity_stale",
979
- "identity_ambiguous",
980
- "identity_revoked",
981
- "identity_unlinked"
982
- ]);
983
- var CEAL_NOTION_STRING_VALUE_TYPES = vocabulary(["text", "select", "url", "email", "phone"]);
984
-
985
1010
  // packages/ceal-client-protocol/src/opaque-reference.ts
986
1011
  var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
987
1012
  var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
@@ -993,7 +1018,7 @@ var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_o
993
1018
  // packages/ceal-client-protocol/src/resource-row.ts
994
1019
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
995
1020
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
996
- var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
1021
+ 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"]);
997
1022
  var CEAL_RESOURCE_TIME_BASES = vocabulary(["created", "modified", "viewed"]);
998
1023
  var CEAL_REPOSITORY_VISIBILITIES = vocabulary(["public", "private", "internal"]);
999
1024
  var CEAL_ACTIVITY_V3_KINDS = vocabulary(["authored", "edited", "messaged", "responded"]);
@@ -1014,6 +1039,7 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
1014
1039
  "resource_modified"
1015
1040
  ]);
1016
1041
  var CEAL_ACTIVITY_CONNECTOR_KINDS = vocabulary(["slack", "github", "google-calendar", "google-drive", "notion"]);
1042
+ var CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE = "unsupported_event_kind";
1017
1043
  var CEAL_ACTIVITY_GAP_CODES = vocabulary([
1018
1044
  "provider_page_budget",
1019
1045
  "call_deadline",
@@ -1021,7 +1047,7 @@ var CEAL_ACTIVITY_GAP_CODES = vocabulary([
1021
1047
  "provider_cursor_unavailable",
1022
1048
  "malformed_provider_page",
1023
1049
  "visibility_incomplete",
1024
- "unsupported_event_kind",
1050
+ CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE,
1025
1051
  "direct_messages_excluded"
1026
1052
  ]);
1027
1053
  var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
@@ -1072,6 +1098,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1072
1098
  ref: { required: true, timestamp: false },
1073
1099
  next: { required: true, timestamp: false },
1074
1100
  kind: { required: true, timestamp: false },
1101
+ matched_kinds: { required: false, timestamp: false },
1075
1102
  title: { required: true, timestamp: false },
1076
1103
  container_label: { required: true, timestamp: false },
1077
1104
  container_ref: { required: false, timestamp: false },
@@ -1085,6 +1112,8 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1085
1112
  modified_at: { required: false, timestamp: true },
1086
1113
  modified_at_local: { required: false, timestamp: true },
1087
1114
  viewed_by_me_at: { required: false, timestamp: true },
1115
+ own_ceal_write: { required: false, timestamp: false },
1116
+ owned_by_me: { required: false, timestamp: false },
1088
1117
  visibility: { required: false, timestamp: false },
1089
1118
  start: { required: false, timestamp: false },
1090
1119
  end: { required: false, timestamp: false },
@@ -1118,6 +1147,15 @@ function cealExcludedTargetCoverageJsonSchema() {
1118
1147
  };
1119
1148
  }
1120
1149
 
1150
+ // packages/ceal-client-protocol/src/gateway-write-identity.ts
1151
+ var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1152
+ var CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN = /^gateway-write-request:[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/u;
1153
+ var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1154
+ replay_identity: "idempotency_claim_sha256",
1155
+ lookup_handle: "write_request_sha256",
1156
+ collision_evidence: "normalized_mutation_sha256"
1157
+ });
1158
+
1121
1159
  // packages/ceal-client-protocol/src/write-safety-finding.ts
1122
1160
  var CEAL_WRITE_SAFETY_FINDING_STATUSES = vocabulary(["warning", "unchecked"]);
1123
1161
  var CEAL_WRITE_SAFETY_AUDIENCES = vocabulary(["workspace", "person", "members", "unknown"]);
@@ -1145,7 +1183,11 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
1145
1183
  "refresh_revoked",
1146
1184
  "authority_replaced",
1147
1185
  "refresh_recovery_unavailable",
1148
- "refresh_recovery_expired"
1186
+ "refresh_recovery_expired",
1187
+ // The serving gate answers this while the Gateway is still starting and the
1188
+ // refresh was never attempted; the same attempt stays retryable. It is never
1189
+ // stored by the session owner, only answered at the serving edge.
1190
+ "gateway_starting"
1149
1191
  ]);
1150
1192
 
1151
1193
  // packages/ceal-client-protocol/src/request-validation-primitives.ts
@@ -1178,7 +1220,7 @@ var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
1178
1220
  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"]);
1179
1221
  var CEAL_REQUEST_JSON_SCHEMA = {
1180
1222
  type: "object",
1181
- maxProperties: 10,
1223
+ maxProperties: 11,
1182
1224
  additionalProperties: false,
1183
1225
  properties: {
1184
1226
  schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
@@ -1202,7 +1244,8 @@ var CEAL_REQUEST_JSON_SCHEMA = {
1202
1244
  host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1203
1245
  host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1204
1246
  host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1205
- requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." }
1247
+ requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
1248
+ 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." }
1206
1249
  },
1207
1250
  required: CEAL_REQUEST_KEYS
1208
1251
  };
@@ -1261,6 +1304,7 @@ var REQUEST_EXECUTION_PREPARE_REFUSAL_PROPERTIES = {
1261
1304
  retryable: { type: "boolean" },
1262
1305
  source: { type: "string", enum: ["provider", "gateway"] },
1263
1306
  retry_after_ms: { type: "integer", minimum: 0, maximum: CEAL_RETRY_AFTER_MAX_MS },
1307
+ write_request_ref: { type: "string", pattern: CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN.source, maxLength: 128 },
1264
1308
  excluded_targets: cealExcludedTargetCoverageJsonSchema(),
1265
1309
  repaired_input: {},
1266
1310
  unrepaired_pointers: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 256 } },
@@ -1319,12 +1363,55 @@ function isCealHostInputKind(value) {
1319
1363
  return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
1320
1364
  }
1321
1365
  var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
1366
+ var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
1367
+ var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
1368
+ function cealHostObservationRef(producer, digest2) {
1369
+ if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(digest2)) invalid("Ceal Host observation reference parts are invalid.");
1370
+ return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${digest2}`;
1371
+ }
1372
+ var HOST_TOKEN_OBSERVATION_BREAKDOWN_KEYS = vocabulary(["cached_input", "cache_write_input", "reasoning_output"]);
1373
+ function createHostTokenObservationValue(counts, source) {
1374
+ const core = validTokenCore(counts);
1375
+ if (typeof source !== "string" || source.length === 0 || source.length > 256) invalid("Ceal Host token observation source is invalid.");
1376
+ const value = { status: "available", unit: "tokens", input: core.input, output: core.output, total: core.total, source };
1377
+ for (const breakdown of validTokenBreakdowns(counts)) value[breakdown.key] = breakdown.tokens;
1378
+ assertHostObservationValue(value, "token");
1379
+ return value;
1380
+ }
1381
+ function validTokenCore(counts) {
1382
+ if (!isOperationRecord(counts) || !safeCount(counts.input) || !safeCount(counts.output) || !safeCount(counts.total)) invalid("Ceal Host token observation counts are invalid.");
1383
+ if (counts.total !== counts.input + counts.output) invalid("Ceal Host token observation counts are invalid.");
1384
+ return { input: counts.input, output: counts.output, total: counts.total };
1385
+ }
1386
+ function validTokenBreakdowns(counts) {
1387
+ const breakdowns = [
1388
+ { key: "cached_input", tokens: counts.cached_input },
1389
+ { key: "cache_write_input", tokens: counts.cache_write_input },
1390
+ { key: "reasoning_output", tokens: counts.reasoning_output }
1391
+ ];
1392
+ const stated = breakdowns.filter((breakdown) => breakdown.tokens !== void 0).map((breakdown) => ({ key: breakdown.key, tokens: Number(breakdown.tokens) }));
1393
+ for (const breakdown of stated) {
1394
+ if (!safeCount(breakdown.tokens)) invalid("Ceal Host token observation counts are invalid.");
1395
+ }
1396
+ const cached = counts.cached_input ?? 0;
1397
+ const created = counts.cache_write_input ?? 0;
1398
+ const reasoning = counts.reasoning_output ?? 0;
1399
+ if (cached + created > counts.input || reasoning > counts.output) invalid("Ceal Host token observation counts are invalid.");
1400
+ return stated;
1401
+ }
1402
+ function hostObservationNotRecorded(reason) {
1403
+ if (typeof reason !== "string" || reason.length === 0 || reason.length > 256) invalid("Ceal Host observation gap reason is invalid.");
1404
+ return { status: "not_recorded", reason };
1405
+ }
1406
+ function safeCount(value) {
1407
+ return Number.isSafeInteger(value) && Number(value) >= 0;
1408
+ }
1322
1409
  function validateCealHostObservationDraft(value) {
1323
- const record6 = operationRecord(value, "Ceal Host observation draft is invalid.");
1324
- requirePrefixedRef(record6.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1325
- requireClosedValue(record6.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1326
- requireIsoTimestamp(record6.observed_at, "Ceal Host observation time is invalid.");
1327
- assertHostObservationValue(record6.value, record6.observation_kind);
1410
+ const record5 = operationRecord(value, "Ceal Host observation draft is invalid.");
1411
+ requirePrefixedRef(record5.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1412
+ requireClosedValue(record5.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1413
+ requireIsoTimestamp(record5.observed_at, "Ceal Host observation time is invalid.");
1414
+ assertHostObservationValue(record5.value, record5.observation_kind);
1328
1415
  }
1329
1416
  var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
1330
1417
  var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "cost", "timing"]);
@@ -1354,6 +1441,10 @@ function inspectHostKeys(value) {
1354
1441
  // packages/ceal-client-protocol/src/host-prompt-ref.ts
1355
1442
  var CEAL_HOST_PROMPT_DIGEST_PATTERN = "^[a-f0-9]{64}$";
1356
1443
  var PROMPT_DIGEST = new RegExp(CEAL_HOST_PROMPT_DIGEST_PATTERN, "u");
1444
+ function hostProducerPromptRef(promptSha256) {
1445
+ if (!PROMPT_DIGEST.test(promptSha256)) throw new TypeError("Canonical Host prompt digest is invalid.");
1446
+ return `ceal-host-prompt:${promptSha256}`;
1447
+ }
1357
1448
 
1358
1449
  // packages/ceal-client-protocol/src/request-effect-approval.ts
1359
1450
  var CEAL_EFFECT_APPROVAL_DECISION_CHECK_PHRASES = Object.freeze([
@@ -1456,12 +1547,94 @@ var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = new TextEncoder().enc
1456
1547
  expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
1457
1548
  })}
1458
1549
  `).byteLength;
1550
+ var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
1551
+ "request_authority_unavailable",
1552
+ "request_missing",
1553
+ "authority_mismatch",
1554
+ "host_source_mismatch",
1555
+ "host_session_mismatch",
1556
+ "host_turn_mismatch",
1557
+ "host_input_mismatch",
1558
+ "host_prompt_mismatch"
1559
+ ]);
1560
+ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
1561
+ "invalid_arguments",
1562
+ "options_invalid",
1563
+ "session_unavailable",
1564
+ "context_unavailable",
1565
+ "context_replaced",
1566
+ "no_binding",
1567
+ "context_stale",
1568
+ "context_expired",
1569
+ "host_source_refused",
1570
+ "host_credential_refused",
1571
+ "gateway_unavailable",
1572
+ "gateway_rate_limited",
1573
+ "gateway_refused",
1574
+ "gateway_response_invalid",
1575
+ "producer_frame_invalid",
1576
+ "relay_permission_denied",
1577
+ "relay_unavailable",
1578
+ "transient_retryable",
1579
+ "mint_failed",
1580
+ "relay_failed",
1581
+ "host_refusal_invalid"
1582
+ ]);
1583
+ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1584
+ invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1585
+ options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1586
+ session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1587
+ context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "refresh_host_invocation_context", remedy: "the Host has no invocation context from the current transcript turn; run `ceal-host install` to repair the Host hook registration, then retry this command on a fresh Host turn" }),
1588
+ context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1589
+ no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1590
+ context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1591
+ context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "refresh_host_invocation_context" }),
1592
+ host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
1593
+ host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
1594
+ gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
1595
+ gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
1596
+ gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
1597
+ gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
1598
+ 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" }),
1599
+ 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" }),
1600
+ relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
1601
+ transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
1602
+ mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
1603
+ relay_failed: Object.freeze({ classification: "host_relay_failed", next_action: "answer_with_gap" }),
1604
+ host_refusal_invalid: Object.freeze({ classification: "host_refusal_invalid", next_action: "answer_with_gap" })
1605
+ });
1606
+
1607
+ // packages/ceal-client-protocol/src/host-service-diagnostic.ts
1608
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
1609
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
1610
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1611
+ "hook_invoked",
1612
+ "registration_written",
1613
+ "hook_failed",
1614
+ "watcher_turn_observed",
1615
+ "current_prompt_registered",
1616
+ "current_prompt_registration_failed",
1617
+ "activation_recorded",
1618
+ "activation_failed",
1619
+ "watcher_scan_failed",
1620
+ "activation_wait_skipped",
1621
+ "activation_wait_engaged",
1622
+ "activation_wait_completed",
1623
+ "activation_wait_exhausted"
1624
+ ]);
1625
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_FAILURE_KINDS = vocabulary([
1626
+ "malformed_line",
1627
+ "other",
1628
+ "payload_rejected",
1629
+ "read_error",
1630
+ "size_budget",
1631
+ "symlink"
1632
+ ]);
1459
1633
 
1460
1634
  // packages/ceal-client-protocol/src/client-result-materialization.ts
1461
1635
  var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
1462
- var CEAL_MODEL_VISIBLE_COMMAND_OUTPUT_MAX_BYTES = 32 * 1024;
1463
- var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id"]);
1464
- var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "read_argv", "local_result_root", "continue_argv", "model_disclosure", "item_read_argv"]);
1636
+ var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
1637
+ var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
1465
1638
 
1466
1639
  // packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
1467
1640
  var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -1491,14 +1664,6 @@ var CEAL_GATEWAY_ANNOUNCEMENT_POLICY_NON_CLAIMS = vocabulary([
1491
1664
  "target_specific_scope_not_declared"
1492
1665
  ]);
1493
1666
 
1494
- // packages/ceal-client-protocol/src/gateway-write-identity.ts
1495
- var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1496
- var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1497
- replay_identity: "idempotency_claim_sha256",
1498
- lookup_handle: "write_request_sha256",
1499
- collision_evidence: "normalized_mutation_sha256"
1500
- });
1501
-
1502
1667
  // packages/ceal-client-protocol/src/gateway-scoped-identity-projection-validation.ts
1503
1668
  var PROJECTION_REVISION_KEYS = Object.freeze(["graph_revision", "subject_key_revision", "projection_revision"]);
1504
1669
 
@@ -1511,6 +1676,18 @@ var CEAL_SUPPORTED_GATEWAY_PROTOCOL_RANGE = Object.freeze({
1511
1676
  // packages/ceal-client-protocol/src/admin-projection-vocabulary.ts
1512
1677
  var ADMIN_PROJECTION_STATES = vocabulary(["ready", "empty", "partial", "unknown"]);
1513
1678
  var ADMIN_VIEW_KINDS = vocabulary(["request_detail", "resource_access_timeline", "weekly_usage"]);
1679
+ var ADMIN_RECORD_PROJECTION_KINDS = vocabulary([
1680
+ "request_list",
1681
+ "usage_series",
1682
+ "connector_health",
1683
+ "person_connections",
1684
+ "client_sessions",
1685
+ "knowledge_base",
1686
+ "change_history",
1687
+ "approval_queue",
1688
+ "access_group_list",
1689
+ "capability_catalogue"
1690
+ ]);
1514
1691
  var ADMIN_GAP_KINDS = vocabulary(["unknown", "unavailable", "partial"]);
1515
1692
  var ADMIN_UNAVAILABLE_REASONS = vocabulary([
1516
1693
  "not_recorded",
@@ -1534,7 +1711,11 @@ var ADMIN_SOURCE_OWNERS = vocabulary([
1534
1711
  "person",
1535
1712
  "resource",
1536
1713
  "prompt",
1537
- "usage"
1714
+ "usage",
1715
+ "connector",
1716
+ "session",
1717
+ "knowledge",
1718
+ "policy"
1538
1719
  ]);
1539
1720
  var ADMIN_COMPLETENESS_STATES = vocabulary(["complete", "partial", "unknown"]);
1540
1721
  var ADMIN_OPERATION_OUTCOMES = vocabulary(["completed", "failed", "denied", "cancelled", "unknown"]);
@@ -1578,14 +1759,19 @@ var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
1578
1759
  var HOST_PRODUCER_SERVE_COMMAND_PREFIX = ["serve", "--config"];
1579
1760
  var HostProducerServiceError = class extends Error {
1580
1761
  code;
1581
- constructor(code3, message = code3) {
1582
- super(message === code3 ? code3 : `${code3}: ${message}`);
1762
+ constructor(code2, message = code2) {
1763
+ super(message === code2 ? code2 : `${code2}: ${message}`);
1583
1764
  this.name = "HostProducerServiceError";
1584
- this.code = code3;
1765
+ this.code = code2;
1585
1766
  }
1586
1767
  };
1587
1768
  var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
1588
1769
  var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
1770
+ function hostProducerExtraPath(extras, key) {
1771
+ const value = extras[key];
1772
+ if (value === void 0) throw hostProducerError("invalid_config");
1773
+ return value;
1774
+ }
1589
1775
  function parseHostProducerServeArgs(args, platform = process.platform) {
1590
1776
  return parseProducerServeArgs(args, platform, hostProducerError);
1591
1777
  }
@@ -1612,8 +1798,8 @@ async function serveHostProducer(descriptor, config, signal, dependencies = {})
1612
1798
  async function runHostProducerCli(descriptor, args, signal, dependencies = {}) {
1613
1799
  await serveHostProducer(descriptor, await readHostProducerConfig(descriptor, parseHostProducerServeArgs(args)), signal, dependencies);
1614
1800
  }
1615
- function hostProducerError(code3, message = code3) {
1616
- return new HostProducerServiceError(code3, message);
1801
+ function hostProducerError(code2, message = code2) {
1802
+ return new HostProducerServiceError(code2, message);
1617
1803
  }
1618
1804
  function parseProducerServeArgs(args, platform, error) {
1619
1805
  if (args.length !== HOST_PRODUCER_SERVE_COMMAND_PREFIX.length + 1 || args[0] !== HOST_PRODUCER_SERVE_COMMAND_PREFIX[0] || args[1] !== HOST_PRODUCER_SERVE_COMMAND_PREFIX[1]) throw error("invalid_arguments", refusalFact("args", args, "serve --config <absolute path>"));
@@ -1764,11 +1950,20 @@ var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
1764
1950
  function isHostProducerFrameKind(value) {
1765
1951
  return typeof value === "string" && FRAME_KINDS.has(value);
1766
1952
  }
1953
+ var HOST_PRODUCER_FRAME_ERROR_CODES = [
1954
+ "invalid_frame_encoding",
1955
+ "invalid_frame_length",
1956
+ "frame_truncated",
1957
+ "unsupported_frame_schema",
1958
+ "invalid_frame_shape",
1959
+ "invalid_producer_identity",
1960
+ "frame_too_large"
1961
+ ];
1767
1962
  var HostProducerFrameError = class extends Error {
1768
1963
  code;
1769
- constructor(code3, message = code3) {
1964
+ constructor(code2, message = code2) {
1770
1965
  super(message);
1771
- this.code = code3;
1966
+ this.code = code2;
1772
1967
  }
1773
1968
  };
1774
1969
  function encodeHostProducerFrame(frame) {
@@ -1798,6 +1993,9 @@ function decodeHostProducerFrame(input) {
1798
1993
  validateFrame(value);
1799
1994
  return value;
1800
1995
  }
1996
+ function isHostSafeRef(value) {
1997
+ return safeRef(value);
1998
+ }
1801
1999
  function validateFrame(value) {
1802
2000
  if (!record(value)) throw new HostProducerFrameError("invalid_frame_shape");
1803
2001
  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)}`);
@@ -1885,6 +2083,15 @@ function exactOptional(value, required, optional) {
1885
2083
  return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
1886
2084
  }
1887
2085
 
2086
+ // host/relay/src/host-producer-core.ts
2087
+ import { createHash as createHash3 } from "node:crypto";
2088
+ import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
2089
+ import { mkdir as mkdir2, open as open3, readFile, rename as rename2, writeFile } from "node:fs/promises";
2090
+ import { join as join2 } from "node:path";
2091
+
2092
+ // host/relay/src/relay-ipc.ts
2093
+ import { createConnection, createServer } from "node:net";
2094
+
1888
2095
  // host/relay/src/durable-atomic-replacement.ts
1889
2096
  import { O_RDONLY } from "node:constants";
1890
2097
  import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
@@ -1973,17 +2180,6 @@ function errorCode(error) {
1973
2180
  // host/relay/src/relay-request-binding-store.ts
1974
2181
  var MAX_STATE_BYTES = 8 * 1024 * 1024;
1975
2182
 
1976
- // host/relay/src/relay-quarantine-store.ts
1977
- var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
1978
-
1979
- // host/relay/src/relay-admission-store.ts
1980
- var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
1981
- var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
1982
- var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
1983
-
1984
- // host/relay/src/relay-ipc.ts
1985
- import { createConnection, createServer } from "node:net";
1986
-
1987
2183
  // host/relay/src/current-prompt-custody.ts
1988
2184
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
1989
2185
 
@@ -2004,6 +2200,8 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2004
2200
  "host_input_ref"
2005
2201
  ]);
2006
2202
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
2203
+ var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2204
+ var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
2007
2205
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2008
2206
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
2009
2207
  var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
@@ -2014,10 +2212,31 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
2014
2212
  "gateway_unavailable",
2015
2213
  "gateway_refused",
2016
2214
  "gateway_response_invalid",
2215
+ "gateway_rate_limited",
2216
+ "producer_frame_invalid",
2017
2217
  "deadline",
2018
2218
  "cancelled",
2019
2219
  "mint_failed",
2020
- "relay_failed"
2220
+ "relay_failed",
2221
+ "request_authority_unavailable",
2222
+ "request_missing",
2223
+ "authority_mismatch",
2224
+ "host_source_mismatch",
2225
+ "host_session_mismatch",
2226
+ "host_turn_mismatch",
2227
+ "host_input_mismatch",
2228
+ "host_prompt_mismatch"
2229
+ ]);
2230
+ var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
2231
+ "context_stale",
2232
+ "context_expired",
2233
+ "host_source_refused",
2234
+ "host_credential_refused",
2235
+ "gateway_unavailable",
2236
+ "gateway_rate_limited",
2237
+ "gateway_refused",
2238
+ "gateway_response_invalid",
2239
+ "producer_frame_invalid"
2021
2240
  ]);
2022
2241
  var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
2023
2242
  var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
@@ -2034,12 +2253,15 @@ var HostRelayIpcError = class extends Error {
2034
2253
  code;
2035
2254
  /** Set only when the client-side socket exchange itself failed. */
2036
2255
  failure;
2037
- constructor(code3, message = code3, failure) {
2256
+ constructor(code2, message = code2, failure) {
2038
2257
  super(message);
2039
- this.code = code3;
2258
+ this.code = code2;
2040
2259
  this.failure = failure;
2041
2260
  }
2042
2261
  };
2262
+ function hostRelayExchangeFailure(error) {
2263
+ return error instanceof HostRelayIpcError ? error.failure : void 0;
2264
+ }
2043
2265
  function socketErrorFailure(error) {
2044
2266
  switch (errorCode2(error)) {
2045
2267
  case "EACCES":
@@ -2066,6 +2288,19 @@ var HostRelayIpcAdmissionPort = class {
2066
2288
  return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
2067
2289
  }
2068
2290
  };
2291
+ var HostRelayCurrentPromptPort = class {
2292
+ endpoint;
2293
+ timeout_ms;
2294
+ constructor(endpoint, timeoutMs = 5e3) {
2295
+ this.endpoint = endpoint;
2296
+ this.timeout_ms = timeoutMs;
2297
+ }
2298
+ async register(input) {
2299
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2300
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2301
+ if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2302
+ }
2303
+ };
2069
2304
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2070
2305
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
2071
2306
  }
@@ -2144,276 +2379,235 @@ function decodeResponseBuffer(buffer) {
2144
2379
  if (buffer.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC returned trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: buffer.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
2145
2380
  return decodeHostRelayIpcResponse(buffer);
2146
2381
  }
2147
- function errorCode2(error) {
2148
- return record2(error) && typeof error.code === "string" ? error.code : null;
2149
- }
2150
-
2151
- // host/claude/src/claude-producer.ts
2152
- import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile } from "node:fs/promises";
2153
- import { join as join3 } from "node:path";
2154
-
2155
- // host/claude/src/claude-outbox.ts
2156
- import { open as open3, mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
2157
- import { join as join2 } from "node:path";
2158
-
2159
- // host/claude/src/claude-types.ts
2160
- var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "storage", "configuration"];
2161
- function isClaudeHostErrorCode(value) {
2162
- return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code3) => code3 === value);
2382
+ function decodeCurrentPromptResponseBuffer(buffer) {
2383
+ const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2384
+ if (value === null) return null;
2385
+ if (!record2(value) || !exactKeys2(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "current_prompt_unavailable") throw new HostRelayIpcError("relay_unavailable");
2386
+ return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2163
2387
  }
2164
- var CLAUDE_OUTBOX_SCHEMA = "ceal.claude_hook_outbox.v1";
2165
- var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
2166
- var ClaudeHostError = class extends Error {
2167
- name = "ClaudeHostError";
2168
- code;
2169
- constructor(code3, message) {
2170
- super(message);
2171
- if (!isClaudeHostErrorCode(code3)) throw new TypeError("Claude Host error code is invalid.");
2172
- this.code = code3;
2388
+ function decodeLengthPrefixedJson(input, maximum) {
2389
+ if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
2390
+ const length = input.readUInt32BE(0);
2391
+ if (length === 0 || length > maximum) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message length is invalid: ${refusalFact("declared_bytes", length, `between 1 and ${maximum}`)}.`);
2392
+ if (input.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
2393
+ if (input.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message has trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
2394
+ try {
2395
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
2396
+ } catch {
2397
+ throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC message is invalid.");
2173
2398
  }
2174
- };
2175
-
2176
- // host/invocation-context.ts
2177
- var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
2178
- var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
2179
- function invocationContextKey(identity) {
2180
- return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
2181
- }
2182
- function hasHostInvocationIdentityKeys(value) {
2183
- return exactKeys2(value, HOST_INVOCATION_IDENTITY_KEYS);
2184
- }
2185
- function hasHostInvocationContextKeys(value, optional = []) {
2186
- return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
2187
2399
  }
2188
- function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
2189
- const context = {
2190
- schema_version: descriptor.schema_version,
2191
- identity,
2192
- prompt_ref: promptRef,
2193
- expires_at: expiresAt,
2194
- ...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
2195
- };
2196
- descriptor.validate(context);
2197
- return Object.freeze(context);
2400
+ function encodeLengthPrefixed(value) {
2401
+ const body = Buffer.from(JSON.stringify(value), "utf8");
2402
+ const output = Buffer.allocUnsafe(body.length + HOST_RELAY_IPC_LENGTH_BYTES);
2403
+ output.writeUInt32BE(body.length, 0);
2404
+ body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
2405
+ return output;
2198
2406
  }
2199
- function exactKeys2(value, keys) {
2200
- const actual = Object.keys(value);
2201
- return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
2407
+ function exactKeys2(value, required, optional = []) {
2408
+ const keys = Object.keys(value);
2409
+ return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2202
2410
  }
2203
- function requiredKeys(value, required, optional) {
2204
- const actual = Object.keys(value);
2205
- const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
2206
- return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
2411
+ function errorCode2(error) {
2412
+ return record2(error) && typeof error.code === "string" ? error.code : null;
2207
2413
  }
2208
2414
 
2209
- // host/claude/src/claude-invocation-context.ts
2210
- function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory) {
2211
- return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory });
2212
- }
2213
- function validateClaudeHostInvocationContext(value) {
2214
- if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2215
- validateIdentity2(value.identity);
2216
- validateContextReferences(value);
2217
- validateContextTimes(value);
2218
- }
2219
- function contextShape(value) {
2220
- return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory"]);
2221
- }
2222
- function validateIdentity2(value) {
2223
- if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2224
- }
2225
- function validateContextReferences(value) {
2226
- if (!safeRef2(value.prompt_ref) || !optionalSafeRef(value.request_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2415
+ // host/relay/src/host-producer-core.ts
2416
+ var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
2417
+ function isHostTranscriptCursorReason(value) {
2418
+ return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2419
+ }
2420
+ var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2421
+ var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2422
+ async function readTranscriptCompleteChunk(handle, offset, size) {
2423
+ const available = Math.max(0, size - offset);
2424
+ if (available === 0) return "empty";
2425
+ const chunks = [];
2426
+ let position = offset;
2427
+ let total = 0;
2428
+ let lastNewline = -1;
2429
+ while (position < size) {
2430
+ const buffer = Buffer.alloc(Math.min(HOST_TRANSCRIPT_READ_CHUNK_BYTES, size - position));
2431
+ const read = await handle.read(buffer, 0, buffer.length, position);
2432
+ if (read.bytesRead === 0) break;
2433
+ const chunk = buffer.subarray(0, read.bytesRead);
2434
+ chunks.push(chunk);
2435
+ const newline = chunk.lastIndexOf(10);
2436
+ if (newline >= 0) lastNewline = total + newline;
2437
+ total += chunk.length;
2438
+ position += chunk.length;
2439
+ if (lastNewline >= 0) break;
2440
+ }
2441
+ if (lastNewline < 0) return "partial";
2442
+ return Buffer.concat(chunks, total).subarray(0, lastNewline + 1);
2443
+ }
2444
+ async function readTranscriptAnchor(handle, offset, anchorBytes) {
2445
+ const start = Math.max(0, offset - anchorBytes);
2446
+ const anchor = Buffer.alloc(offset - start);
2447
+ if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, start);
2448
+ return { start, sha256: transcriptSha256(anchor) };
2449
+ }
2450
+ async function transcriptAnchorMatches(handle, cursor) {
2451
+ const buffer = Buffer.alloc(cursor.offset - cursor.anchor_start);
2452
+ if (buffer.length > 0) await handle.read(buffer, 0, buffer.length, cursor.anchor_start);
2453
+ return transcriptSha256(buffer) === cursor.anchor_sha256;
2454
+ }
2455
+ function transcriptCursorIdentityReason(cursor, transcriptPath, device, inode) {
2456
+ if (cursor.device !== device || cursor.inode !== inode || cursor.transcript_path !== transcriptPath) return "transcript_replaced";
2457
+ return null;
2227
2458
  }
2228
- function validateContextTimes(value) {
2229
- if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2230
- 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)}`)}).`);
2459
+ async function transcriptCursorBoundsReason(handle, cursor, size) {
2460
+ if (size < cursor.offset) return "transcript_truncated";
2461
+ if (cursor.offset > 0 && !await transcriptAnchorMatches(handle, cursor)) return "transcript_rewritten";
2462
+ return null;
2231
2463
  }
2232
- function validIdentity(value) {
2233
- return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef2);
2464
+ function transcriptSha256(value) {
2465
+ return createHash3("sha256").update(value).digest("hex");
2234
2466
  }
2235
- function optionalSafeRef(value) {
2236
- return value === void 0 || safeRef2(value);
2467
+ function transcriptObjectSha256(value) {
2468
+ return createHash3("sha256").update(JSON.stringify(value), "utf8").digest("hex");
2237
2469
  }
2238
- function optionalTimestamp(value) {
2239
- return value === void 0 || timestamp2(value);
2470
+ function isTranscriptRecord(value) {
2471
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2240
2472
  }
2241
- function optionalDirectory(value) {
2242
- return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
2473
+ function isMissingTranscriptError(error) {
2474
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2243
2475
  }
2244
- function safeRef2(value) {
2245
- return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2476
+ function transcriptTimestamp(now, fail) {
2477
+ const value = now();
2478
+ if (!Number.isFinite(value.getTime())) throw fail();
2479
+ return value.toISOString();
2246
2480
  }
2247
- function timestamp2(value) {
2248
- return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
2481
+ function transcriptExpiration(now, retentionMs, fail) {
2482
+ return new Date(Date.parse(transcriptTimestamp(now, fail)) + retentionMs).toISOString();
2249
2483
  }
2250
- function record3(value) {
2251
- return value !== null && typeof value === "object" && !Array.isArray(value);
2484
+ function createTranscriptWatcherBase(options, owner) {
2485
+ if (typeof options.outbox_dir !== "string" || options.outbox_dir.length === 0) throw owner.invalid_outbox();
2486
+ return {
2487
+ outboxRetentionMs: options.retention_ms ?? owner.default_retention_ms,
2488
+ now: options.now ?? (() => /* @__PURE__ */ new Date()),
2489
+ log: options.log ?? ((line) => {
2490
+ process.stderr.write(`${line}
2491
+ `);
2492
+ }),
2493
+ producerInstallationRef: options.producer_installation_ref
2494
+ };
2252
2495
  }
2253
-
2254
- // host/claude/src/claude-outbox.ts
2255
- var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2256
- var ClaudeOutboxStore = class {
2257
- entries;
2258
- directory;
2259
- activeContexts;
2260
- constructor(directory) {
2261
- if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2262
- this.directory = directory;
2263
- this.entries = join2(directory, "entries");
2264
- this.activeContexts = join2(directory, "active-contexts");
2265
- }
2266
- async prepare() {
2267
- await mkdir2(this.entries, { recursive: true, mode: 448 });
2268
- await mkdir2(this.activeContexts, { recursive: true, mode: 448 });
2496
+ async function scanTranscriptRegistrations(registrations, scanOne) {
2497
+ let records = 0;
2498
+ let enqueued = 0;
2499
+ let quarantined = 0;
2500
+ for (const registration of registrations) {
2501
+ const result = await scanOne(registration);
2502
+ records += result.records;
2503
+ enqueued += result.enqueued;
2504
+ if (result.quarantined) quarantined += 1;
2269
2505
  }
2270
- async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory) {
2271
- const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory);
2272
- await this.prepare();
2273
- await durableAtomicReplace(this.activeContextPath(identity), JSON.stringify(context));
2506
+ return { registrations: registrations.length, records, enqueued, quarantined };
2507
+ }
2508
+ function formatRetiredTranscriptLog(producerLabel, transcriptPath) {
2509
+ return `${producerLabel} retired a transcript registration: transcript_path=${JSON.stringify(transcriptPath)}, reason=${TRANSCRIPT_MISSING_REASON}.`;
2510
+ }
2511
+ var HostTranscriptWatcher = class {
2512
+ outboxRetentionMs;
2513
+ now;
2514
+ log;
2515
+ producerInstallationRef;
2516
+ clockFailure;
2517
+ constructor(options, owner) {
2518
+ const base = createTranscriptWatcherBase(options, owner);
2519
+ this.outboxRetentionMs = base.outboxRetentionMs;
2520
+ this.now = base.now;
2521
+ this.log = base.log;
2522
+ this.producerInstallationRef = base.producerInstallationRef;
2523
+ this.clockFailure = owner.clock_failure;
2274
2524
  }
2275
- async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
2276
- validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now);
2277
- await this.prepare();
2278
- return readInvocationContextFile(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, now);
2525
+ async scanOnce() {
2526
+ return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2279
2527
  }
2280
- async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
2281
- await this.prepare();
2282
- const contexts = [];
2283
- for (const name of await readdir(this.activeContexts)) {
2284
- if (!name.endsWith(".json")) continue;
2285
- try {
2286
- const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
2287
- validateClaudeHostInvocationContext(value);
2288
- if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
2289
- } catch {
2290
- }
2291
- }
2292
- return contexts;
2528
+ async scanRegistration(registration) {
2529
+ return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2530
+ missing: () => this.retireMissing(registration),
2531
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2532
+ });
2293
2533
  }
2294
- async put(batch) {
2295
- validateBatch(batch);
2296
- await this.prepare();
2297
- const path = this.path(batch.batch_ref);
2534
+ async openTranscript(registration, scan, handlers) {
2535
+ let handle;
2298
2536
  try {
2299
- const handle = await open3(path, "wx", 384);
2300
- try {
2301
- await handle.writeFile(JSON.stringify(batch));
2302
- await handle.sync();
2303
- } finally {
2304
- await handle.close();
2305
- }
2537
+ handle = await open3(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2538
+ return await scan(handle);
2306
2539
  } catch (error) {
2307
- if (code(error) !== "EEXIST") throw new ClaudeHostError("storage", `Unable to persist Claude hook batch (${refusalFact("error_code", code(error), "EEXIST for an idempotent retry")}).`);
2308
- const existing = JSON.parse(await readFile(path, "utf8"));
2309
- validateBatch(existing);
2310
- if (JSON.stringify(existing) !== JSON.stringify(batch)) throw new ClaudeHostError("storage", "Claude hook batch identity conflicts with different bytes.");
2540
+ if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2541
+ if (hostRelayExchangeFailure(error) !== void 0) throw error;
2542
+ return handlers.failed(handle, error);
2543
+ } finally {
2544
+ await handle?.close();
2311
2545
  }
2312
2546
  }
2313
- async list() {
2314
- await this.prepare();
2315
- const batches = [];
2316
- let malformed = 0;
2317
- for (const name of await readdir(this.entries)) {
2318
- if (!name.endsWith(".json")) continue;
2319
- try {
2320
- const value = JSON.parse(await readFile(join2(this.entries, name), "utf8"));
2321
- validateBatch(value);
2322
- batches.push(value);
2323
- } catch {
2324
- malformed += 1;
2325
- }
2326
- }
2327
- batches.sort(compareBatch);
2328
- return { entries: batches, malformed };
2547
+ /**
2548
+ * A transcript the Host deleted never comes back, so retrying its
2549
+ * registration once per reconcile interval is a failure with no end.
2550
+ * Retire it at the scan that finds it missing and say so once. Only
2551
+ * ENOENT means gone; a permission or busy failure is about this moment,
2552
+ * not about the file, and keeps the registration for the next scan.
2553
+ */
2554
+ async retireMissingTranscript(transcriptPath, producerLabel, retire) {
2555
+ await retire();
2556
+ this.log(formatRetiredTranscriptLog(producerLabel, transcriptPath));
2557
+ return { records: 0, enqueued: 0, quarantined: false };
2329
2558
  }
2330
- async remove(batch) {
2331
- try {
2332
- await unlink2(this.path(batch.batch_ref));
2333
- } catch (error) {
2334
- if (code(error) !== "ENOENT") throw error;
2335
- }
2559
+ stamp() {
2560
+ return transcriptTimestamp(this.now, this.clockFailure);
2336
2561
  }
2337
- path(ref) {
2338
- return join2(this.entries, `${cealRequestSha256(ref)}.json`);
2562
+ expire(retentionMs) {
2563
+ return transcriptExpiration(this.now, retentionMs, this.clockFailure);
2339
2564
  }
2340
- activeContextPath(identity) {
2341
- return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
2565
+ sha(value) {
2566
+ return transcriptSha256(value);
2342
2567
  }
2343
- };
2344
- function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
2345
- 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.");
2346
- }
2347
- async function readInvocationContextFile(file, hostSourceRef, hostSessionRef, now) {
2348
- try {
2349
- const context = JSON.parse(await readFile(file, "utf8"));
2350
- validateClaudeHostInvocationContext(context);
2351
- return contextMatches(context, hostSourceRef, hostSessionRef) && Date.parse(context.expires_at) > now.getTime() ? context : null;
2352
- } catch (error) {
2353
- if (code(error) === "ENOENT") return null;
2354
- if (error instanceof ClaudeHostError) throw error;
2355
- throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
2568
+ objectSha(value) {
2569
+ return transcriptObjectSha256(value);
2356
2570
  }
2357
- }
2358
- function contextMatches(context, hostSourceRef, hostSessionRef) {
2359
- return context.identity.host_source_ref === hostSourceRef && context.identity.host_session_ref === hostSessionRef;
2360
- }
2361
- function compareBatch(left, right) {
2362
- return rank(left.hook_event) - rank(right.hook_event) || left.occurred_at.localeCompare(right.occurred_at) || left.batch_ref.localeCompare(right.batch_ref);
2363
- }
2364
- function rank(event) {
2365
- return event === "SessionStart" ? 0 : event === "UserPromptSubmit" ? 1 : 2;
2366
- }
2367
- function validateBatch(value) {
2368
- if (!validBatchHeader(value)) throw new ClaudeHostError("storage", "Claude hook batch is malformed.");
2369
- for (const [index, draft] of value.frames.entries()) {
2370
- if (!record4(draft) || !hostFrameKind(draft.frame_kind) || !record4(draft.payload)) throw new ClaudeHostError("storage", "Claude frame draft is malformed.");
2371
- 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 });
2571
+ isRecord(value) {
2572
+ return isTranscriptRecord(value);
2372
2573
  }
2373
- }
2374
- function validBatchHeader(value) {
2375
- return record4(value) && value.schema_version === CLAUDE_OUTBOX_SCHEMA && safeRef3(value.batch_ref) && claudeEvent(value.hook_event) && safeRef3(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;
2376
- }
2377
- function claudeEvent(value) {
2378
- return typeof value === "string" && ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure"].includes(value);
2379
- }
2380
- function hostFrameKind(value) {
2381
- return isHostProducerFrameKind(value);
2382
- }
2383
- function safeRef3(value) {
2384
- return typeof value === "string" && SAFE_REF4.test(value);
2385
- }
2386
- function optionalRef(value) {
2387
- return value === void 0 || typeof value === "string" && SAFE_REF4.test(value);
2388
- }
2389
- function timestamp3(value) {
2390
- return typeof value === "string" && Number.isFinite(Date.parse(value));
2391
- }
2392
- function record4(value) {
2393
- return value !== null && typeof value === "object" && !Array.isArray(value);
2394
- }
2395
- function code(error) {
2396
- return record4(error) && typeof error.code === "string" ? error.code : null;
2397
- }
2398
-
2399
- // host/claude/src/claude-producer.ts
2400
- var STATE_SCHEMA = "ceal.claude_producer_state.v1";
2401
- var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2402
- var ClaudeProducer = class {
2403
- store;
2574
+ readChunk(handle, offset, size) {
2575
+ return readTranscriptCompleteChunk(handle, offset, size);
2576
+ }
2577
+ readAnchor(handle, offset, anchorBytes) {
2578
+ return readTranscriptAnchor(handle, offset, anchorBytes);
2579
+ }
2580
+ cursorIdentityReason(cursor, transcriptPath, device, inode) {
2581
+ return transcriptCursorIdentityReason(cursor, transcriptPath, device, inode);
2582
+ }
2583
+ cursorBoundsReason(handle, cursor, size) {
2584
+ return transcriptCursorBoundsReason(handle, cursor, size);
2585
+ }
2586
+ };
2587
+ var HOST_PRODUCER_STATE_IDENTITY = {
2588
+ codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
2589
+ claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
2590
+ };
2591
+ var HostProducerDrainCore = class {
2592
+ host;
2593
+ relay;
2404
2594
  statePath;
2405
- installation;
2595
+ stateSchema;
2596
+ installationRef;
2406
2597
  epoch;
2407
- options;
2598
+ now;
2408
2599
  tail = Promise.resolve();
2409
- constructor(options) {
2410
- if (!SAFE_REF5.test(options.producer_installation_ref)) throw new ClaudeHostError("configuration", "Claude producer installation identity is invalid.");
2411
- this.options = options;
2412
- this.epoch = options.producer_epoch ?? 1;
2413
- if (!Number.isSafeInteger(this.epoch) || this.epoch <= 0) throw new ClaudeHostError("configuration", "Claude producer epoch is invalid.");
2414
- this.store = new ClaudeOutboxStore(options.outbox_dir);
2415
- this.statePath = join3(options.outbox_dir, "claude-producer-state.json");
2416
- this.installation = options.producer_installation_ref;
2600
+ constructor(host, runtime) {
2601
+ if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
2602
+ const epoch = runtime.producer_epoch ?? 1;
2603
+ if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
2604
+ this.host = host;
2605
+ this.relay = runtime.relay;
2606
+ this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
2607
+ this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
2608
+ this.installationRef = runtime.producer_installation_ref;
2609
+ this.epoch = epoch;
2610
+ this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
2417
2611
  }
2418
2612
  drainOutbox() {
2419
2613
  const run = this.tail.then(() => this.drainLocked());
@@ -2421,14 +2615,21 @@ var ClaudeProducer = class {
2421
2615
  return run;
2422
2616
  }
2423
2617
  async drainLocked() {
2424
- const read = await this.store.list();
2618
+ const read = await this.host.listEnvelopes();
2425
2619
  let admitted = 0;
2620
+ let expired = 0;
2426
2621
  let retained = 0;
2427
- for (const batch of read.entries) {
2428
- const frames = await this.reserve(batch);
2622
+ for (const envelope of read.entries) {
2623
+ const reserved = await this.hasReservation(this.host.submissionRefOf(envelope));
2624
+ if (!reserved && Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) {
2625
+ await this.host.removeEnvelope(envelope);
2626
+ expired += 1;
2627
+ continue;
2628
+ }
2629
+ const frames = await this.reserve(envelope);
2429
2630
  let complete = true;
2430
2631
  for (const frame of frames) {
2431
- const outcome = await this.options.relay.admit(frame);
2632
+ const outcome = await this.relay.admit(frame);
2432
2633
  if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2433
2634
  complete = false;
2434
2635
  break;
@@ -2438,105 +2639,117 @@ var ClaudeProducer = class {
2438
2639
  retained += 1;
2439
2640
  break;
2440
2641
  }
2441
- await this.store.remove(batch);
2442
- await this.release(batch.batch_ref);
2642
+ await this.host.removeEnvelope(envelope);
2643
+ await this.release(this.host.submissionRefOf(envelope));
2443
2644
  admitted += 1;
2444
2645
  }
2445
- return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - retained), malformed: read.malformed };
2646
+ return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
2446
2647
  }
2447
- async reserve(batch) {
2648
+ async hasReservation(submissionRef2) {
2649
+ return (await this.readState()).pending[submissionRef2] !== void 0;
2650
+ }
2651
+ async reserve(envelope) {
2448
2652
  const state = await this.readState();
2449
- const existing = state.pending[batch.batch_ref];
2653
+ const submissionRef2 = this.host.submissionRefOf(envelope);
2654
+ const existing = state.pending[submissionRef2];
2450
2655
  if (existing !== void 0) return existing;
2451
- 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 }));
2452
- await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [batch.batch_ref]: frames } });
2656
+ const drafts = await this.host.mapEnvelope(envelope);
2657
+ const frames = drafts.map((draft, offset) => ({
2658
+ schema_version: "ceal.host_producer_frame.v1",
2659
+ producer_id: this.host.producer_id,
2660
+ producer_installation_ref: this.installationRef,
2661
+ producer_epoch: this.epoch,
2662
+ sequence: state.next_sequence + offset,
2663
+ frame_kind: draft.frame_kind,
2664
+ occurred_at: this.host.createdAtOf(envelope),
2665
+ payload: draft.payload
2666
+ }));
2667
+ await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef2]: frames } });
2453
2668
  return frames;
2454
2669
  }
2455
- async release(ref) {
2670
+ async release(submissionRef2) {
2456
2671
  const state = await this.readState();
2457
- if (state.pending[ref] === void 0) return;
2672
+ if (state.pending[submissionRef2] === void 0) return;
2458
2673
  const pending = { ...state.pending };
2459
- delete pending[ref];
2674
+ delete pending[submissionRef2];
2460
2675
  await this.writeState({ ...state, pending });
2461
2676
  }
2462
2677
  async readState() {
2463
2678
  try {
2464
- return decodeState(JSON.parse(await readFile2(this.statePath, "utf8")), this.installation, this.epoch);
2679
+ const value = JSON.parse(await readFile(this.statePath, "utf8"));
2680
+ return this.decodeState(value);
2465
2681
  } catch (error) {
2466
- if (code2(error) !== "ENOENT") throw error;
2467
- return { schema_version: STATE_SCHEMA, producer_installation_ref: this.installation, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2682
+ if (errorCode3(error) !== "ENOENT") throw error;
2683
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2468
2684
  }
2469
2685
  }
2470
2686
  async writeState(state) {
2471
- await mkdir3(this.store.directory, { recursive: true, mode: 448 });
2472
- const temp = `${this.statePath}.${process.pid}.new`;
2473
- await writeFile(temp, JSON.stringify(state), { mode: 384 });
2474
- await rename2(temp, this.statePath);
2687
+ await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
2688
+ const temporary = `${this.statePath}.${process.pid}.new`;
2689
+ await writeFile(temporary, JSON.stringify(state), { mode: 384 });
2690
+ await rename2(temporary, this.statePath);
2691
+ }
2692
+ decodeState(value) {
2693
+ const label = this.host.producer_label;
2694
+ const fail = (code2, message) => this.host.fail(code2, message);
2695
+ if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
2696
+ throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
2697
+ { name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
2698
+ { name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
2699
+ ])}).`);
2700
+ }
2701
+ 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}`)}).`);
2702
+ const pending = {};
2703
+ const storedEpoch = Number(value.producer_epoch);
2704
+ for (const [submissionRef2, frames] of Object.entries(value.pending)) {
2705
+ if (!isHostSafeRef(submissionRef2) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
2706
+ pending[submissionRef2] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
2707
+ }
2708
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
2709
+ }
2710
+ decodeStoredFrame(value, storedEpoch) {
2711
+ const label = this.host.producer_label;
2712
+ const json = Buffer.from(JSON.stringify(value), "utf8");
2713
+ const encoded = Buffer.allocUnsafe(json.length + 4);
2714
+ encoded.writeUInt32BE(json.length, 0);
2715
+ json.copy(encoded, 4);
2716
+ const frame = decodeHostProducerFrame(encoded);
2717
+ 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([
2718
+ { name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
2719
+ { name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
2720
+ { name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
2721
+ ])}).`);
2722
+ return frame;
2475
2723
  }
2476
2724
  };
2477
- function decodeState(value, installation, epoch) {
2478
- if (!validStateHeader(value, installation)) throw new ClaudeHostError("storage", `Claude producer state header is malformed or belongs to another installation (${refusalFacts([
2479
- { name: "schema_version", value: record5(value) ? value.schema_version : void 0, expected: STATE_SCHEMA },
2480
- { name: "producer_installation_ref", value: record5(value) ? value.producer_installation_ref : void 0, expected: installation }
2481
- ])}).`);
2482
- 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}`)}).`);
2483
- const pending = {};
2484
- for (const [ref, frames] of Object.entries(value.pending)) {
2485
- if (!SAFE_REF5.test(ref) || !Array.isArray(frames) || frames.length === 0) throw new ClaudeHostError("storage", "Claude producer reservation is malformed.");
2486
- pending[ref] = frames.map((frame) => decodeFrame(frame, installation, Number(value.producer_epoch)));
2487
- }
2488
- return { schema_version: STATE_SCHEMA, producer_installation_ref: installation, producer_epoch: epoch, next_sequence: Number(value.next_sequence), pending };
2489
- }
2490
- function validStateHeader(value, installation) {
2491
- return record5(value) && value.schema_version === STATE_SCHEMA && value.producer_installation_ref === installation && positiveInteger(value.producer_epoch) && positiveInteger(value.next_sequence) && record5(value.pending);
2492
- }
2493
- function positiveInteger(value) {
2494
- return Number.isSafeInteger(value) && Number(value) > 0;
2495
- }
2496
- function decodeFrame(value, installation, epoch) {
2497
- const json = Buffer.from(JSON.stringify(value));
2498
- const encoded = Buffer.allocUnsafe(json.length + 4);
2499
- encoded.writeUInt32BE(json.length, 0);
2500
- json.copy(encoded, 4);
2501
- const frame = decodeHostProducerFrame(encoded);
2502
- 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}` }])}).`);
2503
- return frame;
2725
+ function isDrainStateHeader(value, schema, installationRef) {
2726
+ 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);
2504
2727
  }
2505
- function record5(value) {
2506
- return value !== null && typeof value === "object" && !Array.isArray(value);
2507
- }
2508
- function code2(error) {
2509
- return record5(error) && typeof error.code === "string" ? error.code : null;
2728
+ function errorCode3(error) {
2729
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2510
2730
  }
2511
2731
 
2512
- // host/claude/src/claude-producer-runtime.ts
2513
- async function drainClaudeProducerOnce(options) {
2514
- 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();
2515
- }
2732
+ // host/relay/src/relay-quarantine-store.ts
2733
+ var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
2734
+ var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
2735
+ var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
2516
2736
 
2517
- // host/claude/src/claude-producer-service.ts
2518
- var CLAUDE_PRODUCER_CONFIG_SCHEMA = "ceal.claude_producer_config.v1";
2519
- var CLAUDE_PRODUCER_DESCRIPTOR = {
2520
- schema: CLAUDE_PRODUCER_CONFIG_SCHEMA,
2521
- producer_id: "claude",
2522
- config: (shared) => shared,
2523
- runtime_options: (config) => Object.freeze({
2524
- outbox_dir: config.outbox_dir,
2525
- producer_installation_ref: config.producer_installation_ref,
2526
- producer_epoch: config.producer_epoch,
2527
- relay_endpoint: config.relay_endpoint,
2528
- timeout_ms: config.ipc_timeout_ms
2529
- }),
2530
- drain: drainClaudeProducerOnce
2531
- };
2532
- function runClaudeProducerCli(args, signal, dependencies = {}) {
2533
- return runHostProducerCli(CLAUDE_PRODUCER_DESCRIPTOR, args, signal, dependencies);
2534
- }
2737
+ // host/relay/src/relay-admission-store.ts
2738
+ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
2739
+ var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
2740
+ var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
2535
2741
 
2536
2742
  // host/service-log.ts
2537
2743
  import { copyFile, open as open4, stat, truncate } from "node:fs/promises";
2538
2744
  import { isAbsolute } from "node:path";
2539
2745
  var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
2746
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind) {
2747
+ try {
2748
+ process.stderr.write(`${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {} })}
2749
+ `);
2750
+ } catch {
2751
+ }
2752
+ }
2540
2753
  var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2541
2754
  var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
2542
2755
  function hostServiceLogPreviousPath(path) {
@@ -2563,7 +2776,7 @@ function boundHostServiceLog(environment = process.env) {
2563
2776
  rotateHostServiceLog(path).catch((error) => {
2564
2777
  if (reported) return;
2565
2778
  reported = true;
2566
- process.stderr.write(`[host-service-log] rotation_failed code=${errorCode3(error)}
2779
+ process.stderr.write(`[host-service-log] rotation_failed code=${errorCode4(error)}
2567
2780
  `);
2568
2781
  });
2569
2782
  };
@@ -2574,12 +2787,1180 @@ function boundHostServiceLog(environment = process.env) {
2574
2787
  }
2575
2788
  var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2576
2789
  function isNotFound(error) {
2577
- return errorCode3(error) === "ENOENT";
2790
+ return errorCode4(error) === "ENOENT";
2578
2791
  }
2579
- function errorCode3(error) {
2792
+ function errorCode4(error) {
2580
2793
  return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
2581
2794
  }
2582
2795
 
2796
+ // host/claude/src/claude-decoder.ts
2797
+ import { createHash as createHash4 } from "node:crypto";
2798
+
2799
+ // host/claude/src/claude-types.ts
2800
+ var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "payload_rejected", "storage", "configuration"];
2801
+ function isClaudeHostErrorCode(value) {
2802
+ return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code2) => code2 === value);
2803
+ }
2804
+ var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
2805
+ var CLAUDE_OUTBOX_SCHEMA_VERSION = "ceal.claude_observation_outbox.v1";
2806
+ var CLAUDE_SESSION_SCHEMA_VERSION = "ceal.claude_session.v1";
2807
+ var CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.claude_transcript_registration.v1";
2808
+ var CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.claude_transcript_cursor.v1";
2809
+ var CLAUDE_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
2810
+ var CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES = 1024;
2811
+ var CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES = 64;
2812
+ var CLAUDE_TRANSCRIPT_ANCHOR_BYTES = 256;
2813
+ var ClaudeHostError = class extends Error {
2814
+ name = "ClaudeHostError";
2815
+ code;
2816
+ diagnostic_kind;
2817
+ constructor(code2, message, diagnostic_kind) {
2818
+ super(message);
2819
+ if (!isClaudeHostErrorCode(code2)) throw new TypeError("Claude Host error code is invalid.");
2820
+ this.code = code2;
2821
+ this.diagnostic_kind = diagnostic_kind;
2822
+ }
2823
+ };
2824
+
2825
+ // host/claude/src/claude-decoder.ts
2826
+ var MAX_HOOK_BYTES = 256 * 1024;
2827
+ function claudeLocalTurnRef(sessionId, turnId) {
2828
+ return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
2829
+ }
2830
+ function claudeLocalInputRef(sessionId, turnId) {
2831
+ return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
2832
+ }
2833
+ function claudeWorkspaceRef(cwd) {
2834
+ return `workspace:${digest(cwd)}`;
2835
+ }
2836
+ function digest(value) {
2837
+ return createHash4("sha256").update(value, "utf8").digest("hex");
2838
+ }
2839
+
2840
+ // host/claude/src/claude-outbox.ts
2841
+ import { mkdir as mkdir3, readdir, readFile as readFile2, unlink as unlink2 } from "node:fs/promises";
2842
+ import { isAbsolute as isAbsolute2, join as join3 } from "node:path";
2843
+
2844
+ // host/invocation-context.ts
2845
+ var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
2846
+ var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
2847
+ function invocationContextKey(identity) {
2848
+ return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
2849
+ }
2850
+ function hasHostInvocationIdentityKeys(value) {
2851
+ return exactKeys3(value, HOST_INVOCATION_IDENTITY_KEYS);
2852
+ }
2853
+ function hasHostInvocationContextKeys(value, optional = []) {
2854
+ return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
2855
+ }
2856
+ async function readLatestSessionProjection(file, hostSourceRef, hostSessionRef, readProjection) {
2857
+ const context = await readProjection(file);
2858
+ if (context === null || context.identity.host_source_ref !== hostSourceRef || context.identity.host_session_ref !== hostSessionRef) return null;
2859
+ return context;
2860
+ }
2861
+ function isLiveSessionProjection(context, now) {
2862
+ return context !== null && Date.parse(context.expires_at) > now.getTime();
2863
+ }
2864
+ function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
2865
+ const context = {
2866
+ schema_version: descriptor.schema_version,
2867
+ identity,
2868
+ prompt_ref: promptRef,
2869
+ expires_at: expiresAt,
2870
+ ...descriptor.turn_generation_ref === void 0 ? {} : { turn_generation_ref: descriptor.turn_generation_ref },
2871
+ ...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
2872
+ };
2873
+ descriptor.validate(context);
2874
+ return Object.freeze(context);
2875
+ }
2876
+ function isHostInvocationTurnSelection(value) {
2877
+ return isRecord2(value) && Object.keys(value).length === 5 && value.schema_version === "ceal.host_invocation_turn_selection.v1" && ["host_source_ref", "host_session_ref", "prompt_ref", "turn_generation_ref"].every((key) => safeRef2(value[key]));
2878
+ }
2879
+ function invocationTurnSelectionMatches(context, selection) {
2880
+ 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;
2881
+ }
2882
+ function exactKeys3(value, keys) {
2883
+ const actual = Object.keys(value);
2884
+ return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
2885
+ }
2886
+ function requiredKeys(value, required, optional) {
2887
+ const actual = Object.keys(value);
2888
+ const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
2889
+ return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
2890
+ }
2891
+ function isRecord2(value) {
2892
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2893
+ }
2894
+ function safeRef2(value) {
2895
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2896
+ }
2897
+
2898
+ // host/claude/src/claude-invocation-context.ts
2899
+ function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
2900
+ return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory, turn_generation_ref: turnGenerationRef });
2901
+ }
2902
+ function validateClaudeHostInvocationContext(value) {
2903
+ if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2904
+ validateIdentity2(value.identity);
2905
+ validateContextReferences(value);
2906
+ validateContextTimes(value);
2907
+ }
2908
+ function contextShape(value) {
2909
+ return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory", "turn_generation_ref"]);
2910
+ }
2911
+ function validateIdentity2(value) {
2912
+ if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2913
+ }
2914
+ function validateContextReferences(value) {
2915
+ if (!safeRef3(value.prompt_ref) || !optionalSafeRef(value.request_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2916
+ }
2917
+ function validateContextTimes(value) {
2918
+ if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2919
+ 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)}`)}).`);
2920
+ }
2921
+ function validIdentity(value) {
2922
+ return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef3);
2923
+ }
2924
+ function optionalSafeRef(value) {
2925
+ return value === void 0 || safeRef3(value);
2926
+ }
2927
+ function optionalTimestamp(value) {
2928
+ return value === void 0 || timestamp2(value);
2929
+ }
2930
+ function optionalDirectory(value) {
2931
+ return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
2932
+ }
2933
+ function safeRef3(value) {
2934
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2935
+ }
2936
+ function timestamp2(value) {
2937
+ return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
2938
+ }
2939
+ function record3(value) {
2940
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2941
+ }
2942
+
2943
+ // host/claude/src/claude-outbox.ts
2944
+ var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2945
+ var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
2946
+ var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
2947
+ var ClaudeOutboxStore = class {
2948
+ envelopes;
2949
+ sessions;
2950
+ registrations;
2951
+ cursors;
2952
+ directory;
2953
+ activeContexts;
2954
+ turnSelections;
2955
+ constructor(directory) {
2956
+ if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2957
+ this.directory = directory;
2958
+ this.envelopes = join3(directory, "envelopes");
2959
+ this.sessions = join3(directory, "sessions");
2960
+ this.registrations = join3(directory, "transcript-registrations");
2961
+ this.cursors = join3(directory, "transcript-cursors");
2962
+ this.activeContexts = join3(directory, "active-contexts");
2963
+ this.turnSelections = join3(directory, "turn-selections");
2964
+ }
2965
+ async prepare() {
2966
+ for (const path of [this.envelopes, this.sessions, this.registrations, this.cursors, this.activeContexts, this.turnSelections]) {
2967
+ await mkdir3(path, { recursive: true, mode: 448 });
2968
+ }
2969
+ }
2970
+ async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
2971
+ const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef);
2972
+ await this.prepare();
2973
+ await durableAtomicReplace(this.activeContextPath(identity), JSON.stringify(context));
2974
+ }
2975
+ async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
2976
+ validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now);
2977
+ const context = await this.readRawInvocationContext(hostSourceRef, hostSessionRef);
2978
+ return isLiveSessionProjection(context, now) ? context : null;
2979
+ }
2980
+ async writeInvocationTurnSelection(selection) {
2981
+ if (!isHostInvocationTurnSelection(selection)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection is invalid.");
2982
+ await this.prepare();
2983
+ await durableAtomicReplace(this.turnSelectionPath(selection.host_source_ref, selection.host_session_ref), JSON.stringify(selection));
2984
+ }
2985
+ async attachInvocationTurnGeneration(identity, promptRef, turnGenerationRef) {
2986
+ const selection = await this.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
2987
+ if (selection === null || selection.prompt_ref !== promptRef || selection.turn_generation_ref !== turnGenerationRef) return false;
2988
+ const path = this.activeContextPath(identity);
2989
+ const active = await readProjectionFile(path);
2990
+ if (active === null || active.prompt_ref !== promptRef || active.identity.host_turn_ref !== identity.host_turn_ref) return false;
2991
+ const updated = { ...active, turn_generation_ref: turnGenerationRef };
2992
+ validateClaudeHostInvocationContext(updated);
2993
+ await durableAtomicReplace(path, JSON.stringify(updated));
2994
+ return true;
2995
+ }
2996
+ /**
2997
+ * The latest Claude session projection even when past expiry, so the
2998
+ * invocation authenticator can report an expired context distinctly from
2999
+ * a missing one. Every other caller keeps the fail-closed read above.
3000
+ */
3001
+ async readRawInvocationContext(hostSourceRef, hostSessionRef) {
3002
+ if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
3003
+ await this.prepare();
3004
+ const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
3005
+ return invocationTurnSelectionMatches(context, await this.readInvocationTurnSelection(hostSourceRef, hostSessionRef)) ? context : null;
3006
+ }
3007
+ async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
3008
+ await this.prepare();
3009
+ const contexts = [];
3010
+ for (const name of await readdir(this.activeContexts)) {
3011
+ if (!name.endsWith(".json")) continue;
3012
+ try {
3013
+ const value = JSON.parse(await readFile2(join3(this.activeContexts, name), "utf8"));
3014
+ validateClaudeHostInvocationContext(value);
3015
+ 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);
3016
+ } catch {
3017
+ }
3018
+ }
3019
+ return contexts;
3020
+ }
3021
+ async putEnvelope(entry) {
3022
+ validateEnvelope(entry);
3023
+ await this.prepare();
3024
+ await durableAtomicReplace(this.envelopePath(entry.submission_ref), JSON.stringify(entry));
3025
+ }
3026
+ async listEnvelopes() {
3027
+ await this.prepare();
3028
+ const entries = [];
3029
+ let malformed = 0;
3030
+ for (const name of await readdir(this.envelopes)) {
3031
+ if (!name.endsWith(".json")) continue;
3032
+ try {
3033
+ const value = JSON.parse(await readFile2(join3(this.envelopes, name), "utf8"));
3034
+ validateEnvelope(value);
3035
+ entries.push(value);
3036
+ } catch {
3037
+ malformed += 1;
3038
+ }
3039
+ }
3040
+ entries.sort((left, right) => left.created_at.localeCompare(right.created_at));
3041
+ return { entries, malformed };
3042
+ }
3043
+ async removeEnvelope(entry) {
3044
+ validateEnvelope(entry);
3045
+ try {
3046
+ await unlink2(this.envelopePath(entry.submission_ref));
3047
+ } catch (error) {
3048
+ if (code(error) !== "ENOENT") throw error;
3049
+ }
3050
+ }
3051
+ async saveSession(snapshot) {
3052
+ validateSession(snapshot);
3053
+ await this.prepare();
3054
+ await durableAtomicReplace(join3(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
3055
+ }
3056
+ async readSession(sourceRef, sessionRef) {
3057
+ if (!SAFE_REF4.test(sourceRef) || !SAFE_REF4.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
3058
+ await this.prepare();
3059
+ try {
3060
+ const value = JSON.parse(await readFile2(join3(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
3061
+ validateSession(value);
3062
+ return value;
3063
+ } catch (error) {
3064
+ if (code(error) === "ENOENT") return null;
3065
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3066
+ }
3067
+ }
3068
+ async registerTranscript(registration) {
3069
+ validateRegistration(registration);
3070
+ await this.prepare();
3071
+ const path = this.registrationPath(registration.host_source_ref, registration.host_session_ref);
3072
+ const existing = await this.readRegistrationFile(path);
3073
+ if (existing !== null && (existing.claude_session_id !== registration.claude_session_id || existing.transcript_path !== registration.transcript_path)) {
3074
+ throw new ClaudeHostError("storage", "Claude transcript registration conflicts with the source-bound session.");
3075
+ }
3076
+ await durableAtomicReplace(path, JSON.stringify(registration));
3077
+ }
3078
+ /**
3079
+ * End a registration whose transcript the Host has deleted. The cursor is
3080
+ * keyed by the same session and describes byte offsets of a file that no
3081
+ * longer exists, so it leaves with the registration.
3082
+ */
3083
+ async retireTranscriptRegistration(registration) {
3084
+ validateRegistration(registration);
3085
+ await this.prepare();
3086
+ await this.removeStateFile(this.registrationPath(registration.host_source_ref, registration.host_session_ref));
3087
+ await this.removeStateFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref));
3088
+ }
3089
+ async listTranscriptRegistrations() {
3090
+ await this.prepare();
3091
+ const registrations = [];
3092
+ for (const name of await readdir(this.registrations)) {
3093
+ if (!name.endsWith(".json")) continue;
3094
+ try {
3095
+ const value = JSON.parse(await readFile2(join3(this.registrations, name), "utf8"));
3096
+ validateRegistration(value);
3097
+ registrations.push(value);
3098
+ } catch {
3099
+ }
3100
+ }
3101
+ return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
3102
+ }
3103
+ async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
3104
+ if (!SAFE_REF4.test(hostSourceRef) || !SAFE_REF4.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
3105
+ await this.prepare();
3106
+ try {
3107
+ const value = JSON.parse(await readFile2(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
3108
+ if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
3109
+ return value;
3110
+ } catch (error) {
3111
+ if (code(error) === "ENOENT") return null;
3112
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Unable to read the Claude Host turn selection.");
3113
+ }
3114
+ }
3115
+ async readTranscriptCursor(registration) {
3116
+ validateRegistration(registration);
3117
+ await this.prepare();
3118
+ try {
3119
+ const value = JSON.parse(await readFile2(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
3120
+ validateCursor(value);
3121
+ return value;
3122
+ } catch (error) {
3123
+ if (code(error) === "ENOENT") return null;
3124
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3125
+ }
3126
+ }
3127
+ async writeTranscriptCursor(cursor) {
3128
+ validateCursor(cursor);
3129
+ await this.prepare();
3130
+ await durableAtomicReplace(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), JSON.stringify(cursor));
3131
+ }
3132
+ envelopePath(ref) {
3133
+ return join3(this.envelopes, `${cealRequestSha256(ref)}.json`);
3134
+ }
3135
+ registrationPath(sourceRef, sessionRef) {
3136
+ return join3(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3137
+ }
3138
+ turnSelectionPath(sourceRef, sessionRef) {
3139
+ return join3(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3140
+ }
3141
+ cursorPath(sourceRef, sessionRef) {
3142
+ return join3(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
3143
+ }
3144
+ async readRegistrationFile(path) {
3145
+ try {
3146
+ const value = JSON.parse(await readFile2(path, "utf8"));
3147
+ validateRegistration(value);
3148
+ return value;
3149
+ } catch (error) {
3150
+ if (code(error) === "ENOENT") return null;
3151
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3152
+ }
3153
+ }
3154
+ async removeStateFile(path) {
3155
+ try {
3156
+ await unlink2(path);
3157
+ } catch (error) {
3158
+ if (code(error) !== "ENOENT") throw error;
3159
+ }
3160
+ }
3161
+ activeContextPath(identity) {
3162
+ return join3(this.activeContexts, `${invocationContextKey(identity)}.json`);
3163
+ }
3164
+ };
3165
+ function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
3166
+ 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.");
3167
+ }
3168
+ async function readProjectionFile(file) {
3169
+ try {
3170
+ const context = JSON.parse(await readFile2(file, "utf8"));
3171
+ validateClaudeHostInvocationContext(context);
3172
+ return context;
3173
+ } catch (error) {
3174
+ if (code(error) === "ENOENT") return null;
3175
+ if (error instanceof ClaudeHostError) throw error;
3176
+ throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
3177
+ }
3178
+ }
3179
+ function safeRef4(value) {
3180
+ return typeof value === "string" && SAFE_REF4.test(value);
3181
+ }
3182
+ function timestamp3(value) {
3183
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
3184
+ }
3185
+ function record4(value) {
3186
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3187
+ }
3188
+ function code(error) {
3189
+ return record4(error) && typeof error.code === "string" ? error.code : null;
3190
+ }
3191
+ function validateEnvelope(value) {
3192
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
3193
+ if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "input_kind", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"], ["request_ref"])) {
3194
+ throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
3195
+ }
3196
+ 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}`)}).`);
3197
+ validateEnvelopeIdentity(value);
3198
+ validateEnvelopeObservations(value.observations);
3199
+ if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
3200
+ if (!timestamp3(value.created_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
3201
+ }
3202
+ function validateEnvelopeIdentity(value) {
3203
+ validateIdentity3(value.identity);
3204
+ if (!safeRef4(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
3205
+ if (!isCealHostInputKind(value.input_kind)) throw new ClaudeHostError("storage", "Claude outbox input provenance is malformed.");
3206
+ if (!safeRef4(value.prompt_ref) || !safeRef4(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
3207
+ if (value.request_ref !== void 0 && !safeRef4(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
3208
+ }
3209
+ function validateEnvelopeObservations(value) {
3210
+ if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3211
+ try {
3212
+ for (const observation2 of value) validateCealHostObservationDraft(observation2);
3213
+ } catch {
3214
+ throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3215
+ }
3216
+ }
3217
+ function validateSession(value) {
3218
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3219
+ if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) {
3220
+ throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
3221
+ }
3222
+ 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}`)}).`);
3223
+ if (!safeRef4(value.host_session_ref) || !safeRef4(value.host_source_ref) || !timestamp3(value.observed_at) || !timestamp3(value.expires_at)) {
3224
+ throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
3225
+ }
3226
+ }
3227
+ function validateRegistration(value) {
3228
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3229
+ if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "claude_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) {
3230
+ throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
3231
+ }
3232
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3233
+ if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) {
3234
+ throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
3235
+ }
3236
+ validateRegistrationPaths(value.cwd, value.transcript_path);
3237
+ if (!timestamp3(value.registered_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
3238
+ }
3239
+ function validateRegistrationPaths(cwd, transcriptPath) {
3240
+ if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute2(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
3241
+ throw new ClaudeHostError("storage", "Claude transcript registration working directory is malformed.");
3242
+ }
3243
+ if (typeof transcriptPath !== "string" || transcriptPath.length === 0 || transcriptPath.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS) {
3244
+ throw new ClaudeHostError("storage", "Claude transcript registration path is malformed.");
3245
+ }
3246
+ }
3247
+ function validateCursor(value) {
3248
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3249
+ 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"])) {
3250
+ throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
3251
+ }
3252
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3253
+ 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.");
3254
+ if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
3255
+ validateCursorPosition(value);
3256
+ validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
3257
+ validateSubagentCursors(value.subagents);
3258
+ if (value.reason !== void 0 && !isHostTranscriptCursorReason(value.reason)) throw new ClaudeHostError("storage", "Claude transcript cursor reason is malformed.");
3259
+ }
3260
+ function validateCursorPosition(value) {
3261
+ if (!validCursorPosition(value)) throw new ClaudeHostError("storage", "Claude transcript cursor position is malformed.");
3262
+ if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
3263
+ if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
3264
+ if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
3265
+ if (value.status !== "active" && value.status !== "quarantined" || !timestamp3(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
3266
+ }
3267
+ function validateCursorTurn(openTurn, unattributed, sessionObserved) {
3268
+ if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
3269
+ if (unattributed !== void 0 && !validUnattributed(unattributed)) throw new ClaudeHostError("storage", "Claude transcript unattributed cursor is malformed.");
3270
+ if (sessionObserved !== void 0 && typeof sessionObserved !== "boolean") throw new ClaudeHostError("storage", "Claude transcript cursor session marker is malformed.");
3271
+ }
3272
+ function validateSubagentCursors(value) {
3273
+ if (!record4(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
3274
+ for (const [name, entry] of Object.entries(value)) validateSubagentEntry(name, entry);
3275
+ }
3276
+ function validateSubagentEntry(name, entry) {
3277
+ if (name.length === 0 || name.length > CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS || name.includes("/")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3278
+ if (name.includes("\0")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3279
+ 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.");
3280
+ validateSubagentIdentity(entry);
3281
+ }
3282
+ function validateSubagentIdentity(entry) {
3283
+ if (entry.device !== void 0 && typeof entry.device !== "string" || entry.inode !== void 0 && typeof entry.inode !== "string") {
3284
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3285
+ }
3286
+ if (!nonNegativeInteger(entry.anchor_start) || typeof entry.anchor_sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(entry.anchor_sha256)) {
3287
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3288
+ }
3289
+ if (entry.device === void 0 && Number(entry.offset) !== 0) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is unpinned past its start.");
3290
+ }
3291
+ function validCursorPosition(value) {
3292
+ return nonNegativeInteger(value.offset) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256);
3293
+ }
3294
+ function validOpenTurn(value) {
3295
+ 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;
3296
+ 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;
3297
+ if (!timestamp3(value.started_at) || !timestamp3(value.last_observed_at)) return false;
3298
+ if (!validMessageIds(value.message_ids)) return false;
3299
+ if (!validTurnCounts(value.counts)) return false;
3300
+ return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
3301
+ }
3302
+ function validMessageIds(value) {
3303
+ if (!Array.isArray(value) || value.length > CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES) return false;
3304
+ if (!value.every((candidate) => typeof candidate === "string" && candidate.length > 0 && candidate.length <= 256)) return false;
3305
+ return new Set(value).size === value.length;
3306
+ }
3307
+ function validUnattributed(value) {
3308
+ if (!record4(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
3309
+ if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
3310
+ if (!timestamp3(value.last_observed_at)) return false;
3311
+ return validTurnCounts(value);
3312
+ }
3313
+ function validTurnCounts(value) {
3314
+ if (!record4(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
3315
+ return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
3316
+ }
3317
+ function validateIdentity3(value) {
3318
+ if (!record4(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate))) {
3319
+ throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
3320
+ }
3321
+ }
3322
+ function nonNegativeInteger(value) {
3323
+ return Number.isSafeInteger(value) && Number(value) >= 0;
3324
+ }
3325
+ function sameKeys(value, required, optional = []) {
3326
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
3327
+ }
3328
+
3329
+ // host/claude/src/claude-producer.ts
3330
+ var ClaudeProducer = class {
3331
+ producer_id = "claude";
3332
+ producer_label = "Claude";
3333
+ outbox_directory;
3334
+ store;
3335
+ core;
3336
+ constructor(options) {
3337
+ this.store = new ClaudeOutboxStore(options.outbox_dir);
3338
+ this.outbox_directory = this.store.directory;
3339
+ this.core = new HostProducerDrainCore(this, options);
3340
+ }
3341
+ drainOutbox() {
3342
+ return this.core.drainOutbox();
3343
+ }
3344
+ fail(code2, message) {
3345
+ return new ClaudeHostError(code2, message);
3346
+ }
3347
+ listEnvelopes() {
3348
+ return this.store.listEnvelopes();
3349
+ }
3350
+ removeEnvelope(envelope) {
3351
+ return this.store.removeEnvelope(envelope);
3352
+ }
3353
+ submissionRefOf(envelope) {
3354
+ return envelope.submission_ref;
3355
+ }
3356
+ expiresAtOf(envelope) {
3357
+ return envelope.expires_at;
3358
+ }
3359
+ createdAtOf(envelope) {
3360
+ return envelope.created_at;
3361
+ }
3362
+ async mapEnvelope(envelope) {
3363
+ const prompt = observation(envelope, "prompt");
3364
+ if (prompt !== void 0) return this.mapInput(envelope, prompt);
3365
+ return [mapTerminal(envelope)];
3366
+ }
3367
+ async mapInput(envelope, prompt) {
3368
+ const drafts = [];
3369
+ const session = observation(envelope, "session");
3370
+ if (session !== void 0) {
3371
+ const registrations = await this.store.listTranscriptRegistrations();
3372
+ const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
3373
+ const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
3374
+ drafts.push({
3375
+ frame_kind: "session_started",
3376
+ payload: compact({
3377
+ local_session_ref: envelope.identity.host_session_ref,
3378
+ working_directory_ref: claudeWorkspaceRef(directoryIdentity),
3379
+ model: recordText(session.value, "model"),
3380
+ observations: [session]
3381
+ })
3382
+ });
3383
+ }
3384
+ const turn = observation(envelope, "turn");
3385
+ if (turn !== void 0) drafts.push({
3386
+ frame_kind: "turn_started",
3387
+ payload: compact({
3388
+ local_session_ref: envelope.identity.host_session_ref,
3389
+ local_turn_ref: localTurnRef(envelope),
3390
+ observations: [turn]
3391
+ })
3392
+ });
3393
+ if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
3394
+ drafts.push({
3395
+ frame_kind: "user_input",
3396
+ payload: compact({
3397
+ local_session_ref: envelope.identity.host_session_ref,
3398
+ local_turn_ref: localTurnRef(envelope),
3399
+ local_input_ref: envelope.host_input_ref,
3400
+ input_kind: envelope.input_kind,
3401
+ origin_input: envelope.origin_input,
3402
+ observations: [prompt],
3403
+ context_expires_at: envelope.expires_at
3404
+ })
3405
+ });
3406
+ return drafts;
3407
+ }
3408
+ };
3409
+ function mapTerminal(envelope) {
3410
+ const usage = envelope.observations.filter((item) => ["token", "cost"].includes(item.observation_kind));
3411
+ return {
3412
+ frame_kind: "turn_terminal",
3413
+ payload: compact({
3414
+ local_session_ref: envelope.identity.host_session_ref,
3415
+ local_turn_ref: localTurnRef(envelope),
3416
+ terminal_state: "completed",
3417
+ usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
3418
+ observations: envelope.observations
3419
+ })
3420
+ };
3421
+ }
3422
+ function observation(envelope, kind) {
3423
+ return envelope.observations.find((item) => item.observation_kind === kind);
3424
+ }
3425
+ function localTurnRef(envelope) {
3426
+ return envelope.identity.host_turn_ref;
3427
+ }
3428
+ function compact(value) {
3429
+ return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
3430
+ }
3431
+ function asRecord(value) {
3432
+ return isRecord3(value) ? value : void 0;
3433
+ }
3434
+ function recordText(value, key) {
3435
+ const record5 = asRecord(value);
3436
+ return typeof record5?.[key] === "string" ? record5[key] : void 0;
3437
+ }
3438
+ function isRecord3(value) {
3439
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3440
+ }
3441
+
3442
+ // host/claude/src/claude-transcript-watcher.ts
3443
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
3444
+ import { open as open5, readdir as readdir2 } from "node:fs/promises";
3445
+ import { join as join4 } from "node:path";
3446
+ var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
3447
+ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3448
+ store;
3449
+ registerCurrentPrompt;
3450
+ diagnostic;
3451
+ constructor(options) {
3452
+ super(options, {
3453
+ default_retention_ms: CLAUDE_DEFAULT_RETENTION_MS,
3454
+ invalid_outbox: () => new ClaudeHostError("configuration", "Claude transcript watcher outbox is invalid."),
3455
+ clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
3456
+ });
3457
+ this.store = new ClaudeOutboxStore(options.outbox_dir);
3458
+ this.registerCurrentPrompt = options.register_current_prompt;
3459
+ this.diagnostic = options.diagnostic ?? (() => void 0);
3460
+ }
3461
+ transcriptPathOf(registration) {
3462
+ return registration.transcript_path;
3463
+ }
3464
+ async listRegistrations() {
3465
+ return this.store.listTranscriptRegistrations();
3466
+ }
3467
+ async scanOpened(handle, registration) {
3468
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3469
+ const cursor = await this.preflight(registration, handle, size, device, inode);
3470
+ if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
3471
+ if (cursor.status === "quarantined") return { records: 0, enqueued: 0, quarantined: true };
3472
+ const draft = mutableCursor(cursor);
3473
+ await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
3474
+ return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
3475
+ }
3476
+ async scanFailed(handle, registration, error) {
3477
+ this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
3478
+ if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
3479
+ if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
3480
+ await this.quarantineFailure(registration, handle);
3481
+ return { records: 0, enqueued: 0, quarantined: true };
3482
+ }
3483
+ async retireMissing(registration) {
3484
+ return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
3485
+ }
3486
+ initialCursor(registration) {
3487
+ return {
3488
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3489
+ host_source_ref: registration.host_source_ref,
3490
+ host_session_ref: registration.host_session_ref,
3491
+ transcript_path: registration.transcript_path,
3492
+ offset: 0,
3493
+ anchor_start: 0,
3494
+ anchor_sha256: this.sha(Buffer.alloc(0)),
3495
+ subagents: {},
3496
+ status: "active",
3497
+ updated_at: this.stamp()
3498
+ };
3499
+ }
3500
+ async scanOpenedTranscript(registration, handle, draft, size, device, inode) {
3501
+ const main = await this.readChunk(handle, draft.offset, size);
3502
+ if (main !== "empty" && main !== "partial") {
3503
+ await this.consumeMainChunk(registration, main, draft);
3504
+ draft.offset += main.length;
3505
+ }
3506
+ draft.consumed += await this.consumeSubagents(registration, draft);
3507
+ await this.emitOpenTerminal(registration, draft);
3508
+ await this.commitCursor(registration, handle, draft, device, inode);
3509
+ }
3510
+ async preflight(registration, handle, size, device, inode) {
3511
+ const existing = await this.store.readTranscriptCursor(registration);
3512
+ if (existing?.status === "quarantined") return existing;
3513
+ const cursor = existing ?? this.initialCursor(registration);
3514
+ if (cursor.device === void 0) {
3515
+ if (cursor.offset !== 0) return await this.quarantineStored(cursor, device, inode, "transcript_replaced");
3516
+ return { ...cursor, device, inode };
3517
+ }
3518
+ const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
3519
+ if (reason !== null) return await this.quarantineStored(cursor, device, inode, reason);
3520
+ return cursor;
3521
+ }
3522
+ async consumeMainChunk(registration, complete, draft) {
3523
+ for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3524
+ draft.consumed += 1;
3525
+ const record5 = parseTranscriptLine(line);
3526
+ if (record5 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
3527
+ if (isUserTurnLine(record5)) await this.openTurn(registration, draft, record5);
3528
+ else if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
3529
+ else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3530
+ }
3531
+ }
3532
+ async openTurn(registration, draft, record5) {
3533
+ this.diagnostic("watcher_turn_observed");
3534
+ await this.closeTurn(registration, draft, record5.timestamp);
3535
+ const sessionRef = registration.host_session_ref;
3536
+ const turnRef = claudeLocalTurnRef(registration.claude_session_id, record5.uuid);
3537
+ const inputRef = claudeLocalInputRef(registration.claude_session_id, record5.uuid);
3538
+ const promptRef = hostProducerPromptRef(this.sha(record5.message.content));
3539
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
3540
+ const observations = [
3541
+ { 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) } },
3542
+ { 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 } }
3543
+ ];
3544
+ if (!draft.session_observed) {
3545
+ const session = await this.store.readSession(registration.host_source_ref, sessionRef);
3546
+ if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record5.timestamp, value: session.value });
3547
+ draft.session_observed = true;
3548
+ }
3549
+ await this.store.putEnvelope({
3550
+ schema_version: "ceal.claude_observation_outbox.v1",
3551
+ identity,
3552
+ host_input_ref: inputRef,
3553
+ input_kind: "human",
3554
+ prompt_ref: promptRef,
3555
+ observations,
3556
+ origin_input: true,
3557
+ submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3558
+ created_at: record5.timestamp,
3559
+ expires_at: this.expire(this.outboxRetentionMs)
3560
+ });
3561
+ draft.enqueued += 1;
3562
+ await this.registerPrompt(sessionRef, turnRef, promptRef, record5.message.content);
3563
+ const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3564
+ const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3565
+ await this.activateContext(identity, promptRef, turnGenerationRef);
3566
+ const folded = foldUnattributed(draft.unattributed);
3567
+ draft.open_turn = {
3568
+ turn_key: turnRef,
3569
+ user_uuid: record5.uuid,
3570
+ prompt_ref: promptRef,
3571
+ host_input_ref: inputRef,
3572
+ started_at: record5.timestamp,
3573
+ last_observed_at: folded?.last_observed_at ?? record5.timestamp,
3574
+ message_ids: folded?.message_ids ?? [],
3575
+ counts: folded?.counts ?? zeroCounts()
3576
+ };
3577
+ draft.unattributed = void 0;
3578
+ }
3579
+ async closeTurn(registration, draft, at) {
3580
+ const turn = draft.open_turn;
3581
+ draft.open_turn = void 0;
3582
+ if (turn === void 0) return;
3583
+ const digest2 = terminalDigest(turn);
3584
+ if (digest2 === turn.last_terminal_digest) return;
3585
+ await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3586
+ draft.enqueued += 1;
3587
+ }
3588
+ accumulateUsage(draft, usage) {
3589
+ const turn = draft.open_turn;
3590
+ if (turn === void 0) {
3591
+ draft.unattributed = accumulateUnattributed(draft.unattributed, usage);
3592
+ return;
3593
+ }
3594
+ if (turn.message_ids.includes(usage.uuid)) return;
3595
+ 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");
3596
+ draft.open_turn = {
3597
+ ...turn,
3598
+ last_observed_at: usage.observed_at > turn.last_observed_at ? usage.observed_at : turn.last_observed_at,
3599
+ message_ids: [...turn.message_ids, usage.uuid],
3600
+ counts: addCounts(turn.counts, usage)
3601
+ };
3602
+ }
3603
+ /**
3604
+ * Subagent transcripts live beside the session transcript at
3605
+ * `<session-id>/subagents/*.jsonl` with a `.meta.json` sibling the scan
3606
+ * ignores. Their usage belongs to the turn open when the scan runs: a
3607
+ * subagent only runs while its parent turn is in flight. A straggler line
3608
+ * that lands after the next user message therefore attributes to the open
3609
+ * turn, never to a closed one.
3610
+ */
3611
+ async consumeSubagents(registration, draft) {
3612
+ const directory = subagentsDirectory(registration.transcript_path);
3613
+ let names;
3614
+ try {
3615
+ names = (await readdir2(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
3616
+ } catch (error) {
3617
+ if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
3618
+ throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
3619
+ }
3620
+ if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
3621
+ this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
3622
+ names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
3623
+ }
3624
+ let consumed = 0;
3625
+ for (const name of names) {
3626
+ consumed += await this.consumeSubagentFile(draft, directory, name);
3627
+ }
3628
+ return consumed;
3629
+ }
3630
+ async consumeSubagentFile(draft, directory, name) {
3631
+ const path = join4(directory, name);
3632
+ let handle;
3633
+ try {
3634
+ handle = await open5(path, O_RDONLY3 | O_NOFOLLOW2);
3635
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3636
+ const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
3637
+ const pinned = await this.preflightSubagent(entry, size, device, inode);
3638
+ const chunk = await this.readChunk(handle, pinned.offset, size);
3639
+ if (chunk === "empty" || chunk === "partial") {
3640
+ draft.subagents[name] = pinned;
3641
+ return 0;
3642
+ }
3643
+ const consumed = this.consumeSubagentLines(draft, chunk);
3644
+ const offset = pinned.offset + chunk.length;
3645
+ const anchor = await this.readAnchor(handle, offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
3646
+ draft.subagents[name] = { device, inode, offset, anchor_start: anchor.start, anchor_sha256: anchor.sha256 };
3647
+ return consumed;
3648
+ } catch (error) {
3649
+ if (isMissingTranscriptError(error)) {
3650
+ delete draft.subagents[name];
3651
+ return 0;
3652
+ }
3653
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("payload_rejected", "Claude subagent transcript is unavailable.");
3654
+ } finally {
3655
+ await handle?.close();
3656
+ }
3657
+ }
3658
+ consumeSubagentLines(draft, chunk) {
3659
+ let consumed = 0;
3660
+ for (const line of chunk.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3661
+ consumed += 1;
3662
+ const record5 = parseTranscriptLine(line);
3663
+ if (record5 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
3664
+ if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
3665
+ else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
3666
+ }
3667
+ return consumed;
3668
+ }
3669
+ async preflightSubagent(entry, size, device, inode) {
3670
+ if (entry.device === void 0) {
3671
+ if (entry.offset !== 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript cursor is unpinned past its start.");
3672
+ return { device, inode, offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
3673
+ }
3674
+ if (entry.device !== device || entry.inode !== inode) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was replaced.");
3675
+ if (size < entry.offset) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was truncated.");
3676
+ return { device, inode, offset: entry.offset, anchor_start: entry.anchor_start, anchor_sha256: entry.anchor_sha256 };
3677
+ }
3678
+ /**
3679
+ * The open turn's terminal is emitted optimistically on every scan whose
3680
+ * new lines changed its totals, so usage is visible before the next user
3681
+ * message closes the turn. The emission digest changes with the content,
3682
+ * so a repeated scan over unchanged state emits nothing.
3683
+ */
3684
+ async emitOpenTerminal(registration, draft) {
3685
+ const turn = draft.open_turn;
3686
+ if (turn === void 0 || turn.message_ids.length === 0) return;
3687
+ const digest2 = terminalDigest(turn);
3688
+ if (digest2 === turn.last_terminal_digest) return;
3689
+ await this.putTerminalEnvelope(registration, turn, digest2, void 0);
3690
+ draft.enqueued += 1;
3691
+ draft.open_turn = { ...turn, last_terminal_digest: digest2 };
3692
+ }
3693
+ async putTerminalEnvelope(registration, turn, digest2, gapAt) {
3694
+ const observedAt = gapAt ?? turn.last_observed_at;
3695
+ const tokenValue = turn.message_ids.length === 0 ? hostObservationNotRecorded("claude_turn_usage_not_observed") : availableTokenValue(turn.counts);
3696
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: turn.turn_key };
3697
+ await this.store.putEnvelope({
3698
+ schema_version: "ceal.claude_observation_outbox.v1",
3699
+ identity,
3700
+ host_input_ref: turn.host_input_ref,
3701
+ input_kind: "host_automatic",
3702
+ prompt_ref: turn.prompt_ref,
3703
+ observations: [
3704
+ { 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 } },
3705
+ { observation_ref: observationRef("transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue },
3706
+ { observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
3707
+ ],
3708
+ origin_input: false,
3709
+ submission_ref: submissionRef("terminal", turn.turn_key, digest2, turn.prompt_ref),
3710
+ created_at: observedAt,
3711
+ expires_at: this.expire(this.outboxRetentionMs)
3712
+ });
3713
+ }
3714
+ async commitCursor(registration, handle, draft, device, inode) {
3715
+ const anchor = await this.readAnchor(handle, draft.offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
3716
+ await this.store.writeTranscriptCursor({
3717
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3718
+ host_source_ref: registration.host_source_ref,
3719
+ host_session_ref: registration.host_session_ref,
3720
+ transcript_path: registration.transcript_path,
3721
+ device,
3722
+ inode,
3723
+ offset: draft.offset,
3724
+ anchor_start: anchor.start,
3725
+ anchor_sha256: anchor.sha256,
3726
+ subagents: draft.subagents,
3727
+ ...draft.open_turn === void 0 ? {} : { open_turn: draft.open_turn },
3728
+ ...draft.unattributed === void 0 ? {} : { unattributed: draft.unattributed },
3729
+ ...draft.session_observed ? { session_observed: true } : {},
3730
+ status: "active",
3731
+ updated_at: this.stamp()
3732
+ });
3733
+ }
3734
+ async registerPrompt(sessionRef, turnRef, promptRef, prompt) {
3735
+ if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
3736
+ try {
3737
+ await this.registerCurrentPrompt({
3738
+ producer_id: "claude",
3739
+ producer_installation_ref: this.producerInstallationRef,
3740
+ local_session_ref: sessionRef,
3741
+ local_turn_ref: turnRef,
3742
+ prompt_ref: promptRef,
3743
+ current_prompt: prompt,
3744
+ context_expires_at: this.expire(this.outboxRetentionMs)
3745
+ });
3746
+ this.diagnostic("current_prompt_registered");
3747
+ } catch (error) {
3748
+ this.diagnostic("current_prompt_registration_failed");
3749
+ throw error;
3750
+ }
3751
+ }
3752
+ async activateContext(identity, promptRef, turnGenerationRef) {
3753
+ try {
3754
+ await this.store.activateInvocationContext(identity, promptRef, this.expire(this.outboxRetentionMs), void 0, turnGenerationRef);
3755
+ this.diagnostic("activation_recorded");
3756
+ } catch (error) {
3757
+ this.diagnostic("activation_failed");
3758
+ throw error;
3759
+ }
3760
+ }
3761
+ async quarantineStored(cursor, device, inode, reason) {
3762
+ await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
3763
+ return null;
3764
+ }
3765
+ async quarantineUnopened(registration, reason) {
3766
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
3767
+ await this.store.writeTranscriptCursor({ ...existing ?? this.initialCursor(registration), transcript_path: registration.transcript_path, status: "quarantined", reason, updated_at: this.stamp() });
3768
+ return { records: 0, enqueued: 0, quarantined: true };
3769
+ }
3770
+ async quarantineDraft(cursor) {
3771
+ await this.store.writeTranscriptCursor({ ...cursor, status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
3772
+ return { records: 0, enqueued: 0, quarantined: true };
3773
+ }
3774
+ async quarantineFailure(registration, handle) {
3775
+ if (handle === void 0) return;
3776
+ const stats = await handle.stat().catch(() => null);
3777
+ if (stats === null) return;
3778
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
3779
+ const cursor = existing ?? this.initialCursor(registration);
3780
+ await this.store.writeTranscriptCursor({ ...cursor, device: String(stats.dev), inode: String(stats.ino), status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
3781
+ }
3782
+ async preservedCursor(registration) {
3783
+ return await this.store.readTranscriptCursor(registration).catch(() => null) ?? this.initialCursor(registration);
3784
+ }
3785
+ };
3786
+ function mutableCursor(cursor) {
3787
+ return {
3788
+ offset: cursor.offset,
3789
+ ...cursor.open_turn === void 0 ? {} : { open_turn: cursor.open_turn },
3790
+ ...cursor.unattributed === void 0 ? {} : { unattributed: cursor.unattributed },
3791
+ subagents: Object.fromEntries(Object.entries(cursor.subagents).map(([name, entry]) => [name, { ...entry }])),
3792
+ session_observed: cursor.session_observed ?? false,
3793
+ consumed: 0,
3794
+ enqueued: 0
3795
+ };
3796
+ }
3797
+ function zeroCounts() {
3798
+ return { input: 0, cached_input: 0, cache_write_input: 0, output: 0 };
3799
+ }
3800
+ function terminalDigest(turn) {
3801
+ return transcriptObjectSha256({ turn: turn.turn_key, messages: turn.message_ids, counts: turn.counts });
3802
+ }
3803
+ function availableTokenValue(counts) {
3804
+ const input = counts.input;
3805
+ const output = counts.output;
3806
+ if (!Number.isSafeInteger(input + output)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3807
+ return createHostTokenObservationValue({
3808
+ input,
3809
+ output,
3810
+ total: input + output,
3811
+ cached_input: counts.cached_input,
3812
+ cache_write_input: counts.cache_write_input
3813
+ }, TOKEN_OBSERVATION_SOURCE);
3814
+ }
3815
+ function addCounts(current, next) {
3816
+ const counts = {
3817
+ input: current.input + next.input,
3818
+ cached_input: current.cached_input + next.cached_input,
3819
+ cache_write_input: current.cache_write_input + next.cache_write_input,
3820
+ output: current.output + next.output
3821
+ };
3822
+ if (![counts.input, counts.cached_input, counts.cache_write_input, counts.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
3823
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3824
+ }
3825
+ return counts;
3826
+ }
3827
+ function accumulateUnattributed(current, usage) {
3828
+ const base = current ?? { input: 0, cached_input: 0, cache_write_input: 0, output: 0, message_ids: [], last_observed_at: usage.observed_at };
3829
+ if (base.message_ids.includes(usage.uuid)) return base;
3830
+ 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");
3831
+ const counts = addCounts(base, usage);
3832
+ return {
3833
+ ...counts,
3834
+ message_ids: [...base.message_ids, usage.uuid],
3835
+ last_observed_at: usage.observed_at > base.last_observed_at ? usage.observed_at : base.last_observed_at
3836
+ };
3837
+ }
3838
+ function foldUnattributed(bucket) {
3839
+ if (bucket === void 0) return void 0;
3840
+ if (![bucket.input, bucket.cached_input, bucket.cache_write_input, bucket.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
3841
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3842
+ }
3843
+ 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 };
3844
+ }
3845
+ function toUsage(uuid, observedAt, usage) {
3846
+ const input = usageNumber(usage.input_tokens);
3847
+ const cached = usageNumber(usage.cache_read_input_tokens ?? 0);
3848
+ const created = usageNumber(usage.cache_creation_input_tokens ?? 0);
3849
+ const output = usageNumber(usage.output_tokens);
3850
+ if (input === null || cached === null || created === null || output === null) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3851
+ const total = input + cached + created;
3852
+ if (![total, output].every((value) => Number.isSafeInteger(value) && value >= 0)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3853
+ return { uuid, observed_at: observedAt, input: total, cached_input: cached, cache_write_input: created, output };
3854
+ }
3855
+ function usageNumber(value) {
3856
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
3857
+ }
3858
+ function parseTranscriptLine(line) {
3859
+ try {
3860
+ return JSON.parse(line);
3861
+ } catch {
3862
+ return null;
3863
+ }
3864
+ }
3865
+ function lineIdentity(record5) {
3866
+ if (typeof record5.uuid !== "string" || record5.uuid.length === 0 || record5.uuid.length > 256) return null;
3867
+ if (typeof record5.timestamp !== "string" || !Number.isFinite(Date.parse(record5.timestamp))) return null;
3868
+ return { uuid: record5.uuid, timestamp: record5.timestamp };
3869
+ }
3870
+ function isUserTurnLine(record5) {
3871
+ if (!isTranscriptRecord(record5) || record5.type !== "user") return false;
3872
+ if (!isTranscriptRecord(record5.message) || typeof record5.message.content !== "string") return false;
3873
+ if (record5.isMeta === true && (!isTranscriptRecord(record5.origin) || typeof record5.origin.kind !== "string")) return false;
3874
+ const identity = lineIdentity(record5);
3875
+ if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
3876
+ return true;
3877
+ }
3878
+ function claudeTranscriptScanFailureKind(error) {
3879
+ if (isSymlinkTranscript(error)) return "symlink";
3880
+ if (error instanceof ClaudeHostError) {
3881
+ if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
3882
+ if (error.code === "payload_rejected") return "payload_rejected";
3883
+ if (error.code === "storage") return "read_error";
3884
+ }
3885
+ const code2 = transcriptErrorCode(error);
3886
+ if (code2 === "EFBIG") return "size_budget";
3887
+ if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
3888
+ return "other";
3889
+ }
3890
+ function isAssistantLine(record5) {
3891
+ return isTranscriptRecord(record5) && record5.type === "assistant" && isTranscriptRecord(record5.message);
3892
+ }
3893
+ function isAssistantUsageLine(record5) {
3894
+ if (!isTranscriptRecord(record5)) return false;
3895
+ if (!isAssistantLine(record5) || record5.message.usage === void 0) return false;
3896
+ const identity = lineIdentity(record5);
3897
+ if (identity === null || !isTranscriptRecord(record5.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
3898
+ return true;
3899
+ }
3900
+ function subagentsDirectory(transcriptPath) {
3901
+ const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
3902
+ return join4(stem, "subagents");
3903
+ }
3904
+ function observationRef(event, identity, value) {
3905
+ return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
3906
+ }
3907
+ function submissionRef(kind, turn, digest2, prompt) {
3908
+ return `claude-submission:${transcriptObjectSha256({ kind, turn, digest: digest2, prompt })}`;
3909
+ }
3910
+ async function ownedClaudeTranscriptFile(handle) {
3911
+ const stats = await handle.stat();
3912
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
3913
+ if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
3914
+ if (stats.uid !== uid) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
3915
+ return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
3916
+ }
3917
+ function isNotDirectory(error) {
3918
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
3919
+ }
3920
+ function isSymlinkTranscript(error) {
3921
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
3922
+ }
3923
+ function transcriptErrorCode(error) {
3924
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
3925
+ }
3926
+
3927
+ // host/claude/src/claude-producer-runtime.ts
3928
+ async function drainClaudeProducerOnce(options) {
3929
+ const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
3930
+ const scan = await new ClaudeTranscriptWatcher({
3931
+ outbox_dir: options.outbox_dir,
3932
+ producer_installation_ref: options.producer_installation_ref,
3933
+ register_current_prompt: (input) => promptPort.register(input),
3934
+ diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("claude", event, failure_kind)
3935
+ }).scanOnce().catch((error) => {
3936
+ writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
3937
+ throw error;
3938
+ });
3939
+ 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 } });
3940
+ return { scanned: scan.enqueued, ...await producer.drainOutbox() };
3941
+ }
3942
+
3943
+ // host/claude/src/claude-producer-service.ts
3944
+ var CLAUDE_PRODUCER_CONFIG_SCHEMA = "ceal.claude_producer_config.v1";
3945
+ var CLAUDE_TRANSCRIPTS_ROOT = "claude_transcripts_root";
3946
+ var CLAUDE_PRODUCER_DESCRIPTOR = {
3947
+ schema: CLAUDE_PRODUCER_CONFIG_SCHEMA,
3948
+ producer_id: "claude",
3949
+ extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
3950
+ config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
3951
+ runtime_options: (config) => Object.freeze({
3952
+ outbox_dir: config.outbox_dir,
3953
+ producer_installation_ref: config.producer_installation_ref,
3954
+ producer_epoch: config.producer_epoch,
3955
+ relay_endpoint: config.relay_endpoint,
3956
+ timeout_ms: config.ipc_timeout_ms
3957
+ }),
3958
+ drain: drainClaudeProducerOnce
3959
+ };
3960
+ function runClaudeProducerCli(args, signal, dependencies = {}) {
3961
+ return runHostProducerCli(CLAUDE_PRODUCER_DESCRIPTOR, args, signal, dependencies);
3962
+ }
3963
+
2583
3964
  // host/producer-command.ts
2584
3965
  async function runProducerCommand(schemaVersion, run) {
2585
3966
  const controller = new AbortController();
@@ -2590,8 +3971,8 @@ async function runProducerCommand(schemaVersion, run) {
2590
3971
  try {
2591
3972
  await run(process.argv.slice(2), controller.signal);
2592
3973
  } catch (error) {
2593
- const code3 = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "producer_failed";
2594
- process.stderr.write(`${JSON.stringify({ schema_version: schemaVersion, ok: false, code: code3 })}
3974
+ const code2 = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "producer_failed";
3975
+ process.stderr.write(`${JSON.stringify({ schema_version: schemaVersion, ok: false, code: code2 })}
2595
3976
  `);
2596
3977
  process.exitCode = 1;
2597
3978
  } finally {