@corca-org/ceal-host-win32-x64 1.0.18 → 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"]);
@@ -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([
@@ -1086,6 +1112,8 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1086
1112
  modified_at: { required: false, timestamp: true },
1087
1113
  modified_at_local: { required: false, timestamp: true },
1088
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 },
1089
1117
  visibility: { required: false, timestamp: false },
1090
1118
  start: { required: false, timestamp: false },
1091
1119
  end: { required: false, timestamp: false },
@@ -1119,6 +1147,15 @@ function cealExcludedTargetCoverageJsonSchema() {
1119
1147
  };
1120
1148
  }
1121
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
+
1122
1159
  // packages/ceal-client-protocol/src/write-safety-finding.ts
1123
1160
  var CEAL_WRITE_SAFETY_FINDING_STATUSES = vocabulary(["warning", "unchecked"]);
1124
1161
  var CEAL_WRITE_SAFETY_AUDIENCES = vocabulary(["workspace", "person", "members", "unknown"]);
@@ -1146,7 +1183,11 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
1146
1183
  "refresh_revoked",
1147
1184
  "authority_replaced",
1148
1185
  "refresh_recovery_unavailable",
1149
- "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"
1150
1191
  ]);
1151
1192
 
1152
1193
  // packages/ceal-client-protocol/src/request-validation-primitives.ts
@@ -1179,7 +1220,7 @@ var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
1179
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"]);
1180
1221
  var CEAL_REQUEST_JSON_SCHEMA = {
1181
1222
  type: "object",
1182
- maxProperties: 10,
1223
+ maxProperties: 11,
1183
1224
  additionalProperties: false,
1184
1225
  properties: {
1185
1226
  schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
@@ -1203,7 +1244,8 @@ var CEAL_REQUEST_JSON_SCHEMA = {
1203
1244
  host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1204
1245
  host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1205
1246
  host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1206
- requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." }
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." }
1207
1249
  },
1208
1250
  required: CEAL_REQUEST_KEYS
1209
1251
  };
@@ -1262,6 +1304,7 @@ var REQUEST_EXECUTION_PREPARE_REFUSAL_PROPERTIES = {
1262
1304
  retryable: { type: "boolean" },
1263
1305
  source: { type: "string", enum: ["provider", "gateway"] },
1264
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 },
1265
1308
  excluded_targets: cealExcludedTargetCoverageJsonSchema(),
1266
1309
  repaired_input: {},
1267
1310
  unrepaired_pointers: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 256 } },
@@ -1320,12 +1363,55 @@ function isCealHostInputKind(value) {
1320
1363
  return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
1321
1364
  }
1322
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
+ }
1323
1409
  function validateCealHostObservationDraft(value) {
1324
- const record6 = operationRecord(value, "Ceal Host observation draft is invalid.");
1325
- requirePrefixedRef(record6.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1326
- requireClosedValue(record6.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1327
- requireIsoTimestamp(record6.observed_at, "Ceal Host observation time is invalid.");
1328
- 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);
1329
1415
  }
1330
1416
  var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
1331
1417
  var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "cost", "timing"]);
@@ -1355,6 +1441,10 @@ function inspectHostKeys(value) {
1355
1441
  // packages/ceal-client-protocol/src/host-prompt-ref.ts
1356
1442
  var CEAL_HOST_PROMPT_DIGEST_PATTERN = "^[a-f0-9]{64}$";
1357
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
+ }
1358
1448
 
1359
1449
  // packages/ceal-client-protocol/src/request-effect-approval.ts
1360
1450
  var CEAL_EFFECT_APPROVAL_DECISION_CHECK_PHRASES = Object.freeze([
@@ -1467,12 +1557,84 @@ var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
1467
1557
  "host_input_mismatch",
1468
1558
  "host_prompt_mismatch"
1469
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
+ ]);
1470
1633
 
1471
1634
  // packages/ceal-client-protocol/src/client-result-materialization.ts
1472
1635
  var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
