@corca-org/ceal-host-linux-arm64 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,20 +3,29 @@ import { createRequire as __cealCreateRequire } from "node:module";
3
3
  const require = __cealCreateRequire(import.meta.url);
4
4
 
5
5
  // host/claude/src/claude-types.ts
6
- var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure"];
7
- var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "storage", "configuration"];
6
+ var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit"];
7
+ var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "payload_rejected", "storage", "configuration"];
8
8
  function isClaudeHostErrorCode(value) {
9
- return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code3) => code3 === value);
9
+ return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code2) => code2 === value);
10
10
  }
11
- var CLAUDE_OUTBOX_SCHEMA = "ceal.claude_hook_outbox.v1";
12
11
  var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
12
+ var CLAUDE_OUTBOX_SCHEMA_VERSION = "ceal.claude_observation_outbox.v1";
13
+ var CLAUDE_SESSION_SCHEMA_VERSION = "ceal.claude_session.v1";
14
+ var CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.claude_transcript_registration.v1";
15
+ var CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.claude_transcript_cursor.v1";
16
+ var CLAUDE_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
17
+ var CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES = 1024;
18
+ var CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES = 64;
19
+ var CLAUDE_TRANSCRIPT_ANCHOR_BYTES = 256;
13
20
  var ClaudeHostError = class extends Error {
14
21
  name = "ClaudeHostError";
15
22
  code;
16
- constructor(code3, message) {
23
+ diagnostic_kind;
24
+ constructor(code2, message, diagnostic_kind) {
17
25
  super(message);
18
- if (!isClaudeHostErrorCode(code3)) throw new TypeError("Claude Host error code is invalid.");
19
- this.code = code3;
26
+ if (!isClaudeHostErrorCode(code2)) throw new TypeError("Claude Host error code is invalid.");
27
+ this.code = code2;
28
+ this.diagnostic_kind = diagnostic_kind;
20
29
  }
21
30
  };
22
31
  function isClaudeHookEvent(value) {
@@ -133,6 +142,9 @@ var client_wire_contract_default = {
133
142
  "document"
134
143
  ],
135
144
  optional_fields: [
145
+ "operation_id",
146
+ "visible_output_max_bytes",
147
+ "local_delivery",
136
148
  "verified_artifact"
137
149
  ],
138
150
  bare_document_fields: [
@@ -246,6 +258,7 @@ var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verifie
246
258
  var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
247
259
  var CEAL_PROTOCOL_VERSION = "1.4.0";
248
260
  var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
261
+ var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
249
262
  var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
250
263
  "audited_call_pending",
251
264
  "continuation_read_pending",
@@ -269,6 +282,7 @@ var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
269
282
  // packages/ceal-client-protocol/src/operation-next-actions.ts
270
283
  var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
271
284
  var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
285
+ var CEAL_ANSWER_WITH_GAP_NEXT_ACTION = "answer_with_gap";
272
286
  var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
273
287
  "select_did_you_mean",
274
288
  "choose_new_output_path",
@@ -322,10 +336,20 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
322
336
  "retry_after",
323
337
  // The ref names a container of rows (a Notion database or data source), which
324
338
  // has no body of its own; the caller searches its rows instead of reading it.
325
- "search_container_rows"
339
+ "search_container_rows",
340
+ // A settled unknown write names the exact receipt Operation that can inspect
341
+ // its outcome without issuing another provider mutation.
342
+ "write_receipt.get",
343
+ // Command resolution carries the exact required Operation sequence beside
344
+ // this action, so the caller can execute each declared input in order.
345
+ "follow_required_action_sequence",
346
+ // The command needs caller-owned local materialization (for example an
347
+ // @path file) before the Gateway can receive the argument.
348
+ "materialize_local_argument"
326
349
  ];
327
350
  var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
328
351
  var NON_REMEDY_NEXT_ACTION_VALUES = [
352
+ CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
329
353
  // Only an administrator can take the named step -- approving a place for an
330
354
  // Operation, granting a withheld capability -- so a non-administrator asks
331
355
  // one. The refusal names the administrator step; this word is the ask.
@@ -373,7 +397,58 @@ var NON_REMEDY_NEXT_ACTION_VALUES = [
373
397
  "select_exact_approval"
374
398
  ];
375
399
  var CEAL_NON_REMEDY_NEXT_ACTIONS = Object.freeze(NON_REMEDY_NEXT_ACTION_VALUES);
376
- var DECLARED = /* @__PURE__ */ new Set([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
400
+ var CEAL_DECLARED_NEXT_ACTIONS = Object.freeze([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
401
+ var DECLARED = new Set(CEAL_DECLARED_NEXT_ACTIONS);
402
+ function defineCealNonModelNextActions(actions) {
403
+ return actions;
404
+ }
405
+ var NON_MODEL_NEXT_ACTION_VALUES = defineCealNonModelNextActions([
406
+ CEAL_INSPECT_RECEIPT_NEXT_ACTION,
407
+ "await_pending_approval",
408
+ "inspect_gateway",
409
+ "operator_action",
410
+ "none",
411
+ "check_gateway",
412
+ "inspect_context_store",
413
+ "inspect_local_cache",
414
+ "inspect_local_client",
415
+ "inspect_local_command",
416
+ "inspect_local_output",
417
+ "inspect_local_route",
418
+ "inspect_request",
419
+ "reconcile",
420
+ "reconcile_occurrence",
421
+ "retry_after_owner_recovery",
422
+ "select_exact_approval"
423
+ ]);
424
+ var NON_MODEL_NEXT_ACTIONS = new Set(NON_MODEL_NEXT_ACTION_VALUES);
425
+ function isCealModelActionableNonRemedyNextAction(action) {
426
+ return !NON_MODEL_NEXT_ACTIONS.has(action);
427
+ }
428
+ var CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS = Object.freeze([
429
+ ...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS,
430
+ ...CEAL_NON_REMEDY_NEXT_ACTIONS.filter(isCealModelActionableNonRemedyNextAction)
431
+ ]);
432
+ var MODEL_ACTIONABLE = new Set(CEAL_MODEL_ACTIONABLE_NEXT_ACTIONS);
433
+ var NON_MODEL_ACTION_TO_MODEL_ACTION = Object.freeze({
434
+ [CEAL_INSPECT_RECEIPT_NEXT_ACTION]: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
435
+ await_pending_approval: "ask_admin",
436
+ inspect_gateway: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
437
+ operator_action: "ask_admin",
438
+ none: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
439
+ check_gateway: "ask_admin",
440
+ inspect_context_store: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
441
+ inspect_local_cache: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
442
+ inspect_local_client: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
443
+ inspect_local_command: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
444
+ inspect_local_output: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
445
+ inspect_local_route: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
446
+ inspect_request: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
447
+ reconcile: CEAL_ANSWER_WITH_GAP_NEXT_ACTION,
448
+ reconcile_occurrence: "ask_admin",
449
+ retry_after_owner_recovery: "ask_admin",
450
+ select_exact_approval: "ask_admin"
451
+ });
377
452
  var RETRY_PROMISE_NEXT_ACTIONS = ["retry", "retry_after"];
378
453
  var RETRY_PROMISE = new Set(RETRY_PROMISE_NEXT_ACTIONS);
379
454
  var RECOVERABLE = new Set(CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS);
@@ -430,9 +505,9 @@ var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
430
505
  var CealProtocolValidationError = class extends Error {
431
506
  name = "CealProtocolValidationError";
432
507
  code;
433
- constructor(code3) {
434
- super(code3 === "invalid_gateway_request" ? "Ceal Gateway request is invalid." : "Ceal client response is invalid.");
435
- this.code = code3;
508
+ constructor(code2) {
509
+ super(code2 === "invalid_gateway_request" ? "Ceal Gateway request is invalid." : "Ceal client response is invalid.");
510
+ this.code = code2;
436
511
  }
437
512
  };
438
513
  var SAFE_REF = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
@@ -465,10 +540,10 @@ function assertSafeJsonArray(value, options, depth, count) {
465
540
  if (value.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
466
541
  for (const item of value) assertSafeJsonValue(item, options, depth + 1, count);
467
542
  }
468
- function assertSafeJsonRecord(record7, options, depth, count) {
469
- const entries = Object.entries(record7);
543
+ function assertSafeJsonRecord(record6, options, depth, count) {
544
+ const entries = Object.entries(record6);
470
545
  if (entries.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
471
- const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record7.fields) : null;
546
+ const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record6.fields) : null;
472
547
  for (const [key, child] of entries) {
473
548
  if (key === "credential_material_included" && child !== false) invalidByContext(options);
474
549
  if (!isSafeNegativeMaterialAssertion(key, child)) assertSafeJsonKey(key, options);
@@ -643,21 +718,15 @@ var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
643
718
  // packages/ceal-client-protocol/src/domain-vocabulary.ts
644
719
  var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
645
720
  var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
646
- var CEAL_PROVIDER_BOUNDARY_KINDS = vocabulary([
647
- "github_account",
648
- "github_organization",
649
- "google_workspace_domain",
650
- "notion_workspace",
651
- "slack_workspace"
652
- ]);
653
- var CEAL_PROVIDER_BOUNDARY_EVIDENCE = vocabulary(["provider_readback", "operator_stated"]);
654
721
  var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
655
722
  var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
656
723
  var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
657
724
  var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
658
725
  var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
659
- var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
726
+ var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
727
+ var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
660
728
  var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
729
+ var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
661
730
  var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
662
731
 
663
732
  // packages/ceal-client-protocol/src/safe-json-budget.ts
@@ -833,7 +902,7 @@ var CEAL_SCHEMA_LITERALS = [
833
902
  "ceal.operation_receipt.v1",
834
903
  "ceal.operation_result.v1",
835
904
  "ceal.operation_result.compact.v1",
836
- "ceal.local_result_materialization.v1",
905
+ "ceal.local_result_file.v1",
837
906
  "ceal.local_result_root.v1",
838
907
  "ceal.owner_bootstrap_exchange_request.v1",
839
908
  "ceal.owner_bootstrap_exchange_result.v1",
@@ -927,41 +996,6 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
927
996
  }
928
997
  });
929
998
 
930
- // packages/ceal-client-protocol/src/provider-operation-vocabulary.ts
931
- var CEAL_FILE_TYPE_FAMILIES = vocabulary(["all", "image", "pdf"]);
932
- var CEAL_GITHUB_ISSUE_STATES = vocabulary(["open", "closed", "all"]);
933
- var CEAL_NOTION_UPDATABLE_BLOCK_TYPES = vocabulary([
934
- "paragraph",
935
- "heading_1",
936
- "heading_2",
937
- "heading_3",
938
- "bulleted_list_item",
939
- "numbered_list_item",
940
- "to_do",
941
- "code"
942
- ]);
943
- var CEAL_NOTION_NEUTRAL_TYPES = vocabulary([
944
- "text",
945
- "number",
946
- "boolean",
947
- "date",
948
- "select",
949
- "multi_select",
950
- "people",
951
- "url",
952
- "email",
953
- "phone",
954
- "unsupported"
955
- ]);
956
- var CEAL_NOTION_IDENTITY_GAP_REASONS = vocabulary([
957
- "identity_unavailable",
958
- "identity_stale",
959
- "identity_ambiguous",
960
- "identity_revoked",
961
- "identity_unlinked"
962
- ]);
963
- var CEAL_NOTION_STRING_VALUE_TYPES = vocabulary(["text", "select", "url", "email", "phone"]);
964
-
965
999
  // packages/ceal-client-protocol/src/opaque-reference.ts
966
1000
  var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
967
1001
  var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
@@ -971,6 +1005,7 @@ var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_f
971
1005
  var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
972
1006
 
973
1007
  // packages/ceal-client-protocol/src/resource-row.ts
1008
+ var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
974
1009
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
975
1010
  var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
976
1011
  var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "authored_by", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
@@ -994,6 +1029,7 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
994
1029
  "resource_modified"
995
1030
  ]);
996
1031
  var CEAL_ACTIVITY_CONNECTOR_KINDS = vocabulary(["slack", "github", "google-calendar", "google-drive", "notion"]);
1032
+ var CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE = "unsupported_event_kind";
997
1033
  var CEAL_ACTIVITY_GAP_CODES = vocabulary([
998
1034
  "provider_page_budget",
999
1035
  "call_deadline",
@@ -1001,7 +1037,7 @@ var CEAL_ACTIVITY_GAP_CODES = vocabulary([
1001
1037
  "provider_cursor_unavailable",
1002
1038
  "malformed_provider_page",
1003
1039
  "visibility_incomplete",
1004
- "unsupported_event_kind",
1040
+ CEAL_ACTIVITY_UNSUPPORTED_EVENT_KIND_CODE,
1005
1041
  "direct_messages_excluded"
1006
1042
  ]);
1007
1043
  var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
@@ -1053,6 +1089,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1053
1089
  next: { required: true, timestamp: false },
1054
1090
  kind: { required: true, timestamp: false },
1055
1091
  matched_kinds: { required: false, timestamp: false },
1092
+ title_match: { required: false, timestamp: false },
1056
1093
  title: { required: true, timestamp: false },
1057
1094
  container_label: { required: true, timestamp: false },
1058
1095
  container_ref: { required: false, timestamp: false },
@@ -1066,6 +1103,8 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1066
1103
  modified_at: { required: false, timestamp: true },
1067
1104
  modified_at_local: { required: false, timestamp: true },
1068
1105
  viewed_by_me_at: { required: false, timestamp: true },
1106
+ own_ceal_write: { required: false, timestamp: false },
1107
+ owned_by_me: { required: false, timestamp: false },
1069
1108
  visibility: { required: false, timestamp: false },
1070
1109
  start: { required: false, timestamp: false },
1071
1110
  end: { required: false, timestamp: false },
@@ -1099,6 +1138,15 @@ function cealExcludedTargetCoverageJsonSchema() {
1099
1138
  };
1100
1139
  }
1101
1140
 
1141
+ // packages/ceal-client-protocol/src/gateway-write-identity.ts
1142
+ var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1143
+ var CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN = /^gateway-write-request:[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/u;
1144
+ var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1145
+ replay_identity: "idempotency_claim_sha256",
1146
+ lookup_handle: "write_request_sha256",
1147
+ collision_evidence: "normalized_mutation_sha256"
1148
+ });
1149
+
1102
1150
  // packages/ceal-client-protocol/src/write-safety-finding.ts
1103
1151
  var CEAL_WRITE_SAFETY_FINDING_STATUSES = vocabulary(["warning", "unchecked"]);
1104
1152
  var CEAL_WRITE_SAFETY_AUDIENCES = vocabulary(["workspace", "person", "members", "unknown"]);
@@ -1126,7 +1174,11 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
1126
1174
  "refresh_revoked",
1127
1175
  "authority_replaced",
1128
1176
  "refresh_recovery_unavailable",
1129
- "refresh_recovery_expired"
1177
+ "refresh_recovery_expired",
1178
+ // The serving gate answers this while the Gateway is still starting and the
1179
+ // refresh was never attempted; the same attempt stays retryable. It is never
1180
+ // stored by the session owner, only answered at the serving edge.
1181
+ "gateway_starting"
1130
1182
  ]);
1131
1183
 
1132
1184
  // packages/ceal-client-protocol/src/request-validation-primitives.ts
@@ -1159,7 +1211,7 @@ var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
1159
1211
  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"]);
1160
1212
  var CEAL_REQUEST_JSON_SCHEMA = {
1161
1213
  type: "object",
1162
- maxProperties: 10,
1214
+ maxProperties: 11,
1163
1215
  additionalProperties: false,
1164
1216
  properties: {
1165
1217
  schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
@@ -1183,7 +1235,8 @@ var CEAL_REQUEST_JSON_SCHEMA = {
1183
1235
  host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1184
1236
  host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1185
1237
  host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1186
- requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." }
1238
+ requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
1239
+ 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." }
1187
1240
  },
1188
1241
  required: CEAL_REQUEST_KEYS
1189
1242
  };
@@ -1242,6 +1295,7 @@ var REQUEST_EXECUTION_PREPARE_REFUSAL_PROPERTIES = {
1242
1295
  retryable: { type: "boolean" },
1243
1296
  source: { type: "string", enum: ["provider", "gateway"] },
1244
1297
  retry_after_ms: { type: "integer", minimum: 0, maximum: CEAL_RETRY_AFTER_MAX_MS },
1298
+ write_request_ref: { type: "string", pattern: CEAL_GATEWAY_WRITE_REQUEST_REF_PATTERN.source, maxLength: 128 },
1245
1299
  excluded_targets: cealExcludedTargetCoverageJsonSchema(),
1246
1300
  repaired_input: {},
1247
1301
  unrepaired_pointers: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 256 } },
@@ -1306,12 +1360,49 @@ function cealHostObservationRef(producer, digest2) {
1306
1360
  if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(digest2)) invalid("Ceal Host observation reference parts are invalid.");
1307
1361
  return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${digest2}`;
1308
1362
  }
1363
+ var HOST_TOKEN_OBSERVATION_BREAKDOWN_KEYS = vocabulary(["cached_input", "cache_write_input", "reasoning_output"]);
1364
+ function createHostTokenObservationValue(counts, source) {
1365
+ const core = validTokenCore(counts);
1366
+ if (typeof source !== "string" || source.length === 0 || source.length > 256) invalid("Ceal Host token observation source is invalid.");
1367
+ const value = { status: "available", unit: "tokens", input: core.input, output: core.output, total: core.total, source };
1368
+ for (const breakdown of validTokenBreakdowns(counts)) value[breakdown.key] = breakdown.tokens;
1369
+ assertHostObservationValue(value, "token");
1370
+ return value;
1371
+ }
1372
+ function validTokenCore(counts) {
1373
+ if (!isOperationRecord(counts) || !safeCount(counts.input) || !safeCount(counts.output) || !safeCount(counts.total)) invalid("Ceal Host token observation counts are invalid.");
1374
+ if (counts.total !== counts.input + counts.output) invalid("Ceal Host token observation counts are invalid.");
1375
+ return { input: counts.input, output: counts.output, total: counts.total };
1376
+ }
1377
+ function validTokenBreakdowns(counts) {
1378
+ const breakdowns = [
1379
+ { key: "cached_input", tokens: counts.cached_input },
1380
+ { key: "cache_write_input", tokens: counts.cache_write_input },
1381
+ { key: "reasoning_output", tokens: counts.reasoning_output }
1382
+ ];
1383
+ const stated = breakdowns.filter((breakdown) => breakdown.tokens !== void 0).map((breakdown) => ({ key: breakdown.key, tokens: Number(breakdown.tokens) }));
1384
+ for (const breakdown of stated) {
1385
+ if (!safeCount(breakdown.tokens)) invalid("Ceal Host token observation counts are invalid.");
1386
+ }
1387
+ const cached = counts.cached_input ?? 0;
1388
+ const created = counts.cache_write_input ?? 0;
1389
+ const reasoning = counts.reasoning_output ?? 0;
1390
+ if (cached + created > counts.input || reasoning > counts.output) invalid("Ceal Host token observation counts are invalid.");
1391
+ return stated;
1392
+ }
1393
+ function hostObservationNotRecorded(reason) {
1394
+ if (typeof reason !== "string" || reason.length === 0 || reason.length > 256) invalid("Ceal Host observation gap reason is invalid.");
1395
+ return { status: "not_recorded", reason };
1396
+ }
1397
+ function safeCount(value) {
1398
+ return Number.isSafeInteger(value) && Number(value) >= 0;
1399
+ }
1309
1400
  function validateCealHostObservationDraft(value) {
1310
- const record7 = operationRecord(value, "Ceal Host observation draft is invalid.");
1311
- requirePrefixedRef(record7.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1312
- requireClosedValue(record7.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1313
- requireIsoTimestamp(record7.observed_at, "Ceal Host observation time is invalid.");
1314
- assertHostObservationValue(record7.value, record7.observation_kind);
1401
+ const record6 = operationRecord(value, "Ceal Host observation draft is invalid.");
1402
+ requirePrefixedRef(record6.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1403
+ requireClosedValue(record6.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1404
+ requireIsoTimestamp(record6.observed_at, "Ceal Host observation time is invalid.");
1405
+ assertHostObservationValue(record6.value, record6.observation_kind);
1315
1406
  }
1316
1407
  var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
1317
1408
  var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "cost", "timing"]);
@@ -1457,12 +1548,85 @@ var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
1457
1548
  "host_input_mismatch",
1458
1549
  "host_prompt_mismatch"
1459
1550
  ]);
1551
+ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
1552
+ "invalid_arguments",
1553
+ "options_invalid",
1554
+ "session_unavailable",
1555
+ "context_unavailable",
1556
+ "context_replaced",
1557
+ "no_binding",
1558
+ "context_stale",
1559
+ "context_expired",
1560
+ "host_source_refused",
1561
+ "host_credential_refused",
1562
+ "gateway_unavailable",
1563
+ "gateway_rate_limited",
1564
+ "gateway_refused",
1565
+ "gateway_response_invalid",
1566
+ "producer_frame_invalid",
1567
+ "relay_permission_denied",
1568
+ "relay_unavailable",
1569
+ "transient_retryable",
1570
+ "mint_failed",
1571
+ "relay_failed",
1572
+ "host_refusal_invalid"
1573
+ ]);
1574
+ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1575
+ invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1576
+ options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1577
+ session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1578
+ context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "refresh_host_invocation_context", remedy: "the Host has no invocation context from the current transcript turn; send a new message to start a fresh Host turn, then retry this command on that turn (run `ceal-host install` only if the Host hook registration is missing or drifted)" }),
1579
+ context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1580
+ no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1581
+ context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1582
+ context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "refresh_host_invocation_context" }),
1583
+ host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
1584
+ host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
1585
+ gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
1586
+ gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
1587
+ gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
1588
+ gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
1589
+ 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" }),
1590
+ 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" }),
1591
+ relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
1592
+ transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
1593
+ mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
1594
+ relay_failed: Object.freeze({ classification: "host_relay_failed", next_action: "answer_with_gap" }),
1595
+ host_refusal_invalid: Object.freeze({ classification: "host_refusal_invalid", next_action: "answer_with_gap" })
1596
+ });
1597
+
1598
+ // packages/ceal-client-protocol/src/host-service-diagnostic.ts
1599
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
1600
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
1601
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1602
+ "hook_invoked",
1603
+ "registration_written",
1604
+ "hook_failed",
1605
+ "watcher_turn_observed",
1606
+ "current_prompt_registered",
1607
+ "current_prompt_registration_failed",
1608
+ "current_prompt_capacity_exhausted",
1609
+ "activation_recorded",
1610
+ "activation_failed",
1611
+ "watcher_scan_failed",
1612
+ "activation_wait_skipped",
1613
+ "activation_wait_engaged",
1614
+ "activation_wait_completed",
1615
+ "activation_wait_exhausted"
1616
+ ]);
1617
+ var CEAL_HOST_SERVICE_DIAGNOSTIC_FAILURE_KINDS = vocabulary([
1618
+ "malformed_line",
1619
+ "other",
1620
+ "payload_rejected",
1621
+ "read_error",
1622
+ "size_budget",
1623
+ "symlink"
1624
+ ]);
1460
1625
 
1461
1626
  // packages/ceal-client-protocol/src/client-result-materialization.ts
1462
1627
  var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
1463
- var CEAL_MODEL_VISIBLE_COMMAND_OUTPUT_MAX_BYTES = 32 * 1024;
1464
- var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "read_argv"]);
1465
- var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "local_result_root", "continue_argv", "model_disclosure"]);
1628
+ var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
1629
+ var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
1466
1630
 
1467
1631
  // packages/ceal-client-protocol/src/session-start-affordance.ts
1468
1632
  var CEAL_SESSION_ENTRY_INVOCATION = "ceal";
@@ -1495,14 +1659,6 @@ var CEAL_GATEWAY_ANNOUNCEMENT_POLICY_NON_CLAIMS = vocabulary([
1495
1659
  "target_specific_scope_not_declared"
1496
1660
  ]);
1497
1661
 
1498
- // packages/ceal-client-protocol/src/gateway-write-identity.ts
1499
- var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
1500
- var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
1501
- replay_identity: "idempotency_claim_sha256",
1502
- lookup_handle: "write_request_sha256",
1503
- collision_evidence: "normalized_mutation_sha256"
1504
- });
1505
-
1506
1662
  // packages/ceal-client-protocol/src/gateway-scoped-identity-projection-validation.ts
1507
1663
  var PROJECTION_REVISION_KEYS = Object.freeze(["graph_revision", "subject_key_revision", "projection_revision"]);
1508
1664
 
@@ -1515,6 +1671,18 @@ var CEAL_SUPPORTED_GATEWAY_PROTOCOL_RANGE = Object.freeze({
1515
1671
  // packages/ceal-client-protocol/src/admin-projection-vocabulary.ts
1516
1672
  var ADMIN_PROJECTION_STATES = vocabulary(["ready", "empty", "partial", "unknown"]);
1517
1673
  var ADMIN_VIEW_KINDS = vocabulary(["request_detail", "resource_access_timeline", "weekly_usage"]);
1674
+ var ADMIN_RECORD_PROJECTION_KINDS = vocabulary([
1675
+ "request_list",
1676
+ "usage_series",
1677
+ "connector_health",
1678
+ "person_connections",
1679
+ "client_sessions",
1680
+ "knowledge_base",
1681
+ "change_history",
1682
+ "approval_queue",
1683
+ "access_group_list",
1684
+ "capability_catalogue"
1685
+ ]);
1518
1686
  var ADMIN_GAP_KINDS = vocabulary(["unknown", "unavailable", "partial"]);
1519
1687
  var ADMIN_UNAVAILABLE_REASONS = vocabulary([
1520
1688
  "not_recorded",
@@ -1538,7 +1706,11 @@ var ADMIN_SOURCE_OWNERS = vocabulary([
1538
1706
  "person",
1539
1707
  "resource",
1540
1708
  "prompt",
1541
- "usage"
1709
+ "usage",
1710
+ "connector",
1711
+ "session",
1712
+ "knowledge",
1713
+ "policy"
1542
1714
  ]);
1543
1715
  var ADMIN_COMPLETENESS_STATES = vocabulary(["complete", "partial", "unknown"]);
1544
1716
  var ADMIN_OPERATION_OUTCOMES = vocabulary(["completed", "failed", "denied", "cancelled", "unknown"]);
@@ -1565,10 +1737,7 @@ var FAILURE_RESPONSE_KEYS = Object.freeze(["error", "ok", "proof_ref_or_unavaila
1565
1737
 
1566
1738
  // host/claude/src/claude-decoder.ts
1567
1739
  var MAX_HOOK_BYTES = 256 * 1024;
1568
- var MAX_TEXT = 128 * 1024;
1569
1740
  var SESSION_SOURCES = ["startup", "resume", "clear", "compact", "fork"];
1570
- var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
1571
- var STOP_FAILURES = ["rate_limit", "overloaded", "authentication_failed", "oauth_org_not_allowed", "account_on_hold", "billing_error", "invalid_request", "model_not_found", "server_error", "max_output_tokens", "unknown"];
1572
1741
  function decodeClaudeHookInput(event, raw) {
1573
1742
  const rawBytes = Buffer.byteLength(raw, "utf8");
1574
1743
  if (rawBytes > MAX_HOOK_BYTES) throw invalid2(`Claude hook input is too large (${refusalFact("bytes", rawBytes, `at most ${MAX_HOOK_BYTES} UTF-8 bytes`)}).`);
@@ -1579,70 +1748,33 @@ function decodeClaudeHookInput(event, raw) {
1579
1748
  throw invalid2("Claude hook input is not valid JSON.");
1580
1749
  }
1581
1750
  if (!record(value) || value.hook_event_name !== event) throw invalid2(`Claude hook event identity does not match the configured event (${refusalFact("hook_event_name", record(value) ? value.hook_event_name : value, `expected ${event}`)}).`);
1582
- const common = decodeCommon(value, event);
1583
- if (event === "SessionStart") return decodeSessionStart(value, common);
1584
- const promptId = requiredText(value.prompt_id, 256, "Claude turn hook is missing prompt_id.");
1585
- if (event === "UserPromptSubmit") return decodeUserPrompt(value, common, promptId);
1586
- return event === "Stop" ? decodeStop(value, common, promptId) : decodeStopFailure(value, common, promptId);
1587
- }
1588
- function decodeSessionStart(value, common) {
1751
+ const common = {
1752
+ session_id: requiredText(value.session_id, 256, "Claude session identity is invalid."),
1753
+ transcript_path: requiredAbsolutePath(value.transcript_path, "Claude transcript path is invalid."),
1754
+ cwd: requiredAbsolutePath(value.cwd, "Claude working directory is invalid.")
1755
+ };
1756
+ if (event === "UserPromptSubmit") {
1757
+ return { event, ...common, hook_event_name: event, prompt: requiredText(value.prompt, 256 * 1024, "Claude submitted prompt is invalid.") };
1758
+ }
1589
1759
  if (!isSessionSource(value.source)) throw invalid2("Claude SessionStart source is invalid.");
1590
1760
  const model = optionalText(value.model, 256);
1591
- return { ...common, hook_event_name: "SessionStart", source: value.source, ...model === void 0 ? {} : { model } };
1592
- }
1593
- function decodeUserPrompt(value, common, promptId) {
1594
- return { ...common, hook_event_name: "UserPromptSubmit", prompt_id: promptId, prompt: requiredText(value.prompt, MAX_TEXT, "Claude prompt is invalid.") };
1595
- }
1596
- function decodeStop(value, common, promptId) {
1597
- if (typeof value.stop_hook_active !== "boolean") throw invalid2("Claude Stop state is invalid.");
1598
- return { ...common, hook_event_name: "Stop", prompt_id: promptId, stop_hook_active: value.stop_hook_active, last_assistant_message: requiredText(value.last_assistant_message, MAX_TEXT, "Claude Stop response is invalid.") };
1599
- }
1600
- function decodeStopFailure(value, common, promptId) {
1601
- if (!isStopFailure(value.error)) throw invalid2("Claude StopFailure error is invalid.");
1602
- const errorDetails = optionalText(value.error_details, 4096);
1603
- const lastMessage = optionalText(value.last_assistant_message, MAX_TEXT);
1604
- return { ...common, hook_event_name: "StopFailure", prompt_id: promptId, error: value.error, ...errorDetails === void 0 ? {} : { error_details: errorDetails }, ...lastMessage === void 0 ? {} : { last_assistant_message: lastMessage } };
1761
+ return { event, ...common, hook_event_name: "SessionStart", source: value.source, ...model === void 0 ? {} : { model } };
1605
1762
  }
1606
1763
  function claudeLocalSessionRef(sessionId) {
1607
1764
  return `claude-session:${digest(sessionId)}`;
1608
1765
  }
1609
- function claudeLocalTurnRef(sessionId, promptId) {
1610
- return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: promptId })}`;
1766
+ function claudeLocalTurnRef(sessionId, turnId) {
1767
+ return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
1611
1768
  }
1612
- function claudeLocalInputRef(sessionId, promptId) {
1613
- return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: promptId })}`;
1769
+ function claudeLocalInputRef(sessionId, turnId) {
1770
+ return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
1614
1771
  }
1615
1772
  function claudeWorkspaceRef(cwd) {
1616
1773
  return `workspace:${digest(cwd)}`;
1617
1774
  }
1618
- function decodeCommon(value, event) {
1619
- const effort = decodeEffort(value.effort);
1620
- const promptId = optionalText(value.prompt_id, 256);
1621
- const permissionMode = optionalText(value.permission_mode, 64);
1622
- return {
1623
- session_id: requiredText(value.session_id, 256, "Claude session identity is invalid."),
1624
- ...promptId === void 0 ? {} : { prompt_id: promptId },
1625
- transcript_path: requiredAbsolutePath(value.transcript_path, "Claude transcript path is invalid."),
1626
- cwd: requiredAbsolutePath(value.cwd, "Claude working directory is invalid."),
1627
- ...permissionMode === void 0 ? {} : { permission_mode: permissionMode },
1628
- ...effort === void 0 ? {} : { effort },
1629
- hook_event_name: event
1630
- };
1631
- }
1632
- function decodeEffort(value) {
1633
- if (value === void 0) return void 0;
1634
- if (!record(value) || !isEffortLevel(value.level)) throw invalid2("Claude effort is invalid.");
1635
- return { level: value.level };
1636
- }
1637
1775
  function isSessionSource(value) {
1638
1776
  return typeof value === "string" && SESSION_SOURCES.some((source) => source === value);
1639
1777
  }
1640
- function isEffortLevel(value) {
1641
- return typeof value === "string" && EFFORT_LEVELS.some((effort) => effort === value);
1642
- }
1643
- function isStopFailure(value) {
1644
- return typeof value === "string" && STOP_FAILURES.some((failure) => failure === value);
1645
- }
1646
1778
  function requiredAbsolutePath(value, message) {
1647
1779
  const text = requiredText(value, 4096, message);
1648
1780
  if (!text.startsWith("/") || text.includes("\0")) throw invalid2(message);
@@ -1670,8 +1802,62 @@ function invalid2(message) {
1670
1802
  }
1671
1803
 
1672
1804
  // host/claude/src/claude-outbox.ts
1673
- import { open as open2, mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
1674
- import { join as join2 } from "node:path";
1805
+ import { mkdir as mkdir3, readdir, readFile as readFile2, unlink as unlink2 } from "node:fs/promises";
1806
+ import { isAbsolute, join as join3 } from "node:path";
1807
+
1808
+ // host/invocation-context.ts
1809
+ var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
1810
+ var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
1811
+ function invocationContextKey(identity) {
1812
+ return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
1813
+ }
1814
+ function hasHostInvocationIdentityKeys(value) {
1815
+ return exactKeys(value, HOST_INVOCATION_IDENTITY_KEYS);
1816
+ }
1817
+ function hasHostInvocationContextKeys(value, optional = []) {
1818
+ return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
1819
+ }
1820
+ async function readLatestSessionProjection(file, hostSourceRef, hostSessionRef, readProjection) {
1821
+ const context = await readProjection(file);
1822
+ if (context === null || context.identity.host_source_ref !== hostSourceRef || context.identity.host_session_ref !== hostSessionRef) return null;
1823
+ return context;
1824
+ }
1825
+ function isLiveSessionProjection(context, now) {
1826
+ return context !== null && Date.parse(context.expires_at) > now.getTime();
1827
+ }
1828
+ function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
1829
+ const context = {
1830
+ schema_version: descriptor.schema_version,
1831
+ identity,
1832
+ prompt_ref: promptRef,
1833
+ expires_at: expiresAt,
1834
+ ...descriptor.turn_generation_ref === void 0 ? {} : { turn_generation_ref: descriptor.turn_generation_ref },
1835
+ ...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
1836
+ };
1837
+ descriptor.validate(context);
1838
+ return Object.freeze(context);
1839
+ }
1840
+ function isHostInvocationTurnSelection(value) {
1841
+ return isRecord(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) => safeRef(value[key]));
1842
+ }
1843
+ function invocationTurnSelectionMatches(context, selection) {
1844
+ 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;
1845
+ }
1846
+ function exactKeys(value, keys) {
1847
+ const actual = Object.keys(value);
1848
+ return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
1849
+ }
1850
+ function requiredKeys(value, required, optional) {
1851
+ const actual = Object.keys(value);
1852
+ const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
1853
+ return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
1854
+ }
1855
+ function isRecord(value) {
1856
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1857
+ }
1858
+ function safeRef(value) {
1859
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
1860
+ }
1675
1861
 
1676
1862
  // host/relay/src/producer-frame.ts
1677
1863
  var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
@@ -1684,11 +1870,20 @@ var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
1684
1870
  function isHostProducerFrameKind(value) {
1685
1871
  return typeof value === "string" && FRAME_KINDS.has(value);
1686
1872
  }
1873
+ var HOST_PRODUCER_FRAME_ERROR_CODES = [
1874
+ "invalid_frame_encoding",
1875
+ "invalid_frame_length",
1876
+ "frame_truncated",
1877
+ "unsupported_frame_schema",
1878
+ "invalid_frame_shape",
1879
+ "invalid_producer_identity",
1880
+ "frame_too_large"
1881
+ ];
1687
1882
  var HostProducerFrameError = class extends Error {
1688
1883
  code;
1689
- constructor(code3, message = code3) {
1884
+ constructor(code2, message = code2) {
1690
1885
  super(message);
1691
- this.code = code3;
1886
+ this.code = code2;
1692
1887
  }
1693
1888
  };
1694
1889
  function encodeHostProducerFrame(frame) {
@@ -1718,11 +1913,14 @@ function decodeHostProducerFrame(input) {
1718
1913
  validateFrame(value);
1719
1914
  return value;
1720
1915
  }
1916
+ function isHostSafeRef(value) {
1917
+ return safeRef2(value);
1918
+ }
1721
1919
  function validateFrame(value) {
1722
1920
  if (!record2(value)) throw new HostProducerFrameError("invalid_frame_shape");
1723
1921
  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)}`);
1724
- if (!exactKeys(value, ["schema_version", "producer_id", "producer_installation_ref", "producer_epoch", "sequence", "frame_kind", "occurred_at", "payload"])) throw new HostProducerFrameError("invalid_frame_shape");
1725
- if (!PRODUCERS.has(String(value.producer_id)) || !safeRef(value.producer_installation_ref)) throw new HostProducerFrameError("invalid_producer_identity");
1922
+ if (!exactKeys2(value, ["schema_version", "producer_id", "producer_installation_ref", "producer_epoch", "sequence", "frame_kind", "occurred_at", "payload"])) throw new HostProducerFrameError("invalid_frame_shape");
1923
+ if (!PRODUCERS.has(String(value.producer_id)) || !safeRef2(value.producer_installation_ref)) throw new HostProducerFrameError("invalid_producer_identity");
1726
1924
  if (!positive(value.producer_epoch) || !positive(value.sequence) || !hostProducerFrameKind(value.frame_kind) || !timestamp(value.occurred_at) || !record2(value.payload)) throw new HostProducerFrameError("invalid_frame_shape");
1727
1925
  validatePayload(value.frame_kind, value.payload);
1728
1926
  }
@@ -1731,13 +1929,13 @@ function validatePayload(kind, value) {
1731
1929
  if (!valid) throw new HostProducerFrameError("invalid_frame_shape");
1732
1930
  }
1733
1931
  function validSession(value) {
1734
- return exactOptional(value, ["local_session_ref", "working_directory_ref"], ["model", "host_version", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.working_directory_ref) && optionalText2(value.model, 256) && optionalText2(value.host_version, 256) && validObservations(value.observations);
1932
+ return exactOptional(value, ["local_session_ref", "working_directory_ref"], ["model", "host_version", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.working_directory_ref) && optionalText2(value.model, 256) && optionalText2(value.host_version, 256) && validObservations(value.observations);
1735
1933
  }
1736
1934
  function validTurn(value) {
1737
- return exactOptional(value, ["local_session_ref", "local_turn_ref"], ["model", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && optionalText2(value.model, 256) && validObservations(value.observations);
1935
+ return exactOptional(value, ["local_session_ref", "local_turn_ref"], ["model", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && optionalText2(value.model, 256) && validObservations(value.observations);
1738
1936
  }
1739
1937
  function validInput(value) {
1740
- return exactOptional(value, ["local_session_ref", "local_turn_ref", "local_input_ref", "input_kind", "origin_input", "observations"], ["local_turn_aliases", "context_expires_at"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && safeRef(value.local_input_ref) && validInputObservations(value.observations) && isCealHostInputKind(value.input_kind) && typeof value.origin_input === "boolean" && validInputOptionals(value);
1938
+ return exactOptional(value, ["local_session_ref", "local_turn_ref", "local_input_ref", "input_kind", "origin_input", "observations"], ["local_turn_aliases", "context_expires_at"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && safeRef2(value.local_input_ref) && validInputObservations(value.observations) && isCealHostInputKind(value.input_kind) && typeof value.origin_input === "boolean" && validInputOptionals(value);
1741
1939
  }
1742
1940
  function validInputObservations(value) {
1743
1941
  return validObservations(value) && Array.isArray(value) && value.filter((item) => record2(item) && item.observation_kind === "prompt").length === 1;
@@ -1746,13 +1944,13 @@ function validInputOptionals(value) {
1746
1944
  return optionalRefs(value.local_turn_aliases) && (value.context_expires_at === void 0 || timestamp(value.context_expires_at));
1747
1945
  }
1748
1946
  function validTerminal(value) {
1749
- return exactOptional(value, ["local_session_ref", "local_turn_ref", "terminal_state"], ["abort_reason", "duration_ms", "time_to_first_token_ms", "usage", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && validTerminalDisposition(value.terminal_state, value.abort_reason) && optionalNonNegative(value.duration_ms) && optionalNonNegative(value.time_to_first_token_ms) && (value.usage === void 0 || jsonValue(value.usage)) && validObservations(value.observations);
1947
+ return exactOptional(value, ["local_session_ref", "local_turn_ref", "terminal_state"], ["abort_reason", "duration_ms", "time_to_first_token_ms", "usage", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && validTerminalDisposition(value.terminal_state, value.abort_reason) && optionalNonNegative(value.duration_ms) && optionalNonNegative(value.time_to_first_token_ms) && (value.usage === void 0 || jsonValue(value.usage)) && validObservations(value.observations);
1750
1948
  }
1751
1949
  function validTerminalDisposition(state, reason) {
1752
- return (state === "completed" || state === "aborted") && (reason === void 0 || state === "aborted" && safeRef(reason));
1950
+ return (state === "completed" || state === "aborted") && (reason === void 0 || state === "aborted" && safeRef2(reason));
1753
1951
  }
1754
1952
  function validStatus(value) {
1755
- return exactOptional(value, ["status", "reason_code"], ["detail"]) && ["ready", "degraded", "stopping"].includes(String(value.status)) && safeRef(value.reason_code) && optionalText2(value.detail, 4096);
1953
+ return exactOptional(value, ["status", "reason_code"], ["detail"]) && ["ready", "degraded", "stopping"].includes(String(value.status)) && safeRef2(value.reason_code) && optionalText2(value.detail, 4096);
1756
1954
  }
1757
1955
  function optionalText2(value, maxLength) {
1758
1956
  return value === void 0 || typeof value === "string" && value.length > 0 && value.length <= maxLength;
@@ -1761,7 +1959,7 @@ function optionalNonNegative(value) {
1761
1959
  return value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0;
1762
1960
  }
1763
1961
  function optionalRefs(value) {
1764
- return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef);
1962
+ return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef2);
1765
1963
  }
1766
1964
  function validObservations(value) {
1767
1965
  if (value === void 0) return true;
@@ -1785,7 +1983,7 @@ function jsonValue(value, depth = 0) {
1785
1983
  function record2(value) {
1786
1984
  return value !== null && typeof value === "object" && !Array.isArray(value);
1787
1985
  }
1788
- function safeRef(value) {
1986
+ function safeRef2(value) {
1789
1987
  return typeof value === "string" && SAFE_REF2.test(value);
1790
1988
  }
1791
1989
  function positive(value) {
@@ -1797,7 +1995,7 @@ function timestamp(value) {
1797
1995
  function hostProducerFrameKind(value) {
1798
1996
  return isHostProducerFrameKind(value);
1799
1997
  }
1800
- function exactKeys(value, keys) {
1998
+ function exactKeys2(value, keys) {
1801
1999
  return JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
1802
2000
  }
1803
2001
  function exactOptional(value, required, optional) {
@@ -1805,6 +2003,15 @@ function exactOptional(value, required, optional) {
1805
2003
  return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
1806
2004
  }
1807
2005
 
2006
+ // host/relay/src/host-producer-core.ts
2007
+ import { createHash as createHash3 } from "node:crypto";
2008
+ import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
2009
+ import { mkdir as mkdir2, open as open2, readFile, rename as rename2, writeFile } from "node:fs/promises";
2010
+ import { join as join2 } from "node:path";
2011
+
2012
+ // host/relay/src/relay-ipc.ts
2013
+ import { createConnection, createServer } from "node:net";
2014
+
1808
2015
  // host/relay/src/durable-atomic-replacement.ts
1809
2016
  import { O_RDONLY } from "node:constants";
1810
2017
  import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
@@ -1893,20 +2100,24 @@ function errorCode(error) {
1893
2100
  // host/relay/src/relay-request-binding-store.ts
1894
2101
  var MAX_STATE_BYTES = 8 * 1024 * 1024;
1895
2102
 
1896
- // host/relay/src/relay-quarantine-store.ts
1897
- var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
1898
-
1899
- // host/relay/src/relay-admission-store.ts
1900
- var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
1901
- var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
1902
- var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
1903
-
1904
- // host/relay/src/relay-ipc.ts
1905
- import { createConnection, createServer } from "node:net";
1906
-
1907
2103
  // host/relay/src/current-prompt-custody.ts
1908
2104
  var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
1909
2105
 
2106
+ // host/service-log.ts
2107
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
2108
+ const line = `${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {}, ...event === "watcher_scan_failed" && cause !== void 0 ? { cause } : {} })}
2109
+ `;
2110
+ try {
2111
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
2112
+ } catch {
2113
+ }
2114
+ }
2115
+ function defaultHostServiceDiagnosticSink(line) {
2116
+ process.stderr.write(line);
2117
+ }
2118
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2119
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2120
+
1910
2121
  // host/relay/src/relay-ipc.ts
1911
2122
  var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
1912
2123
  "schema_version",
@@ -1925,6 +2136,7 @@ var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
1925
2136
  ]);
1926
2137
  var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
1927
2138
  var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2139
+ var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
1928
2140
  var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
1929
2141
  var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
1930
2142
  var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
@@ -1936,6 +2148,8 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
1936
2148
  "gateway_unavailable",
1937
2149
  "gateway_refused",
1938
2150
  "gateway_response_invalid",
2151
+ "gateway_rate_limited",
2152
+ "producer_frame_invalid",
1939
2153
  "deadline",
1940
2154
  "cancelled",
1941
2155
  "mint_failed",
@@ -1949,6 +2163,17 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
1949
2163
  "host_input_mismatch",
1950
2164
  "host_prompt_mismatch"
1951
2165
  ]);
2166
+ var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
2167
+ "context_stale",
2168
+ "context_expired",
2169
+ "host_source_refused",
2170
+ "host_credential_refused",
2171
+ "gateway_unavailable",
2172
+ "gateway_rate_limited",
2173
+ "gateway_refused",
2174
+ "gateway_response_invalid",
2175
+ "producer_frame_invalid"
2176
+ ]);
1952
2177
  var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
1953
2178
  var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
1954
2179
  "permission_denied",
@@ -1964,12 +2189,21 @@ var HostRelayIpcError = class extends Error {
1964
2189
  code;
1965
2190
  /** Set only when the client-side socket exchange itself failed. */
1966
2191
  failure;
1967
- constructor(code3, message = code3, failure) {
2192
+ constructor(code2, message = code2, failure) {
1968
2193
  super(message);
1969
- this.code = code3;
2194
+ this.code = code2;
1970
2195
  this.failure = failure;
1971
2196
  }
1972
2197
  };
2198
+ var HostRelayCurrentPromptCapacityRefusal = class extends Error {
2199
+ constructor() {
2200
+ super("current_prompt_capacity_exhausted");
2201
+ this.name = "HostRelayCurrentPromptCapacityRefusal";
2202
+ }
2203
+ };
2204
+ function hostRelayExchangeFailure(error) {
2205
+ return error instanceof HostRelayIpcError ? error.failure : void 0;
2206
+ }
1973
2207
  function socketErrorFailure(error) {
1974
2208
  switch (errorCode2(error)) {
1975
2209
  case "EACCES":
@@ -2006,14 +2240,20 @@ var HostRelayCurrentPromptPort = class {
2006
2240
  async register(input) {
2007
2241
  const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2008
2242
  const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2243
+ if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
2009
2244
  if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2010
2245
  }
2246
+ async release(identity) {
2247
+ const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
2248
+ const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2249
+ if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
2250
+ }
2011
2251
  };
2012
2252
  function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2013
2253
  return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
2014
2254
  }
2015
2255
  function exchange(endpoint, encoded, timeoutMs, decode, signal) {
2016
- return new Promise((resolve, reject) => {
2256
+ return new Promise((resolve2, reject) => {
2017
2257
  const socket = createConnection(endpoint);
2018
2258
  let buffer = Buffer.alloc(0);
2019
2259
  let settled = false;
@@ -2024,7 +2264,7 @@ function exchange(endpoint, encoded, timeoutMs, decode, signal) {
2024
2264
  signal?.removeEventListener("abort", aborted);
2025
2265
  socket.destroy();
2026
2266
  if (error !== null) reject(error);
2027
- else if (response2 !== void 0) resolve(response2);
2267
+ else if (response2 !== void 0) resolve2(response2);
2028
2268
  };
2029
2269
  if (timeoutMs !== void 0) socket.setTimeout(timeoutMs, () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC timed out.", "timed_out")));
2030
2270
  if (signal?.aborted === true) {
@@ -2090,7 +2330,7 @@ function decodeResponseBuffer(buffer) {
2090
2330
  function decodeCurrentPromptResponseBuffer(buffer) {
2091
2331
  const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2092
2332
  if (value === null) return null;
2093
- if (!record3(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");
2333
+ if (!record3(value) || !exactKeys3(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "capacity_exhausted" && value.status !== "current_prompt_unavailable" && value.status !== "released") throw new HostRelayIpcError("relay_unavailable", "Host Relay current-prompt response is invalid.", "response_invalid");
2094
2334
  return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2095
2335
  }
2096
2336
  function decodeLengthPrefixedJson(input, maximum) {
@@ -2112,119 +2352,477 @@ function encodeLengthPrefixed(value) {
2112
2352
  body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
2113
2353
  return output;
2114
2354
  }
2115
- function exactKeys2(value, keys) {
2116
- return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
2355
+ function exactKeys3(value, required, optional = []) {
2356
+ const keys = Object.keys(value);
2357
+ return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2117
2358
  }
2118
2359
  function errorCode2(error) {
2119
2360
  return record3(error) && typeof error.code === "string" ? error.code : null;
2120
2361
  }
2121
2362
 
2122
- // host/invocation-context.ts
2123
- var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
2124
- var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
2125
- function invocationContextKey(identity) {
2126
- return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
2127
- }
2128
- function hasHostInvocationIdentityKeys(value) {
2129
- return exactKeys3(value, HOST_INVOCATION_IDENTITY_KEYS);
2130
- }
2131
- function hasHostInvocationContextKeys(value, optional = []) {
2132
- return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
2133
- }
2134
- function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
2135
- const context = {
2136
- schema_version: descriptor.schema_version,
2137
- identity,
2138
- prompt_ref: promptRef,
2139
- expires_at: expiresAt,
2140
- ...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
2141
- };
2142
- descriptor.validate(context);
2143
- return Object.freeze(context);
2144
- }
2145
- function exactKeys3(value, keys) {
2146
- const actual = Object.keys(value);
2147
- return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
2148
- }
2149
- function requiredKeys(value, required, optional) {
2150
- const actual = Object.keys(value);
2151
- const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
2152
- return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
2153
- }
2154
-
2155
- // host/claude/src/claude-invocation-context.ts
2156
- function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory) {
2157
- return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory });
2158
- }
2159
- function validateClaudeHostInvocationContext(value) {
2160
- if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2161
- validateIdentity(value.identity);
2162
- validateContextReferences(value);
2163
- validateContextTimes(value);
2363
+ // host/relay/src/host-producer-core.ts
2364
+ var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
2365
+ function isHostTranscriptCursorReason(value) {
2366
+ return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
2367
+ }
2368
+ var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2369
+ var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
2370
+ function transcriptScanFailureKind(error, adapterFailureKind) {
2371
+ if (transcriptErrorCode(error) === "ELOOP") return "symlink";
2372
+ const adapter = adapterFailureKind(error);
2373
+ if (adapter !== void 0) return adapter;
2374
+ const code2 = transcriptErrorCode(error);
2375
+ if (code2 === "EFBIG") return "size_budget";
2376
+ if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
2377
+ return "other";
2378
+ }
2379
+ async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
2380
+ try {
2381
+ await register();
2382
+ diagnostic("current_prompt_registered");
2383
+ return true;
2384
+ } catch (error) {
2385
+ if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
2386
+ throw error;
2387
+ }
2164
2388
  }
2165
- function validateClaudeIdentity(value) {
2166
- if (!validIdentity(value)) throw new ClaudeHostError("invalid_input", "Claude Host session identity is invalid.");
2389
+ function transcriptErrorCode(error) {
2390
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2167
2391
  }
2168
- function contextShape(value) {
2169
- return record4(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory"]);
2392
+ var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
2393
+ async function readTranscriptCompleteChunk(handle, offset, size) {
2394
+ const available = Math.max(0, size - offset);
2395
+ if (available === 0) return "empty";
2396
+ const chunks = [];
2397
+ let position = offset;
2398
+ let total = 0;
2399
+ let lastNewline = -1;
2400
+ while (position < size) {
2401
+ const buffer = Buffer.alloc(Math.min(HOST_TRANSCRIPT_READ_CHUNK_BYTES, size - position));
2402
+ const read = await handle.read(buffer, 0, buffer.length, position);
2403
+ if (read.bytesRead === 0) break;
2404
+ const chunk = buffer.subarray(0, read.bytesRead);
2405
+ chunks.push(chunk);
2406
+ const newline = chunk.lastIndexOf(10);
2407
+ if (newline >= 0) lastNewline = total + newline;
2408
+ total += chunk.length;
2409
+ position += chunk.length;
2410
+ if (lastNewline >= 0) break;
2411
+ }
2412
+ if (lastNewline < 0) return "partial";
2413
+ return Buffer.concat(chunks, total).subarray(0, lastNewline + 1);
2414
+ }
2415
+ async function readTranscriptAnchor(handle, offset, anchorBytes) {
2416
+ const start = Math.max(0, offset - anchorBytes);
2417
+ const anchor = Buffer.alloc(offset - start);
2418
+ if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, start);
2419
+ return { start, sha256: transcriptSha256(anchor) };
2420
+ }
2421
+ async function transcriptAnchorMatches(handle, cursor) {
2422
+ const buffer = Buffer.alloc(cursor.offset - cursor.anchor_start);
2423
+ if (buffer.length > 0) await handle.read(buffer, 0, buffer.length, cursor.anchor_start);
2424
+ return transcriptSha256(buffer) === cursor.anchor_sha256;
2425
+ }
2426
+ function transcriptCursorIdentityReason(cursor, transcriptPath, device, inode) {
2427
+ if (cursor.device !== device || cursor.inode !== inode || cursor.transcript_path !== transcriptPath) return "transcript_replaced";
2428
+ return null;
2170
2429
  }
2171
- function validateIdentity(value) {
2172
- if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2430
+ async function transcriptCursorBoundsReason(handle, cursor, size) {
2431
+ if (size < cursor.offset) return "transcript_truncated";
2432
+ if (cursor.offset > 0 && !await transcriptAnchorMatches(handle, cursor)) return "transcript_rewritten";
2433
+ return null;
2173
2434
  }
2174
- function validateContextReferences(value) {
2175
- if (!safeRef2(value.prompt_ref) || !optionalSafeRef(value.request_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2435
+ function transcriptSha256(value) {
2436
+ return createHash3("sha256").update(value).digest("hex");
2176
2437
  }
2177
- function validateContextTimes(value) {
2178
- if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2179
- 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)}`)}).`);
2438
+ function transcriptObjectSha256(value) {
2439
+ return createHash3("sha256").update(JSON.stringify(value), "utf8").digest("hex");
2180
2440
  }
2181
- function validIdentity(value) {
2182
- return record4(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef2);
2441
+ function isTranscriptRecord(value) {
2442
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2183
2443
  }
2184
- function optionalSafeRef(value) {
2185
- return value === void 0 || safeRef2(value);
2444
+ function isMissingTranscriptError(error) {
2445
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2186
2446
  }
2187
- function optionalTimestamp(value) {
2188
- return value === void 0 || timestamp2(value);
2447
+ function transcriptTimestamp(now, fail) {
2448
+ const value = now();
2449
+ if (!Number.isFinite(value.getTime())) throw fail();
2450
+ return value.toISOString();
2189
2451
  }
2190
- function optionalDirectory(value) {
2191
- return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
2452
+ function transcriptExpiration(now, retentionMs, fail) {
2453
+ return new Date(Date.parse(transcriptTimestamp(now, fail)) + retentionMs).toISOString();
2192
2454
  }
2193
- function safeRef2(value) {
2194
- return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2455
+ function createTranscriptWatcherBase(options, owner) {
2456
+ if (typeof options.outbox_dir !== "string" || options.outbox_dir.length === 0) throw owner.invalid_outbox();
2457
+ return {
2458
+ outboxRetentionMs: options.retention_ms ?? owner.default_retention_ms,
2459
+ now: options.now ?? (() => /* @__PURE__ */ new Date()),
2460
+ log: options.log ?? ((line) => {
2461
+ process.stderr.write(`${line}
2462
+ `);
2463
+ }),
2464
+ producerInstallationRef: options.producer_installation_ref
2465
+ };
2195
2466
  }
2196
- function timestamp2(value) {
2197
- return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
2467
+ async function scanTranscriptRegistrations(registrations, scanOne) {
2468
+ let records = 0;
2469
+ let enqueued = 0;
2470
+ let quarantined = 0;
2471
+ for (const registration of registrations) {
2472
+ const result = await scanOne(registration);
2473
+ records += result.records;
2474
+ enqueued += result.enqueued;
2475
+ if (result.quarantined) quarantined += 1;
2476
+ }
2477
+ return { registrations: registrations.length, records, enqueued, quarantined };
2198
2478
  }
2199
- function record4(value) {
2200
- return value !== null && typeof value === "object" && !Array.isArray(value);
2479
+ function formatRetiredTranscriptLog(producerLabel, transcriptPath) {
2480
+ return `${producerLabel} retired a transcript registration: transcript_path=${JSON.stringify(transcriptPath)}, reason=${TRANSCRIPT_MISSING_REASON}.`;
2201
2481
  }
2202
-
2203
- // host/claude/src/claude-outbox.ts
2204
- var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2205
- var ClaudeOutboxStore = class {
2206
- entries;
2207
- directory;
2208
- activeContexts;
2209
- constructor(directory) {
2210
- if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2211
- this.directory = directory;
2212
- this.entries = join2(directory, "entries");
2213
- this.activeContexts = join2(directory, "active-contexts");
2214
- }
2215
- async prepare() {
2216
- await mkdir2(this.entries, { recursive: true, mode: 448 });
2217
- await mkdir2(this.activeContexts, { recursive: true, mode: 448 });
2482
+ var HostTranscriptWatcher = class {
2483
+ outboxRetentionMs;
2484
+ now;
2485
+ log;
2486
+ producerInstallationRef;
2487
+ clockFailure;
2488
+ constructor(options, owner) {
2489
+ const base = createTranscriptWatcherBase(options, owner);
2490
+ this.outboxRetentionMs = base.outboxRetentionMs;
2491
+ this.now = base.now;
2492
+ this.log = base.log;
2493
+ this.producerInstallationRef = base.producerInstallationRef;
2494
+ this.clockFailure = owner.clock_failure;
2218
2495
  }
2219
- async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory) {
2220
- const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory);
2221
- await this.prepare();
2222
- await durableAtomicReplace(this.activeContextPath(identity), JSON.stringify(context));
2496
+ async scanOnce() {
2497
+ return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2223
2498
  }
2224
- async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
2499
+ async scanRegistration(registration) {
2500
+ try {
2501
+ return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2502
+ missing: () => this.retireMissing(registration),
2503
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2504
+ });
2505
+ } catch (error) {
2506
+ if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
2507
+ throw error;
2508
+ }
2509
+ }
2510
+ async openTranscript(registration, scan, handlers) {
2511
+ let handle;
2512
+ try {
2513
+ handle = await open2(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2514
+ return await scan(handle);
2515
+ } catch (error) {
2516
+ if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2517
+ if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
2518
+ return handlers.failed(handle, error);
2519
+ } finally {
2520
+ await handle?.close();
2521
+ }
2522
+ }
2523
+ /**
2524
+ * A transcript the Host deleted never comes back, so retrying its
2525
+ * registration once per reconcile interval is a failure with no end.
2526
+ * Retire it at the scan that finds it missing and say so once. Only
2527
+ * ENOENT means gone; a permission or busy failure is about this moment,
2528
+ * not about the file, and keeps the registration for the next scan.
2529
+ */
2530
+ async retireMissingTranscript(transcriptPath, producerLabel, retire) {
2531
+ await retire();
2532
+ this.log(formatRetiredTranscriptLog(producerLabel, transcriptPath));
2533
+ return { records: 0, enqueued: 0, quarantined: false };
2534
+ }
2535
+ stamp() {
2536
+ return transcriptTimestamp(this.now, this.clockFailure);
2537
+ }
2538
+ expire(retentionMs) {
2539
+ return transcriptExpiration(this.now, retentionMs, this.clockFailure);
2540
+ }
2541
+ sha(value) {
2542
+ return transcriptSha256(value);
2543
+ }
2544
+ objectSha(value) {
2545
+ return transcriptObjectSha256(value);
2546
+ }
2547
+ isRecord(value) {
2548
+ return isTranscriptRecord(value);
2549
+ }
2550
+ readChunk(handle, offset, size) {
2551
+ return readTranscriptCompleteChunk(handle, offset, size);
2552
+ }
2553
+ readAnchor(handle, offset, anchorBytes) {
2554
+ return readTranscriptAnchor(handle, offset, anchorBytes);
2555
+ }
2556
+ cursorIdentityReason(cursor, transcriptPath, device, inode) {
2557
+ return transcriptCursorIdentityReason(cursor, transcriptPath, device, inode);
2558
+ }
2559
+ cursorBoundsReason(handle, cursor, size) {
2560
+ return transcriptCursorBoundsReason(handle, cursor, size);
2561
+ }
2562
+ };
2563
+ var HOST_PRODUCER_STATE_IDENTITY = {
2564
+ codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
2565
+ claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
2566
+ };
2567
+ var HostProducerDrainCore = class {
2568
+ host;
2569
+ relay;
2570
+ statePath;
2571
+ stateSchema;
2572
+ installationRef;
2573
+ epoch;
2574
+ now;
2575
+ tail = Promise.resolve();
2576
+ constructor(host, runtime) {
2577
+ if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
2578
+ const epoch = runtime.producer_epoch ?? 1;
2579
+ if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
2580
+ this.host = host;
2581
+ this.relay = runtime.relay;
2582
+ this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
2583
+ this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
2584
+ this.installationRef = runtime.producer_installation_ref;
2585
+ this.epoch = epoch;
2586
+ this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
2587
+ }
2588
+ drainOutbox() {
2589
+ const run = this.tail.then(() => this.drainLocked());
2590
+ this.tail = run.then(() => void 0, () => void 0);
2591
+ return run;
2592
+ }
2593
+ async drainLocked() {
2594
+ const read = await this.host.listEnvelopes();
2595
+ let admitted = 0;
2596
+ let expired = 0;
2597
+ let retained = 0;
2598
+ for (const envelope of read.entries) {
2599
+ const reserved = await this.hasReservation(this.host.submissionRefOf(envelope));
2600
+ if (!reserved && Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) {
2601
+ await this.host.removeEnvelope(envelope);
2602
+ expired += 1;
2603
+ continue;
2604
+ }
2605
+ const frames = await this.reserve(envelope);
2606
+ let complete = true;
2607
+ for (const frame of frames) {
2608
+ const outcome = await this.relay.admit(frame);
2609
+ if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2610
+ complete = false;
2611
+ break;
2612
+ }
2613
+ }
2614
+ if (!complete) {
2615
+ retained += 1;
2616
+ break;
2617
+ }
2618
+ await this.host.removeEnvelope(envelope);
2619
+ await this.release(this.host.submissionRefOf(envelope));
2620
+ admitted += 1;
2621
+ }
2622
+ return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
2623
+ }
2624
+ async hasReservation(submissionRef2) {
2625
+ return (await this.readState()).pending[submissionRef2] !== void 0;
2626
+ }
2627
+ async reserve(envelope) {
2628
+ const state = await this.readState();
2629
+ const submissionRef2 = this.host.submissionRefOf(envelope);
2630
+ const existing = state.pending[submissionRef2];
2631
+ if (existing !== void 0) return existing;
2632
+ const drafts = await this.host.mapEnvelope(envelope);
2633
+ const frames = drafts.map((draft, offset) => ({
2634
+ schema_version: "ceal.host_producer_frame.v1",
2635
+ producer_id: this.host.producer_id,
2636
+ producer_installation_ref: this.installationRef,
2637
+ producer_epoch: this.epoch,
2638
+ sequence: state.next_sequence + offset,
2639
+ frame_kind: draft.frame_kind,
2640
+ occurred_at: this.host.createdAtOf(envelope),
2641
+ payload: draft.payload
2642
+ }));
2643
+ await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef2]: frames } });
2644
+ return frames;
2645
+ }
2646
+ async release(submissionRef2) {
2647
+ const state = await this.readState();
2648
+ if (state.pending[submissionRef2] === void 0) return;
2649
+ const pending = { ...state.pending };
2650
+ delete pending[submissionRef2];
2651
+ await this.writeState({ ...state, pending });
2652
+ }
2653
+ async readState() {
2654
+ try {
2655
+ const value = JSON.parse(await readFile(this.statePath, "utf8"));
2656
+ return this.decodeState(value);
2657
+ } catch (error) {
2658
+ if (errorCode3(error) !== "ENOENT") throw error;
2659
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2660
+ }
2661
+ }
2662
+ async writeState(state) {
2663
+ await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
2664
+ const temporary = `${this.statePath}.${process.pid}.new`;
2665
+ await writeFile(temporary, JSON.stringify(state), { mode: 384 });
2666
+ await rename2(temporary, this.statePath);
2667
+ }
2668
+ decodeState(value) {
2669
+ const label = this.host.producer_label;
2670
+ const fail = (code2, message) => this.host.fail(code2, message);
2671
+ if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
2672
+ throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
2673
+ { name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
2674
+ { name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
2675
+ ])}).`);
2676
+ }
2677
+ 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}`)}).`);
2678
+ const pending = {};
2679
+ const storedEpoch = Number(value.producer_epoch);
2680
+ for (const [submissionRef2, frames] of Object.entries(value.pending)) {
2681
+ if (!isHostSafeRef(submissionRef2) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
2682
+ pending[submissionRef2] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
2683
+ }
2684
+ return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
2685
+ }
2686
+ decodeStoredFrame(value, storedEpoch) {
2687
+ const label = this.host.producer_label;
2688
+ const json = Buffer.from(JSON.stringify(value), "utf8");
2689
+ const encoded = Buffer.allocUnsafe(json.length + 4);
2690
+ encoded.writeUInt32BE(json.length, 0);
2691
+ json.copy(encoded, 4);
2692
+ const frame = decodeHostProducerFrame(encoded);
2693
+ 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([
2694
+ { name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
2695
+ { name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
2696
+ { name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
2697
+ ])}).`);
2698
+ return frame;
2699
+ }
2700
+ };
2701
+ function isDrainStateHeader(value, schema, installationRef) {
2702
+ 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);
2703
+ }
2704
+ function errorCode3(error) {
2705
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2706
+ }
2707
+
2708
+ // host/relay/src/relay-quarantine-store.ts
2709
+ var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
2710
+ var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
2711
+ var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
2712
+
2713
+ // host/relay/src/relay-admission-store.ts
2714
+ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
2715
+ var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
2716
+ var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
2717
+
2718
+ // host/claude/src/claude-invocation-context.ts
2719
+ function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
2720
+ return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory, turn_generation_ref: turnGenerationRef });
2721
+ }
2722
+ function validateClaudeHostInvocationContext(value) {
2723
+ if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2724
+ validateIdentity(value.identity);
2725
+ validateContextReferences(value);
2726
+ validateContextTimes(value);
2727
+ }
2728
+ function contextShape(value) {
2729
+ return record4(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory", "turn_generation_ref"]);
2730
+ }
2731
+ function validateIdentity(value) {
2732
+ if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2733
+ }
2734
+ function validateContextReferences(value) {
2735
+ if (!safeRef3(value.prompt_ref) || !optionalSafeRef(value.request_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2736
+ }
2737
+ function validateContextTimes(value) {
2738
+ if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2739
+ 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)}`)}).`);
2740
+ }
2741
+ function validIdentity(value) {
2742
+ return record4(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef3);
2743
+ }
2744
+ function optionalSafeRef(value) {
2745
+ return value === void 0 || safeRef3(value);
2746
+ }
2747
+ function optionalTimestamp(value) {
2748
+ return value === void 0 || timestamp2(value);
2749
+ }
2750
+ function optionalDirectory(value) {
2751
+ return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
2752
+ }
2753
+ function safeRef3(value) {
2754
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2755
+ }
2756
+ function timestamp2(value) {
2757
+ return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
2758
+ }
2759
+ function record4(value) {
2760
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2761
+ }
2762
+
2763
+ // host/claude/src/claude-outbox.ts
2764
+ var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2765
+ var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
2766
+ var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
2767
+ var ClaudeOutboxStore = class {
2768
+ envelopes;
2769
+ sessions;
2770
+ registrations;
2771
+ cursors;
2772
+ directory;
2773
+ activeContexts;
2774
+ turnSelections;
2775
+ constructor(directory) {
2776
+ if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2777
+ this.directory = directory;
2778
+ this.envelopes = join3(directory, "envelopes");
2779
+ this.sessions = join3(directory, "sessions");
2780
+ this.registrations = join3(directory, "transcript-registrations");
2781
+ this.cursors = join3(directory, "transcript-cursors");
2782
+ this.activeContexts = join3(directory, "active-contexts");
2783
+ this.turnSelections = join3(directory, "turn-selections");
2784
+ }
2785
+ async prepare() {
2786
+ for (const path of [this.envelopes, this.sessions, this.registrations, this.cursors, this.activeContexts, this.turnSelections]) {
2787
+ await mkdir3(path, { recursive: true, mode: 448 });
2788
+ }
2789
+ }
2790
+ async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
2791
+ const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef);
2792
+ await this.prepare();
2793
+ await durableAtomicReplace(this.activeContextPath(identity), JSON.stringify(context));
2794
+ }
2795
+ async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
2225
2796
  validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now);
2797
+ const context = await this.readRawInvocationContext(hostSourceRef, hostSessionRef);
2798
+ return isLiveSessionProjection(context, now) ? context : null;
2799
+ }
2800
+ async writeInvocationTurnSelection(selection) {
2801
+ if (!isHostInvocationTurnSelection(selection)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection is invalid.");
2802
+ await this.prepare();
2803
+ await durableAtomicReplace(this.turnSelectionPath(selection.host_source_ref, selection.host_session_ref), JSON.stringify(selection));
2804
+ }
2805
+ async attachInvocationTurnGeneration(identity, promptRef, turnGenerationRef) {
2806
+ const selection = await this.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
2807
+ if (selection === null || selection.prompt_ref !== promptRef || selection.turn_generation_ref !== turnGenerationRef) return false;
2808
+ const path = this.activeContextPath(identity);
2809
+ const active = await readProjectionFile(path);
2810
+ if (active === null || active.prompt_ref !== promptRef || active.identity.host_turn_ref !== identity.host_turn_ref) return false;
2811
+ const updated = { ...active, turn_generation_ref: turnGenerationRef };
2812
+ validateClaudeHostInvocationContext(updated);
2813
+ await durableAtomicReplace(path, JSON.stringify(updated));
2814
+ return true;
2815
+ }
2816
+ /**
2817
+ * The latest Claude session projection even when past expiry, so the
2818
+ * invocation authenticator can report an expired context distinctly from
2819
+ * a missing one. Every other caller keeps the fail-closed read above.
2820
+ */
2821
+ async readRawInvocationContext(hostSourceRef, hostSessionRef) {
2822
+ if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2226
2823
  await this.prepare();
2227
- return readInvocationContextFile(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, now);
2824
+ const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
2825
+ return invocationTurnSelectionMatches(context, await this.readInvocationTurnSelection(hostSourceRef, hostSessionRef)) ? context : null;
2228
2826
  }
2229
2827
  async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
2230
2828
  await this.prepare();
@@ -2232,204 +2830,1116 @@ var ClaudeOutboxStore = class {
2232
2830
  for (const name of await readdir(this.activeContexts)) {
2233
2831
  if (!name.endsWith(".json")) continue;
2234
2832
  try {
2235
- const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
2833
+ const value = JSON.parse(await readFile2(join3(this.activeContexts, name), "utf8"));
2236
2834
  validateClaudeHostInvocationContext(value);
2237
- if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
2835
+ 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);
2238
2836
  } catch {
2239
2837
  }
2240
2838
  }
2241
2839
  return contexts;
2242
2840
  }
2243
- async put(batch) {
2244
- validateBatch(batch);
2841
+ async putEnvelope(entry) {
2842
+ validateEnvelope(entry);
2245
2843
  await this.prepare();
2246
- const path = this.path(batch.batch_ref);
2247
- try {
2248
- const handle = await open2(path, "wx", 384);
2844
+ await durableAtomicReplace(this.envelopePath(entry.submission_ref), JSON.stringify(entry));
2845
+ }
2846
+ async listEnvelopes() {
2847
+ await this.prepare();
2848
+ const entries = [];
2849
+ let malformed = 0;
2850
+ for (const name of await readdir(this.envelopes)) {
2851
+ if (!name.endsWith(".json")) continue;
2249
2852
  try {
2250
- await handle.writeFile(JSON.stringify(batch));
2251
- await handle.sync();
2252
- } finally {
2253
- await handle.close();
2853
+ const value = JSON.parse(await readFile2(join3(this.envelopes, name), "utf8"));
2854
+ validateEnvelope(value);
2855
+ entries.push(value);
2856
+ } catch {
2857
+ malformed += 1;
2254
2858
  }
2859
+ }
2860
+ entries.sort((left, right) => left.created_at.localeCompare(right.created_at));
2861
+ return { entries, malformed };
2862
+ }
2863
+ async removeEnvelope(entry) {
2864
+ validateEnvelope(entry);
2865
+ try {
2866
+ await unlink2(this.envelopePath(entry.submission_ref));
2255
2867
  } catch (error) {
2256
- if (code(error) !== "EEXIST") throw new ClaudeHostError("storage", `Unable to persist Claude hook batch (${refusalFact("error_code", code(error), "EEXIST for an idempotent retry")}).`);
2257
- const existing = JSON.parse(await readFile(path, "utf8"));
2258
- validateBatch(existing);
2259
- if (JSON.stringify(existing) !== JSON.stringify(batch)) throw new ClaudeHostError("storage", "Claude hook batch identity conflicts with different bytes.");
2868
+ if (code(error) !== "ENOENT") throw error;
2260
2869
  }
2261
2870
  }
2262
- async list() {
2871
+ async saveSession(snapshot) {
2872
+ validateSession(snapshot);
2263
2873
  await this.prepare();
2264
- const batches = [];
2265
- let malformed = 0;
2266
- for (const name of await readdir(this.entries)) {
2874
+ await durableAtomicReplace(join3(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
2875
+ }
2876
+ async readSession(sourceRef, sessionRef) {
2877
+ if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
2878
+ await this.prepare();
2879
+ try {
2880
+ const value = JSON.parse(await readFile2(join3(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
2881
+ validateSession(value);
2882
+ return value;
2883
+ } catch (error) {
2884
+ if (code(error) === "ENOENT") return null;
2885
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude session snapshot is malformed.");
2886
+ }
2887
+ }
2888
+ async registerTranscript(registration) {
2889
+ validateRegistration(registration);
2890
+ await this.prepare();
2891
+ const path = this.registrationPath(registration.host_source_ref, registration.host_session_ref);
2892
+ const existing = await this.readRegistrationFile(path);
2893
+ if (existing !== null && (existing.claude_session_id !== registration.claude_session_id || existing.transcript_path !== registration.transcript_path)) {
2894
+ throw new ClaudeHostError("storage", "Claude transcript registration conflicts with the source-bound session.");
2895
+ }
2896
+ await durableAtomicReplace(path, JSON.stringify(registration));
2897
+ }
2898
+ /**
2899
+ * End a registration whose transcript the Host has deleted. The cursor is
2900
+ * keyed by the same session and describes byte offsets of a file that no
2901
+ * longer exists, so it leaves with the registration.
2902
+ */
2903
+ async retireTranscriptRegistration(registration) {
2904
+ validateRegistration(registration);
2905
+ await this.prepare();
2906
+ await this.removeStateFile(this.registrationPath(registration.host_source_ref, registration.host_session_ref));
2907
+ await this.removeStateFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref));
2908
+ }
2909
+ async listTranscriptRegistrations() {
2910
+ await this.prepare();
2911
+ const registrations = [];
2912
+ for (const name of await readdir(this.registrations)) {
2267
2913
  if (!name.endsWith(".json")) continue;
2268
2914
  try {
2269
- const value = JSON.parse(await readFile(join2(this.entries, name), "utf8"));
2270
- validateBatch(value);
2271
- batches.push(value);
2915
+ const value = JSON.parse(await readFile2(join3(this.registrations, name), "utf8"));
2916
+ validateRegistration(value);
2917
+ registrations.push(value);
2272
2918
  } catch {
2273
- malformed += 1;
2274
2919
  }
2275
2920
  }
2276
- batches.sort(compareBatch);
2277
- return { entries: batches, malformed };
2921
+ return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
2278
2922
  }
2279
- async remove(batch) {
2923
+ async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
2924
+ if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
2925
+ await this.prepare();
2280
2926
  try {
2281
- await unlink2(this.path(batch.batch_ref));
2927
+ const value = JSON.parse(await readFile2(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
2928
+ if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
2929
+ return value;
2282
2930
  } catch (error) {
2283
- if (code(error) !== "ENOENT") throw error;
2931
+ if (code(error) === "ENOENT") return null;
2932
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Unable to read the Claude Host turn selection.");
2933
+ }
2934
+ }
2935
+ async readTranscriptCursor(registration) {
2936
+ validateRegistration(registration);
2937
+ await this.prepare();
2938
+ try {
2939
+ const value = JSON.parse(await readFile2(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
2940
+ validateCursor(value);
2941
+ return value;
2942
+ } catch (error) {
2943
+ if (code(error) === "ENOENT") return null;
2944
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
2945
+ }
2946
+ }
2947
+ async writeTranscriptCursor(cursor) {
2948
+ validateCursor(cursor);
2949
+ await this.prepare();
2950
+ await durableAtomicReplace(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), JSON.stringify(cursor));
2951
+ }
2952
+ envelopePath(ref) {
2953
+ return join3(this.envelopes, `${cealRequestSha256(ref)}.json`);
2954
+ }
2955
+ registrationPath(sourceRef, sessionRef) {
2956
+ return join3(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2957
+ }
2958
+ turnSelectionPath(sourceRef, sessionRef) {
2959
+ return join3(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2960
+ }
2961
+ cursorPath(sourceRef, sessionRef) {
2962
+ return join3(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2963
+ }
2964
+ async readRegistrationFile(path) {
2965
+ try {
2966
+ const value = JSON.parse(await readFile2(path, "utf8"));
2967
+ validateRegistration(value);
2968
+ return value;
2969
+ } catch (error) {
2970
+ if (code(error) === "ENOENT") return null;
2971
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("storage", "Claude transcript registration is malformed.");
2284
2972
  }
2285
2973
  }
2286
- path(ref) {
2287
- return join2(this.entries, `${cealRequestSha256(ref)}.json`);
2974
+ async removeStateFile(path) {
2975
+ try {
2976
+ await unlink2(path);
2977
+ } catch (error) {
2978
+ if (code(error) !== "ENOENT") throw error;
2979
+ }
2288
2980
  }
2289
2981
  activeContextPath(identity) {
2290
- return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
2982
+ return join3(this.activeContexts, `${invocationContextKey(identity)}.json`);
2291
2983
  }
2292
2984
  };
2293
2985
  function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
2294
2986
  if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef) || !(now instanceof Date) || !Number.isFinite(now.getTime())) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2295
2987
  }
2296
- async function readInvocationContextFile(file, hostSourceRef, hostSessionRef, now) {
2988
+ async function readProjectionFile(file) {
2297
2989
  try {
2298
- const context = JSON.parse(await readFile(file, "utf8"));
2990
+ const context = JSON.parse(await readFile2(file, "utf8"));
2299
2991
  validateClaudeHostInvocationContext(context);
2300
- return contextMatches(context, hostSourceRef, hostSessionRef) && Date.parse(context.expires_at) > now.getTime() ? context : null;
2992
+ return context;
2301
2993
  } catch (error) {
2302
2994
  if (code(error) === "ENOENT") return null;
2303
2995
  if (error instanceof ClaudeHostError) throw error;
2304
2996
  throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
2305
2997
  }
2306
2998
  }
2307
- function contextMatches(context, hostSourceRef, hostSessionRef) {
2308
- return context.identity.host_source_ref === hostSourceRef && context.identity.host_session_ref === hostSessionRef;
2999
+ function safeRef4(value) {
3000
+ return typeof value === "string" && SAFE_REF3.test(value);
3001
+ }
3002
+ function timestamp3(value) {
3003
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
2309
3004
  }
2310
- function compareBatch(left, right) {
2311
- return rank(left.hook_event) - rank(right.hook_event) || left.occurred_at.localeCompare(right.occurred_at) || left.batch_ref.localeCompare(right.batch_ref);
3005
+ function record5(value) {
3006
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2312
3007
  }
2313
- function rank(event) {
2314
- return event === "SessionStart" ? 0 : event === "UserPromptSubmit" ? 1 : 2;
3008
+ function code(error) {
3009
+ return record5(error) && typeof error.code === "string" ? error.code : null;
2315
3010
  }
2316
- function validateBatch(value) {
2317
- if (!validBatchHeader(value)) throw new ClaudeHostError("storage", "Claude hook batch is malformed.");
2318
- for (const [index, draft] of value.frames.entries()) {
2319
- if (!record5(draft) || !hostFrameKind(draft.frame_kind) || !record5(draft.payload)) throw new ClaudeHostError("storage", "Claude frame draft is malformed.");
2320
- 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 });
3011
+ function validateEnvelope(value) {
3012
+ if (!record5(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
3013
+ if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "input_kind", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"], ["request_ref"])) {
3014
+ throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
3015
+ }
3016
+ 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}`)}).`);
3017
+ validateEnvelopeIdentity(value);
3018
+ validateEnvelopeObservations(value.observations);
3019
+ if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
3020
+ if (!timestamp3(value.created_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
3021
+ }
3022
+ function validateEnvelopeIdentity(value) {
3023
+ validateIdentity2(value.identity);
3024
+ if (!safeRef4(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
3025
+ if (!isCealHostInputKind(value.input_kind)) throw new ClaudeHostError("storage", "Claude outbox input provenance is malformed.");
3026
+ if (!safeRef4(value.prompt_ref) || !safeRef4(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
3027
+ if (value.request_ref !== void 0 && !safeRef4(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
3028
+ }
3029
+ function validateEnvelopeObservations(value) {
3030
+ if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3031
+ try {
3032
+ for (const observation2 of value) validateCealHostObservationDraft(observation2);
3033
+ } catch {
3034
+ throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
2321
3035
  }
2322
3036
  }
2323
- function validBatchHeader(value) {
2324
- return record5(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;
3037
+ function validateSession(value) {
3038
+ if (!record5(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3039
+ if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) {
3040
+ throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
3041
+ }
3042
+ 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}`)}).`);
3043
+ if (!safeRef4(value.host_session_ref) || !safeRef4(value.host_source_ref) || !timestamp3(value.observed_at) || !timestamp3(value.expires_at)) {
3044
+ throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
3045
+ }
2325
3046
  }
2326
- function hostFrameKind(value) {
2327
- return isHostProducerFrameKind(value);
3047
+ function validateRegistration(value) {
3048
+ if (!record5(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3049
+ if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "claude_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) {
3050
+ throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
3051
+ }
3052
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3053
+ if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
3054
+ throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
3055
+ }
3056
+ validateRegistrationPaths(value.cwd, value.transcript_path);
3057
+ if (!timestamp3(value.registered_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
2328
3058
  }
2329
- function safeRef3(value) {
2330
- return typeof value === "string" && SAFE_REF3.test(value);
3059
+ function validateRegistrationPaths(cwd, transcriptPath) {
3060
+ if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
3061
+ throw new ClaudeHostError("storage", "Claude transcript registration working directory is malformed.");
3062
+ }
3063
+ if (typeof transcriptPath !== "string" || transcriptPath.length === 0 || transcriptPath.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS) {
3064
+ throw new ClaudeHostError("storage", "Claude transcript registration path is malformed.");
3065
+ }
2331
3066
  }
2332
- function optionalRef(value) {
2333
- return value === void 0 || typeof value === "string" && SAFE_REF3.test(value);
3067
+ function validateCursor(value) {
3068
+ if (!record5(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3069
+ 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"])) {
3070
+ throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
3071
+ }
3072
+ if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3073
+ if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) throw new ClaudeHostError("storage", "Claude transcript cursor references are malformed.");
3074
+ if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
3075
+ validateCursorPosition(value);
3076
+ validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
3077
+ validateSubagentCursors(value.subagents);
3078
+ if (value.reason !== void 0 && !isHostTranscriptCursorReason(value.reason)) throw new ClaudeHostError("storage", "Claude transcript cursor reason is malformed.");
3079
+ }
3080
+ function validateCursorPosition(value) {
3081
+ if (!validCursorPosition(value)) throw new ClaudeHostError("storage", "Claude transcript cursor position is malformed.");
3082
+ if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
3083
+ if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
3084
+ if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
3085
+ if (value.status !== "active" && value.status !== "quarantined" || !timestamp3(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
3086
+ }
3087
+ function validateCursorTurn(openTurn, unattributed, sessionObserved) {
3088
+ if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
3089
+ if (unattributed !== void 0 && !validUnattributed(unattributed)) throw new ClaudeHostError("storage", "Claude transcript unattributed cursor is malformed.");
3090
+ if (sessionObserved !== void 0 && typeof sessionObserved !== "boolean") throw new ClaudeHostError("storage", "Claude transcript cursor session marker is malformed.");
3091
+ }
3092
+ function validateSubagentCursors(value) {
3093
+ if (!record5(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
3094
+ for (const [name, entry] of Object.entries(value)) validateSubagentEntry(name, entry);
3095
+ }
3096
+ function validateSubagentEntry(name, entry) {
3097
+ if (name.length === 0 || name.length > CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS || name.includes("/")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3098
+ if (name.includes("\0")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3099
+ if (!record5(entry) || !sameKeys(entry, ["offset", "anchor_start", "anchor_sha256"], ["device", "inode"]) || !nonNegativeInteger(entry.offset)) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3100
+ validateSubagentIdentity(entry);
3101
+ }
3102
+ function validateSubagentIdentity(entry) {
3103
+ if (entry.device !== void 0 && typeof entry.device !== "string" || entry.inode !== void 0 && typeof entry.inode !== "string") {
3104
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3105
+ }
3106
+ if (!nonNegativeInteger(entry.anchor_start) || typeof entry.anchor_sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(entry.anchor_sha256)) {
3107
+ throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3108
+ }
3109
+ if (entry.device === void 0 && Number(entry.offset) !== 0) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is unpinned past its start.");
3110
+ }
3111
+ function validCursorPosition(value) {
3112
+ return nonNegativeInteger(value.offset) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256);
3113
+ }
3114
+ function validOpenTurn(value) {
3115
+ if (!record5(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;
3116
+ if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) return false;
3117
+ if (!timestamp3(value.started_at) || !timestamp3(value.last_observed_at)) return false;
3118
+ if (!validMessageIds(value.message_ids)) return false;
3119
+ if (!validTurnCounts(value.counts)) return false;
3120
+ return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
3121
+ }
3122
+ function validMessageIds(value) {
3123
+ if (!Array.isArray(value) || value.length > CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES) return false;
3124
+ if (!value.every((candidate) => typeof candidate === "string" && candidate.length > 0 && candidate.length <= 256)) return false;
3125
+ return new Set(value).size === value.length;
3126
+ }
3127
+ function validUnattributed(value) {
3128
+ if (!record5(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
3129
+ if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
3130
+ if (!timestamp3(value.last_observed_at)) return false;
3131
+ return validTurnCounts(value);
3132
+ }
3133
+ function validTurnCounts(value) {
3134
+ if (!record5(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
3135
+ return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
3136
+ }
3137
+ function validateIdentity2(value) {
3138
+ if (!record5(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
3139
+ throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
3140
+ }
2334
3141
  }
2335
- function timestamp3(value) {
2336
- return typeof value === "string" && Number.isFinite(Date.parse(value));
3142
+ function nonNegativeInteger(value) {
3143
+ return Number.isSafeInteger(value) && Number(value) >= 0;
2337
3144
  }
2338
- function record5(value) {
2339
- return value !== null && typeof value === "object" && !Array.isArray(value);
3145
+ function sameKeys(value, required, optional = []) {
3146
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
2340
3147
  }
2341
- function code(error) {
2342
- return record5(error) && typeof error.code === "string" ? error.code : null;
3148
+
3149
+ // host/claude/src/claude-transcript-watcher.ts
3150
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
3151
+ import { open as open3, readdir as readdir2 } from "node:fs/promises";
3152
+ import { join as join4 } from "node:path";
3153
+ var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
3154
+ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3155
+ store;
3156
+ registerCurrentPrompt;
3157
+ releaseCurrentPrompt;
3158
+ diagnostic;
3159
+ constructor(options) {
3160
+ super(options, {
3161
+ default_retention_ms: CLAUDE_DEFAULT_RETENTION_MS,
3162
+ invalid_outbox: () => new ClaudeHostError("configuration", "Claude transcript watcher outbox is invalid."),
3163
+ clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
3164
+ });
3165
+ this.store = new ClaudeOutboxStore(options.outbox_dir);
3166
+ this.registerCurrentPrompt = options.register_current_prompt;
3167
+ this.releaseCurrentPrompt = options.release_current_prompt;
3168
+ this.diagnostic = options.diagnostic ?? (() => void 0);
3169
+ }
3170
+ transcriptPathOf(registration) {
3171
+ return registration.transcript_path;
3172
+ }
3173
+ async listRegistrations() {
3174
+ return this.store.listTranscriptRegistrations();
3175
+ }
3176
+ async scanOpened(handle, registration) {
3177
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3178
+ const cursor = await this.preflight(registration, handle, size, device, inode);
3179
+ if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
3180
+ if (cursor.status === "quarantined") {
3181
+ if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
3182
+ return { records: 0, enqueued: 0, quarantined: true };
3183
+ }
3184
+ const draft = mutableCursor(cursor);
3185
+ await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
3186
+ return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
3187
+ }
3188
+ async scanFailed(handle, registration, error) {
3189
+ this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
3190
+ if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
3191
+ if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
3192
+ await this.quarantineFailure(registration, handle);
3193
+ return { records: 0, enqueued: 0, quarantined: true };
3194
+ }
3195
+ async retireMissing(registration) {
3196
+ await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
3197
+ return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
3198
+ }
3199
+ initialCursor(registration) {
3200
+ return {
3201
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3202
+ host_source_ref: registration.host_source_ref,
3203
+ host_session_ref: registration.host_session_ref,
3204
+ transcript_path: registration.transcript_path,
3205
+ offset: 0,
3206
+ anchor_start: 0,
3207
+ anchor_sha256: this.sha(Buffer.alloc(0)),
3208
+ subagents: {},
3209
+ status: "active",
3210
+ updated_at: this.stamp()
3211
+ };
3212
+ }
3213
+ async scanOpenedTranscript(registration, handle, draft, size, device, inode) {
3214
+ const main2 = await this.readChunk(handle, draft.offset, size);
3215
+ if (main2 !== "empty" && main2 !== "partial") {
3216
+ await this.consumeMainChunk(registration, main2, draft);
3217
+ draft.offset += main2.length;
3218
+ }
3219
+ draft.consumed += await this.consumeSubagents(registration, draft);
3220
+ await this.emitOpenTerminal(registration, draft);
3221
+ await this.commitCursor(registration, handle, draft, device, inode);
3222
+ }
3223
+ async preflight(registration, handle, size, device, inode) {
3224
+ const existing = await this.store.readTranscriptCursor(registration);
3225
+ if (existing?.status === "quarantined") return existing;
3226
+ const cursor = existing ?? this.initialCursor(registration);
3227
+ if (cursor.device === void 0) {
3228
+ if (cursor.offset !== 0) return await this.quarantineStored(cursor, device, inode, "transcript_replaced");
3229
+ return { ...cursor, device, inode };
3230
+ }
3231
+ const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
3232
+ if (reason !== null) return await this.quarantineStored(cursor, device, inode, reason);
3233
+ return cursor;
3234
+ }
3235
+ async consumeMainChunk(registration, complete, draft) {
3236
+ for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3237
+ draft.consumed += 1;
3238
+ const record6 = parseTranscriptLine(line);
3239
+ if (record6 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
3240
+ if (isUserTurnLine(record6)) await this.openTurn(registration, draft, record6);
3241
+ else if (isAssistantUsageLine(record6)) this.accumulateUsage(draft, toUsage(record6.uuid, record6.timestamp, record6.message.usage));
3242
+ else if (isAssistantLine(record6) && record6.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3243
+ if (isAssistantTerminalLine(record6)) await this.releaseOpenTurn(registration, draft);
3244
+ }
3245
+ }
3246
+ async openTurn(registration, draft, record6) {
3247
+ await this.closeTurn(registration, draft, record6.timestamp);
3248
+ const sessionRef = registration.host_session_ref;
3249
+ const turnRef = claudeLocalTurnRef(registration.claude_session_id, record6.uuid);
3250
+ const inputRef = claudeLocalInputRef(registration.claude_session_id, record6.uuid);
3251
+ const promptRef = hostProducerPromptRef(this.sha(record6.message.content));
3252
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
3253
+ const observations = [
3254
+ { observation_ref: observationRef("transcript_user_text", inputRef, record6.message.content), observation_kind: "prompt", observed_at: record6.timestamp, value: { body_sha256: this.sha(record6.message.content) } },
3255
+ { observation_ref: observationRef("transcript_turn_started", turnRef, record6.uuid), observation_kind: "turn", observed_at: record6.timestamp, value: { status: "started", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turnRef } }
3256
+ ];
3257
+ if (!draft.session_observed) {
3258
+ const session = await this.store.readSession(registration.host_source_ref, sessionRef);
3259
+ if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record6.timestamp, value: session.value });
3260
+ draft.session_observed = true;
3261
+ }
3262
+ await this.persistOpenedTurnContext(registration, draft, record6, inputRef, turnRef, promptRef, identity, observations);
3263
+ }
3264
+ async persistOpenedTurnContext(registration, draft, record6, inputRef, turnRef, promptRef, identity, observations) {
3265
+ const sessionRef = registration.host_session_ref;
3266
+ const expiresAt = this.expire(this.outboxRetentionMs);
3267
+ const custodyRegistered = await this.registerPrompt(sessionRef, turnRef, promptRef, record6.message.content, expiresAt);
3268
+ if (custodyRegistered) this.diagnostic("watcher_turn_observed");
3269
+ try {
3270
+ await this.store.putEnvelope({
3271
+ schema_version: "ceal.claude_observation_outbox.v1",
3272
+ identity,
3273
+ host_input_ref: inputRef,
3274
+ input_kind: "human",
3275
+ prompt_ref: promptRef,
3276
+ observations,
3277
+ origin_input: true,
3278
+ submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3279
+ created_at: record6.timestamp,
3280
+ expires_at: expiresAt
3281
+ });
3282
+ draft.enqueued += 1;
3283
+ await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
3284
+ this.setOpenedTurn(draft, record6, inputRef, turnRef, promptRef);
3285
+ } catch (error) {
3286
+ if (custodyRegistered) await this.releasePrompt(sessionRef, { turn_key: turnRef, prompt_ref: promptRef }).catch(() => void 0);
3287
+ throw error;
3288
+ }
3289
+ }
3290
+ async activateOpenedTurnContext(identity, promptRef, expiresAt) {
3291
+ const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3292
+ const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3293
+ await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
3294
+ }
3295
+ setOpenedTurn(draft, record6, inputRef, turnRef, promptRef) {
3296
+ const folded = foldUnattributed(draft.unattributed);
3297
+ draft.open_turn = {
3298
+ turn_key: turnRef,
3299
+ user_uuid: record6.uuid,
3300
+ prompt_ref: promptRef,
3301
+ host_input_ref: inputRef,
3302
+ started_at: record6.timestamp,
3303
+ last_observed_at: folded?.last_observed_at ?? record6.timestamp,
3304
+ message_ids: folded?.message_ids ?? [],
3305
+ counts: folded?.counts ?? zeroCounts()
3306
+ };
3307
+ draft.unattributed = void 0;
3308
+ }
3309
+ async closeTurn(registration, draft, at) {
3310
+ const turn = draft.open_turn;
3311
+ draft.open_turn = void 0;
3312
+ if (turn === void 0) return;
3313
+ const digest2 = terminalDigest(turn);
3314
+ if (digest2 !== turn.last_terminal_digest) {
3315
+ await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3316
+ draft.enqueued += 1;
3317
+ }
3318
+ await this.releasePrompt(registration.host_session_ref, turn);
3319
+ }
3320
+ async releaseOpenTurn(registration, draft) {
3321
+ if (draft.open_turn !== void 0) await this.releasePrompt(registration.host_session_ref, draft.open_turn);
3322
+ }
3323
+ async releasePrompt(sessionRef, turn) {
3324
+ if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
3325
+ await this.releaseCurrentPrompt({ producer_id: "claude", producer_installation_ref: this.producerInstallationRef, local_session_ref: sessionRef, local_turn_ref: turn.turn_key, prompt_ref: turn.prompt_ref });
3326
+ }
3327
+ accumulateUsage(draft, usage) {
3328
+ const turn = draft.open_turn;
3329
+ if (turn === void 0) {
3330
+ draft.unattributed = accumulateUnattributed(draft.unattributed, usage);
3331
+ return;
3332
+ }
3333
+ if (turn.message_ids.includes(usage.uuid)) return;
3334
+ 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");
3335
+ draft.open_turn = {
3336
+ ...turn,
3337
+ last_observed_at: usage.observed_at > turn.last_observed_at ? usage.observed_at : turn.last_observed_at,
3338
+ message_ids: [...turn.message_ids, usage.uuid],
3339
+ counts: addCounts(turn.counts, usage)
3340
+ };
3341
+ }
3342
+ /**
3343
+ * Subagent transcripts live beside the session transcript at
3344
+ * `<session-id>/subagents/*.jsonl` with a `.meta.json` sibling the scan
3345
+ * ignores. Their usage belongs to the turn open when the scan runs: a
3346
+ * subagent only runs while its parent turn is in flight. A straggler line
3347
+ * that lands after the next user message therefore attributes to the open
3348
+ * turn, never to a closed one.
3349
+ */
3350
+ async consumeSubagents(registration, draft) {
3351
+ const directory = subagentsDirectory(registration.transcript_path);
3352
+ let names;
3353
+ try {
3354
+ names = (await readdir2(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
3355
+ } catch (error) {
3356
+ if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
3357
+ throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
3358
+ }
3359
+ const listed = new Set(names);
3360
+ for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
3361
+ if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
3362
+ this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
3363
+ names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
3364
+ }
3365
+ let consumed = 0;
3366
+ for (const name of names) {
3367
+ consumed += await this.consumeSubagentFile(draft, directory, name);
3368
+ }
3369
+ return consumed;
3370
+ }
3371
+ async consumeSubagentFile(draft, directory, name) {
3372
+ const path = join4(directory, name);
3373
+ let handle;
3374
+ try {
3375
+ handle = await open3(path, O_RDONLY3 | O_NOFOLLOW2);
3376
+ const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3377
+ const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
3378
+ const pinned = await this.preflightSubagent(entry, size, device, inode);
3379
+ const chunk = await this.readChunk(handle, pinned.offset, size);
3380
+ if (chunk === "empty" || chunk === "partial") {
3381
+ draft.subagents[name] = pinned;
3382
+ return 0;
3383
+ }
3384
+ const consumed = this.consumeSubagentLines(draft, chunk);
3385
+ const offset = pinned.offset + chunk.length;
3386
+ const anchor = await this.readAnchor(handle, offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
3387
+ draft.subagents[name] = { device, inode, offset, anchor_start: anchor.start, anchor_sha256: anchor.sha256 };
3388
+ return consumed;
3389
+ } catch (error) {
3390
+ if (isMissingTranscriptError(error)) {
3391
+ delete draft.subagents[name];
3392
+ return 0;
3393
+ }
3394
+ throw error instanceof ClaudeHostError ? error : new ClaudeHostError("payload_rejected", "Claude subagent transcript is unavailable.");
3395
+ } finally {
3396
+ await handle?.close();
3397
+ }
3398
+ }
3399
+ consumeSubagentLines(draft, chunk) {
3400
+ let consumed = 0;
3401
+ for (const line of chunk.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3402
+ consumed += 1;
3403
+ const record6 = parseTranscriptLine(line);
3404
+ if (record6 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
3405
+ if (isAssistantUsageLine(record6)) this.accumulateUsage(draft, toUsage(record6.uuid, record6.timestamp, record6.message.usage));
3406
+ else if (isAssistantLine(record6) && record6.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
3407
+ }
3408
+ return consumed;
3409
+ }
3410
+ async preflightSubagent(entry, size, device, inode) {
3411
+ if (entry.device === void 0) {
3412
+ if (entry.offset !== 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript cursor is unpinned past its start.");
3413
+ return { device, inode, offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
3414
+ }
3415
+ if (entry.device !== device || entry.inode !== inode) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was replaced.");
3416
+ if (size < entry.offset) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript was truncated.");
3417
+ return { device, inode, offset: entry.offset, anchor_start: entry.anchor_start, anchor_sha256: entry.anchor_sha256 };
3418
+ }
3419
+ /**
3420
+ * The open turn's terminal is emitted optimistically on every scan whose
3421
+ * new lines changed its totals, so usage is visible before the next user
3422
+ * message closes the turn. The emission digest changes with the content,
3423
+ * so a repeated scan over unchanged state emits nothing.
3424
+ */
3425
+ async emitOpenTerminal(registration, draft) {
3426
+ const turn = draft.open_turn;
3427
+ if (turn === void 0 || turn.message_ids.length === 0) return;
3428
+ const digest2 = terminalDigest(turn);
3429
+ if (digest2 === turn.last_terminal_digest) return;
3430
+ await this.putTerminalEnvelope(registration, turn, digest2, void 0);
3431
+ draft.enqueued += 1;
3432
+ draft.open_turn = { ...turn, last_terminal_digest: digest2 };
3433
+ }
3434
+ async putTerminalEnvelope(registration, turn, digest2, gapAt) {
3435
+ const observedAt = gapAt ?? turn.last_observed_at;
3436
+ const tokenValue = turn.message_ids.length === 0 ? hostObservationNotRecorded("claude_turn_usage_not_observed") : availableTokenValue(turn.counts);
3437
+ const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: turn.turn_key };
3438
+ await this.store.putEnvelope({
3439
+ schema_version: "ceal.claude_observation_outbox.v1",
3440
+ identity,
3441
+ host_input_ref: turn.host_input_ref,
3442
+ input_kind: "host_automatic",
3443
+ prompt_ref: turn.prompt_ref,
3444
+ observations: [
3445
+ { 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 } },
3446
+ { observation_ref: observationRef("transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue },
3447
+ { observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
3448
+ ],
3449
+ origin_input: false,
3450
+ submission_ref: submissionRef("terminal", turn.turn_key, digest2, turn.prompt_ref),
3451
+ created_at: observedAt,
3452
+ expires_at: this.expire(this.outboxRetentionMs)
3453
+ });
3454
+ }
3455
+ async commitCursor(registration, handle, draft, device, inode) {
3456
+ const anchor = await this.readAnchor(handle, draft.offset, CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
3457
+ await this.store.writeTranscriptCursor({
3458
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3459
+ host_source_ref: registration.host_source_ref,
3460
+ host_session_ref: registration.host_session_ref,
3461
+ transcript_path: registration.transcript_path,
3462
+ device,
3463
+ inode,
3464
+ offset: draft.offset,
3465
+ anchor_start: anchor.start,
3466
+ anchor_sha256: anchor.sha256,
3467
+ subagents: draft.subagents,
3468
+ ...draft.open_turn === void 0 ? {} : { open_turn: draft.open_turn },
3469
+ ...draft.unattributed === void 0 ? {} : { unattributed: draft.unattributed },
3470
+ ...draft.session_observed ? { session_observed: true } : {},
3471
+ status: "active",
3472
+ updated_at: this.stamp()
3473
+ });
3474
+ }
3475
+ async registerPrompt(sessionRef, turnRef, promptRef, prompt, expiresAt) {
3476
+ if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
3477
+ const register = this.registerCurrentPrompt;
3478
+ const installationRef = this.producerInstallationRef;
3479
+ return registerHostCurrentPromptDiagnosed(
3480
+ () => register({
3481
+ producer_id: "claude",
3482
+ producer_installation_ref: installationRef,
3483
+ local_session_ref: sessionRef,
3484
+ local_turn_ref: turnRef,
3485
+ prompt_ref: promptRef,
3486
+ current_prompt: prompt,
3487
+ context_expires_at: expiresAt
3488
+ }),
3489
+ (event) => this.diagnostic(event)
3490
+ );
3491
+ }
3492
+ async releaseStoredPromptBestEffort(sessionRef, cursor) {
3493
+ await this.releaseOpenTurnBestEffort(sessionRef, cursor?.open_turn);
3494
+ }
3495
+ async releaseOpenTurnBestEffort(sessionRef, turn) {
3496
+ if (turn === void 0) return;
3497
+ await this.releasePrompt(sessionRef, turn).catch(() => void 0);
3498
+ }
3499
+ async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
3500
+ try {
3501
+ await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
3502
+ this.diagnostic("activation_recorded");
3503
+ } catch (error) {
3504
+ this.diagnostic("activation_failed");
3505
+ throw error;
3506
+ }
3507
+ }
3508
+ async quarantineStored(cursor, device, inode, reason) {
3509
+ await this.releaseStoredPromptBestEffort(cursor.host_session_ref, cursor);
3510
+ await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
3511
+ return null;
3512
+ }
3513
+ async quarantineUnopened(registration, reason) {
3514
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
3515
+ await this.store.writeTranscriptCursor({ ...existing ?? this.initialCursor(registration), transcript_path: registration.transcript_path, status: "quarantined", reason, updated_at: this.stamp() });
3516
+ return { records: 0, enqueued: 0, quarantined: true };
3517
+ }
3518
+ async quarantineDraft(cursor) {
3519
+ await this.store.writeTranscriptCursor({ ...cursor, status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
3520
+ return { records: 0, enqueued: 0, quarantined: true };
3521
+ }
3522
+ async quarantineFailure(registration, handle) {
3523
+ if (handle === void 0) return;
3524
+ const stats = await handle.stat().catch(() => null);
3525
+ if (stats === null) return;
3526
+ const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
3527
+ const cursor = existing ?? this.initialCursor(registration);
3528
+ await this.store.writeTranscriptCursor({ ...cursor, device: String(stats.dev), inode: String(stats.ino), status: "quarantined", reason: "transcript_malformed", updated_at: this.stamp() });
3529
+ }
3530
+ async preservedCursor(registration) {
3531
+ return await this.store.readTranscriptCursor(registration).catch(() => null) ?? this.initialCursor(registration);
3532
+ }
3533
+ };
3534
+ function mutableCursor(cursor) {
3535
+ return {
3536
+ offset: cursor.offset,
3537
+ ...cursor.open_turn === void 0 ? {} : { open_turn: cursor.open_turn },
3538
+ ...cursor.unattributed === void 0 ? {} : { unattributed: cursor.unattributed },
3539
+ subagents: Object.fromEntries(Object.entries(cursor.subagents).map(([name, entry]) => [name, { ...entry }])),
3540
+ session_observed: cursor.session_observed ?? false,
3541
+ consumed: 0,
3542
+ enqueued: 0
3543
+ };
3544
+ }
3545
+ function zeroCounts() {
3546
+ return { input: 0, cached_input: 0, cache_write_input: 0, output: 0 };
3547
+ }
3548
+ function terminalDigest(turn) {
3549
+ return transcriptObjectSha256({ turn: turn.turn_key, messages: turn.message_ids, counts: turn.counts });
3550
+ }
3551
+ function availableTokenValue(counts) {
3552
+ const input = counts.input;
3553
+ const output = counts.output;
3554
+ if (!Number.isSafeInteger(input + output)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3555
+ return createHostTokenObservationValue({
3556
+ input,
3557
+ output,
3558
+ total: input + output,
3559
+ cached_input: counts.cached_input,
3560
+ cache_write_input: counts.cache_write_input
3561
+ }, TOKEN_OBSERVATION_SOURCE);
3562
+ }
3563
+ function addCounts(current, next) {
3564
+ const counts = {
3565
+ input: current.input + next.input,
3566
+ cached_input: current.cached_input + next.cached_input,
3567
+ cache_write_input: current.cache_write_input + next.cache_write_input,
3568
+ output: current.output + next.output
3569
+ };
3570
+ if (![counts.input, counts.cached_input, counts.cache_write_input, counts.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
3571
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3572
+ }
3573
+ return counts;
3574
+ }
3575
+ function accumulateUnattributed(current, usage) {
3576
+ const base = current ?? { input: 0, cached_input: 0, cache_write_input: 0, output: 0, message_ids: [], last_observed_at: usage.observed_at };
3577
+ if (base.message_ids.includes(usage.uuid)) return base;
3578
+ 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");
3579
+ const counts = addCounts(base, usage);
3580
+ return {
3581
+ ...counts,
3582
+ message_ids: [...base.message_ids, usage.uuid],
3583
+ last_observed_at: usage.observed_at > base.last_observed_at ? usage.observed_at : base.last_observed_at
3584
+ };
3585
+ }
3586
+ function foldUnattributed(bucket) {
3587
+ if (bucket === void 0) return void 0;
3588
+ if (![bucket.input, bucket.cached_input, bucket.cache_write_input, bucket.output].every((value) => Number.isSafeInteger(value) && value >= 0)) {
3589
+ throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3590
+ }
3591
+ 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 };
3592
+ }
3593
+ function toUsage(uuid, observedAt, usage) {
3594
+ const input = usageNumber(usage.input_tokens);
3595
+ const cached = usageNumber(usage.cache_read_input_tokens ?? 0);
3596
+ const created = usageNumber(usage.cache_creation_input_tokens ?? 0);
3597
+ const output = usageNumber(usage.output_tokens);
3598
+ if (input === null || cached === null || created === null || output === null) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3599
+ const total = input + cached + created;
3600
+ if (![total, output].every((value) => Number.isSafeInteger(value) && value >= 0)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage exceeds the supported range.");
3601
+ return { uuid, observed_at: observedAt, input: total, cached_input: cached, cache_write_input: created, output };
3602
+ }
3603
+ function usageNumber(value) {
3604
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
3605
+ }
3606
+ function parseTranscriptLine(line) {
3607
+ try {
3608
+ return JSON.parse(line);
3609
+ } catch {
3610
+ return null;
3611
+ }
3612
+ }
3613
+ function lineIdentity(record6) {
3614
+ if (typeof record6.uuid !== "string" || record6.uuid.length === 0 || record6.uuid.length > 256) return null;
3615
+ if (typeof record6.timestamp !== "string" || !Number.isFinite(Date.parse(record6.timestamp))) return null;
3616
+ return { uuid: record6.uuid, timestamp: record6.timestamp };
3617
+ }
3618
+ function isUserTurnLine(record6) {
3619
+ if (!isTranscriptRecord(record6) || record6.type !== "user") return false;
3620
+ if (!isTranscriptRecord(record6.message) || typeof record6.message.content !== "string") return false;
3621
+ if (record6.isMeta === true && (!isTranscriptRecord(record6.origin) || typeof record6.origin.kind !== "string")) return false;
3622
+ const identity = lineIdentity(record6);
3623
+ if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
3624
+ return true;
3625
+ }
3626
+ function isAssistantLine(record6) {
3627
+ return isTranscriptRecord(record6) && record6.type === "assistant" && isTranscriptRecord(record6.message);
3628
+ }
3629
+ function isAssistantTerminalLine(record6) {
3630
+ return isAssistantLine(record6) && (record6.message.stop_reason === "end_turn" || record6.message.stop_reason === "stop_sequence");
3631
+ }
3632
+ function isAssistantUsageLine(record6) {
3633
+ if (!isTranscriptRecord(record6)) return false;
3634
+ if (!isAssistantLine(record6) || record6.message.usage === void 0) return false;
3635
+ const identity = lineIdentity(record6);
3636
+ if (identity === null || !isTranscriptRecord(record6.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
3637
+ return true;
3638
+ }
3639
+ function subagentsDirectory(transcriptPath) {
3640
+ const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
3641
+ return join4(stem, "subagents");
3642
+ }
3643
+ function observationRef(event, identity, value) {
3644
+ return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
3645
+ }
3646
+ function submissionRef(kind, turn, digest2, prompt) {
3647
+ return `claude-submission:${transcriptObjectSha256({ kind, turn, digest: digest2, prompt })}`;
3648
+ }
3649
+ async function ownedClaudeTranscriptFile(handle) {
3650
+ const stats = await handle.stat();
3651
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
3652
+ if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
3653
+ if (stats.uid !== uid) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
3654
+ return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
3655
+ }
3656
+ function isNotDirectory(error) {
3657
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
3658
+ }
3659
+ function claudeTranscriptScanCause(handle, error) {
3660
+ if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
3661
+ const code2 = transcriptErrorCode(error);
3662
+ if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
3663
+ return `${handle === void 0 ? "open" : "scan"}:unknown`;
3664
+ }
3665
+ function claudeTranscriptScanFailureKind(error) {
3666
+ return transcriptScanFailureKind(error, (candidate) => {
3667
+ if (!(candidate instanceof ClaudeHostError)) return void 0;
3668
+ if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
3669
+ if (candidate.code === "payload_rejected") return "payload_rejected";
3670
+ if (candidate.code === "storage") return "read_error";
3671
+ return void 0;
3672
+ });
3673
+ }
3674
+ function isSymlinkTranscript(error) {
3675
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
2343
3676
  }
2344
3677
 
2345
3678
  // host/claude/src/claude-adapter.ts
2346
- import { createHash as createHash3 } from "node:crypto";
3679
+ import { createHash as createHash4, randomUUID } from "node:crypto";
3680
+ import { O_NOFOLLOW as O_NOFOLLOW3, O_RDONLY as O_RDONLY4 } from "node:constants";
3681
+ import { open as open4, readdir as readdir3 } from "node:fs/promises";
3682
+ import { homedir } from "node:os";
3683
+ import { isAbsolute as isAbsolute2, join as join5, relative, resolve } from "node:path";
2347
3684
  var ClaudeHookAdapter = class {
2348
3685
  store;
2349
3686
  now;
3687
+ optionsSourceRef;
3688
+ transcriptsRoot;
3689
+ randomUuid;
2350
3690
  constructor(options) {
2351
3691
  this.store = new ClaudeOutboxStore(options.outbox_dir);
2352
3692
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
2353
3693
  this.optionsSourceRef = options.host_source_ref ?? "claude-host:local";
2354
- this.producerInstallationRef = options.producer_installation_ref;
2355
- this.registerCurrentPrompt = options.register_current_prompt;
3694
+ this.transcriptsRoot = options.claude_transcripts_root;
3695
+ this.randomUuid = options.random_uuid ?? randomUUID;
2356
3696
  }
2357
3697
  async handle(event, raw) {
2358
3698
  let input;
2359
3699
  try {
2360
3700
  input = decodeClaudeHookInput(event, raw);
2361
3701
  } catch (error) {
2362
- return { event, disposition: "rejected", reason: error instanceof Error ? refusalFact("error", error) : "invalid_claude_hook_input" };
3702
+ return { event, disposition: "rejected", reason: error instanceof Error ? error.message.slice(0, 128) : "invalid_claude_hook_input" };
2363
3703
  }
2364
- const occurredAt = timestamp4(this.now);
2365
- const sessionRef = claudeLocalSessionRef(input.session_id);
2366
- const turnRef = input.prompt_id === void 0 ? void 0 : claudeLocalTurnRef(input.session_id, input.prompt_id);
2367
- const frames = mapFrames(input, occurredAt);
2368
- if (input.hook_event_name === "UserPromptSubmit" && turnRef !== void 0 && this.producerInstallationRef !== void 0 && this.registerCurrentPrompt !== void 0) {
2369
- const promptRef = hostProducerPromptRef(createHash3("sha256").update(input.prompt, "utf8").digest("hex"));
2370
- await this.registerCurrentPrompt({ producer_id: "claude", producer_installation_ref: this.producerInstallationRef, local_session_ref: sessionRef, local_turn_ref: turnRef, prompt_ref: promptRef, current_prompt: input.prompt, context_expires_at: new Date(Date.parse(occurredAt) + 24 * 60 * 60 * 1e3).toISOString() });
3704
+ try {
3705
+ return input.event === "SessionStart" ? await this.registerSession(input) : await this.registerCurrentTurn(input);
3706
+ } catch (error) {
3707
+ return { event, disposition: "rejected", reason: error instanceof Error ? error.message.slice(0, 128) : "claude_hook_failed" };
2371
3708
  }
2372
- const batchRef = `claude-batch:${cealRequestSha256({ event, session_ref: sessionRef, turn_ref: turnRef ?? null, occurred_at: occurredAt, frames })}`;
2373
- const batch = { schema_version: CLAUDE_OUTBOX_SCHEMA, batch_ref: batchRef, hook_event: event, local_session_ref: sessionRef, ...turnRef === void 0 ? {} : { local_turn_ref: turnRef }, occurred_at: occurredAt, frames };
2374
- await this.store.put(batch);
2375
- await this.activatePromptContext(input, frames, sessionRef, turnRef);
2376
- return { event, disposition: "queued", batch_ref: batchRef, local_session_ref: sessionRef, ...turnRef === void 0 ? {} : { local_turn_ref: turnRef } };
2377
- }
2378
- async activatePromptContext(input, frames, sessionRef, turnRef) {
2379
- if (input.hook_event_name !== "UserPromptSubmit" || turnRef === void 0) return;
2380
- const identity = { host_source_ref: this.optionsSourceRef, host_session_ref: sessionRef, host_turn_ref: turnRef };
2381
- validateClaudeIdentity(identity);
2382
- const inputFrame = frames.find((frame) => frame.frame_kind === "user_input");
2383
- const expiresAt = inputFrame?.payload.context_expires_at;
2384
- if (typeof expiresAt !== "string") throw new ClaudeHostError("storage", "Claude prompt context expiry is unavailable.");
2385
- const observations = inputFrame?.payload.observations;
2386
- const prompt = Array.isArray(observations) ? observations.find((observation) => isRecord(observation) && observation.observation_kind === "prompt") : void 0;
2387
- const promptValue = isRecord(prompt) ? prompt.value : void 0;
2388
- if (!isCealHostPromptObservationValue(promptValue)) throw new ClaudeHostError("storage", "Claude prompt observation is unavailable.");
2389
- await this.store.activateInvocationContext(identity, hostProducerPromptRef(promptValue.body_sha256), expiresAt, input.cwd);
2390
3709
  }
2391
- optionsSourceRef;
2392
- producerInstallationRef;
2393
- registerCurrentPrompt;
3710
+ async registerSession(input) {
3711
+ const defaultRoot = join5(homedir(), ".claude", "projects");
3712
+ const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? defaultRoot);
3713
+ const hostSessionRef = claudeLocalSessionRef(input.session_id);
3714
+ const observedAt = timestamp4(this.now);
3715
+ const expiresAt = new Date(Date.parse(observedAt) + CLAUDE_DEFAULT_RETENTION_MS).toISOString();
3716
+ const registration = {
3717
+ schema_version: "ceal.claude_transcript_registration.v1",
3718
+ host_source_ref: this.optionsSourceRef,
3719
+ host_session_ref: hostSessionRef,
3720
+ claude_session_id: input.session_id,
3721
+ cwd: input.cwd,
3722
+ transcript_path: transcriptPath,
3723
+ registered_at: observedAt,
3724
+ expires_at: expiresAt
3725
+ };
3726
+ const existing = (await this.store.listTranscriptRegistrations()).find((candidate) => candidate.host_source_ref === registration.host_source_ref && candidate.host_session_ref === registration.host_session_ref);
3727
+ if (existing === void 0) {
3728
+ await this.store.writeTranscriptCursor((await captureClaudeTranscriptBaseline(registration, this.now)).cursor);
3729
+ }
3730
+ await this.store.registerTranscript(registration);
3731
+ const snapshot = {
3732
+ schema_version: "ceal.claude_session.v1",
3733
+ host_session_ref: hostSessionRef,
3734
+ host_source_ref: this.optionsSourceRef,
3735
+ observed_at: observedAt,
3736
+ value: sessionObservationValue(input),
3737
+ expires_at: expiresAt
3738
+ };
3739
+ await this.store.saveSession(snapshot);
3740
+ return { event: input.event, disposition: "ignored", reason: "transcript_registered", local_session_ref: hostSessionRef };
3741
+ }
3742
+ async registerCurrentTurn(input) {
3743
+ const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? join5(homedir(), ".claude", "projects"));
3744
+ const hostSessionRef = claudeLocalSessionRef(input.session_id);
3745
+ const observedAt = timestamp4(this.now);
3746
+ const expiresAt = new Date(Date.parse(observedAt) + CLAUDE_DEFAULT_RETENTION_MS).toISOString();
3747
+ const registration = {
3748
+ schema_version: "ceal.claude_transcript_registration.v1",
3749
+ host_source_ref: this.optionsSourceRef,
3750
+ host_session_ref: hostSessionRef,
3751
+ claude_session_id: input.session_id,
3752
+ cwd: input.cwd,
3753
+ transcript_path: transcriptPath,
3754
+ registered_at: observedAt,
3755
+ expires_at: expiresAt
3756
+ };
3757
+ const promptRef = hostProducerPromptRef(createHash4("sha256").update(input.prompt, "utf8").digest("hex"));
3758
+ const turnGenerationRef = this.randomUuid();
3759
+ const selection = { schema_version: "ceal.host_invocation_turn_selection.v1", host_source_ref: this.optionsSourceRef, host_session_ref: hostSessionRef, prompt_ref: promptRef, turn_generation_ref: turnGenerationRef };
3760
+ await this.store.writeInvocationTurnSelection(selection);
3761
+ const existing = (await this.store.listTranscriptRegistrations()).find((candidate) => candidate.host_source_ref === registration.host_source_ref && candidate.host_session_ref === registration.host_session_ref);
3762
+ const baseline = await captureClaudeTranscriptBaseline(registration, this.now, input.prompt);
3763
+ const transcriptTurnRef = baseline.current_turn_ref;
3764
+ if (existing === void 0) await this.store.writeTranscriptCursor(baseline.cursor);
3765
+ else await refreshCurrentTurnCursor(this.store, registration, baseline, promptRef, turnGenerationRef, transcriptTurnRef);
3766
+ await this.store.registerTranscript(registration);
3767
+ return { event: input.event, disposition: "ignored", reason: "transcript_registered", local_session_ref: hostSessionRef, ...transcriptTurnRef === void 0 ? {} : { local_turn_ref: transcriptTurnRef } };
3768
+ }
2394
3769
  };
2395
- function mapFrames(input, at) {
2396
- if (input.hook_event_name === "SessionStart") return sessionFrames(input, at);
2397
- if (input.hook_event_name === "UserPromptSubmit") return inputFrames(input, at);
2398
- return terminalFrames(input, at);
2399
- }
2400
- function sessionFrames(input, at) {
2401
- const sessionRef = claudeLocalSessionRef(input.session_id);
2402
- const observation = obs("session", sessionRef, at, { status: "started", host_kind: "claude", source: input.source, model: input.model ?? "unavailable", decoder_evidence_version: "claude-code-2.1.258" });
2403
- return [{ frame_kind: "session_started", payload: compact({ local_session_ref: sessionRef, working_directory_ref: claudeWorkspaceRef(input.cwd), model: input.model, observations: [observation] }) }];
2404
- }
2405
- function inputFrames(input, at) {
2406
- const sessionRef = claudeLocalSessionRef(input.session_id);
2407
- const turnRef = claudeLocalTurnRef(input.session_id, input.prompt_id);
2408
- const promptDigest = createHash3("sha256").update(input.prompt, "utf8").digest("hex");
2409
- const turn = obs("turn", turnRef, at, { status: "started", prompt_id_sha256: createHash3("sha256").update(input.prompt_id, "utf8").digest("hex"), effort: input.effort?.level ?? "unavailable" });
2410
- const prompt = obs("prompt", claudeLocalInputRef(input.session_id, input.prompt_id), at, { body_sha256: promptDigest });
2411
- return [{ frame_kind: "turn_started", payload: { local_session_ref: sessionRef, local_turn_ref: turnRef, observations: [turn] } }, { frame_kind: "user_input", payload: compact({ local_session_ref: sessionRef, local_turn_ref: turnRef, local_input_ref: claudeLocalInputRef(input.session_id, input.prompt_id), input_kind: "human", origin_input: true, local_turn_aliases: [safeAlias(input.prompt_id)], context_expires_at: new Date(Date.parse(at) + 24 * 60 * 60 * 1e3).toISOString(), observations: [prompt] }) }];
2412
- }
2413
- function terminalFrames(input, at) {
2414
- const sessionRef = claudeLocalSessionRef(input.session_id);
2415
- const turnRef = claudeLocalTurnRef(input.session_id, input.prompt_id);
2416
- const aborted = input.hook_event_name === "StopFailure";
2417
- const reason = aborted ? `api_${input.error}` : void 0;
2418
- const terminal = obs("turn", `${turnRef}:${aborted ? "failed" : "stopped"}`, at, compact({ status: aborted ? "aborted" : "completed", reason }));
2419
- const timing = obs("timing", `${turnRef}:timing`, at, { status: "unavailable", reason: "claude_hook_does_not_expose_timing_or_usage" });
2420
- return [{ frame_kind: "turn_terminal", payload: compact({ local_session_ref: sessionRef, local_turn_ref: turnRef, terminal_state: aborted ? "aborted" : "completed", abort_reason: reason, observations: [terminal, timing] }) }];
2421
- }
2422
- function obs(kind, identity, at, value) {
2423
- return { observation_ref: cealHostObservationRef("claude", cealRequestSha256({ kind, identity, at, value })), observation_kind: kind, observed_at: at, value };
2424
- }
2425
- function safeAlias(value) {
2426
- return /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value) ? value : `prompt:${createHash3("sha256").update(value).digest("hex")}`;
3770
+ function sessionObservationValue(input) {
3771
+ return { status: "started", host_kind: "claude", source: input.source, model: input.model ?? "unavailable", decoder_evidence_version: "claude-code-2.1.258" };
3772
+ }
3773
+ function sourceBoundTranscriptPath(path, transcriptsRoot) {
3774
+ if (!isAbsolute2(path) || !isAbsolute2(transcriptsRoot)) throw new ClaudeHostError("configuration", "Claude transcript registration paths must be absolute.");
3775
+ const transcript = resolve(path);
3776
+ const root = resolve(transcriptsRoot);
3777
+ const child = relative(root, transcript);
3778
+ if (child === "" || child === ".." || child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(child)) {
3779
+ throw new ClaudeHostError("invalid_input", "Claude transcript path is outside the configured transcripts root.");
3780
+ }
3781
+ return transcript;
2427
3782
  }
2428
- function isRecord(value) {
3783
+ async function refreshCurrentTurnCursor(store, registration, baseline, promptRef, turnGenerationRef, transcriptTurnRef) {
3784
+ const cursor = await store.readTranscriptCursor(registration);
3785
+ if (cursor?.status !== "active") {
3786
+ await store.writeTranscriptCursor(baseline.cursor);
3787
+ return;
3788
+ }
3789
+ if (baseline.current_line_start === void 0 || baseline.current_user_uuid === void 0 || transcriptTurnRef === void 0 || cursor.offset <= baseline.current_line_start || cursor.open_turn?.user_uuid !== baseline.current_user_uuid) return;
3790
+ await store.attachInvocationTurnGeneration({
3791
+ host_source_ref: registration.host_source_ref,
3792
+ host_session_ref: registration.host_session_ref,
3793
+ host_turn_ref: transcriptTurnRef
3794
+ }, promptRef, turnGenerationRef);
3795
+ }
3796
+ async function captureClaudeTranscriptBaseline(registration, now, currentPrompt) {
3797
+ let handle;
3798
+ try {
3799
+ handle = await open4(registration.transcript_path, O_RDONLY4 | O_NOFOLLOW3);
3800
+ } catch (error) {
3801
+ if (isNotFound(error)) return { cursor: initialCursor(registration, now) };
3802
+ throw error;
3803
+ }
3804
+ try {
3805
+ const file = await inspectClaudeTranscript(handle);
3806
+ const current = currentPrompt === void 0 ? void 0 : await currentClaudeUserLine(handle, file.size, registration.claude_session_id, currentPrompt);
3807
+ const offset = current?.offset ?? file.size;
3808
+ const cursor = await makeClaudeBaselineCursor(handle, registration, now, file, offset);
3809
+ return {
3810
+ cursor,
3811
+ ...current?.user === void 0 ? {} : {
3812
+ current_turn_ref: claudeLocalTurnRef(registration.claude_session_id, current.user.uuid),
3813
+ current_user_uuid: current.user.uuid,
3814
+ current_line_start: current.offset
3815
+ }
3816
+ };
3817
+ } finally {
3818
+ await handle?.close();
3819
+ }
3820
+ }
3821
+ async function inspectClaudeTranscript(handle) {
3822
+ const stats = await handle.stat();
3823
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
3824
+ if (!stats.isFile()) throw new ClaudeHostError("storage", "Claude transcript source is not a regular file.");
3825
+ if (stats.uid !== uid) throw new ClaudeHostError("storage", "Claude transcript source is owned by another user.");
3826
+ return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
3827
+ }
3828
+ async function currentClaudeUserLine(handle, size, sessionId, prompt) {
3829
+ const lastLine = await readLastClaudeTranscriptLine(handle, size);
3830
+ if (lastLine === null) return void 0;
3831
+ const user = parseClaudeUserLine(lastLine.bytes.toString("utf8"), sessionId);
3832
+ if (user?.content === prompt) return { offset: lastLine.start, user };
3833
+ return { offset: lastLine.complete ? size : lastLine.start };
3834
+ }
3835
+ async function makeClaudeBaselineCursor(handle, registration, now, file, offset) {
3836
+ const anchorStart = Math.max(0, offset - CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
3837
+ const anchor = Buffer.alloc(offset - anchorStart);
3838
+ if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, anchorStart);
3839
+ return {
3840
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3841
+ host_source_ref: registration.host_source_ref,
3842
+ host_session_ref: registration.host_session_ref,
3843
+ transcript_path: registration.transcript_path,
3844
+ device: file.device,
3845
+ inode: file.inode,
3846
+ offset,
3847
+ anchor_start: anchorStart,
3848
+ anchor_sha256: createHash4("sha256").update(anchor).digest("hex"),
3849
+ subagents: await readClaudeSubagentBaseline(registration.transcript_path),
3850
+ status: "active",
3851
+ updated_at: timestamp4(now)
3852
+ };
3853
+ }
3854
+ async function readClaudeSubagentBaseline(transcriptPath) {
3855
+ const directory = subagentsDirectory(transcriptPath);
3856
+ let names;
3857
+ try {
3858
+ names = (await readdir3(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
3859
+ } catch {
3860
+ return {};
3861
+ }
3862
+ const cursors = {};
3863
+ for (const name of names) {
3864
+ const seed = await readClaudeSubagentSeed(directory, name).catch(() => void 0);
3865
+ if (seed !== void 0) cursors[name] = seed;
3866
+ }
3867
+ return cursors;
3868
+ }
3869
+ async function readClaudeSubagentSeed(directory, name) {
3870
+ let handle;
3871
+ try {
3872
+ handle = await open4(join5(directory, name), O_RDONLY4 | O_NOFOLLOW3);
3873
+ const stats = await handle.stat();
3874
+ if (!stats.isFile()) throw new ClaudeHostError("storage", "Claude subagent baseline source is not a regular file.");
3875
+ return {
3876
+ device: String(stats.dev),
3877
+ inode: String(stats.ino),
3878
+ offset: stats.size,
3879
+ anchor_start: 0,
3880
+ anchor_sha256: createHash4("sha256").update(Buffer.alloc(0)).digest("hex")
3881
+ };
3882
+ } finally {
3883
+ await handle?.close();
3884
+ }
3885
+ }
3886
+ async function readLastClaudeTranscriptLine(handle, size) {
3887
+ if (size === 0) return null;
3888
+ const last = Buffer.alloc(1);
3889
+ await handle.read(last, 0, 1, size - 1);
3890
+ const complete = last[0] === 10;
3891
+ let end = complete ? size - 1 : size;
3892
+ if (end === 0) return null;
3893
+ let position = end;
3894
+ const chunks = [];
3895
+ let total = 0;
3896
+ const maximumBytes = 512 * 1024;
3897
+ while (position > 0 && total <= maximumBytes) {
3898
+ const start = Math.max(0, position - 64 * 1024);
3899
+ const buffer = Buffer.alloc(position - start);
3900
+ const read = await handle.read(buffer, 0, buffer.length, start);
3901
+ const chunk = buffer.subarray(0, read.bytesRead);
3902
+ const newline = chunk.lastIndexOf(10);
3903
+ if (newline >= 0) return { start: start + newline + 1, bytes: Buffer.concat([chunk.subarray(newline + 1), ...chunks]), complete };
3904
+ chunks.unshift(chunk);
3905
+ total += chunk.length;
3906
+ position = start;
3907
+ }
3908
+ if (position === 0) return { start: 0, bytes: Buffer.concat(chunks), complete };
3909
+ return null;
3910
+ }
3911
+ function parseClaudeUserLine(line, sessionId) {
3912
+ const row = parseClaudeJsonObject(line);
3913
+ if (row === null || row.type !== "user" || row.sessionId !== sessionId || typeof row.uuid !== "string" || row.uuid.length === 0 || !isRecord2(row.message)) return null;
3914
+ return typeof row.message.content === "string" ? { uuid: row.uuid, content: row.message.content } : null;
3915
+ }
3916
+ function parseClaudeJsonObject(line) {
3917
+ try {
3918
+ const value = JSON.parse(line);
3919
+ return isRecord2(value) ? value : null;
3920
+ } catch {
3921
+ return null;
3922
+ }
3923
+ }
3924
+ function isRecord2(value) {
2429
3925
  return value !== null && typeof value === "object" && !Array.isArray(value);
2430
3926
  }
2431
- function compact(value) {
2432
- return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
3927
+ function isNotFound(error) {
3928
+ return isRecord2(error) && error.code === "ENOENT";
3929
+ }
3930
+ function initialCursor(registration, now) {
3931
+ return {
3932
+ schema_version: CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION,
3933
+ host_source_ref: registration.host_source_ref,
3934
+ host_session_ref: registration.host_session_ref,
3935
+ transcript_path: registration.transcript_path,
3936
+ offset: 0,
3937
+ anchor_start: 0,
3938
+ anchor_sha256: createHash4("sha256").update(Buffer.alloc(0)).digest("hex"),
3939
+ subagents: {},
3940
+ status: "active",
3941
+ updated_at: timestamp4(now)
3942
+ };
2433
3943
  }
2434
3944
  function timestamp4(now) {
2435
3945
  const value = now();
@@ -2438,134 +3948,144 @@ function timestamp4(now) {
2438
3948
  }
2439
3949
 
2440
3950
  // host/claude/src/claude-producer.ts
2441
- import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile } from "node:fs/promises";
2442
- import { join as join3 } from "node:path";
2443
- var STATE_SCHEMA = "ceal.claude_producer_state.v1";
2444
- var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2445
3951
  var ClaudeProducer = class {
3952
+ producer_id = "claude";
3953
+ producer_label = "Claude";
3954
+ outbox_directory;
2446
3955
  store;
2447
- statePath;
2448
- installation;
2449
- epoch;
2450
- options;
2451
- tail = Promise.resolve();
3956
+ core;
2452
3957
  constructor(options) {
2453
- if (!SAFE_REF4.test(options.producer_installation_ref)) throw new ClaudeHostError("configuration", "Claude producer installation identity is invalid.");
2454
- this.options = options;
2455
- this.epoch = options.producer_epoch ?? 1;
2456
- if (!Number.isSafeInteger(this.epoch) || this.epoch <= 0) throw new ClaudeHostError("configuration", "Claude producer epoch is invalid.");
2457
3958
  this.store = new ClaudeOutboxStore(options.outbox_dir);
2458
- this.statePath = join3(options.outbox_dir, "claude-producer-state.json");
2459
- this.installation = options.producer_installation_ref;
3959
+ this.outbox_directory = this.store.directory;
3960
+ this.core = new HostProducerDrainCore(this, options);
2460
3961
  }
2461
3962
  drainOutbox() {
2462
- const run = this.tail.then(() => this.drainLocked());
2463
- this.tail = run.then(() => void 0, () => void 0);
2464
- return run;
3963
+ return this.core.drainOutbox();
2465
3964
  }
2466
- async drainLocked() {
2467
- const read = await this.store.list();
2468
- let admitted = 0;
2469
- let retained = 0;
2470
- for (const batch of read.entries) {
2471
- const frames = await this.reserve(batch);
2472
- let complete = true;
2473
- for (const frame of frames) {
2474
- const outcome = await this.options.relay.admit(frame);
2475
- if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2476
- complete = false;
2477
- break;
2478
- }
2479
- }
2480
- if (!complete) {
2481
- retained += 1;
2482
- break;
2483
- }
2484
- await this.store.remove(batch);
2485
- await this.release(batch.batch_ref);
2486
- admitted += 1;
2487
- }
2488
- return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - retained), malformed: read.malformed };
3965
+ fail(code2, message) {
3966
+ return new ClaudeHostError(code2, message);
2489
3967
  }
2490
- async reserve(batch) {
2491
- const state = await this.readState();
2492
- const existing = state.pending[batch.batch_ref];
2493
- if (existing !== void 0) return existing;
2494
- 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 }));
2495
- await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [batch.batch_ref]: frames } });
2496
- return frames;
3968
+ listEnvelopes() {
3969
+ return this.store.listEnvelopes();
2497
3970
  }
2498
- async release(ref) {
2499
- const state = await this.readState();
2500
- if (state.pending[ref] === void 0) return;
2501
- const pending = { ...state.pending };
2502
- delete pending[ref];
2503
- await this.writeState({ ...state, pending });
3971
+ removeEnvelope(envelope) {
3972
+ return this.store.removeEnvelope(envelope);
2504
3973
  }
2505
- async readState() {
2506
- try {
2507
- return decodeState(JSON.parse(await readFile2(this.statePath, "utf8")), this.installation, this.epoch);
2508
- } catch (error) {
2509
- if (code2(error) !== "ENOENT") throw error;
2510
- return { schema_version: STATE_SCHEMA, producer_installation_ref: this.installation, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2511
- }
3974
+ submissionRefOf(envelope) {
3975
+ return envelope.submission_ref;
2512
3976
  }
2513
- async writeState(state) {
2514
- await mkdir3(this.store.directory, { recursive: true, mode: 448 });
2515
- const temp = `${this.statePath}.${process.pid}.new`;
2516
- await writeFile(temp, JSON.stringify(state), { mode: 384 });
2517
- await rename2(temp, this.statePath);
3977
+ expiresAtOf(envelope) {
3978
+ return envelope.expires_at;
3979
+ }
3980
+ createdAtOf(envelope) {
3981
+ return envelope.created_at;
3982
+ }
3983
+ async mapEnvelope(envelope) {
3984
+ const prompt = observation(envelope, "prompt");
3985
+ if (prompt !== void 0) return this.mapInput(envelope, prompt);
3986
+ return [mapTerminal(envelope)];
3987
+ }
3988
+ async mapInput(envelope, prompt) {
3989
+ const drafts = [];
3990
+ const session = observation(envelope, "session");
3991
+ if (session !== void 0) {
3992
+ const registrations = await this.store.listTranscriptRegistrations();
3993
+ const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
3994
+ const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
3995
+ drafts.push({
3996
+ frame_kind: "session_started",
3997
+ payload: compact({
3998
+ local_session_ref: envelope.identity.host_session_ref,
3999
+ working_directory_ref: claudeWorkspaceRef(directoryIdentity),
4000
+ model: recordText(session.value, "model"),
4001
+ observations: [session]
4002
+ })
4003
+ });
4004
+ }
4005
+ const turn = observation(envelope, "turn");
4006
+ if (turn !== void 0) drafts.push({
4007
+ frame_kind: "turn_started",
4008
+ payload: compact({
4009
+ local_session_ref: envelope.identity.host_session_ref,
4010
+ local_turn_ref: localTurnRef(envelope),
4011
+ observations: [turn]
4012
+ })
4013
+ });
4014
+ if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
4015
+ drafts.push({
4016
+ frame_kind: "user_input",
4017
+ payload: compact({
4018
+ local_session_ref: envelope.identity.host_session_ref,
4019
+ local_turn_ref: localTurnRef(envelope),
4020
+ local_input_ref: envelope.host_input_ref,
4021
+ input_kind: envelope.input_kind,
4022
+ origin_input: envelope.origin_input,
4023
+ observations: [prompt],
4024
+ context_expires_at: envelope.expires_at
4025
+ })
4026
+ });
4027
+ return drafts;
2518
4028
  }
2519
4029
  };
2520
- function decodeState(value, installation, epoch) {
2521
- if (!validStateHeader(value, installation)) throw new ClaudeHostError("storage", `Claude producer state header is malformed or belongs to another installation (${refusalFacts([
2522
- { name: "schema_version", value: record6(value) ? value.schema_version : void 0, expected: STATE_SCHEMA },
2523
- { name: "producer_installation_ref", value: record6(value) ? value.producer_installation_ref : void 0, expected: installation }
2524
- ])}).`);
2525
- 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}`)}).`);
2526
- const pending = {};
2527
- for (const [ref, frames] of Object.entries(value.pending)) {
2528
- if (!SAFE_REF4.test(ref) || !Array.isArray(frames) || frames.length === 0) throw new ClaudeHostError("storage", "Claude producer reservation is malformed.");
2529
- pending[ref] = frames.map((frame) => decodeFrame(frame, installation, Number(value.producer_epoch)));
2530
- }
2531
- return { schema_version: STATE_SCHEMA, producer_installation_ref: installation, producer_epoch: epoch, next_sequence: Number(value.next_sequence), pending };
2532
- }
2533
- function validStateHeader(value, installation) {
2534
- return record6(value) && value.schema_version === STATE_SCHEMA && value.producer_installation_ref === installation && positiveInteger(value.producer_epoch) && positiveInteger(value.next_sequence) && record6(value.pending);
2535
- }
2536
- function positiveInteger(value) {
2537
- return Number.isSafeInteger(value) && Number(value) > 0;
4030
+ function mapTerminal(envelope) {
4031
+ const usage = envelope.observations.filter((item) => ["token", "cost"].includes(item.observation_kind));
4032
+ return {
4033
+ frame_kind: "turn_terminal",
4034
+ payload: compact({
4035
+ local_session_ref: envelope.identity.host_session_ref,
4036
+ local_turn_ref: localTurnRef(envelope),
4037
+ terminal_state: "completed",
4038
+ usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
4039
+ observations: envelope.observations
4040
+ })
4041
+ };
2538
4042
  }
2539
- function decodeFrame(value, installation, epoch) {
2540
- const json = Buffer.from(JSON.stringify(value));
2541
- const encoded = Buffer.allocUnsafe(json.length + 4);
2542
- encoded.writeUInt32BE(json.length, 0);
2543
- json.copy(encoded, 4);
2544
- const frame = decodeHostProducerFrame(encoded);
2545
- 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}` }])}).`);
2546
- return frame;
4043
+ function observation(envelope, kind) {
4044
+ return envelope.observations.find((item) => item.observation_kind === kind);
2547
4045
  }
2548
- function record6(value) {
2549
- return value !== null && typeof value === "object" && !Array.isArray(value);
4046
+ function localTurnRef(envelope) {
4047
+ return envelope.identity.host_turn_ref;
4048
+ }
4049
+ function compact(value) {
4050
+ return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
2550
4051
  }
2551
- function code2(error) {
2552
- return record6(error) && typeof error.code === "string" ? error.code : null;
4052
+ function asRecord(value) {
4053
+ return isRecord3(value) ? value : void 0;
4054
+ }
4055
+ function recordText(value, key) {
4056
+ const record6 = asRecord(value);
4057
+ return typeof record6?.[key] === "string" ? record6[key] : void 0;
4058
+ }
4059
+ function isRecord3(value) {
4060
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2553
4061
  }
2554
4062
 
2555
4063
  // host/claude/src/claude-producer-runtime.ts
2556
4064
  async function drainClaudeProducerOnce(options) {
2557
- 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();
4065
+ const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
4066
+ const scan = await new ClaudeTranscriptWatcher({
4067
+ outbox_dir: options.outbox_dir,
4068
+ producer_installation_ref: options.producer_installation_ref,
4069
+ register_current_prompt: (input) => promptPort.register(input),
4070
+ release_current_prompt: (identity) => promptPort.release(identity),
4071
+ diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
4072
+ }).scanOnce().catch((error) => {
4073
+ writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
4074
+ throw error;
4075
+ });
4076
+ 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 } });
4077
+ return { scanned: scan.enqueued, ...await producer.drainOutbox() };
2558
4078
  }
2559
4079
 
2560
4080
  // host/claude/src/claude-hook-runner.ts
2561
- import { homedir } from "node:os";
2562
- import { join as join4 } from "node:path";
4081
+ import { homedir as homedir2 } from "node:os";
4082
+ import { join as join6 } from "node:path";
2563
4083
 
2564
4084
  // host/producer-service.ts
2565
4085
  import { posix as posix3, win32 as win323 } from "node:path";
2566
4086
 
2567
4087
  // host/runtime-source-paths.ts
2568
- import { createHash as createHash4 } from "node:crypto";
4088
+ import { createHash as createHash5 } from "node:crypto";
2569
4089
  import { posix as posix2, win32 as win322 } from "node:path";
2570
4090
 
2571
4091
  // host/absolute-path.ts
@@ -2587,7 +4107,7 @@ function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platfor
2587
4107
  const sourcesRoot = path.join(stateRoot, "sources");
2588
4108
  const sourceRoot = path.join(sourcesRoot, sourceId);
2589
4109
  const runtimeRoot = path.join(stateRoot, "run");
2590
- const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash4("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
4110
+ const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash5("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
2591
4111
  assertHostRuntimeIpcLength(endpoint, platform);
2592
4112
  return { sourceId, sourcesRoot, sourceRoot, relayRoot: path.join(sourceRoot, "relay"), codexOutbox: path.join(sourceRoot, "producers", "codex", "outbox"), claudeOutbox: path.join(sourceRoot, "producers", "claude", "outbox"), endpoint, runtimeRoot };
2593
4113
  }
@@ -2603,19 +4123,24 @@ function assertHostProducerSourcePaths(stateRoot, config, producer, platform = p
2603
4123
 
2604
4124
  // host/producer-service.ts
2605
4125
  import { constants } from "node:fs";
2606
- import { lstat as lstat2, open as open3 } from "node:fs/promises";
2607
- var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
4126
+ import { lstat as lstat2, open as open5 } from "node:fs/promises";
4127
+ var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2608
4128
  var HOST_PRODUCER_CONFIG_FLAG = "--ceal-host-config";
2609
4129
  var HostProducerServiceError = class extends Error {
2610
4130
  code;
2611
- constructor(code3, message = code3) {
2612
- super(message === code3 ? code3 : `${code3}: ${message}`);
4131
+ constructor(code2, message = code2) {
4132
+ super(message === code2 ? code2 : `${code2}: ${message}`);
2613
4133
  this.name = "HostProducerServiceError";
2614
- this.code = code3;
4134
+ this.code = code2;
2615
4135
  }
2616
4136
  };
2617
4137
  var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
2618
4138
  var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
4139
+ function hostProducerExtraPath(extras, key) {
4140
+ const value = extras[key];
4141
+ if (value === void 0) throw hostProducerError("invalid_config");
4142
+ return value;
4143
+ }
2619
4144
  function parseHostProducerConfigFlag(args, platform = process.platform) {
2620
4145
  return parseProducerHostConfigFlag(args, HOST_PRODUCER_CONFIG_FLAG, platform, hostProducerError);
2621
4146
  }
@@ -2636,8 +4161,8 @@ function decodeHostProducerConfigText(descriptor, text, platform = process.platf
2636
4161
  const { extra_paths: extras, ...core } = decoded;
2637
4162
  return Object.freeze(descriptor.config({ schema_version: descriptor.schema, ...core }, extras));
2638
4163
  }
2639
- function hostProducerError(code3, message = code3) {
2640
- return new HostProducerServiceError(code3, message);
4164
+ function hostProducerError(code2, message = code2) {
4165
+ return new HostProducerServiceError(code2, message);
2641
4166
  }
2642
4167
  function parseProducerHostConfigFlag(args, flag, platform, error) {
2643
4168
  const prefix = `${flag}=`;
@@ -2664,10 +4189,10 @@ async function readProducerConfigText(configPath, platform, error) {
2664
4189
  }
2665
4190
  function decodeProducerConfigCore(text, options) {
2666
4191
  const value = parseCanonicalJson(text, options.error);
2667
- if (!isRecord2(value) || !hasConfigKeys(value, options.keys) || value.schema_version !== options.schema) throw options.error("invalid_config");
4192
+ if (!isRecord4(value) || !hasConfigKeys(value, options.keys) || value.schema_version !== options.schema) throw options.error("invalid_config");
2668
4193
  const hostSourceRef = typeof value.host_source_ref === "string" ? value.host_source_ref : "";
2669
4194
  const installationRef = typeof value.producer_installation_ref === "string" ? value.producer_installation_ref : "";
2670
- const producerEpoch = validateIdentity2(hostSourceRef, installationRef, value.producer_epoch, options.error);
4195
+ const producerEpoch = validateIdentity3(hostSourceRef, installationRef, value.producer_epoch, options.error);
2671
4196
  assertProducerAbsolutePath(value.outbox_dir, options.platform, "runtime", options.error);
2672
4197
  assertProducerRelayEndpoint(value.relay_endpoint, options.platform, options.error);
2673
4198
  const extraPaths = {};
@@ -2709,7 +4234,7 @@ function validateConfigFile(value, platform, error) {
2709
4234
  }
2710
4235
  async function openConfigFile(configPath, flags, error) {
2711
4236
  try {
2712
- return await open3(configPath, flags);
4237
+ return await open5(configPath, flags);
2713
4238
  } catch {
2714
4239
  throw error("config_unavailable");
2715
4240
  }
@@ -2748,8 +4273,8 @@ function firstDifferenceIndex(left, right) {
2748
4273
  for (let index = 0; index < shared; index += 1) if (left[index] !== right[index]) return index;
2749
4274
  return shared;
2750
4275
  }
2751
- function validateIdentity2(sourceRef, installationRef, epoch, error) {
2752
- if (!SAFE_REF5.test(sourceRef) || !SAFE_REF5.test(installationRef) || !positive2(epoch, 2147483647)) throw error("invalid_config");
4276
+ function validateIdentity3(sourceRef, installationRef, epoch, error) {
4277
+ if (!SAFE_REF4.test(sourceRef) || !SAFE_REF4.test(installationRef) || !positive2(epoch, 2147483647)) throw error("invalid_config");
2753
4278
  return epoch;
2754
4279
  }
2755
4280
  function validateIntervals(reconcile, retry, timeout, error) {
@@ -2759,7 +4284,7 @@ function validateIntervals(reconcile, retry, timeout, error) {
2759
4284
  function hasConfigKeys(value, required) {
2760
4285
  return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || Object.hasOwn(HOST_PRODUCER_TIMING_DEFAULTS, key));
2761
4286
  }
2762
- function isRecord2(value) {
4287
+ function isRecord4(value) {
2763
4288
  return value !== null && typeof value === "object" && !Array.isArray(value);
2764
4289
  }
2765
4290
  function positive2(value, maximum) {
@@ -2771,10 +4296,12 @@ function boundedMilliseconds(value) {
2771
4296
 
2772
4297
  // host/claude/src/claude-producer-service.ts
2773
4298
  var CLAUDE_PRODUCER_CONFIG_SCHEMA = "ceal.claude_producer_config.v1";
4299
+ var CLAUDE_TRANSCRIPTS_ROOT = "claude_transcripts_root";
2774
4300
  var CLAUDE_PRODUCER_DESCRIPTOR = {
2775
4301
  schema: CLAUDE_PRODUCER_CONFIG_SCHEMA,
2776
4302
  producer_id: "claude",
2777
- config: (shared) => shared,
4303
+ extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
4304
+ config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
2778
4305
  runtime_options: (config) => Object.freeze({
2779
4306
  outbox_dir: config.outbox_dir,
2780
4307
  producer_installation_ref: config.producer_installation_ref,
@@ -2794,39 +4321,38 @@ function claudeHookEventFromArgs(args) {
2794
4321
  const value = args.find((arg) => arg.startsWith("--ceal-hook-event="))?.slice("--ceal-hook-event=".length);
2795
4322
  return isClaudeHookEvent(value) ? value : null;
2796
4323
  }
2797
- async function runClaudeHookProcess(args, stdin, options) {
2798
- const event = claudeHookEventFromArgs(args);
2799
- if (event === null) return 0;
4324
+ async function runClaudeHook(event, stdin, options) {
2800
4325
  try {
2801
4326
  const result = await runClaudeHookFromStdin(event, stdin, options);
2802
- if (result.disposition === "rejected") writeClaudeHookDiagnostic(result);
4327
+ if (result.disposition === "rejected") writeHostServiceDiagnostic("claude", "hook_failed", void 0, void 0, options.diagnostic_sink);
4328
+ else if (result.reason === "transcript_registered") writeHostServiceDiagnostic("claude", "registration_written", void 0, void 0, options.diagnostic_sink);
2803
4329
  return 0;
2804
- } catch (error) {
2805
- writeClaudeHookDiagnostic({ event, disposition: "rejected", reason: error instanceof Error ? error.message.slice(0, 128) : "claude_hook_failed" });
4330
+ } catch {
4331
+ writeHostServiceDiagnostic("claude", "hook_failed", void 0, void 0, options.diagnostic_sink);
2806
4332
  return 0;
2807
4333
  }
2808
4334
  }
2809
- function writeClaudeHookDiagnostic(result) {
4335
+ async function runClaudeHookCli() {
4336
+ const args = process.argv.slice(2);
4337
+ const event = claudeHookEventFromArgs(args);
4338
+ if (event === null) return 0;
4339
+ writeHostServiceDiagnostic("claude", "hook_invoked");
2810
4340
  try {
2811
- process.stderr.write(`${JSON.stringify({ schema_version: "ceal.claude_sessionstart_diagnostic.v1", event: result.event, disposition: result.disposition, ...result.reason === void 0 ? {} : { reason: result.reason } })}
2812
- `);
4341
+ const options = hasHostConfigArgument(args) ? await installedClaudeHookRunnerOptions(args) : standaloneClaudeHookRunnerOptions();
4342
+ return runClaudeHook(event, process.stdin, options);
2813
4343
  } catch {
4344
+ writeHostServiceDiagnostic("claude", "hook_failed");
4345
+ return 0;
2814
4346
  }
2815
4347
  }
2816
- async function runClaudeHookCli() {
2817
- const args = process.argv.slice(2);
2818
- const options = hasHostConfigArgument(args) ? await installedClaudeHookRunnerOptions(args) : standaloneClaudeHookRunnerOptions();
2819
- return runClaudeHookProcess(args, process.stdin, options);
2820
- }
2821
4348
  async function installedClaudeHookRunnerOptions(args) {
2822
4349
  const config = await readHostProducerConfig(CLAUDE_PRODUCER_DESCRIPTOR, parseHostProducerConfigFlag(args));
2823
- const port = new HostRelayCurrentPromptPort(config.relay_endpoint, config.ipc_timeout_ms);
2824
- const options = { outbox_dir: config.outbox_dir, producer_installation_ref: config.producer_installation_ref, register_current_prompt: (input) => port.register(input) };
4350
+ const options = { outbox_dir: config.outbox_dir, claude_transcripts_root: config.claude_transcripts_root };
2825
4351
  Object.defineProperty(options, "host_source_ref", { value: config.host_source_ref, enumerable: false });
2826
4352
  return options;
2827
4353
  }
2828
4354
  function standaloneClaudeHookRunnerOptions() {
2829
- return { outbox_dir: process.env.CEAL_CLAUDE_OUTBOX_DIR ?? join4(homedir(), ".claude", ".ceal", "claude-observation-outbox") };
4355
+ return { outbox_dir: process.env.CEAL_CLAUDE_OUTBOX_DIR ?? join6(homedir2(), ".claude", ".ceal", "claude-observation-outbox"), claude_transcripts_root: process.env.CLAUDE_CONFIG_DIR !== void 0 ? join6(process.env.CLAUDE_CONFIG_DIR, "projects") : join6(homedir2(), ".claude", "projects") };
2830
4356
  }
2831
4357
  function hasHostConfigArgument(args) {
2832
4358
  return args.some((argument) => argument === HOST_PRODUCER_CONFIG_FLAG || argument.startsWith(`${HOST_PRODUCER_CONFIG_FLAG}=`));
@@ -2858,6 +4384,7 @@ var DOCTOR_FLAGS = Object.freeze({
2858
4384
  var execFileAsync = promisify(execFile);
2859
4385
 
2860
4386
  // host/claude/src/claude-installer.ts
4387
+ var ACTIVE_CLAUDE_HOOK_EVENTS = new Set(CLAUDE_HOOK_EVENTS);
2861
4388
  var CLAUDE_LOCAL_CAPABILITY_ACTIVATION = Object.freeze({ environment: "CLAUDE_CODE_ENTRYPOINT", value: "cli" });
2862
4389
  var CLAUDE_CLIENT_ALLOW_RULE = `Bash(${CEAL_SESSION_ENTRY_INVOCATION}:*)`;
2863
4390