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

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