1473
- var CEAL_MODEL_VISIBLE_COMMAND_OUTPUT_MAX_BYTES = 32 * 1024;
1474
- var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "read_argv"]);
1475
- var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "local_result_root", "continue_argv", "model_disclosure"]);
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"]);
1476
1638
 
1477
1639
  // packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
1478
1640
  var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -1502,14 +1664,6 @@ var CEAL_GATEWAY_ANNOUNCEMENT_POLICY_NON_CLAIMS = vocabulary([
1502
1664
  "target_specific_scope_not_declared"
1503
1665
  ]);
1504
1666
 
1505
- // packages/ceal-client-protocol/src/gateway-write-identity.ts
1506
- var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1507
- var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1508
- replay_identity: "idempotency_claim_sha256",
1509
- lookup_handle: "write_request_sha256",
1510
- collision_evidence: "normalized_mutation_sha256"
1511
- });
1512
-
1513
1667
  // packages/ceal-client-protocol/src/gateway-scoped-identity-projection-validation.ts
1514
1668
  var PROJECTION_REVISION_KEYS = Object.freeze(["graph_revision", "subject_key_revision", "projection_revision"]);
1515
1669
 
@@ -1522,6 +1676,18 @@ var CEAL_SUPPORTED_GATEWAY_PROTOCOL_RANGE = Object.freeze({
1522
1676
  // packages/ceal-client-protocol/src/admin-projection-vocabulary.ts
1523
1677
  var ADMIN_PROJECTION_STATES = vocabulary(["ready", "empty", "partial", "unknown"]);
1524
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
+ ]);
1525
1691
  var ADMIN_GAP_KINDS = vocabulary(["unknown", "unavailable", "partial"]);
1526
1692
  var ADMIN_UNAVAILABLE_REASONS = vocabulary([
1527
1693
  "not_recorded",
@@ -1545,7 +1711,11 @@ var ADMIN_SOURCE_OWNERS = vocabulary([
1545
1711
  "person",
1546
1712
  "resource",
1547
1713
  "prompt",
1548
- "usage"
1714
+ "usage",
1715
+ "connector",
1716
+ "session",
1717
+ "knowledge",
1718
+ "policy"
1549
1719
  ]);
1550
1720
  var ADMIN_COMPLETENESS_STATES = vocabulary(["complete", "partial", "unknown"]);
1551
1721
  var ADMIN_OPERATION_OUTCOMES = vocabulary(["completed", "failed", "denied", "cancelled", "unknown"]);
@@ -1589,14 +1759,19 @@ var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
1589
1759
  var HOST_PRODUCER_SERVE_COMMAND_PREFIX = ["serve", "--config"];
1590
1760
  var HostProducerServiceError = class extends Error {
1591
1761
  code;
1592
- constructor(code3, message = code3) {
1593
- super(message === code3 ? code3 : `${code3}: ${message}`);
1762
+ constructor(code2, message = code2) {
1763
+ super(message === code2 ? code2 : `${code2}: ${message}`);
1594
1764
  this.name = "HostProducerServiceError";
1595
- this.code = code3;
1765
+ this.code = code2;
1596
1766
  }
1597
1767
  };
1598
1768
  var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
1599
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
+ }
1600
1775
  function parseHostProducerServeArgs(args, platform = process.platform) {
1601
1776
  return parseProducerServeArgs(args, platform, hostProducerError);
1602
1777
  }
@@ -1623,8 +1798,8 @@ async function serveHostProducer(descriptor, config, signal, dependencies = {})
1623
1798
  async function runHostProducerCli(descriptor, args, signal, dependencies = {}) {
1624
1799
  await serveHostProducer(descriptor, await readHostProducerConfig(descriptor, parseHostProducerServeArgs(args)), signal, dependencies);
1625
1800
  }
1626
- function hostProducerError(code3, message = code3) {
1627
- return new HostProducerServiceError(code3, message);
1801
+ function hostProducerError(code2, message = code2) {
1802
+ return new HostProducerServiceError(code2, message);
1628
1803
  }
1629
1804
  function parseProducerServeArgs(args, platform, error) {
1630
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>"));
@@ -1775,11 +1950,20 @@ var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
1775
1950
  function isHostProducerFrameKind(value) {
1776
1951
  return typeof value === "string" && FRAME_KINDS.has(value);
1777
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
+ ];
1778
1962
  var HostProducerFrameError = class extends Error {
1779
1963
  code;
1780
- constructor(code3, message = code3) {
1964
+ constructor(code2, message = code2) {
1781
1965
  super(message);
1782
- this.code = code3;
1966
+ this.code = code2;
1783
1967
  }
1784
1968
  };
1785
1969
  function encodeHostProducerFrame(frame) {
@@ -1809,6 +1993,9 @@ function decodeHostProducerFrame(input) {
1809
1993
  validateFrame(value);
1810
1994
  return value;
1811
1995
  }
1996
+ function isHostSafeRef(value) {
1997
+ return safeRef(value);
1998
+ }
1812
1999
  function validateFrame(value) {
1813
2000
  if (!record(value)) throw new HostProducerFrameError("invalid_frame_shape");
1814
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)}`);
@@ -1896,6 +2083,15 @@ function exactOptional(value, required, optional) {
1896
2083
  return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
1897
2084
  }
1898
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
+
1899
2095
  // host/relay/src/durable-atomic-replacement.ts
1900
2096
  import { O_RDONLY } from "node:constants";
1901
2097
  import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
@@ -1984,17 +2180,6 @@ function errorCode(error) {
1984
2180
  // host/relay/src/relay-request-binding-store.ts
1985
2181
  var MAX_STATE_BYTES = 8 * 1024 * 1024;
1986
2182
 
1987
- // host/relay/src/relay-quarantine-store.ts
1988
- var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
1989
-
1990
- // host/relay/src/relay-admission-store.ts
1991
- var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
1992
- var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
1993
- var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
1994
-
1995
- // host/relay/src/relay-ipc.ts
1996
- import { createConnection, createServer } from "node:net";
1997
-
1998
2183
  // host/relay/src/current-prompt-custody.ts
1999
2184
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
2000
2185
 
@@ -2015,6 +2200,8 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2015
2200
  "host_input_ref"
2016
2201
  ]);
2017
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";
2018
2205
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2019
2206
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
2020
2207
  var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
@@ -2025,6 +2212,8 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
2025
2212
  "gateway_unavailable",
2026
2213
  "gateway_refused",
2027
2214
  "gateway_response_invalid",
2215
+ "gateway_rate_limited",
2216
+ "producer_frame_invalid",
2028
2217
  "deadline",
2029
2218
  "cancelled",
2030
2219
  "mint_failed",
@@ -2038,6 +2227,17 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
2038
2227
  "host_input_mismatch",
2039
2228
  "host_prompt_mismatch"
2040
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"
2240
+ ]);
2041
2241
  var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
2042
2242
  var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
2043
2243
  "permission_denied",
@@ -2053,12 +2253,15 @@ var HostRelayIpcError = class extends Error {
2053
2253
  code;
2054
2254
  /** Set only when the client-side socket exchange itself failed. */
2055
2255
  failure;
2056
- constructor(code3, message = code3, failure) {
2256
+ constructor(code2, message = code2, failure) {
2057
2257
  super(message);
2058
- this.code = code3;
2258
+ this.code = code2;
2059
2259
  this.failure = failure;
2060
2260
  }
2061
2261
  };
2262
+ function hostRelayExchangeFailure(error) {
2263
+ return error instanceof HostRelayIpcError ? error.failure : void 0;
2264
+ }
2062
2265
  function socketErrorFailure(error) {
2063
2266
  switch (errorCode2(error)) {
2064
2267
  case "EACCES":
@@ -2085,6 +2288,19 @@ var HostRelayIpcAdmissionPort = class {
2085
2288
  return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
2086
2289
  }
2087
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
+ };
2088
2304
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2089
2305
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
2090
2306
  }
@@ -2163,277 +2379,235 @@ function decodeResponseBuffer(buffer) {
2163
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}` }])}.`);
2164
2380
  return decodeHostRelayIpcResponse(buffer);
2165
2381
  }
2166
- function errorCode2(error) {
2167
- return record2(error) && typeof error.code === "string" ? error.code : null;
2168
- }
2169
-
2170
- // host/claude/src/claude-producer.ts
2171
- import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile } from "node:fs/promises";
2172
- import { join as join3 } from "node:path";
2173
-
2174
- // host/claude/src/claude-outbox.ts
2175
- import { open as open3, mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
2176
- import { join as join2 } from "node:path";
2177
-
2178
- // host/claude/src/claude-types.ts
2179
- var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure"];
2180
- var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "storage", "configuration"];
2181
- function isClaudeHostErrorCode(value) {
2182
- 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 };
2183
2387
  }
2184
- var CLAUDE_OUTBOX_SCHEMA = "ceal.claude_hook_outbox.v1";
2185
- var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
2186
- var ClaudeHostError = class extends Error {
2187
- name = "ClaudeHostError";
2188
- code;
2189
- constructor(code3, message) {
2190
- super(message);
2191
- if (!isClaudeHostErrorCode(code3)) throw new TypeError("Claude Host error code is invalid.");
2192
- 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.");
2193
2398
  }
2194
- };
2195
- function isClaudeHookEvent(value) {
2196
- return typeof value === "string" && CLAUDE_HOOK_EVENTS.some((event) => event === value);
2197
- }
2198
-
2199
- // host/invocation-context.ts
2200
- var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
2201
- var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
2202
- function invocationContextKey(identity) {
2203
- return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
2204
- }
2205
- function hasHostInvocationIdentityKeys(value) {
2206
- return exactKeys2(value, HOST_INVOCATION_IDENTITY_KEYS);
2207
2399
  }
2208
- function hasHostInvocationContextKeys(value, optional = []) {
2209
- return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
2210
- }
2211
- function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
2212
- const context = {
2213
- schema_version: descriptor.schema_version,
2214
- identity,
2215
- prompt_ref: promptRef,
2216
- expires_at: expiresAt,
2217
- ...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
2218
- };
2219
- descriptor.validate(context);
2220
- 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;
2221
2406
  }
2222
- function exactKeys2(value, keys) {
2223
- const actual = Object.keys(value);
2224
- 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));
2225
2410
  }
2226
- function requiredKeys(value, required, optional) {
2227
- const actual = Object.keys(value);
2228
- const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
2229
- 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;
2230
2413
  }
2231
2414
 
2232
- // host/claude/src/claude-invocation-context.ts
2233
- function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory) {
2234
- return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory });
2235
- }
2236
- function validateClaudeHostInvocationContext(value) {
2237
- if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2238
- validateIdentity2(value.identity);
2239
- validateContextReferences(value);
2240
- validateContextTimes(value);
2241
- }
2242
- function contextShape(value) {
2243
- return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory"]);
2244
- }
2245
- function validateIdentity2(value) {
2246
- if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2247
- }
2248
- function validateContextReferences(value) {
2249
- 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;
2250
2458
  }
2251
- function validateContextTimes(value) {
2252
- if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2253
- 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;
2254
2463
  }
2255
- function validIdentity(value) {
2256
- 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");
2257
2466
  }
2258
- function optionalSafeRef(value) {
2259
- return value === void 0 || safeRef2(value);
2467
+ function transcriptObjectSha256(value) {
2468
+ return createHash3("sha256").update(JSON.stringify(value), "utf8").digest("hex");
2260
2469
  }
2261
- function optionalTimestamp(value) {
2262
- return value === void 0 || timestamp2(value);
2470
+ function isTranscriptRecord(value) {
2471
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2263
2472
  }
2264
- function optionalDirectory(value) {
2265
- 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";
2266
2475
  }
2267
- function safeRef2(value) {
2268
- 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();
2269
2480
  }
2270
- function timestamp2(value) {
2271
- 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();
2272
2483
  }
2273
- function record3(value) {
2274
- 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
+ };
2275
2495
  }
2276
-
2277
- // host/claude/src/claude-outbox.ts
2278
- var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2279
- var ClaudeOutboxStore = class {
2280
- entries;
2281
- directory;
2282
- activeContexts;
2283
- constructor(directory) {
2284
- if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2285
- this.directory = directory;
2286
- this.entries = join2(directory, "entries");
2287
- this.activeContexts = join2(directory, "active-contexts");
2288
- }
2289
- async prepare() {
2290
- await mkdir2(this.entries, { recursive: true, mode: 448 });
2291
- 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;
2292
2505
  }
2293
- async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory) {
2294
- const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory);
2295
- await this.prepare();
2296
- 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;
2297
2524
  }
2298
- async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
2299
- validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now);
2300
- await this.prepare();
2301
- 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));
2302
2527
  }
2303
- async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
2304
- await this.prepare();
2305
- const contexts = [];
2306
- for (const name of await readdir(this.activeContexts)) {
2307
- if (!name.endsWith(".json")) continue;
2308
- try {
2309
- const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
2310
- validateClaudeHostInvocationContext(value);
2311
- if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
2312
- } catch {
2313
- }
2314
- }
2315
- 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
+ });
2316
2533
  }
2317
- async put(batch) {
2318
- validateBatch(batch);
2319
- await this.prepare();
2320
- const path = this.path(batch.batch_ref);
2534
+ async openTranscript(registration, scan, handlers) {
2535
+ let handle;
2321
2536
  try {
2322
- const handle = await open3(path, "wx", 384);
2323
- try {
2324
- await handle.writeFile(JSON.stringify(batch));
2325
- await handle.sync();
2326
- } finally {
2327
- await handle.close();
2328
- }
2537
+ handle = await open3(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2538
+ return await scan(handle);
2329
2539
  } catch (error) {
2330
- if (code(error) !== "EEXIST") throw new ClaudeHostError("storage", `Unable to persist Claude hook batch (${refusalFact("error_code", code(error), "EEXIST for an idempotent retry")}).`);
2331
- const existing = JSON.parse(await readFile(path, "utf8"));
2332
- validateBatch(existing);
2333
- 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();
2334
2545
  }
2335
2546
  }
2336
- async list() {
2337
- await this.prepare();
2338
- const batches = [];
2339
- let malformed = 0;
2340
- for (const name of await readdir(this.entries)) {
2341
- if (!name.endsWith(".json")) continue;
2342
- try {
2343
- const value = JSON.parse(await readFile(join2(this.entries, name), "utf8"));
2344
- validateBatch(value);
2345
- batches.push(value);
2346
- } catch {
2347
- malformed += 1;
2348
- }
2349
- }
2350
- batches.sort(compareBatch);
2351
- 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 };
2352
2558
  }
2353
- async remove(batch) {
2354
- try {
2355
- await unlink2(this.path(batch.batch_ref));
2356
- } catch (error) {
2357
- if (code(error) !== "ENOENT") throw error;
2358
- }
2559
+ stamp() {
2560
+ return transcriptTimestamp(this.now, this.clockFailure);
2359
2561
  }
2360
- path(ref) {
2361
- return join2(this.entries, `${cealRequestSha256(ref)}.json`);
2562
+ expire(retentionMs) {
2563
+ return transcriptExpiration(this.now, retentionMs, this.clockFailure);
2362
2564
  }
2363
- activeContextPath(identity) {
2364
- return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
2565
+ sha(value) {
2566
+ return transcriptSha256(value);
2365
2567
  }
2366
- };
2367
- function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
2368
- 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.");
2369
- }
2370
- async function readInvocationContextFile(file, hostSourceRef, hostSessionRef, now) {
2371
- try {
2372
- const context = JSON.parse(await readFile(file, "utf8"));
2373
- validateClaudeHostInvocationContext(context);
2374
- return contextMatches(context, hostSourceRef, hostSessionRef) && Date.parse(context.expires_at) > now.getTime() ? context : null;
2375
- } catch (error) {
2376
- if (code(error) === "ENOENT") return null;
2377
- if (error instanceof ClaudeHostError) throw error;
2378
- throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
2568
+ objectSha(value) {
2569
+ return transcriptObjectSha256(value);
2379
2570
  }
2380
- }
2381
- function contextMatches(context, hostSourceRef, hostSessionRef) {
2382
- return context.identity.host_source_ref === hostSourceRef && context.identity.host_session_ref === hostSessionRef;
2383
- }
2384
- function compareBatch(left, right) {
2385
- return rank(left.hook_event) - rank(right.hook_event) || left.occurred_at.localeCompare(right.occurred_at) || left.batch_ref.localeCompare(right.batch_ref);
2386
- }
2387
- function rank(event) {
2388
- return event === "SessionStart" ? 0 : event === "UserPromptSubmit" ? 1 : 2;
2389
- }
2390
- function validateBatch(value) {
2391
- if (!validBatchHeader(value)) throw new ClaudeHostError("storage", "Claude hook batch is malformed.");
2392
- for (const [index, draft] of value.frames.entries()) {
2393
- if (!record4(draft) || !hostFrameKind(draft.frame_kind) || !record4(draft.payload)) throw new ClaudeHostError("storage", "Claude frame draft is malformed.");
2394
- 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);
2395
2573
  }
2396
- }
2397
- function validBatchHeader(value) {
2398
- return record4(value) && value.schema_version === CLAUDE_OUTBOX_SCHEMA && safeRef3(value.batch_ref) && isClaudeHookEvent(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;
2399
- }
2400
- function hostFrameKind(value) {
2401
- return isHostProducerFrameKind(value);
2402
- }
2403
- function safeRef3(value) {
2404
- return typeof value === "string" && SAFE_REF4.test(value);
2405
- }
2406
- function optionalRef(value) {
2407
- return value === void 0 || typeof value === "string" && SAFE_REF4.test(value);
2408
- }
2409
- function timestamp3(value) {
2410
- return typeof value === "string" && Number.isFinite(Date.parse(value));
2411
- }
2412
- function record4(value) {
2413
- return value !== null && typeof value === "object" && !Array.isArray(value);
2414
- }
2415
- function code(error) {
2416
- return record4(error) && typeof error.code === "string" ? error.code : null;
2417
- }
2418
-
2419
- // host/claude/src/claude-producer.ts
2420
- var STATE_SCHEMA = "ceal.claude_producer_state.v1";
2421
- var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2422
- var ClaudeProducer = class {
2423
- 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;
2424
2594
  statePath;
2425
- installation;
2595
+ stateSchema;
2596
+ installationRef;
2426
2597
  epoch;
2427
- options;
2598
+ now;
2428
2599
  tail = Promise.resolve();
2429
- constructor(options) {
2430
- if (!SAFE_REF5.test(options.producer_installation_ref)) throw new ClaudeHostError("configuration", "Claude producer installation identity is invalid.");
2431
- this.options = options;
2432
- this.epoch = options.producer_epoch ?? 1;
2433
- if (!Number.isSafeInteger(this.epoch) || this.epoch <= 0) throw new ClaudeHostError("configuration", "Claude producer epoch is invalid.");
2434
- this.store = new ClaudeOutboxStore(options.outbox_dir);
2435
- this.statePath = join3(options.outbox_dir, "claude-producer-state.json");
2436
- 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());
2437
2611
  }
2438
2612
  drainOutbox() {
2439
2613
  const run = this.tail.then(() => this.drainLocked());
@@ -2441,14 +2615,21 @@ var ClaudeProducer = class {
2441
2615
  return run;
2442
2616
  }
2443
2617
  async drainLocked() {
2444
- const read = await this.store.list();
2618
+ const read = await this.host.listEnvelopes();
2445
2619
  let admitted = 0;
2620
+ let expired = 0;
2446
2621
  let retained = 0;
2447
- for (const batch of read.entries) {
2448
- 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);
2449
2630
  let complete = true;
2450
2631
  for (const frame of frames) {
2451
- const outcome = await this.options.relay.admit(frame);
2632
+ const outcome = await this.relay.admit(frame);
2452
2633
  if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2453
2634
  complete = false;
2454
2635
  break;
@@ -2458,105 +2639,117 @@ var ClaudeProducer = class {
2458
2639
  retained += 1;
2459
2640
  break;
2460
2641
  }
2461
- await this.store.remove(batch);
2462
- await this.release(batch.batch_ref);
2642
+ await this.host.removeEnvelope(envelope);
2643
+ await this.release(this.host.submissionRefOf(envelope));
2463
2644
  admitted += 1;
2464
2645
  }
2465
- 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 };
2466
2647
  }
2467
- async reserve(batch) {
2648
+ async hasReservation(submissionRef2) {
2649
+ return (await this.readState()).pending[submissionRef2] !== void 0;
2650
+ }
2651
+ async reserve(envelope) {
2468
2652
  const state = await this.readState();
2469
- const existing = state.pending[batch.batch_ref];
2653
+ const submissionRef2 = this.host.submissionRefOf(envelope);
2654
+ const existing = state.pending[submissionRef2];
2470
2655
  if (existing !== void 0) return existing;
2471
- 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 }));
2472
- 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 } });
2473
2668
  return frames;
2474
2669
  }
2475
- async release(ref) {
2670
+ async release(submissionRef2) {
2476
2671
  const state = await this.readState();
2477
- if (state.pending[ref] === void 0) return;
2672
+ if (state.pending[submissionRef2] === void 0) return;
2478
2673
  const pending = { ...state.pending };
2479
- delete pending[ref];
2674
+ delete pending[submissionRef2];
2480
2675
  await this.writeState({ ...state, pending });
2481
2676
  }
2482
2677
  async readState() {
2483
2678
  try {
2484
- 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);
2485
2681
  } catch (error) {
2486
- if (code2(error) !== "ENOENT") throw error;
2487
- 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: {} };
2488
2684
  }
2489
2685
  }
2490
2686
  async writeState(state) {
2491
- await mkdir3(this.store.directory, { recursive: true, mode: 448 });
2492
- const temp = `${this.statePath}.${process.pid}.new`;
2493
- await writeFile(temp, JSON.stringify(state), { mode: 384 });
2494
- 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;
2495
2723
  }
2496
2724
  };
2497
- function decodeState(value, installation, epoch) {
2498
- if (!validStateHeader(value, installation)) throw new ClaudeHostError("storage", `Claude producer state header is malformed or belongs to another installation (${refusalFacts([
2499
- { name: "schema_version", value: record5(value) ? value.schema_version : void 0, expected: STATE_SCHEMA },
2500
- { name: "producer_installation_ref", value: record5(value) ? value.producer_installation_ref : void 0, expected: installation }
2501
- ])}).`);
2502
- 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}`)}).`);
2503
- const pending = {};
2504
- for (const [ref, frames] of Object.entries(value.pending)) {
2505
- if (!SAFE_REF5.test(ref) || !Array.isArray(frames) || frames.length === 0) throw new ClaudeHostError("storage", "Claude producer reservation is malformed.");
2506
- pending[ref] = frames.map((frame) => decodeFrame(frame, installation, Number(value.producer_epoch)));
2507
- }
2508
- return { schema_version: STATE_SCHEMA, producer_installation_ref: installation, producer_epoch: epoch, next_sequence: Number(value.next_sequence), pending };
2509
- }
2510
- function validStateHeader(value, installation) {
2511
- return record5(value) && value.schema_version === STATE_SCHEMA && value.producer_installation_ref === installation && positiveInteger(value.producer_epoch) && positiveInteger(value.next_sequence) && record5(value.pending);
2512
- }
2513
- function positiveInteger(value) {
2514
- return Number.isSafeInteger(value) && Number(value) > 0;
2515
- }
2516
- function decodeFrame(value, installation, epoch) {
2517
- const json = Buffer.from(JSON.stringify(value));
2518
- const encoded = Buffer.allocUnsafe(json.length + 4);
2519
- encoded.writeUInt32BE(json.length, 0);
2520
- json.copy(encoded, 4);
2521
- const frame = decodeHostProducerFrame(encoded);
2522
- 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}` }])}).`);
2523
- 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);
2524
2727
  }
2525
- function record5(value) {
2526
- return value !== null && typeof value === "object" && !Array.isArray(value);
2527
- }
2528
- function code2(error) {
2529
- 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;
2530
2730
  }
2531
2731
 
2532
- // host/claude/src/claude-producer-runtime.ts
2533
- async function drainClaudeProducerOnce(options) {
2534
- 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();
2535
- }
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];
2536
2736
 
2537
- // host/claude/src/claude-producer-service.ts
2538
- var CLAUDE_PRODUCER_CONFIG_SCHEMA = "ceal.claude_producer_config.v1";
2539
- var CLAUDE_PRODUCER_DESCRIPTOR = {
2540
- schema: CLAUDE_PRODUCER_CONFIG_SCHEMA,
2541
- producer_id: "claude",
2542
- config: (shared) => shared,
2543
- runtime_options: (config) => Object.freeze({
2544
- outbox_dir: config.outbox_dir,
2545
- producer_installation_ref: config.producer_installation_ref,
2546
- producer_epoch: config.producer_epoch,
2547
- relay_endpoint: config.relay_endpoint,
2548
- timeout_ms: config.ipc_timeout_ms
2549
- }),
2550
- drain: drainClaudeProducerOnce
2551
- };
2552
- function runClaudeProducerCli(args, signal, dependencies = {}) {
2553
- return runHostProducerCli(CLAUDE_PRODUCER_DESCRIPTOR, args, signal, dependencies);
2554
- }
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;
2555
2741
 
2556
2742
  // host/service-log.ts
2557
2743
  import { copyFile, open as open4, stat, truncate } from "node:fs/promises";
2558
2744
  import { isAbsolute } from "node:path";
2559
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
+ }
2560
2753
  var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2561
2754
  var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
2562
2755
  function hostServiceLogPreviousPath(path) {
@@ -2583,7 +2776,7 @@ function boundHostServiceLog(environment = process.env) {
2583
2776
  rotateHostServiceLog(path).catch((error) => {
2584
2777
  if (reported) return;
2585
2778
  reported = true;
2586
- process.stderr.write(`[host-service-log] rotation_failed code=${errorCode3(error)}
2779
+ process.stderr.write(`[host-service-log] rotation_failed code=${errorCode4(error)}
2587
2780
  `);
2588
2781
  });
2589
2782
  };
@@ -2594,12 +2787,1180 @@ function boundHostServiceLog(environment = process.env) {
2594
2787
  }
2595
2788
  var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2596
2789
  function isNotFound(error) {
2597
- return errorCode3(error) === "ENOENT";
2790
+ return errorCode4(error) === "ENOENT";
2598
2791
  }
2599
- function errorCode3(error) {
2792
+ function errorCode4(error) {
2600
2793
  return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
2601
2794
  }
2602
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
+
2603
3964
  // host/producer-command.ts
2604
3965
  async function runProducerCommand(schemaVersion, run) {
2605
3966
  const controller = new AbortController();
@@ -2610,8 +3971,8 @@ async function runProducerCommand(schemaVersion, run) {
2610
3971
  try {
2611
3972
  await run(process.argv.slice(2), controller.signal);
2612
3973
  } catch (error) {
2613
- const code3 = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "producer_failed";
2614
- 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 })}
2615
3976
  `);
2616
3977
  process.exitCode = 1;
2617
3978
  } finally {