@corca-org/ceal-host-win32-x64 1.0.19 → 1.0.21
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.
- package/bin/ceal-host.exe +0 -0
- package/libexec/ceal-host/claude-hook.mjs +1327 -1211
- package/libexec/ceal-host/claude-invocation-authentication.mjs +1986 -1272
- package/libexec/ceal-host/claude-producer.mjs +1376 -1313
- package/libexec/ceal-host/codex-hook.mjs +1215 -1392
- package/libexec/ceal-host/codex-invocation-authentication.mjs +1924 -1364
- package/libexec/ceal-host/codex-producer.mjs +1222 -1286
- package/libexec/ceal-host/control.mjs +2818 -2076
- package/libexec/ceal-host/relay.mjs +1733 -1811
- package/package.json +1 -1
- package/share/ceal-host/bundle-manifest.json +12 -12
- package/share/ceal-host/bundle-manifest.sigstore.json +1 -1
|
@@ -33,7 +33,7 @@ function isClaudeHookEvent(value) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// host/claude/src/claude-decoder.ts
|
|
36
|
-
import { createHash as
|
|
36
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
37
37
|
|
|
38
38
|
// packages/ceal-client-protocol/src/canonical-json.ts
|
|
39
39
|
function cealCanonicalJson(value) {
|
|
@@ -62,16 +62,34 @@ function isPlainRecord(value) {
|
|
|
62
62
|
return prototype === Object.prototype || prototype === null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
66
|
+
import { createHash } from "node:crypto";
|
|
67
|
+
|
|
65
68
|
// packages/ceal-client-protocol/client-wire-contract.json
|
|
66
69
|
var client_wire_contract_default = {
|
|
67
70
|
schema_version: "ceal.client_wire_contract.v1",
|
|
71
|
+
doctor: {
|
|
72
|
+
schema_version: "ceal.doctor.v1"
|
|
73
|
+
},
|
|
74
|
+
client_commands: {
|
|
75
|
+
doctor: ["doctor"],
|
|
76
|
+
version: ["version"],
|
|
77
|
+
access_group_list: ["access-group", "list"]
|
|
78
|
+
},
|
|
79
|
+
client_environment: {
|
|
80
|
+
environment_prefix: "CEAL",
|
|
81
|
+
context_override_suffixes: ["GATEWAY_ORIGIN", "GATEWAY_TRANSPORT_ORIGIN", "ACCESS_TOKEN", "CONTEXT_STORE_PATH"]
|
|
82
|
+
},
|
|
68
83
|
canonical_json: {
|
|
69
84
|
ordering: "utf16_code_unit_ascending"
|
|
70
85
|
},
|
|
71
86
|
artifact_descriptor: {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
operation_contract_bundle_schema_name: "ceal.operation_contract_bundle",
|
|
88
|
+
operation_contract_bundle_fields: ["schema_version", "catalog_revision", "operations", "failure_guidance"],
|
|
89
|
+
schema_name: "ceal.verified_artifact_descriptor",
|
|
90
|
+
fields: ["schema_version", "kind", "media_type", "byte_count", "sha256", "binding"],
|
|
91
|
+
binding_schema_name: "ceal.verified_artifact_binding",
|
|
92
|
+
binding_fields: ["schema_version", "kind", "catalog_revision"],
|
|
75
93
|
kind: "verified_artifact",
|
|
76
94
|
binding_kind: "catalog",
|
|
77
95
|
media_type: "application/json",
|
|
@@ -87,14 +105,16 @@ var client_wire_contract_default = {
|
|
|
87
105
|
length: 72,
|
|
88
106
|
pattern: "^catalog:[a-f0-9]{64}$"
|
|
89
107
|
},
|
|
90
|
-
envelope_max_bytes:
|
|
108
|
+
envelope_max_bytes: 407
|
|
91
109
|
},
|
|
92
110
|
command_request: {
|
|
93
|
-
|
|
111
|
+
schema_name: "ceal.command_request",
|
|
112
|
+
fields: ["schema_version", "argv", "operation", "client_nonce", "context_token"],
|
|
94
113
|
absolute_transport_max_bytes: 65536
|
|
95
114
|
},
|
|
96
115
|
command_resolution: {
|
|
97
|
-
|
|
116
|
+
schema_name: "ceal.gateway_command_resolution",
|
|
117
|
+
fields: ["schema_version", "kind", "ok", "execution_started", "program", "route", "authority", "routes", "operations", "required_action_sequence", "semantic_options", "result_delivery", "operation", "invocation", "entrypoints", "common_concepts", "withheld", "catalog_revision", "operation_id", "input", "transport", "output", "unresolved_inputs", "unresolved_refusal", "attempted_route", "did_you_mean", "error"],
|
|
98
118
|
authority: {
|
|
99
119
|
anonymous: {
|
|
100
120
|
state: "anonymous",
|
|
@@ -134,7 +154,8 @@ var client_wire_contract_default = {
|
|
|
134
154
|
}
|
|
135
155
|
},
|
|
136
156
|
command_response: {
|
|
137
|
-
|
|
157
|
+
schema_name: "ceal.command_response",
|
|
158
|
+
fields: ["schema_version", "exit_code", "document", "operation_id", "visible_output_max_bytes", "local_delivery", "verified_artifact"],
|
|
138
159
|
descriptor_field: "verified_artifact",
|
|
139
160
|
required_fields: [
|
|
140
161
|
"schema_version",
|
|
@@ -158,29 +179,41 @@ var client_wire_contract_default = {
|
|
|
158
179
|
"required_action_sequence",
|
|
159
180
|
"withheld"
|
|
160
181
|
],
|
|
161
|
-
prompt_field: "prompt",
|
|
162
|
-
prompt_schema_version: "ceal.prompt.v1",
|
|
163
|
-
focused_prompt_document_fields: [
|
|
164
|
-
"catalog_revision",
|
|
165
|
-
"prompt"
|
|
166
|
-
],
|
|
167
|
-
focused_prompt_continuation_document_fields: [
|
|
168
|
-
"catalog_revision",
|
|
169
|
-
"remaining_work",
|
|
170
|
-
"prompt"
|
|
171
|
-
],
|
|
172
182
|
discovery_command: "discovery",
|
|
173
|
-
|
|
183
|
+
discovery_manifest_schema_name: "ceal.discovery_manifest",
|
|
184
|
+
discovery_manifest_fields: ["schema_version", "skill", "authority_scope", "connector_kinds", "personal_connector_kinds", "guide_markdown", "renderer_version", "digest"],
|
|
174
185
|
discovery_manifest_document_fields: [
|
|
175
186
|
"discovery_manifest"
|
|
176
187
|
],
|
|
177
188
|
exit_code_max: 125,
|
|
178
189
|
bare_document_max_bytes: 65536,
|
|
179
|
-
complete_max_bytes:
|
|
190
|
+
complete_max_bytes: 66046
|
|
191
|
+
},
|
|
192
|
+
host_invocation_authentication: {
|
|
193
|
+
request_schema_name: "ceal.host_invocation_authentication_request",
|
|
194
|
+
request_fields: [
|
|
195
|
+
"schema_version",
|
|
196
|
+
"client_nonce"
|
|
197
|
+
],
|
|
198
|
+
result_schema_name: "ceal.host_invocation_authentication_result",
|
|
199
|
+
result_fields: [
|
|
200
|
+
"schema_version",
|
|
201
|
+
"context_token",
|
|
202
|
+
"expires_at"
|
|
203
|
+
],
|
|
204
|
+
client_nonce: {
|
|
205
|
+
character_class_source: "A-Za-z0-9_-",
|
|
206
|
+
encoded_characters: 43
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
gateway_host_invocation_context_request: {
|
|
210
|
+
schema_name: "ceal.gateway_host_invocation_context_request",
|
|
211
|
+
fields: ["schema_version", "client_nonce", "context_expires_at", "request_work_context", "current_prompt", "visible_output_max_bytes"]
|
|
180
212
|
},
|
|
181
213
|
resource_stream: {
|
|
182
214
|
path: "/api/ceal/v2/resources",
|
|
183
|
-
|
|
215
|
+
request_schema_name: "ceal.resource_stream_request",
|
|
216
|
+
request_fields: ["schema_version", "resource_ref", "client_nonce", "context_token"],
|
|
184
217
|
ref_pattern: "^resource:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
185
218
|
media_type_pattern: "^[a-z0-9][a-z0-9!#$&^_.+-]{0,63}/[a-z0-9][a-z0-9!#$&^_.+-]{0,63}$",
|
|
186
219
|
filename_max_bytes: 512,
|
|
@@ -197,88 +230,6 @@ var client_wire_contract_default = {
|
|
|
197
230
|
}
|
|
198
231
|
};
|
|
199
232
|
|
|
200
|
-
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
201
|
-
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
202
|
-
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
203
|
-
var commandResolutionAuthority = commandResolution.authority;
|
|
204
|
-
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
205
|
-
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = commandResolution.schema_version;
|
|
206
|
-
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
207
|
-
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
208
|
-
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
209
|
-
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
210
|
-
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = artifact.operation_contract_bundle_schema_version;
|
|
211
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = artifact.schema_version;
|
|
212
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = artifact.binding_schema_version;
|
|
213
|
-
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
214
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
215
|
-
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
216
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
217
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
218
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
219
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
220
|
-
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
221
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
222
|
-
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = request.schema_version;
|
|
223
|
-
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
224
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
225
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_FIELD = response.prompt_field;
|
|
226
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_SCHEMA_VERSION = response.prompt_schema_version;
|
|
227
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
228
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.bare_document_fields, response.prompt_field]);
|
|
229
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_document_fields]);
|
|
230
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_CONTINUATION_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_continuation_document_fields]);
|
|
231
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
232
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = response.discovery_manifest_schema_version;
|
|
233
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
234
|
-
|
|
235
|
-
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
236
|
-
function vocabulary(words) {
|
|
237
|
-
return Object.freeze(words);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
241
|
-
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
242
|
-
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
243
|
-
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
244
|
-
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
245
|
-
var CEAL_GATEWAY_READ_REUSE_DISPOSITIONS = vocabulary(["response_cache_hit", "corpus_hit", "corpus_hit_after_revalidation", "corpus_partial_provider_supplement", "provider_direct", "provider_continuation", "reuse_unavailable"]);
|
|
246
|
-
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
247
|
-
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
248
|
-
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
249
|
-
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
250
|
-
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
251
|
-
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
252
|
-
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
253
|
-
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
254
|
-
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
255
|
-
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
256
|
-
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
257
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
258
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
259
|
-
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
260
|
-
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
261
|
-
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
262
|
-
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
263
|
-
"audited_call_pending",
|
|
264
|
-
"continuation_read_pending",
|
|
265
|
-
"continuation_write_pending",
|
|
266
|
-
"provider_pacing_pending",
|
|
267
|
-
"corpus_replay_pending",
|
|
268
|
-
"corpus_append_pending",
|
|
269
|
-
"corpus_fence_pending",
|
|
270
|
-
"corpus_content_pending",
|
|
271
|
-
"corpus_transaction_pending"
|
|
272
|
-
]);
|
|
273
|
-
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
274
|
-
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
275
|
-
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
276
|
-
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
277
|
-
"connector_ref",
|
|
278
|
-
"connector_revision",
|
|
279
|
-
"policy_decision_revision"
|
|
280
|
-
]);
|
|
281
|
-
|
|
282
233
|
// packages/ceal-client-protocol/src/operation-next-actions.ts
|
|
283
234
|
var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
|
|
284
235
|
var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
|
|
@@ -345,7 +296,10 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
|
|
|
345
296
|
"follow_required_action_sequence",
|
|
346
297
|
// The command needs caller-owned local materialization (for example an
|
|
347
298
|
// @path file) before the Gateway can receive the argument.
|
|
348
|
-
"materialize_local_argument"
|
|
299
|
+
"materialize_local_argument",
|
|
300
|
+
// A peer's derived schema identifier changed with its field set; install or
|
|
301
|
+
// upgrade the CLI and Host Relay to the Gateway's release before retrying.
|
|
302
|
+
"upgrade_ceal_client"
|
|
349
303
|
];
|
|
350
304
|
var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
|
|
351
305
|
var NON_REMEDY_NEXT_ACTION_VALUES = [
|
|
@@ -458,47 +412,53 @@ var CEAL_OPERATION_WITHHELD_NEXT_ACTIONS = Object.freeze({
|
|
|
458
412
|
personal_authorization_required: "connect_personal_account"
|
|
459
413
|
});
|
|
460
414
|
|
|
461
|
-
// packages/ceal-client-protocol/src/
|
|
462
|
-
|
|
415
|
+
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
416
|
+
function vocabulary(words) {
|
|
417
|
+
return Object.freeze(words);
|
|
418
|
+
}
|
|
463
419
|
|
|
464
|
-
// packages/ceal-client-protocol/src/
|
|
465
|
-
var
|
|
466
|
-
var
|
|
467
|
-
var
|
|
468
|
-
var
|
|
469
|
-
var
|
|
470
|
-
var
|
|
471
|
-
var
|
|
472
|
-
var
|
|
473
|
-
var
|
|
474
|
-
var
|
|
475
|
-
var
|
|
476
|
-
var
|
|
477
|
-
var
|
|
478
|
-
var
|
|
479
|
-
var
|
|
480
|
-
var
|
|
481
|
-
var
|
|
482
|
-
var
|
|
483
|
-
var
|
|
484
|
-
var
|
|
485
|
-
var
|
|
486
|
-
var
|
|
487
|
-
var
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
"
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
420
|
+
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
421
|
+
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
422
|
+
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
423
|
+
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
424
|
+
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
425
|
+
var CEAL_GATEWAY_READ_REUSE_DISPOSITIONS = vocabulary(["response_cache_hit", "corpus_hit", "corpus_hit_after_revalidation", "corpus_partial_provider_supplement", "provider_direct", "provider_continuation", "reuse_unavailable"]);
|
|
426
|
+
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
427
|
+
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
428
|
+
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
429
|
+
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
430
|
+
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
431
|
+
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
432
|
+
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
433
|
+
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
434
|
+
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
435
|
+
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
436
|
+
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
437
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
438
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
439
|
+
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
440
|
+
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
441
|
+
var CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION = "ceal.gateway_call_result.v1";
|
|
442
|
+
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
443
|
+
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
444
|
+
"audited_call_pending",
|
|
445
|
+
"continuation_read_pending",
|
|
446
|
+
"continuation_write_pending",
|
|
447
|
+
"provider_pacing_pending",
|
|
448
|
+
"corpus_replay_pending",
|
|
449
|
+
"corpus_append_pending",
|
|
450
|
+
"corpus_fence_pending",
|
|
451
|
+
"corpus_content_pending",
|
|
452
|
+
"corpus_transaction_pending"
|
|
453
|
+
]);
|
|
454
|
+
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
455
|
+
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
456
|
+
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
457
|
+
var CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION = "ceal.gateway_audit_call_detail.v1";
|
|
458
|
+
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
459
|
+
"connector_ref",
|
|
460
|
+
"connector_revision",
|
|
461
|
+
"policy_decision_revision"
|
|
502
462
|
]);
|
|
503
463
|
|
|
504
464
|
// packages/ceal-client-protocol/src/gateway-validation-primitives.ts
|
|
@@ -540,10 +500,10 @@ function assertSafeJsonArray(value, options, depth, count) {
|
|
|
540
500
|
if (value.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
|
|
541
501
|
for (const item of value) assertSafeJsonValue(item, options, depth + 1, count);
|
|
542
502
|
}
|
|
543
|
-
function assertSafeJsonRecord(
|
|
544
|
-
const entries = Object.entries(
|
|
503
|
+
function assertSafeJsonRecord(record4, options, depth, count) {
|
|
504
|
+
const entries = Object.entries(record4);
|
|
545
505
|
if (entries.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
|
|
546
|
-
const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(
|
|
506
|
+
const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record4.fields) : null;
|
|
547
507
|
for (const [key, child] of entries) {
|
|
548
508
|
if (key === "credential_material_included" && child !== false) invalidByContext(options);
|
|
549
509
|
if (!isSafeNegativeMaterialAssertion(key, child)) assertSafeJsonKey(key, options);
|
|
@@ -712,51 +672,6 @@ function cut(text) {
|
|
|
712
672
|
return text.length <= REFUSAL_VALUE_MAX_CHARS ? text : `${text.slice(0, REFUSAL_VALUE_MAX_CHARS)}...(+${text.length - REFUSAL_VALUE_MAX_CHARS} chars)`;
|
|
713
673
|
}
|
|
714
674
|
|
|
715
|
-
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
716
|
-
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
717
|
-
|
|
718
|
-
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
719
|
-
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
720
|
-
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
721
|
-
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
722
|
-
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
723
|
-
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
724
|
-
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
725
|
-
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
726
|
-
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
727
|
-
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
728
|
-
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
729
|
-
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
730
|
-
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
731
|
-
|
|
732
|
-
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
733
|
-
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
734
|
-
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
735
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
736
|
-
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
737
|
-
}
|
|
738
|
-
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
742
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
743
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
744
|
-
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
745
|
-
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
746
|
-
max_depth: 8,
|
|
747
|
-
max_properties: 96,
|
|
748
|
-
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
749
|
-
max_alternatives: 8,
|
|
750
|
-
max_enum_values: 64,
|
|
751
|
-
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
752
|
-
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
753
|
-
max_description_characters: 1024,
|
|
754
|
-
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
755
|
-
max_pattern_characters: 256,
|
|
756
|
-
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
757
|
-
max_literal_nodes: 512
|
|
758
|
-
});
|
|
759
|
-
|
|
760
675
|
// packages/ceal-client-protocol/src/operation-validation-primitives.ts
|
|
761
676
|
var ISO_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?([Zz]|([+-])(\d{2}):(\d{2}))$/u;
|
|
762
677
|
var CEAL_CALENDAR_DATE_PATTERN_SOURCE = "^\\d{4}-\\d{2}-\\d{2}$";
|
|
@@ -824,6 +739,317 @@ function requireIsoTimestamp(value, message) {
|
|
|
824
739
|
if (!isCealIsoTimestamp(value)) throw new TypeError(message);
|
|
825
740
|
}
|
|
826
741
|
|
|
742
|
+
// packages/ceal-client-protocol/src/failure-guidance-table.ts
|
|
743
|
+
var CEAL_FAILURE_REDACTION_CLASSES = vocabulary(["model_safe", "withheld"]);
|
|
744
|
+
var CEAL_GATEWAY_STARTING_FAILURE_CODE = "gateway_starting";
|
|
745
|
+
var CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE = "bootstrap_absent";
|
|
746
|
+
var CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE = "bootstrap_unavailable";
|
|
747
|
+
var LOCAL_COMMAND_INVALID = "The local command or argument is invalid.";
|
|
748
|
+
var LOCAL_TRANSPORT_INCOMPLETE = "The local Ceal transport could not complete; report this as a gap.";
|
|
749
|
+
var GATEWAY_TRANSPORT_UNAVAILABLE = "The Gateway transport is unavailable; ask an administrator to restore Gateway availability.";
|
|
750
|
+
var HOST_AUTHENTICATION_UNAVAILABLE = "The Host could not authenticate this invocation; report this as a gap.";
|
|
751
|
+
var SESSION_CONTEXT_UNAVAILABLE = "The local Gateway session context is unavailable.";
|
|
752
|
+
function guidance(message, next_action, http_status, retryable = false, redaction_class = "model_safe") {
|
|
753
|
+
return Object.freeze({ message, next_action, http_status, retryable, redaction_class });
|
|
754
|
+
}
|
|
755
|
+
var CEAL_FAILURE_GUIDANCE_TABLE = Object.freeze({
|
|
756
|
+
invalid_command: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
757
|
+
invalid_argument: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
758
|
+
command_not_found: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 404),
|
|
759
|
+
unknown_operation: guidance("The requested Operation is absent from the current verified catalog; report this as a gap unless a catalog suggestion names a replacement.", "answer_with_gap", 404),
|
|
760
|
+
local_route_unavailable: guidance("The requested local route is unavailable or its descriptor is invalid; report this as a gap.", "answer_with_gap", 404),
|
|
761
|
+
local_route_ambiguous: guidance("The requested local route is ambiguous; no local executable was selected. Report this as a gap.", "answer_with_gap", 400),
|
|
762
|
+
local_execution_unavailable: guidance("The selected local route executable is unavailable; report this as a gap.", "answer_with_gap", 500),
|
|
763
|
+
local_pickup_failed: guidance("The Host Request pickup failed; the Gateway command was not sent. Report this as a gap.", "answer_with_gap", 502),
|
|
764
|
+
invocation_authentication_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
765
|
+
local_execution_cancellation_unknown: guidance("The selected local route cancellation outcome is unknown; a retry can repeat an effect the cancelled route already completed. Report this as a gap and do not retry.", "answer_with_gap", 409),
|
|
766
|
+
gateway_origin_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
767
|
+
access_token_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
768
|
+
context_pair_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
769
|
+
context_not_selected: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
770
|
+
context_store_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
771
|
+
context_store_conflict: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
772
|
+
refresh_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
773
|
+
refresh_expired: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
774
|
+
refresh_replayed: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
775
|
+
refresh_revoked: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
776
|
+
refresh_recovery_unavailable: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
777
|
+
refresh_custody_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
778
|
+
session_store_unwritable: guidance("The Ceal client could not write its local session store; the operating system refused the write (EACCES, EPERM, or EROFS). rerun_outside_agent_sandbox reruns it with escalated execution.", "rerun_outside_agent_sandbox", 500),
|
|
779
|
+
local_output_exists: guidance("The selected local output path already exists; nothing was written to it.", "choose_new_output_path", 400),
|
|
780
|
+
local_output_outside_result_root: guidance("The selected local output path lies outside this run's result root; only a path inside the result root named here is writable.", "choose_new_output_path", 400),
|
|
781
|
+
local_output_unwritable: guidance("The Ceal client could not write the result file at its local path; the operating system refused the write. When that step followed the Gateway call, the call may already have completed a write effect that a rerun repeats. rerun_outside_agent_sandbox reruns it with escalated execution.", "rerun_outside_agent_sandbox", 500),
|
|
782
|
+
local_output_unavailable: guidance("The Gateway result could not be materialized at the selected local path; report this as a gap.", "answer_with_gap", 500),
|
|
783
|
+
local_materialization_required: guidance("The Gateway result could not be materialized at the selected local path; report this as a gap.", "answer_with_gap", 500),
|
|
784
|
+
contract_artifact_cache_invalid: guidance("The verified contract artifact could not be stored in the private local cache; report this as a gap.", "answer_with_gap", 500),
|
|
785
|
+
response_encoding_failed: guidance("The local response could not be encoded; report this as a gap.", "answer_with_gap", 500),
|
|
786
|
+
request_timeout: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 504),
|
|
787
|
+
request_failed: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 502),
|
|
788
|
+
gateway_unavailable: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 503, true),
|
|
789
|
+
contract_mismatch: guidance("Install or upgrade the Ceal CLI and Host Relay to the same release as the Gateway, then retry.", "upgrade_ceal_client", 409),
|
|
790
|
+
// `gateway_starting` is the one retryable envelope the serving gate answers on every route while starting
|
|
791
|
+
// (`cli/internal/client/gateway_startup_wait.go` re-drives it); the sentence is the client default that renders
|
|
792
|
+
// when the wait ends still unserved, moved from the `localFailureText` default branch.
|
|
793
|
+
[CEAL_GATEWAY_STARTING_FAILURE_CODE]: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503, true),
|
|
794
|
+
gateway_internal_error: guidance("The Gateway failed without exposing internal state; report this as a gap.", "answer_with_gap", 500),
|
|
795
|
+
// The serving gate emits this code with no message; the sentence is the owner-normalized form of the
|
|
796
|
+
// `gateway_internal_error` guidance (decided with the operator 2026-10-13).
|
|
797
|
+
internal_error: guidance("The Gateway failed without exposing internal state.", "answer_with_gap", 500),
|
|
798
|
+
// Owned by the Gateway canonical adapter (`gateway/core/src/gateway-canonical-operation-http-adapter.ts`):
|
|
799
|
+
// the client variant of this sentence in `localFailureText` was retired when the client started reading this table.
|
|
800
|
+
request_body_too_large: guidance("The Gateway request body exceeds its bounded limit.", "reduce_request_size", 413),
|
|
801
|
+
// Restored verbatim from the retired `localFailureText` `unknown_outcome`
|
|
802
|
+
// case: the outcome is unknown, so a read may be retried while a write
|
|
803
|
+
// must receipt-check first. Collapsing both to "do not repeat the call"
|
|
804
|
+
// forbids the safe read retry the sentence exists to allow.
|
|
805
|
+
unknown_outcome: guidance("The transport outcome is unknown: a read may be retried; for a write, check write_receipt.get before repeating.", "answer_with_gap", 500),
|
|
806
|
+
// Restored verbatim from the retired `localFailureText`
|
|
807
|
+
// `refresh_unknown_outcome` case: a refresh that ends unknown leaves the
|
|
808
|
+
// session context unavailable, so the caller repairs or adopts the
|
|
809
|
+
// session instead of reporting a transport gap.
|
|
810
|
+
refresh_unknown_outcome: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
811
|
+
request_cancelled: guidance("The caller cancelled the request before it completed.", "retry", 400, true),
|
|
812
|
+
expired: guidance("The activation transaction or its administrator approval request expired before completion; repeat the original activation arguments to start a new transaction.", "repeat_original_arguments", 410, true),
|
|
813
|
+
denied: guidance("An administrator denied this device's activation request; ask an administrator whether to reconsider it.", "ask_admin", 403),
|
|
814
|
+
device_approval_pending_limit: guidance("Device approval requests are already waiting for an administrator decision; ask an administrator to decide a pending request before adding another device.", "ask_admin", 429),
|
|
815
|
+
session_already_adopted: guidance("The requested enrollment was not started because this machine already has a live session with the Gateway. Report this as a gap if that local session needs replacement.", "answer_with_gap", 409),
|
|
816
|
+
pending_adoption_limit: guidance("This device's pending activation file is full; another activation is refused while an administrator decision is pending. Report this as a gap.", "answer_with_gap", 429),
|
|
817
|
+
recovery_required: guidance("The Gateway could not safely complete the activation transaction automatically; ask an administrator to recover this transaction.", "ask_admin", 500),
|
|
818
|
+
unsupported_feature: guidance("This Ceal client does not support the Gateway's required activation protocol; ask an administrator to update the client.", "ask_admin", 501),
|
|
819
|
+
// Activation commit codes rendered through the `localFailureText` default before phase 2b; `retryable` is moved from the
|
|
820
|
+
// emitting sites in `cli/internal/client/gateway_installation_activation.go` and `activation.go:483`.
|
|
821
|
+
activation_state_commit_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500, true),
|
|
822
|
+
activation_response_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502, true),
|
|
823
|
+
activation_rejected: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
824
|
+
activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
825
|
+
activation_binding: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
826
|
+
activation_crypto: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
827
|
+
activation_state_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
828
|
+
// Long-tail local codes rendered through the `localFailureText` default before phase 2b; sentences moved from that branch.
|
|
829
|
+
invalid_response: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
830
|
+
invalid_configuration: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
831
|
+
activation_state_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
832
|
+
activation_code_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
833
|
+
activation_code_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
834
|
+
activation_code_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 404),
|
|
835
|
+
bootstrap_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
836
|
+
bootstrap_not_active: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
837
|
+
bootstrap_unauthorized: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
838
|
+
// The Gateway owner-bootstrap store file was never created, so the Gateway
|
|
839
|
+
// holds no owner bootstrap to exchange (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
840
|
+
// `absent`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:162`);
|
|
841
|
+
// the sentence is the owner-normalized form of that store error. Non-retryable:
|
|
842
|
+
// repeating the exchange cannot create the store; only an administrator
|
|
843
|
+
// placing a bootstrap (fresh gateway setup) changes the outcome.
|
|
844
|
+
[CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE]: guidance("The Gateway holds no owner bootstrap to exchange; report this as a gap.", "answer_with_gap", 503),
|
|
845
|
+
// The Gateway owner-bootstrap store file exists but cannot be read, so the
|
|
846
|
+
// exchange cannot proceed (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
847
|
+
// `unavailable`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:163`);
|
|
848
|
+
// the sentence is the owner-normalized form of that store error. Retryable:
|
|
849
|
+
// unlike `absent`, a later exchange may succeed once the store is readable.
|
|
850
|
+
[CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE]: guidance("The Gateway owner bootstrap store is unavailable; retry the exchange.", "retry", 503, true),
|
|
851
|
+
context_selection_ambiguous: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
852
|
+
session_authority_unverified: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
853
|
+
session_authority_mismatch: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
854
|
+
local_credential_lost: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
855
|
+
local_capability_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
856
|
+
host_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
857
|
+
host_activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
858
|
+
doctor_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
859
|
+
admin_sign_in_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
860
|
+
// `invalid_request` renders through the client default today (local encode/path faults); Gateway-answered
|
|
861
|
+
// envelopes carry the Gateway message verbatim and never read this sentence.
|
|
862
|
+
invalid_request: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
863
|
+
// Gateway canonical-route codes, sentences moved from their minting adapters; statuses moved from the
|
|
864
|
+
// `valid*Failure` decoder maps in `cli/internal/client/gateway_api.go` and `transport.go`.
|
|
865
|
+
invalid_json: guidance("The Gateway request body must be valid JSON.", "answer_with_gap", 400),
|
|
866
|
+
// Per-route variants name their route; the table holds the one stable guidance and the route's `allow`
|
|
867
|
+
// header stays the measured fact (decided with the operator 2026-10-13).
|
|
868
|
+
method_not_allowed: guidance("The HTTP method is not allowed for this Gateway route.", "answer_with_gap", 405),
|
|
869
|
+
request_target_too_large: guidance("The Gateway request target exceeds its bounded limit.", "reduce_request_size", 414),
|
|
870
|
+
route_not_found: guidance("The Gateway route is not present.", "answer_with_gap", 404),
|
|
871
|
+
artifact_unavailable: guidance("The Gateway artifact is unavailable.", "answer_with_gap", 503),
|
|
872
|
+
discovery_unavailable: guidance("The Gateway discovery manifest is unavailable.", "answer_with_gap", 503),
|
|
873
|
+
serialization_failed: guidance("The Gateway response could not be serialized.", "answer_with_gap", 500),
|
|
874
|
+
request_header_too_large: guidance("A consumed Gateway request header exceeds its bounded limit.", "answer_with_gap", 431),
|
|
875
|
+
request_work_context_missing: guidance("The Gateway command requires a Request work context.", "answer_with_gap", 400),
|
|
876
|
+
request_work_context_invalid: guidance("The Gateway Request work context encoding is invalid.", "answer_with_gap", 400),
|
|
877
|
+
request_work_context_refused: guidance("The Host-only discovery manifest takes no Request work context.", "answer_with_gap", 400),
|
|
878
|
+
// Moved from `gateway/src/operations/http-composition.ts` (`unauthorized`) and `command-runtime.ts:827`
|
|
879
|
+
// (`artifact_stale`); both travel with Gateway messages verbatim online, so these sentences are the
|
|
880
|
+
// owner fallback guidance. `unauthorized` re-authenticates through the session, hence its action.
|
|
881
|
+
unauthorized: guidance("Unauthorized.", "repair_or_adopt_session", 401),
|
|
882
|
+
artifact_stale: guidance("The requested contract artifact is not the current catalog artifact.", "answer_with_gap", 409),
|
|
883
|
+
// Host mint classifications. The CLI emits `invocation_authentication_unavailable` with the classification
|
|
884
|
+
// in its detail fact; rows without their own sentence share that code's moved sentence, while `next_action`
|
|
885
|
+
// is moved from `canonicalHostAuthenticationNextActions` (generated from `CEAL_HOST_AUTHENTICATION_POLICY`)
|
|
886
|
+
// and `retryable` from `hostAuthenticationFailureForCause` (`failure.nextAction == "retry"`). The three rows
|
|
887
|
+
// with their own sentences carry the retired policy remedies as Gateway-owned guidance. Statuses are the
|
|
888
|
+
// Gateway-owned mapping decided with the operator 2026-10-13. This covers every wire cause the maps at
|
|
889
|
+
// `host/invocation-authentication-command.ts:217-242` emit, through their policy classifications.
|
|
890
|
+
host_arguments_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 400),
|
|
891
|
+
host_configuration_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
892
|
+
host_session_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
893
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
894
|
+
// missing exact-turn fact names the fresh Host turn, not a reinstall.
|
|
895
|
+
host_context_unavailable: guidance("The Host has not activated an invocation context for this session yet. Wait for the Host turn hook to register the session, then retry this command.", "retry", 401, true),
|
|
896
|
+
host_invocation_context_stale: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
897
|
+
host_invocation_context_expired: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
898
|
+
host_source_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 403),
|
|
899
|
+
host_registration_credential_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 403),
|
|
900
|
+
host_mint_gateway_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 503),
|
|
901
|
+
host_mint_gateway_rate_limited: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 429),
|
|
902
|
+
host_mint_gateway_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
903
|
+
host_mint_gateway_response_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
904
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
905
|
+
// producer-frame fault names the administrator repair step.
|
|
906
|
+
host_producer_frame_invalid: guidance("Ask an administrator to inspect and repair the producer frame, then start a fresh Host turn.", "ask_admin", 400),
|
|
907
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
908
|
+
// sandboxed Relay socket names the run-alone retry.
|
|
909
|
+
host_relay_permission_denied: guidance("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.", "rerun_outside_agent_sandbox", 403),
|
|
910
|
+
host_relay_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 503),
|
|
911
|
+
host_mint_transient_retryable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 503, true),
|
|
912
|
+
host_mint_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
913
|
+
host_relay_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
914
|
+
host_refusal_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500)
|
|
915
|
+
});
|
|
916
|
+
var FAILURE_GUIDANCE_ENTRY_KEYS = Object.freeze(["message", "next_action", "http_status", "retryable", "redaction_class"]);
|
|
917
|
+
|
|
918
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
919
|
+
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
920
|
+
var CEAL_CLIENT_DOCTOR_SCHEMA_VERSION = CEAL_CLIENT_WIRE_CONTRACT.doctor.schema_version;
|
|
921
|
+
var clientCommands = CEAL_CLIENT_WIRE_CONTRACT.client_commands;
|
|
922
|
+
var CEAL_CLIENT_DOCTOR_COMMAND = Object.freeze([...clientCommands.doctor]);
|
|
923
|
+
var CEAL_CLIENT_VERSION_COMMAND = Object.freeze([...clientCommands.version]);
|
|
924
|
+
var CEAL_CLIENT_ACCESS_GROUP_LIST_COMMAND = Object.freeze([...clientCommands.access_group_list]);
|
|
925
|
+
function deriveCealClientWireSchemaVersion(schema) {
|
|
926
|
+
if (!/^ceal\.[a-z][a-z0-9_]*$/u.test(schema.schema_name) || !schema.fields.includes("schema_version") || new Set(schema.fields).size !== schema.fields.length || schema.fields.some((field) => typeof field !== "string" || !/^[a-z][a-z0-9_]*$/u.test(field))) {
|
|
927
|
+
throw new TypeError("Client wire schema name or field set is invalid.");
|
|
928
|
+
}
|
|
929
|
+
const fields = schema.fields.filter((field) => field !== "schema_version").sort();
|
|
930
|
+
const hash8 = createHash("sha256").update(cealCanonicalJson(fields), "utf8").digest("hex").slice(0, 8);
|
|
931
|
+
return `${schema.schema_name}.v1.${hash8}`;
|
|
932
|
+
}
|
|
933
|
+
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
934
|
+
var commandResolutionAuthority = commandResolution.authority;
|
|
935
|
+
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
936
|
+
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(commandResolution);
|
|
937
|
+
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
938
|
+
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
939
|
+
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
940
|
+
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
941
|
+
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.operation_contract_bundle_schema_name, fields: artifact.operation_contract_bundle_fields });
|
|
942
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(artifact);
|
|
943
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.binding_schema_name, fields: artifact.binding_fields });
|
|
944
|
+
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
945
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
946
|
+
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
947
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
948
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
949
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
950
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
951
|
+
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
952
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
953
|
+
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(request);
|
|
954
|
+
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
955
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
956
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
957
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
958
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: response.discovery_manifest_schema_name, fields: response.discovery_manifest_fields });
|
|
959
|
+
var CEAL_COMMAND_RESPONSE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(response);
|
|
960
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
961
|
+
var CEAL_RESOURCE_STREAM_REQUEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_schema_name, fields: CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_fields });
|
|
962
|
+
var CEAL_RESOURCE_STREAM_REQUEST_FIELDS = Object.freeze([...CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_fields]);
|
|
963
|
+
|
|
964
|
+
// packages/ceal-client-protocol/src/operation-withheld-cause.ts
|
|
965
|
+
var CEAL_OPERATION_WITHHELD_CAUSES = vocabulary(["provider_permission", "access_level", "personal_authorization_required"]);
|
|
966
|
+
|
|
967
|
+
// packages/ceal-client-protocol/src/operation-contract-types.ts
|
|
968
|
+
var CEAL_JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
969
|
+
var CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION = "ceal.operation_descriptor.v1";
|
|
970
|
+
var CEAL_OPERATION_ERROR_SCHEMA_VERSION = "ceal.operation_error.v1";
|
|
971
|
+
var CEAL_ARTIFACT_STAGE_TERMINALS = vocabulary(["chunk_accepted", "artifact_ready", "idempotency_replayed"]);
|
|
972
|
+
var CEAL_RETRY_AFTER_MAX_MS = 60 * 60 * 1e3;
|
|
973
|
+
var CEAL_JSON_SCHEMA_TYPES = vocabulary(["object", "array", "string", "integer", "number", "boolean", "null"]);
|
|
974
|
+
var OBSERVATION_POSTURES = vocabulary(["direct_readback", "approved_no_readback", "operator_mediated", "excluded"]);
|
|
975
|
+
var EFFECT_DOMAINS = vocabulary(["none", "gateway_state", "provider", "external_world"]);
|
|
976
|
+
var SAFETY_CLASSES = vocabulary(["routine", "sensitive", "high_impact"]);
|
|
977
|
+
var IDEMPOTENCY_CLASSES = vocabulary(["idempotent", "conditionally_idempotent", "non_idempotent"]);
|
|
978
|
+
var REVERSIBILITY_CLASSES = vocabulary(["none", "reversible", "compensatable", "irreversible"]);
|
|
979
|
+
var COMPENSATION_OWNERS = vocabulary(["none", "connector", "operator"]);
|
|
980
|
+
var CANCELLATION_BOUNDARIES = vocabulary(["before_admission", "before_handoff", "after_handoff_reconcile"]);
|
|
981
|
+
var HANDOFF_CLASSES = vocabulary(["not_applicable", "submission", "effect"]);
|
|
982
|
+
var AUTHORITATIVE_READBACK_CLASSES = vocabulary(["required", "unavailable"]);
|
|
983
|
+
var PROVIDER_READBACK_CLASSES = vocabulary(["required", "not_available", "not_applicable"]);
|
|
984
|
+
var RECONCILIATION_CLASSES = vocabulary(["none", "automatic", "operator"]);
|
|
985
|
+
var NO_READBACK_APPROVAL_CLASSES = vocabulary(["not_applicable", "explicit", "operator"]);
|
|
986
|
+
var REPLAY_PREREQUISITES = vocabulary(["never", "fresh_admission", "reconciled_non_application", "operator_approval"]);
|
|
987
|
+
var UNKNOWN_OUTCOMES = vocabulary(["reconcile", "operator_action", "excluded"]);
|
|
988
|
+
var RECOVERY_OWNERS = vocabulary(["gateway", "connector", "operator"]);
|
|
989
|
+
var CEAL_OPERATION_ADMISSION_STATES = vocabulary(["proposed", "refused", "admitted"]);
|
|
990
|
+
var CEAL_OPERATION_EXECUTION_STATES = vocabulary(["not_started", "started", "completed"]);
|
|
991
|
+
var CEAL_OPERATION_PROVIDER_HANDOFF_STATES = vocabulary(["not_offered", "offered", "accepted", "rejected", "unknown"]);
|
|
992
|
+
var CEAL_OPERATION_EFFECT_STATES = vocabulary(["none", "applied", "failed", "unknown", "compensated"]);
|
|
993
|
+
var CEAL_OPERATION_OBSERVATION_STATES = vocabulary(["not_started", "not_required", "pending", "observed", "unavailable"]);
|
|
994
|
+
var CEAL_OPERATION_RECOVERY_KINDS = vocabulary(["none", "reconcile", "compensate", "operator_action"]);
|
|
995
|
+
var CEAL_OPERATION_RECOVERY_STATUSES = vocabulary(["not_started", "active", "completed"]);
|
|
996
|
+
var CEAL_OPERATION_PROVIDER_READBACK_AVAILABILITY = vocabulary(["not_started", "not_applicable", "not_available", "pending", "available"]);
|
|
997
|
+
var CEAL_OPERATION_OFFERED_HANDOFF_STATES = vocabulary(["accepted", "rejected", "unknown"]);
|
|
998
|
+
var CEAL_OPERATION_RECEIPT_AUTHORITY_KEYS = Object.freeze(["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]);
|
|
999
|
+
var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
|
|
1000
|
+
"approval_not_required_by_policy",
|
|
1001
|
+
"interactive_pending",
|
|
1002
|
+
"interactive_granted",
|
|
1003
|
+
"interactive_denied",
|
|
1004
|
+
"interactive_expired",
|
|
1005
|
+
"interactive_unavailable"
|
|
1006
|
+
]);
|
|
1007
|
+
|
|
1008
|
+
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
1009
|
+
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
1010
|
+
|
|
1011
|
+
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
1012
|
+
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
1013
|
+
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
1014
|
+
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
1015
|
+
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
1016
|
+
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
1017
|
+
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
1018
|
+
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
1019
|
+
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
1020
|
+
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
1021
|
+
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
1022
|
+
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
1023
|
+
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
1024
|
+
|
|
1025
|
+
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
1026
|
+
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
1027
|
+
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
1028
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
1029
|
+
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
1030
|
+
}
|
|
1031
|
+
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
1035
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
1036
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
1037
|
+
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
1038
|
+
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
1039
|
+
max_depth: 8,
|
|
1040
|
+
max_properties: 96,
|
|
1041
|
+
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
1042
|
+
max_alternatives: 8,
|
|
1043
|
+
max_enum_values: 64,
|
|
1044
|
+
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
1045
|
+
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
1046
|
+
max_description_characters: 1024,
|
|
1047
|
+
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
1048
|
+
max_pattern_characters: 256,
|
|
1049
|
+
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
1050
|
+
max_literal_nodes: 512
|
|
1051
|
+
});
|
|
1052
|
+
|
|
827
1053
|
// packages/ceal-client-protocol/src/operation-json-schema.ts
|
|
828
1054
|
var JSON_DESCRIPTION_CONTROL_CHARACTER = new RegExp("\\p{Cc}", "u");
|
|
829
1055
|
|
|
@@ -865,12 +1091,12 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
865
1091
|
"ceal.enrollment_result.v1",
|
|
866
1092
|
"ceal.first_activation_causal_join.v1",
|
|
867
1093
|
"ceal.gateway_announcement_policy.v1",
|
|
868
|
-
|
|
1094
|
+
CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION,
|
|
869
1095
|
"ceal.gateway_audit_event.v1",
|
|
870
1096
|
"ceal.gateway_audit_readback.v1",
|
|
871
1097
|
"ceal.gateway_authorization_snapshot.v1",
|
|
872
1098
|
"ceal.gateway_cache_origin.v1",
|
|
873
|
-
|
|
1099
|
+
CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION,
|
|
874
1100
|
CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION,
|
|
875
1101
|
"ceal.gateway_connector_route_failure.v1",
|
|
876
1102
|
"ceal.gateway_discovery.v1",
|
|
@@ -895,7 +1121,7 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
895
1121
|
"ceal.host_observation.v1",
|
|
896
1122
|
"ceal.host_registration_request.v1",
|
|
897
1123
|
"ceal.host_registration_result.v1",
|
|
898
|
-
|
|
1124
|
+
CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION,
|
|
899
1125
|
"ceal.operation_error.v1",
|
|
900
1126
|
"ceal.operation_failure.v1",
|
|
901
1127
|
"ceal.operation_failure.compact.v1",
|
|
@@ -1000,11 +1226,22 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
|
|
|
1000
1226
|
var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
|
|
1001
1227
|
var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
|
|
1002
1228
|
|
|
1229
|
+
// packages/ceal-client-protocol/src/client-environment-generated.ts
|
|
1230
|
+
var CEAL_CLIENT_CONTEXT_OVERRIDE_ENVIRONMENTS = Object.freeze(["CEAL_GATEWAY_ORIGIN", "CEAL_GATEWAY_TRANSPORT_ORIGIN", "CEAL_ACCESS_TOKEN", "CEAL_CONTEXT_STORE_PATH"]);
|
|
1231
|
+
|
|
1232
|
+
// packages/ceal-client-protocol/src/client-process-contract.ts
|
|
1233
|
+
var CEAL_CLIENT_VERSION_SCHEMA_SUFFIX = ".version.v1";
|
|
1234
|
+
var CEAL_CLIENT_VERSION_SCHEMA_VERSION = `ceal${CEAL_CLIENT_VERSION_SCHEMA_SUFFIX}`;
|
|
1235
|
+
|
|
1003
1236
|
// packages/ceal-client-protocol/src/bare-document.ts
|
|
1004
1237
|
var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_fields;
|
|
1005
1238
|
var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
|
|
1006
1239
|
|
|
1240
|
+
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1241
|
+
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1242
|
+
|
|
1007
1243
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
1244
|
+
var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
|
|
1008
1245
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
1009
1246
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
1010
1247
|
var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "authored_by", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
|
|
@@ -1017,6 +1254,8 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
|
|
|
1017
1254
|
"resource_created",
|
|
1018
1255
|
"review_submitted",
|
|
1019
1256
|
"comment_created",
|
|
1257
|
+
"reaction_created",
|
|
1258
|
+
"ceal_write_completed",
|
|
1020
1259
|
"event_start",
|
|
1021
1260
|
"all_day_start",
|
|
1022
1261
|
"file_created",
|
|
@@ -1051,6 +1290,10 @@ var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
|
|
|
1051
1290
|
"github.review.submitted",
|
|
1052
1291
|
"github.issue_comment.created",
|
|
1053
1292
|
"github.review_comment.created",
|
|
1293
|
+
"github.reaction.created",
|
|
1294
|
+
"github.reaction.deleted",
|
|
1295
|
+
"github.pull_request.review_requested",
|
|
1296
|
+
"github.pull_request.review_request_removed",
|
|
1054
1297
|
"calendar.organized",
|
|
1055
1298
|
"calendar.accepted_invite",
|
|
1056
1299
|
"calendar.recorded",
|
|
@@ -1072,6 +1315,10 @@ var CEAL_ACTIVITY_EVENT_TIMESTAMP_BASIS_MAP = Object.freeze({
|
|
|
1072
1315
|
"github.pull_request.opened": "resource_created",
|
|
1073
1316
|
"github.issue.edited": "resource_modified",
|
|
1074
1317
|
"github.pull_request.edited": "resource_modified",
|
|
1318
|
+
"github.reaction.created": "reaction_created",
|
|
1319
|
+
"github.reaction.deleted": "ceal_write_completed",
|
|
1320
|
+
"github.pull_request.review_requested": "ceal_write_completed",
|
|
1321
|
+
"github.pull_request.review_request_removed": "ceal_write_completed",
|
|
1075
1322
|
"calendar.organized": "resource_created",
|
|
1076
1323
|
"drive.file.created": "file_created",
|
|
1077
1324
|
"drive.file.modified": "file_modified_by_me",
|
|
@@ -1088,12 +1335,14 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1088
1335
|
next: { required: true, timestamp: false },
|
|
1089
1336
|
kind: { required: true, timestamp: false },
|
|
1090
1337
|
matched_kinds: { required: false, timestamp: false },
|
|
1338
|
+
title_match: { required: false, timestamp: false },
|
|
1091
1339
|
title: { required: true, timestamp: false },
|
|
1092
1340
|
container_label: { required: true, timestamp: false },
|
|
1093
1341
|
container_ref: { required: false, timestamp: false },
|
|
1094
1342
|
source_url: { required: false, timestamp: false },
|
|
1095
1343
|
source: { required: true, timestamp: false },
|
|
1096
1344
|
owner: { required: false, timestamp: false },
|
|
1345
|
+
actor_kind: { required: false, timestamp: false },
|
|
1097
1346
|
counterparts: { required: false, timestamp: false },
|
|
1098
1347
|
attachments: { required: false, timestamp: false },
|
|
1099
1348
|
created_at: { required: false, timestamp: true },
|
|
@@ -1101,9 +1350,18 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1101
1350
|
modified_at: { required: false, timestamp: true },
|
|
1102
1351
|
modified_at_local: { required: false, timestamp: true },
|
|
1103
1352
|
viewed_by_me_at: { required: false, timestamp: true },
|
|
1353
|
+
modified_by_me_at: { required: false, timestamp: true },
|
|
1104
1354
|
own_ceal_write: { required: false, timestamp: false },
|
|
1105
1355
|
owned_by_me: { required: false, timestamp: false },
|
|
1356
|
+
mime_type: { required: false, timestamp: false },
|
|
1357
|
+
size_bytes: { required: false, timestamp: false },
|
|
1358
|
+
version: { required: false, timestamp: false },
|
|
1359
|
+
trashed: { required: false, timestamp: false },
|
|
1106
1360
|
visibility: { required: false, timestamp: false },
|
|
1361
|
+
archived: { required: false, timestamp: false },
|
|
1362
|
+
tag: { required: false, timestamp: false },
|
|
1363
|
+
labels: { required: false, timestamp: false },
|
|
1364
|
+
labels_incomplete: { required: false, timestamp: false },
|
|
1107
1365
|
start: { required: false, timestamp: false },
|
|
1108
1366
|
end: { required: false, timestamp: false },
|
|
1109
1367
|
status: { required: false, timestamp: false },
|
|
@@ -1176,18 +1434,29 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
|
|
|
1176
1434
|
// The serving gate answers this while the Gateway is still starting and the
|
|
1177
1435
|
// refresh was never attempted; the same attempt stays retryable. It is never
|
|
1178
1436
|
// stored by the session owner, only answered at the serving edge.
|
|
1179
|
-
|
|
1437
|
+
CEAL_GATEWAY_STARTING_FAILURE_CODE
|
|
1180
1438
|
]);
|
|
1439
|
+
var CEAL_CLIENT_REVOKE_FAILURE_CODES = vocabulary([
|
|
1440
|
+
"refresh_invalid",
|
|
1441
|
+
"refresh_expired",
|
|
1442
|
+
"refresh_replayed",
|
|
1443
|
+
"refresh_revoked"
|
|
1444
|
+
]);
|
|
1445
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX = "ceal_personal_";
|
|
1446
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX = "ceal_refresh_";
|
|
1447
|
+
var PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN = "[A-Za-z0-9_-]{43}";
|
|
1448
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1449
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1181
1450
|
|
|
1182
1451
|
// packages/ceal-client-protocol/src/request-validation-primitives.ts
|
|
1183
|
-
import { createHash } from "node:crypto";
|
|
1452
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1184
1453
|
var DIGEST = /^[a-f0-9]{64}$/u;
|
|
1185
1454
|
var CEAL_SHA256_DIGEST_PATTERN = DIGEST.source;
|
|
1186
1455
|
function isCealDigest(value) {
|
|
1187
1456
|
return typeof value === "string" && DIGEST.test(value);
|
|
1188
1457
|
}
|
|
1189
1458
|
function cealRequestSha256(value) {
|
|
1190
|
-
return
|
|
1459
|
+
return createHash2("sha256").update(cealCanonicalJson(value), "utf8").digest("hex");
|
|
1191
1460
|
}
|
|
1192
1461
|
function operationRecord(value, message) {
|
|
1193
1462
|
requireOperationRecord(value, message);
|
|
@@ -1203,40 +1472,71 @@ function requirePrefixedRef(value, prefix, message) {
|
|
|
1203
1472
|
// packages/ceal-client-protocol/src/request-authority.ts
|
|
1204
1473
|
var CEAL_REQUEST_SCHEMA_VERSION = "ceal.request.v1";
|
|
1205
1474
|
var CEAL_REQUEST_ARTIFACT_OWNERS = vocabulary(["gateway", "host", "connector"]);
|
|
1206
|
-
var
|
|
1475
|
+
var CEAL_HOST_INVOCATION_IDENTITY_KEYS = vocabulary(["host_session_ref", "host_turn_ref", "host_source_ref"]);
|
|
1207
1476
|
var CEAL_REQUEST_ARTIFACT_RETENTION_CLASSES = vocabulary(["request", "source", "result"]);
|
|
1208
1477
|
var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
|
|
1209
|
-
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"
|
|
1478
|
+
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"]);
|
|
1479
|
+
var CEAL_REQUEST_COMMON_PROPERTIES = Object.freeze({
|
|
1480
|
+
schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
|
|
1481
|
+
ceal_request_ref: { type: "string", minLength: 1, maxLength: 128, pattern: "^ceal-request:[A-Za-z0-9][A-Za-z0-9._:-]{0,114}$" },
|
|
1482
|
+
created_at: { type: "string", minLength: 1, maxLength: 64 },
|
|
1483
|
+
authority: {
|
|
1484
|
+
type: "object",
|
|
1485
|
+
maxProperties: 5,
|
|
1486
|
+
additionalProperties: false,
|
|
1487
|
+
properties: {
|
|
1488
|
+
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1489
|
+
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1490
|
+
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1491
|
+
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1492
|
+
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1493
|
+
},
|
|
1494
|
+
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
var CEAL_REQUEST_HOST_PROPERTIES = Object.freeze({
|
|
1498
|
+
prompt_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1499
|
+
host_session_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1500
|
+
host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1501
|
+
host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1502
|
+
host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1503
|
+
});
|
|
1504
|
+
var CEAL_REQUEST_CALLER_PROPERTIES = Object.freeze({
|
|
1505
|
+
caller_call_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source, description: "Gateway-minted call-group identity for a non-Host caller." },
|
|
1506
|
+
caller_call_input_sha256: { type: "string", minLength: 64, maxLength: 64, pattern: "^[a-f0-9]{64}$", description: "Commitment to the exact input for active non-Host identity-reuse checks; a migrated legacy call group commits to its legacy group key because the original input may no longer exist." }
|
|
1507
|
+
});
|
|
1508
|
+
var CEAL_REQUEST_OPTIONAL_PROPERTIES = Object.freeze({
|
|
1509
|
+
requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
|
|
1510
|
+
device_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source, description: "Device that submitted this Request through its client session, including Host turns when the authenticated Host registration carries the device; absent for older records." }
|
|
1511
|
+
});
|
|
1512
|
+
var CEAL_REQUEST_HOST_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, ...CEAL_HOST_INVOCATION_IDENTITY_KEYS, "prompt_ref"]);
|
|
1513
|
+
var CEAL_REQUEST_CALLER_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, "caller_call_group_ref", "caller_call_input_sha256"]);
|
|
1210
1514
|
var CEAL_REQUEST_JSON_SCHEMA = {
|
|
1211
1515
|
type: "object",
|
|
1212
|
-
|
|
1516
|
+
description: "A Request has exactly one creation identity: the complete Host tuple and prompt reference, or a caller call-group identity. Caller Requests retain no prompt reference or prompt body.",
|
|
1517
|
+
maxProperties: 13,
|
|
1213
1518
|
additionalProperties: false,
|
|
1214
1519
|
properties: {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1520
|
+
...CEAL_REQUEST_COMMON_PROPERTIES,
|
|
1521
|
+
...CEAL_REQUEST_HOST_PROPERTIES,
|
|
1522
|
+
...CEAL_REQUEST_CALLER_PROPERTIES,
|
|
1523
|
+
...CEAL_REQUEST_OPTIONAL_PROPERTIES
|
|
1524
|
+
},
|
|
1525
|
+
required: CEAL_REQUEST_KEYS,
|
|
1526
|
+
anyOf: [
|
|
1527
|
+
{
|
|
1219
1528
|
type: "object",
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1224
|
-
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1225
|
-
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1226
|
-
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1227
|
-
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1228
|
-
},
|
|
1229
|
-
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1529
|
+
required: CEAL_REQUEST_HOST_REQUIRED_KEYS,
|
|
1530
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_HOST_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1531
|
+
additionalProperties: false
|
|
1230
1532
|
},
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
},
|
|
1239
|
-
required: CEAL_REQUEST_KEYS
|
|
1533
|
+
{
|
|
1534
|
+
type: "object",
|
|
1535
|
+
required: CEAL_REQUEST_CALLER_REQUIRED_KEYS,
|
|
1536
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_CALLER_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1537
|
+
additionalProperties: false
|
|
1538
|
+
}
|
|
1539
|
+
]
|
|
1240
1540
|
};
|
|
1241
1541
|
|
|
1242
1542
|
// packages/ceal-client-protocol/src/request-operands.ts
|
|
@@ -1347,10 +1647,6 @@ var CEAL_REQUEST_RUN_BATCH_JSON_SCHEMA = {
|
|
|
1347
1647
|
};
|
|
1348
1648
|
|
|
1349
1649
|
// packages/ceal-client-protocol/src/host-observation.ts
|
|
1350
|
-
var CEAL_HOST_INPUT_KINDS = vocabulary(["human", "host_automatic"]);
|
|
1351
|
-
function isCealHostInputKind(value) {
|
|
1352
|
-
return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
|
|
1353
|
-
}
|
|
1354
1650
|
var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
|
|
1355
1651
|
var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
|
|
1356
1652
|
var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
|
|
@@ -1396,14 +1692,14 @@ function safeCount(value) {
|
|
|
1396
1692
|
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
1397
1693
|
}
|
|
1398
1694
|
function validateCealHostObservationDraft(value) {
|
|
1399
|
-
const
|
|
1400
|
-
requirePrefixedRef(
|
|
1401
|
-
requireClosedValue(
|
|
1402
|
-
requireIsoTimestamp(
|
|
1403
|
-
assertHostObservationValue(
|
|
1695
|
+
const record4 = operationRecord(value, "Ceal Host observation draft is invalid.");
|
|
1696
|
+
requirePrefixedRef(record4.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
|
|
1697
|
+
requireClosedValue(record4.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
|
|
1698
|
+
requireIsoTimestamp(record4.observed_at, "Ceal Host observation time is invalid.");
|
|
1699
|
+
assertHostObservationValue(record4.value, record4.observation_kind);
|
|
1404
1700
|
}
|
|
1405
1701
|
var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
|
|
1406
|
-
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "
|
|
1702
|
+
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "timing"]);
|
|
1407
1703
|
function isCealHostPromptObservationValue(value) {
|
|
1408
1704
|
return isOperationRecord(value) && Object.keys(value).length === 1 && isCealDigest(value.body_sha256);
|
|
1409
1705
|
}
|
|
@@ -1526,16 +1822,41 @@ var CEAL_REQUEST_GRAPH_NODE_PREPARE_READINESS_JSON_SCHEMA = {
|
|
|
1526
1822
|
};
|
|
1527
1823
|
|
|
1528
1824
|
// packages/ceal-client-protocol/src/host-invocation-authentication.ts
|
|
1529
|
-
var
|
|
1530
|
-
var
|
|
1531
|
-
var
|
|
1532
|
-
var
|
|
1533
|
-
var
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1825
|
+
var hostInvocationAuthenticationContract = CEAL_CLIENT_WIRE_CONTRACT.host_invocation_authentication;
|
|
1826
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.request_schema_name, fields: hostInvocationAuthenticationContract.request_fields });
|
|
1827
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.request_fields]);
|
|
1828
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.result_schema_name, fields: hostInvocationAuthenticationContract.result_fields });
|
|
1829
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.result_fields]);
|
|
1830
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE = "^(?:0|[1-9][0-9]{0,8})$";
|
|
1831
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN = new RegExp(CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE, "u");
|
|
1832
|
+
var CEAL_HOST_INVOCATION_CONTEXT_REQUEST_MAX_BYTES = 16 * 1024;
|
|
1833
|
+
var CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES = 2 * 1024;
|
|
1834
|
+
var CEAL_HOST_CONTEXT_TOKEN_PREFIX = "ceal_context_";
|
|
1835
|
+
function deriveCealHostInvocationNonceContract(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1836
|
+
const clientNonce = contract.host_invocation_authentication.client_nonce;
|
|
1837
|
+
return {
|
|
1838
|
+
encoded_characters: clientNonce.encoded_characters,
|
|
1839
|
+
client_nonce_pattern_source: `^[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`,
|
|
1840
|
+
context_token_pattern_source: `^${CEAL_HOST_CONTEXT_TOKEN_PREFIX}[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
var hostNonceContract = deriveCealHostInvocationNonceContract();
|
|
1844
|
+
var CEAL_HOST_NONCE_ENCODED_CHARACTERS = hostNonceContract.encoded_characters;
|
|
1845
|
+
var CEAL_HOST_CLIENT_NONCE_PATTERN = new RegExp(hostNonceContract.client_nonce_pattern_source, "u");
|
|
1846
|
+
var CEAL_HOST_CONTEXT_TOKEN_PATTERN = new RegExp(hostNonceContract.context_token_pattern_source, "u");
|
|
1847
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = deriveCealHostInvocationAuthenticationResultMaxBytes();
|
|
1848
|
+
function deriveCealHostInvocationAuthenticationResultMaxBytes(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1849
|
+
const hostAuthentication = contract.host_invocation_authentication;
|
|
1850
|
+
const values = {
|
|
1851
|
+
schema_version: deriveCealClientWireSchemaVersion({ schema_name: hostAuthentication.result_schema_name, fields: hostAuthentication.result_fields }),
|
|
1852
|
+
context_token: `${CEAL_HOST_CONTEXT_TOKEN_PREFIX}${"A".repeat(hostAuthentication.client_nonce.encoded_characters)}`,
|
|
1853
|
+
expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
|
|
1854
|
+
};
|
|
1855
|
+
const fields = hostAuthentication.result_fields;
|
|
1856
|
+
if (fields.some((field) => !Object.hasOwn(values, field))) throw new TypeError("Host invocation result fields have no bounded sample value.");
|
|
1857
|
+
return new TextEncoder().encode(`${JSON.stringify(Object.fromEntries(fields.map((field) => [field, values[field]])))}
|
|
1538
1858
|
`).byteLength;
|
|
1859
|
+
}
|
|
1539
1860
|
var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
|
|
1540
1861
|
"request_authority_unavailable",
|
|
1541
1862
|
"request_missing",
|
|
@@ -1559,6 +1880,7 @@ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
|
|
|
1559
1880
|
"host_credential_refused",
|
|
1560
1881
|
"gateway_unavailable",
|
|
1561
1882
|
"gateway_rate_limited",
|
|
1883
|
+
"contract_mismatch",
|
|
1562
1884
|
"gateway_refused",
|
|
1563
1885
|
"gateway_response_invalid",
|
|
1564
1886
|
"producer_frame_invalid",
|
|
@@ -1573,19 +1895,23 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1573
1895
|
invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
|
|
1574
1896
|
options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
|
|
1575
1897
|
session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1576
|
-
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "
|
|
1577
|
-
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "
|
|
1578
|
-
no_binding
|
|
1579
|
-
|
|
1580
|
-
|
|
1898
|
+
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "retry" }),
|
|
1899
|
+
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1900
|
+
// `no_binding` now means the producer has no real current Host session. The
|
|
1901
|
+
// Host Relay never uses it for an uncreated Gateway Request; there is no
|
|
1902
|
+
// useful retry until the consumer is running inside a Host session.
|
|
1903
|
+
no_binding: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1904
|
+
context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1905
|
+
context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "retry" }),
|
|
1581
1906
|
host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
|
|
1582
1907
|
host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
|
|
1583
1908
|
gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
|
|
1584
1909
|
gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
|
|
1910
|
+
contract_mismatch: Object.freeze({ classification: "contract_mismatch", next_action: "upgrade_ceal_client" }),
|
|
1585
1911
|
gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
|
|
1586
1912
|
gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
|
|
1587
|
-
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin"
|
|
1588
|
-
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox"
|
|
1913
|
+
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin" }),
|
|
1914
|
+
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox" }),
|
|
1589
1915
|
relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
|
|
1590
1916
|
transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
|
|
1591
1917
|
mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
|
|
@@ -1600,9 +1926,6 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
|
1600
1926
|
"hook_invoked",
|
|
1601
1927
|
"registration_written",
|
|
1602
1928
|
"hook_failed",
|
|
1603
|
-
"watcher_turn_observed",
|
|
1604
|
-
"current_prompt_registered",
|
|
1605
|
-
"current_prompt_registration_failed",
|
|
1606
1929
|
"activation_recorded",
|
|
1607
1930
|
"activation_failed",
|
|
1608
1931
|
"watcher_scan_failed",
|
|
@@ -1628,6 +1951,9 @@ var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_co
|
|
|
1628
1951
|
// packages/ceal-client-protocol/src/session-start-affordance.ts
|
|
1629
1952
|
var CEAL_SESSION_ENTRY_INVOCATION = "ceal";
|
|
1630
1953
|
|
|
1954
|
+
// packages/ceal-client-protocol/src/discovery-manifest.ts
|
|
1955
|
+
var DISCOVERY_MANIFEST_KEYS = CEAL_CLIENT_WIRE_CONTRACT.command_response.discovery_manifest_fields;
|
|
1956
|
+
|
|
1631
1957
|
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1632
1958
|
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1633
1959
|
|
|
@@ -1686,6 +2012,7 @@ var ADMIN_UNAVAILABLE_REASONS = vocabulary([
|
|
|
1686
2012
|
"legacy",
|
|
1687
2013
|
"invalid_observation",
|
|
1688
2014
|
"redacted",
|
|
2015
|
+
"organization_setting_disabled",
|
|
1689
2016
|
"owner_unavailable",
|
|
1690
2017
|
"unit_unknown",
|
|
1691
2018
|
"retention_gap",
|
|
@@ -1715,15 +2042,13 @@ var ADMIN_APPROVAL_DISPOSITIONS = CEAL_OPERATION_APPROVAL_DISPOSITIONS;
|
|
|
1715
2042
|
var ADMIN_APPROVAL_POSTURES = vocabulary([...ADMIN_APPROVAL_DISPOSITIONS, "not_applicable"]);
|
|
1716
2043
|
var ADMIN_LINEAGE_NODE_KINDS = vocabulary(["source_read", "synthesis_preview", "approval", "effect", "readback"]);
|
|
1717
2044
|
|
|
1718
|
-
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1719
|
-
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1720
|
-
|
|
1721
2045
|
// packages/ceal-client-protocol/src/device-enrollment.ts
|
|
1722
2046
|
var CEAL_DEVICE_ENROLLMENT_POLL_FAILURE_CODES = Object.freeze(["unsupported_feature", "recovery_required", "expired", "denied"]);
|
|
1723
2047
|
var POLL_RESULT_KEYS = Object.freeze(["retry_after_ms", "schema_version", "status"]);
|
|
1724
2048
|
|
|
1725
2049
|
// packages/ceal-client-protocol/src/personal-client-session.ts
|
|
1726
2050
|
var REFRESH_FAILURE_CODES = new Set(CEAL_REFRESH_FAILURE_CODES);
|
|
2051
|
+
var REVOKE_FAILURE_CODES = new Set(CEAL_CLIENT_REVOKE_FAILURE_CODES);
|
|
1727
2052
|
|
|
1728
2053
|
// packages/ceal-client-protocol/src/index.ts
|
|
1729
2054
|
var MAX_REQUEST_BYTES = 32 * 1024;
|
|
@@ -1757,6 +2082,60 @@ function decodeClaudeHookInput(event, raw) {
|
|
|
1757
2082
|
const model = optionalText(value.model, 256);
|
|
1758
2083
|
return { event, ...common, hook_event_name: "SessionStart", source: value.source, ...model === void 0 ? {} : { model } };
|
|
1759
2084
|
}
|
|
2085
|
+
function decodeClaudeQueuedPromptRow(row, sessionId) {
|
|
2086
|
+
if (!record(row)) return null;
|
|
2087
|
+
if (row.type === "user") return decodeArrayTextPrompt(row, sessionId);
|
|
2088
|
+
if (row.type === "attachment") return decodeQueuedCommandPrompt(row, sessionId);
|
|
2089
|
+
if (row.type === "queue-operation") return decodeQueueEnqueuePrompt(row, sessionId);
|
|
2090
|
+
return null;
|
|
2091
|
+
}
|
|
2092
|
+
function decodeArrayTextPrompt(row, sessionId) {
|
|
2093
|
+
if (!record(row.message) || !Array.isArray(row.message.content)) return null;
|
|
2094
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
2095
|
+
if (row.isMeta === true && (!record(row.origin) || typeof row.origin.kind !== "string")) return null;
|
|
2096
|
+
const texts = arrayTextBlocks(row.message.content);
|
|
2097
|
+
if (texts === null) return null;
|
|
2098
|
+
const identity = queuedRowIdentity(row);
|
|
2099
|
+
if (identity === null) return null;
|
|
2100
|
+
return { turnId: identity.uuid, timestamp: identity.timestamp, content: texts.join("") };
|
|
2101
|
+
}
|
|
2102
|
+
function arrayTextBlocks(content) {
|
|
2103
|
+
const texts = [];
|
|
2104
|
+
for (const block of content) {
|
|
2105
|
+
if (!record(block) || block.type !== "text" || typeof block.text !== "string") return null;
|
|
2106
|
+
texts.push(block.text);
|
|
2107
|
+
}
|
|
2108
|
+
return texts.length === 0 ? null : texts;
|
|
2109
|
+
}
|
|
2110
|
+
function decodeQueuedCommandPrompt(row, sessionId) {
|
|
2111
|
+
if (!record(row.attachment) || row.attachment.type !== "queued_command" || row.attachment.commandMode !== "prompt") return null;
|
|
2112
|
+
if (typeof row.attachment.prompt !== "string" || row.attachment.prompt.length === 0) return null;
|
|
2113
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
2114
|
+
const identity = queuedRowIdentity(row);
|
|
2115
|
+
if (identity === null) return null;
|
|
2116
|
+
return { turnId: identity.uuid, timestamp: identity.timestamp, content: row.attachment.prompt };
|
|
2117
|
+
}
|
|
2118
|
+
function decodeQueueEnqueuePrompt(row, sessionId) {
|
|
2119
|
+
if (row.operation !== "enqueue" || typeof row.content !== "string" || row.content.length === 0) return null;
|
|
2120
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
2121
|
+
if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
|
|
2122
|
+
return { turnId: queueEnqueueTurnId(sessionId, row.timestamp, row.content), timestamp: row.timestamp, content: row.content };
|
|
2123
|
+
}
|
|
2124
|
+
function sessionMatches(row, sessionId) {
|
|
2125
|
+
const hasSessionId = Object.hasOwn(row, "sessionId");
|
|
2126
|
+
const hasSnakeSessionId = Object.hasOwn(row, "session_id");
|
|
2127
|
+
return (hasSessionId || hasSnakeSessionId) && (!hasSessionId || row.sessionId === sessionId) && (!hasSnakeSessionId || row.session_id === sessionId);
|
|
2128
|
+
}
|
|
2129
|
+
function queuedRowIdentity(row) {
|
|
2130
|
+
if (typeof row.uuid !== "string" || row.uuid.length === 0 || row.uuid.length > 256) return null;
|
|
2131
|
+
if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
|
|
2132
|
+
return { uuid: row.uuid, timestamp: row.timestamp };
|
|
2133
|
+
}
|
|
2134
|
+
function queueEnqueueTurnId(sessionId, timestamp4, content) {
|
|
2135
|
+
return `queue-enqueue:${createHash3("sha256").update(`${sessionId}
|
|
2136
|
+
${timestamp4}
|
|
2137
|
+
${content}`, "utf8").digest("hex").slice(0, 32)}`;
|
|
2138
|
+
}
|
|
1760
2139
|
function claudeLocalSessionRef(sessionId) {
|
|
1761
2140
|
return `claude-session:${digest(sessionId)}`;
|
|
1762
2141
|
}
|
|
@@ -1766,9 +2145,6 @@ function claudeLocalTurnRef(sessionId, turnId) {
|
|
|
1766
2145
|
function claudeLocalInputRef(sessionId, turnId) {
|
|
1767
2146
|
return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
|
|
1768
2147
|
}
|
|
1769
|
-
function claudeWorkspaceRef(cwd) {
|
|
1770
|
-
return `workspace:${digest(cwd)}`;
|
|
1771
|
-
}
|
|
1772
2148
|
function isSessionSource(value) {
|
|
1773
2149
|
return typeof value === "string" && SESSION_SOURCES.some((source) => source === value);
|
|
1774
2150
|
}
|
|
@@ -1789,7 +2165,7 @@ function optionalText(value, max) {
|
|
|
1789
2165
|
return requiredText(value, max, "Claude optional hook field is invalid.");
|
|
1790
2166
|
}
|
|
1791
2167
|
function digest(value) {
|
|
1792
|
-
return
|
|
2168
|
+
return createHash3("sha256").update(value, "utf8").digest("hex");
|
|
1793
2169
|
}
|
|
1794
2170
|
function record(value) {
|
|
1795
2171
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1799,17 +2175,17 @@ function invalid2(message) {
|
|
|
1799
2175
|
}
|
|
1800
2176
|
|
|
1801
2177
|
// host/claude/src/claude-outbox.ts
|
|
1802
|
-
import { mkdir as
|
|
1803
|
-
import { isAbsolute, join as
|
|
2178
|
+
import { mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
|
|
2179
|
+
import { isAbsolute, join as join2 } from "node:path";
|
|
1804
2180
|
|
|
1805
2181
|
// host/invocation-context.ts
|
|
1806
|
-
var
|
|
2182
|
+
var HOST_INVOCATION_TURN_SELECTION_SCHEMA = "ceal.host_invocation_turn_selection.v1";
|
|
1807
2183
|
var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
|
|
1808
2184
|
function invocationContextKey(identity) {
|
|
1809
2185
|
return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
|
|
1810
2186
|
}
|
|
1811
2187
|
function hasHostInvocationIdentityKeys(value) {
|
|
1812
|
-
return exactKeys(value,
|
|
2188
|
+
return exactKeys(value, CEAL_HOST_INVOCATION_IDENTITY_KEYS);
|
|
1813
2189
|
}
|
|
1814
2190
|
function hasHostInvocationContextKeys(value, optional = []) {
|
|
1815
2191
|
return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
|
|
@@ -1835,10 +2211,7 @@ function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
|
|
|
1835
2211
|
return Object.freeze(context);
|
|
1836
2212
|
}
|
|
1837
2213
|
function isHostInvocationTurnSelection(value) {
|
|
1838
|
-
return isRecord(value) && Object.keys(value).length === 5 && value.schema_version ===
|
|
1839
|
-
}
|
|
1840
|
-
function invocationTurnSelectionMatches(context, selection) {
|
|
1841
|
-
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;
|
|
2214
|
+
return isRecord(value) && Object.keys(value).length === 5 && value.schema_version === HOST_INVOCATION_TURN_SELECTION_SCHEMA && ["host_source_ref", "host_session_ref", "prompt_ref", "turn_generation_ref"].every((key) => safeRef(value[key]));
|
|
1842
2215
|
}
|
|
1843
2216
|
function exactKeys(value, keys) {
|
|
1844
2217
|
const actual = Object.keys(value);
|
|
@@ -1857,16 +2230,9 @@ function safeRef(value) {
|
|
|
1857
2230
|
}
|
|
1858
2231
|
|
|
1859
2232
|
// host/relay/src/producer-frame.ts
|
|
1860
|
-
var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
|
|
1861
|
-
var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
|
|
1862
2233
|
var HOST_PRODUCER_FRAME_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
1863
|
-
var
|
|
1864
|
-
var PRODUCERS = /* @__PURE__ */ new Set(["codex", "claude"]);
|
|
1865
|
-
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "user_input", "turn_terminal", "producer_status"];
|
|
2234
|
+
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "turn_terminal", "producer_status"];
|
|
1866
2235
|
var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
|
|
1867
|
-
function isHostProducerFrameKind(value) {
|
|
1868
|
-
return typeof value === "string" && FRAME_KINDS.has(value);
|
|
1869
|
-
}
|
|
1870
2236
|
var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
1871
2237
|
"invalid_frame_encoding",
|
|
1872
2238
|
"invalid_frame_length",
|
|
@@ -1876,457 +2242,127 @@ var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
|
1876
2242
|
"invalid_producer_identity",
|
|
1877
2243
|
"frame_too_large"
|
|
1878
2244
|
];
|
|
1879
|
-
var HostProducerFrameError = class extends Error {
|
|
1880
|
-
code;
|
|
1881
|
-
constructor(code2, message = code2) {
|
|
1882
|
-
super(message);
|
|
1883
|
-
this.code = code2;
|
|
1884
|
-
}
|
|
1885
|
-
};
|
|
1886
|
-
function encodeHostProducerFrame(frame) {
|
|
1887
|
-
validateFrame(frame);
|
|
1888
|
-
const json = Buffer.from(JSON.stringify(frame), "utf8");
|
|
1889
|
-
if (json.byteLength > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("frame_bytes", json.byteLength, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
|
|
1890
|
-
const result = Buffer.allocUnsafe(json.byteLength + HOST_PRODUCER_FRAME_LENGTH_BYTES);
|
|
1891
|
-
result.writeUInt32BE(json.byteLength, 0);
|
|
1892
|
-
json.copy(result, HOST_PRODUCER_FRAME_LENGTH_BYTES);
|
|
1893
|
-
return result;
|
|
1894
|
-
}
|
|
1895
|
-
function decodeHostProducerFrame(input) {
|
|
1896
|
-
if (input.byteLength < HOST_PRODUCER_FRAME_LENGTH_BYTES) throw new HostProducerFrameError("frame_truncated", `frame_truncated: ${refusalFact("frame_bytes", input.byteLength, `at least ${HOST_PRODUCER_FRAME_LENGTH_BYTES}`)}`);
|
|
1897
|
-
const length = input.readUInt32BE(0);
|
|
1898
|
-
if (length > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("declared_bytes", length, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
|
|
1899
|
-
if (length === 0) throw new HostProducerFrameError("invalid_frame_length");
|
|
1900
|
-
if (input.byteLength !== length + HOST_PRODUCER_FRAME_LENGTH_BYTES) throw new HostProducerFrameError(input.byteLength < length + HOST_PRODUCER_FRAME_LENGTH_BYTES ? "frame_truncated" : "invalid_frame_length");
|
|
1901
|
-
const bytes = input.subarray(HOST_PRODUCER_FRAME_LENGTH_BYTES);
|
|
1902
|
-
const text = new TextDecoder("utf-8", { fatal: true });
|
|
1903
|
-
let value;
|
|
1904
|
-
try {
|
|
1905
|
-
value = JSON.parse(text.decode(bytes));
|
|
1906
|
-
} catch (error) {
|
|
1907
|
-
if (error instanceof TypeError) throw new HostProducerFrameError("invalid_frame_encoding");
|
|
1908
|
-
throw new HostProducerFrameError("frame_truncated");
|
|
1909
|
-
}
|
|
1910
|
-
validateFrame(value);
|
|
1911
|
-
return value;
|
|
1912
|
-
}
|
|
1913
|
-
function isHostSafeRef(value) {
|
|
1914
|
-
return safeRef2(value);
|
|
1915
|
-
}
|
|
1916
|
-
function validateFrame(value) {
|
|
1917
|
-
if (!record2(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1918
|
-
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)}`);
|
|
1919
|
-
if (!exactKeys2(value, ["schema_version", "producer_id", "producer_installation_ref", "producer_epoch", "sequence", "frame_kind", "occurred_at", "payload"])) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1920
|
-
if (!PRODUCERS.has(String(value.producer_id)) || !safeRef2(value.producer_installation_ref)) throw new HostProducerFrameError("invalid_producer_identity");
|
|
1921
|
-
if (!positive(value.producer_epoch) || !positive(value.sequence) || !hostProducerFrameKind(value.frame_kind) || !timestamp(value.occurred_at) || !record2(value.payload)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1922
|
-
validatePayload(value.frame_kind, value.payload);
|
|
1923
|
-
}
|
|
1924
|
-
function validatePayload(kind, value) {
|
|
1925
|
-
const valid = kind === "session_started" ? validSession(value) : kind === "turn_started" ? validTurn(value) : kind === "user_input" ? validInput(value) : kind === "turn_terminal" ? validTerminal(value) : validStatus(value);
|
|
1926
|
-
if (!valid) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1927
|
-
}
|
|
1928
|
-
function validSession(value) {
|
|
1929
|
-
return exactOptional(value, ["local_session_ref", "working_directory_ref"], ["model", "host_version", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.working_directory_ref) && optionalText2(value.model, 256) && optionalText2(value.host_version, 256) && validObservations(value.observations);
|
|
1930
|
-
}
|
|
1931
|
-
function validTurn(value) {
|
|
1932
|
-
return exactOptional(value, ["local_session_ref", "local_turn_ref"], ["model", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && optionalText2(value.model, 256) && validObservations(value.observations);
|
|
1933
|
-
}
|
|
1934
|
-
function validInput(value) {
|
|
1935
|
-
return exactOptional(value, ["local_session_ref", "local_turn_ref", "local_input_ref", "input_kind", "origin_input", "observations"], ["local_turn_aliases", "context_expires_at"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && safeRef2(value.local_input_ref) && validInputObservations(value.observations) && isCealHostInputKind(value.input_kind) && typeof value.origin_input === "boolean" && validInputOptionals(value);
|
|
1936
|
-
}
|
|
1937
|
-
function validInputObservations(value) {
|
|
1938
|
-
return validObservations(value) && Array.isArray(value) && value.filter((item) => record2(item) && item.observation_kind === "prompt").length === 1;
|
|
1939
|
-
}
|
|
1940
|
-
function validInputOptionals(value) {
|
|
1941
|
-
return optionalRefs(value.local_turn_aliases) && (value.context_expires_at === void 0 || timestamp(value.context_expires_at));
|
|
1942
|
-
}
|
|
1943
|
-
function validTerminal(value) {
|
|
1944
|
-
return exactOptional(value, ["local_session_ref", "local_turn_ref", "terminal_state"], ["abort_reason", "duration_ms", "time_to_first_token_ms", "usage", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && validTerminalDisposition(value.terminal_state, value.abort_reason) && optionalNonNegative(value.duration_ms) && optionalNonNegative(value.time_to_first_token_ms) && (value.usage === void 0 || jsonValue(value.usage)) && validObservations(value.observations);
|
|
1945
|
-
}
|
|
1946
|
-
function validTerminalDisposition(state, reason) {
|
|
1947
|
-
return (state === "completed" || state === "aborted") && (reason === void 0 || state === "aborted" && safeRef2(reason));
|
|
1948
|
-
}
|
|
1949
|
-
function validStatus(value) {
|
|
1950
|
-
return exactOptional(value, ["status", "reason_code"], ["detail"]) && ["ready", "degraded", "stopping"].includes(String(value.status)) && safeRef2(value.reason_code) && optionalText2(value.detail, 4096);
|
|
1951
|
-
}
|
|
1952
|
-
function optionalText2(value, maxLength) {
|
|
1953
|
-
return value === void 0 || typeof value === "string" && value.length > 0 && value.length <= maxLength;
|
|
1954
|
-
}
|
|
1955
|
-
function optionalNonNegative(value) {
|
|
1956
|
-
return value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1957
|
-
}
|
|
1958
|
-
function optionalRefs(value) {
|
|
1959
|
-
return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef2);
|
|
1960
|
-
}
|
|
1961
|
-
function validObservations(value) {
|
|
1962
|
-
if (value === void 0) return true;
|
|
1963
|
-
if (!Array.isArray(value) || value.length > 32) return false;
|
|
1964
|
-
return value.every((item) => {
|
|
1965
|
-
try {
|
|
1966
|
-
validateCealHostObservationDraft(item);
|
|
1967
|
-
return true;
|
|
1968
|
-
} catch {
|
|
1969
|
-
return false;
|
|
1970
|
-
}
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
function jsonValue(value, depth = 0) {
|
|
1974
|
-
if (depth > 16) return false;
|
|
1975
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1976
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
1977
|
-
if (Array.isArray(value)) return value.length <= 256 && value.every((item) => jsonValue(item, depth + 1));
|
|
1978
|
-
return record2(value) && Object.keys(value).length <= 256 && Object.entries(value).every(([key, item]) => key.length > 0 && key.length <= 128 && jsonValue(item, depth + 1));
|
|
1979
|
-
}
|
|
1980
|
-
function record2(value) {
|
|
1981
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1982
|
-
}
|
|
1983
|
-
function safeRef2(value) {
|
|
1984
|
-
return typeof value === "string" && SAFE_REF2.test(value);
|
|
1985
|
-
}
|
|
1986
|
-
function positive(value) {
|
|
1987
|
-
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
1988
|
-
}
|
|
1989
|
-
function timestamp(value) {
|
|
1990
|
-
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
1991
|
-
}
|
|
1992
|
-
function hostProducerFrameKind(value) {
|
|
1993
|
-
return isHostProducerFrameKind(value);
|
|
1994
|
-
}
|
|
1995
|
-
function exactKeys2(value, keys) {
|
|
1996
|
-
return JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
|
|
1997
|
-
}
|
|
1998
|
-
function exactOptional(value, required, optional) {
|
|
1999
|
-
const keys = Object.keys(value);
|
|
2000
|
-
return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2001
|
-
}
|
|
2002
2245
|
|
|
2003
2246
|
// host/relay/src/host-producer-core.ts
|
|
2004
|
-
import { createHash as
|
|
2005
|
-
import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
|
|
2006
|
-
import { mkdir as mkdir2, open as open2, readFile, rename as rename2, writeFile } from "node:fs/promises";
|
|
2007
|
-
import { join as join2 } from "node:path";
|
|
2008
|
-
|
|
2009
|
-
// host/relay/src/relay-ipc.ts
|
|
2010
|
-
import { createConnection, createServer } from "node:net";
|
|
2011
|
-
|
|
2012
|
-
// host/relay/src/durable-atomic-replacement.ts
|
|
2013
|
-
import { O_RDONLY } from "node:constants";
|
|
2014
|
-
import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
|
|
2015
|
-
import { dirname } from "node:path";
|
|
2016
|
-
var DEFAULT_PORT = {
|
|
2017
|
-
platform: process.platform,
|
|
2018
|
-
mkdir: (path, options) => mkdir(path, options),
|
|
2019
|
-
lstat: (path) => lstat(path),
|
|
2020
|
-
open_file: (path) => open(path, "wx", 384),
|
|
2021
|
-
rename,
|
|
2022
|
-
unlink,
|
|
2023
|
-
open_directory: (path) => open(path, O_RDONLY),
|
|
2024
|
-
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2025
|
-
};
|
|
2026
|
-
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
2027
|
-
const parent = dirname(target);
|
|
2028
|
-
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
2029
|
-
const parentStats = await port.lstat(parent);
|
|
2030
|
-
if (!parentStats.isDirectory() || parentStats.isSymbolicLink() || !ownerPrivate(parentStats, port.platform)) throw new TypeError(`Host Relay durable-state directory is unsafe: ${refusalFacts([{ name: "parent_stats", value: parentStats, expected: "an owner-private directory" }, { name: "platform", value: port.platform }])}.`);
|
|
2031
|
-
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
2032
|
-
let staged;
|
|
2033
|
-
const failures = [];
|
|
2034
|
-
try {
|
|
2035
|
-
staged = await port.open_file(temporary);
|
|
2036
|
-
await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
2037
|
-
await staged.sync();
|
|
2038
|
-
const closing = staged;
|
|
2039
|
-
staged = void 0;
|
|
2040
|
-
await closing.close();
|
|
2041
|
-
await port.rename(temporary, target);
|
|
2042
|
-
await syncParentDirectory(parent, port);
|
|
2043
|
-
} catch (error) {
|
|
2044
|
-
failures.push(error);
|
|
2045
|
-
}
|
|
2046
|
-
await closeStaged(staged, failures);
|
|
2047
|
-
await removeTemporary(temporary, port, failures);
|
|
2048
|
-
throwFailures(failures);
|
|
2049
|
-
}
|
|
2050
|
-
async function syncParentDirectory(path, port) {
|
|
2051
|
-
if (port.platform === "win32") return;
|
|
2052
|
-
const handle = await port.open_directory(path);
|
|
2053
|
-
let failure;
|
|
2054
|
-
try {
|
|
2055
|
-
await handle.sync();
|
|
2056
|
-
} catch (error) {
|
|
2057
|
-
if (!directorySyncUnsupported(error)) failure = error;
|
|
2058
|
-
}
|
|
2059
|
-
try {
|
|
2060
|
-
await handle.close();
|
|
2061
|
-
} catch (error) {
|
|
2062
|
-
if (failure === void 0) failure = error;
|
|
2063
|
-
else failure = new AggregateError([failure, error], "Host Relay directory sync and close failed.");
|
|
2064
|
-
}
|
|
2065
|
-
if (failure !== void 0) throw failure;
|
|
2066
|
-
}
|
|
2067
|
-
function ownerPrivate(stats, platform) {
|
|
2068
|
-
if (platform === "win32") return true;
|
|
2069
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
2070
|
-
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
2071
|
-
}
|
|
2072
|
-
async function closeStaged(staged, failures) {
|
|
2073
|
-
if (staged !== void 0) try {
|
|
2074
|
-
await staged.close();
|
|
2075
|
-
} catch (error) {
|
|
2076
|
-
failures.push(error);
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
async function removeTemporary(path, port, failures) {
|
|
2080
|
-
try {
|
|
2081
|
-
await port.unlink(path);
|
|
2082
|
-
} catch (error) {
|
|
2083
|
-
if (errorCode(error) !== "ENOENT") failures.push(error);
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
function throwFailures(failures) {
|
|
2087
|
-
if (failures.length === 1) throw failures[0];
|
|
2088
|
-
if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
|
|
2089
|
-
}
|
|
2090
|
-
function directorySyncUnsupported(error) {
|
|
2091
|
-
return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode(error) ?? "");
|
|
2092
|
-
}
|
|
2093
|
-
function errorCode(error) {
|
|
2094
|
-
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
// host/relay/src/relay-request-binding-store.ts
|
|
2098
|
-
var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
2247
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2099
2248
|
|
|
2100
|
-
// host/
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
"
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
"local_turn_aliases",
|
|
2111
|
-
"prompt_ref",
|
|
2112
|
-
"request_ref",
|
|
2113
|
-
"bound_at",
|
|
2114
|
-
"host_source_ref",
|
|
2115
|
-
"host_session_ref",
|
|
2116
|
-
"host_turn_ref",
|
|
2117
|
-
"host_input_ref"
|
|
2118
|
-
]);
|
|
2119
|
-
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
2120
|
-
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
2121
|
-
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
2122
|
-
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2123
|
-
var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
|
|
2124
|
-
var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
2125
|
-
"context_stale",
|
|
2126
|
-
"context_expired",
|
|
2127
|
-
"host_source_refused",
|
|
2128
|
-
"host_credential_refused",
|
|
2129
|
-
"gateway_unavailable",
|
|
2130
|
-
"gateway_refused",
|
|
2131
|
-
"gateway_response_invalid",
|
|
2132
|
-
"gateway_rate_limited",
|
|
2133
|
-
"producer_frame_invalid",
|
|
2134
|
-
"deadline",
|
|
2135
|
-
"cancelled",
|
|
2136
|
-
"mint_failed",
|
|
2137
|
-
"relay_failed",
|
|
2138
|
-
"request_authority_unavailable",
|
|
2139
|
-
"request_missing",
|
|
2140
|
-
"authority_mismatch",
|
|
2141
|
-
"host_source_mismatch",
|
|
2142
|
-
"host_session_mismatch",
|
|
2143
|
-
"host_turn_mismatch",
|
|
2144
|
-
"host_input_mismatch",
|
|
2145
|
-
"host_prompt_mismatch"
|
|
2146
|
-
]);
|
|
2147
|
-
var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
|
|
2148
|
-
"context_stale",
|
|
2149
|
-
"context_expired",
|
|
2150
|
-
"host_source_refused",
|
|
2151
|
-
"host_credential_refused",
|
|
2152
|
-
"gateway_unavailable",
|
|
2153
|
-
"gateway_rate_limited",
|
|
2154
|
-
"gateway_refused",
|
|
2155
|
-
"gateway_response_invalid",
|
|
2156
|
-
"producer_frame_invalid"
|
|
2157
|
-
]);
|
|
2158
|
-
var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
|
|
2159
|
-
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
2160
|
-
"permission_denied",
|
|
2161
|
-
"socket_missing",
|
|
2162
|
-
"connection_refused",
|
|
2163
|
-
"closed_early",
|
|
2164
|
-
"response_invalid",
|
|
2165
|
-
"timed_out",
|
|
2166
|
-
"cancelled",
|
|
2167
|
-
"unavailable"
|
|
2168
|
-
]);
|
|
2169
|
-
var HostRelayIpcError = class extends Error {
|
|
2170
|
-
code;
|
|
2171
|
-
/** Set only when the client-side socket exchange itself failed. */
|
|
2172
|
-
failure;
|
|
2173
|
-
constructor(code2, message = code2, failure) {
|
|
2249
|
+
// host/secure-file.ts
|
|
2250
|
+
import { execFile } from "node:child_process";
|
|
2251
|
+
import { constants } from "node:fs";
|
|
2252
|
+
import { lstat, open } from "node:fs/promises";
|
|
2253
|
+
import { dirname, win32 } from "node:path";
|
|
2254
|
+
import { fileURLToPath } from "node:url";
|
|
2255
|
+
var SecureFileCustodyError = class extends Error {
|
|
2256
|
+
name = "SecureFileCustodyError";
|
|
2257
|
+
reason;
|
|
2258
|
+
constructor(reason, message) {
|
|
2174
2259
|
super(message);
|
|
2175
|
-
this.
|
|
2176
|
-
this.failure = failure;
|
|
2177
|
-
}
|
|
2178
|
-
};
|
|
2179
|
-
function hostRelayExchangeFailure(error) {
|
|
2180
|
-
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
2181
|
-
}
|
|
2182
|
-
function socketErrorFailure(error) {
|
|
2183
|
-
switch (errorCode2(error)) {
|
|
2184
|
-
case "EACCES":
|
|
2185
|
-
case "EPERM":
|
|
2186
|
-
return "permission_denied";
|
|
2187
|
-
case "ENOENT":
|
|
2188
|
-
return "socket_missing";
|
|
2189
|
-
case "ECONNREFUSED":
|
|
2190
|
-
return "connection_refused";
|
|
2191
|
-
default:
|
|
2192
|
-
return "unavailable";
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
var HostRelayIpcAdmissionPort = class {
|
|
2196
|
-
endpoint;
|
|
2197
|
-
timeout_ms;
|
|
2198
|
-
constructor(endpoint, timeoutMs = 5e3) {
|
|
2199
|
-
this.endpoint = endpoint;
|
|
2200
|
-
this.timeout_ms = timeoutMs;
|
|
2201
|
-
}
|
|
2202
|
-
async admit(frame) {
|
|
2203
|
-
const response2 = await submitHostProducerFrame(this.endpoint, frame, this.timeout_ms);
|
|
2204
|
-
if (!relayAdmissionCode(response2.code)) throw new HostRelayIpcError(response2.code);
|
|
2205
|
-
return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
|
|
2260
|
+
this.reason = reason;
|
|
2206
2261
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
this.
|
|
2213
|
-
this.timeout_ms = timeoutMs;
|
|
2214
|
-
}
|
|
2215
|
-
async register(input) {
|
|
2216
|
-
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
|
|
2217
|
-
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2218
|
-
if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
|
|
2262
|
+
get code() {
|
|
2263
|
+
if (this.reason === "reparse_point") return "ELOOP";
|
|
2264
|
+
if (this.reason === "not_found") return "ENOENT";
|
|
2265
|
+
if (this.reason === "too_large") return "secure_file_too_large";
|
|
2266
|
+
if (this.reason === "already_exists") return "EEXIST";
|
|
2267
|
+
return this.reason === "not_private" ? "secure_file_not_private" : "secure_file_unavailable";
|
|
2219
2268
|
}
|
|
2220
2269
|
};
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
function
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
if (
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2270
|
+
var WINDOWS_SECUREFS_REFUSAL_SCHEMA = "ceal.host_securefs_refusal.v1";
|
|
2271
|
+
var WINDOWS_SECUREFS_TIMEOUT_MS = 1e4;
|
|
2272
|
+
var WINDOWS_SECUREFS_DIAGNOSTIC_BYTES = 4096;
|
|
2273
|
+
function secureOpenFlags(flags, platform) {
|
|
2274
|
+
if (platform === "win32") return flags;
|
|
2275
|
+
const nofollow = constants.O_NOFOLLOW;
|
|
2276
|
+
if (typeof nofollow === "number" && nofollow !== 0) return flags | nofollow;
|
|
2277
|
+
throw new Error("Secure file reads require O_NOFOLLOW on this platform.");
|
|
2278
|
+
}
|
|
2279
|
+
async function openNoFollow(path, flags = constants.O_RDONLY, mode, platform = process.platform, _custody = {}) {
|
|
2280
|
+
if (platform !== "win32") return await open(path, secureOpenFlags(flags, platform), mode);
|
|
2281
|
+
return await openWindowsIdentityScreened(path, flags, mode);
|
|
2282
|
+
}
|
|
2283
|
+
async function openWindowsIdentityScreened(path, flags, mode) {
|
|
2284
|
+
const handle = await open(path, flags, mode);
|
|
2285
|
+
try {
|
|
2286
|
+
const first = await handle.stat();
|
|
2287
|
+
if (!first.isFile()) throw new SecureFileCustodyError("not_private", "The Windows Host file is not a regular file.");
|
|
2288
|
+
await assertWindowsLinkFree(path, first);
|
|
2289
|
+
await assertWindowsIdentityStable(handle, first, path);
|
|
2290
|
+
return handle;
|
|
2291
|
+
} catch (error) {
|
|
2292
|
+
await handle.close();
|
|
2293
|
+
throw error;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
async function assertWindowsLinkFree(path, first) {
|
|
2297
|
+
const entry = await lstat(path);
|
|
2298
|
+
if (entry.isSymbolicLink() || entry.dev !== first.dev || entry.ino !== first.ino) {
|
|
2299
|
+
throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
async function assertWindowsIdentityStable(handle, first, path) {
|
|
2303
|
+
const second = await handle.stat();
|
|
2304
|
+
if (second.dev !== first.dev || second.ino !== first.ino || second.size !== first.size || second.mtimeMs !== first.mtimeMs) {
|
|
2305
|
+
throw new SecureFileCustodyError("unavailable", "The Windows Host file changed while its identity was checked.");
|
|
2306
|
+
}
|
|
2307
|
+
const entry = await lstat(path);
|
|
2308
|
+
if (entry.isSymbolicLink()) throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2309
|
+
if (entry.dev !== second.dev || entry.ino !== second.ino) throw new SecureFileCustodyError("unavailable", "The Windows Host file path no longer names the opened file.");
|
|
2310
|
+
}
|
|
2311
|
+
async function readPrivateFileWithSecurefs(path, maxBytes, custody = {}) {
|
|
2312
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) throw new TypeError("The securefs read bound must be a positive safe integer.");
|
|
2313
|
+
const result = await runWindowsSecurefs(["internal", "securefs", "read-private-file", path, String(maxBytes)], maxBytes, custody);
|
|
2314
|
+
if (result.exit_code === 0 && result.stderr.length === 0 && result.stdout.length <= maxBytes) return result.stdout;
|
|
2315
|
+
if (result.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned bytes with a refusal.");
|
|
2316
|
+
if (result.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command exceeded its output contract.");
|
|
2317
|
+
throw securefsRefusal(result.stderr);
|
|
2318
|
+
}
|
|
2319
|
+
async function runWindowsSecurefs(args, maxBytes, custody) {
|
|
2320
|
+
if (custody.run_securefs_command !== void 0) return await custody.run_securefs_command(args);
|
|
2321
|
+
const executable = custody.securefs_executable ?? resolveInstalledHostExecutable();
|
|
2322
|
+
const processArgs = [...custody.securefs_args_prefix ?? [], ...args];
|
|
2323
|
+
return await new Promise((resolve2, reject) => {
|
|
2324
|
+
execFile(executable, processArgs, { encoding: "buffer", timeout: WINDOWS_SECUREFS_TIMEOUT_MS, maxBuffer: maxBytes + WINDOWS_SECUREFS_DIAGNOSTIC_BYTES }, (error, stdout, stderr) => {
|
|
2325
|
+
const exitCode = error === null ? 0 : typeof error.code === "number" ? error.code : null;
|
|
2326
|
+
if (exitCode === null) {
|
|
2327
|
+
reject(new SecureFileCustodyError("unavailable", "The installed Windows Host securefs command could not run."));
|
|
2328
|
+
return;
|
|
2256
2329
|
}
|
|
2257
|
-
|
|
2258
|
-
socket.once("end", () => {
|
|
2259
|
-
if (!settled) finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC closed without a response.", "closed_early"));
|
|
2330
|
+
resolve2({ exit_code: exitCode, stdout: Buffer.from(stdout), stderr: Buffer.from(stderr) });
|
|
2260
2331
|
});
|
|
2261
2332
|
});
|
|
2262
2333
|
}
|
|
2263
|
-
function
|
|
2264
|
-
if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response is truncated: ${refusalFact("response_bytes", input.byteLength, `at least ${HOST_RELAY_IPC_LENGTH_BYTES}`)}.`);
|
|
2265
|
-
const length = input.readUInt32BE(0);
|
|
2266
|
-
if (length === 0 || length > HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES || input.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `between 1 and ${HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES}` }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
|
|
2334
|
+
function securefsRefusal(stderr) {
|
|
2267
2335
|
let value;
|
|
2268
2336
|
try {
|
|
2269
|
-
value = JSON.parse(
|
|
2337
|
+
value = JSON.parse(stderr.toString("utf8"));
|
|
2270
2338
|
} catch {
|
|
2271
|
-
|
|
2339
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an unreadable refusal.");
|
|
2272
2340
|
}
|
|
2273
|
-
if (!
|
|
2274
|
-
return
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
return record3(value) && value.schema_version === HOST_RELAY_IPC_RESPONSE_SCHEMA && hostRelayIpcCode(value.code) && nonnegative(value.admitted_through_sequence) && nonnegative(value.delivered_through_sequence) && Object.keys(value).length === 4;
|
|
2341
|
+
if (!isRecord2(value) || value.schema_version !== WINDOWS_SECUREFS_REFUSAL_SCHEMA || typeof value.code !== "string") return new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an unreadable refusal.");
|
|
2342
|
+
if (value.code === "reparse_point") return new SecureFileCustodyError("reparse_point", "The Windows Host securefs file is a reparse point.");
|
|
2343
|
+
if (value.code === "not_found") return new SecureFileCustodyError("not_found", "The Windows Host securefs file was not found.");
|
|
2344
|
+
if (value.code === "too_large") return new SecureFileCustodyError("too_large", "The Windows Host securefs file exceeds its read bound.");
|
|
2345
|
+
if (value.code === "already_exists") return new SecureFileCustodyError("already_exists", "The Windows Host securefs target already exists.");
|
|
2346
|
+
if (value.code === "not_private") return new SecureFileCustodyError("not_private", "The Windows Host securefs file is not private to the current user.");
|
|
2347
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command refused the file read.");
|
|
2281
2348
|
}
|
|
2282
|
-
function
|
|
2283
|
-
|
|
2349
|
+
function resolveInstalledHostExecutable() {
|
|
2350
|
+
const privateRuntimeRoot = dirname(fileURLToPath(import.meta.url));
|
|
2351
|
+
return win32.join(privateRuntimeRoot, "..", "..", "bin", "ceal-host.exe");
|
|
2284
2352
|
}
|
|
2285
|
-
function
|
|
2286
|
-
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
2287
|
-
}
|
|
2288
|
-
function record3(value) {
|
|
2353
|
+
function isRecord2(value) {
|
|
2289
2354
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2290
2355
|
}
|
|
2291
|
-
function
|
|
2292
|
-
|
|
2293
|
-
const length = buffer.readUInt32BE(0);
|
|
2294
|
-
if (length === 0 || length > HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFact("declared_bytes", length, `between 1 and ${HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES}`)}.`);
|
|
2295
|
-
if (buffer.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2296
|
-
if (buffer.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC returned trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: buffer.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
|
|
2297
|
-
return decodeHostRelayIpcResponse(buffer);
|
|
2298
|
-
}
|
|
2299
|
-
function decodeCurrentPromptResponseBuffer(buffer) {
|
|
2300
|
-
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
|
|
2301
|
-
if (value === null) return null;
|
|
2302
|
-
if (!record3(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");
|
|
2303
|
-
return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
|
|
2304
|
-
}
|
|
2305
|
-
function decodeLengthPrefixedJson(input, maximum) {
|
|
2306
|
-
if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2307
|
-
const length = input.readUInt32BE(0);
|
|
2308
|
-
if (length === 0 || length > maximum) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message length is invalid: ${refusalFact("declared_bytes", length, `between 1 and ${maximum}`)}.`);
|
|
2309
|
-
if (input.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2310
|
-
if (input.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message has trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
|
|
2311
|
-
try {
|
|
2312
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
|
|
2313
|
-
} catch {
|
|
2314
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC message is invalid.");
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
function encodeLengthPrefixed(value) {
|
|
2318
|
-
const body = Buffer.from(JSON.stringify(value), "utf8");
|
|
2319
|
-
const output = Buffer.allocUnsafe(body.length + HOST_RELAY_IPC_LENGTH_BYTES);
|
|
2320
|
-
output.writeUInt32BE(body.length, 0);
|
|
2321
|
-
body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
|
|
2322
|
-
return output;
|
|
2356
|
+
function isOwnedBy(uid, expectedUid) {
|
|
2357
|
+
return Number.isSafeInteger(uid) && Number.isSafeInteger(expectedUid) && uid === expectedUid;
|
|
2323
2358
|
}
|
|
2324
|
-
function
|
|
2325
|
-
|
|
2326
|
-
return
|
|
2359
|
+
function isSelfOwned(uid, platform = process.platform, _handle) {
|
|
2360
|
+
if (platform === "win32" || typeof process.geteuid !== "function") return false;
|
|
2361
|
+
return isOwnedBy(uid, process.geteuid());
|
|
2327
2362
|
}
|
|
2328
|
-
function
|
|
2329
|
-
|
|
2363
|
+
function isPrivateFileMode(mode, platform = process.platform, _handle) {
|
|
2364
|
+
if (platform === "win32") return false;
|
|
2365
|
+
return (mode & 63) === 0;
|
|
2330
2366
|
}
|
|
2331
2367
|
|
|
2332
2368
|
// host/relay/src/host-producer-core.ts
|
|
@@ -2335,6 +2371,20 @@ function isHostTranscriptCursorReason(value) {
|
|
|
2335
2371
|
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2336
2372
|
}
|
|
2337
2373
|
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2374
|
+
var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
|
|
2375
|
+
var HOST_INVOCATION_TRANSCRIPT_TAIL_MAX_BYTES = 1024 * 1024;
|
|
2376
|
+
function transcriptScanFailureKind(error, adapterFailureKind) {
|
|
2377
|
+
if (transcriptErrorCode(error) === "ELOOP") return "symlink";
|
|
2378
|
+
const adapter = adapterFailureKind(error);
|
|
2379
|
+
if (adapter !== void 0) return adapter;
|
|
2380
|
+
const code2 = transcriptErrorCode(error);
|
|
2381
|
+
if (code2 === "EFBIG") return "size_budget";
|
|
2382
|
+
if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
|
|
2383
|
+
return "other";
|
|
2384
|
+
}
|
|
2385
|
+
function transcriptErrorCode(error) {
|
|
2386
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
2387
|
+
}
|
|
2338
2388
|
var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
|
|
2339
2389
|
async function readTranscriptCompleteChunk(handle, offset, size) {
|
|
2340
2390
|
const available = Math.max(0, size - offset);
|
|
@@ -2379,10 +2429,10 @@ async function transcriptCursorBoundsReason(handle, cursor, size) {
|
|
|
2379
2429
|
return null;
|
|
2380
2430
|
}
|
|
2381
2431
|
function transcriptSha256(value) {
|
|
2382
|
-
return
|
|
2432
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
2383
2433
|
}
|
|
2384
2434
|
function transcriptObjectSha256(value) {
|
|
2385
|
-
return
|
|
2435
|
+
return createHash4("sha256").update(JSON.stringify(value), "utf8").digest("hex");
|
|
2386
2436
|
}
|
|
2387
2437
|
function isTranscriptRecord(value) {
|
|
2388
2438
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2451,11 +2501,10 @@ var HostTranscriptWatcher = class {
|
|
|
2451
2501
|
async openTranscript(registration, scan, handlers) {
|
|
2452
2502
|
let handle;
|
|
2453
2503
|
try {
|
|
2454
|
-
handle = await
|
|
2504
|
+
handle = await openNoFollow(this.transcriptPathOf(registration));
|
|
2455
2505
|
return await scan(handle);
|
|
2456
2506
|
} catch (error) {
|
|
2457
2507
|
if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
|
|
2458
|
-
if (hostRelayExchangeFailure(error) !== void 0) throw error;
|
|
2459
2508
|
return handlers.failed(handle, error);
|
|
2460
2509
|
} finally {
|
|
2461
2510
|
await handle?.close();
|
|
@@ -2501,29 +2550,12 @@ var HostTranscriptWatcher = class {
|
|
|
2501
2550
|
return transcriptCursorBoundsReason(handle, cursor, size);
|
|
2502
2551
|
}
|
|
2503
2552
|
};
|
|
2504
|
-
var HOST_PRODUCER_STATE_IDENTITY = {
|
|
2505
|
-
codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
|
|
2506
|
-
claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
|
|
2507
|
-
};
|
|
2508
2553
|
var HostProducerDrainCore = class {
|
|
2509
2554
|
host;
|
|
2510
|
-
relay;
|
|
2511
|
-
statePath;
|
|
2512
|
-
stateSchema;
|
|
2513
|
-
installationRef;
|
|
2514
|
-
epoch;
|
|
2515
2555
|
now;
|
|
2516
2556
|
tail = Promise.resolve();
|
|
2517
2557
|
constructor(host, runtime) {
|
|
2518
|
-
if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
|
|
2519
|
-
const epoch = runtime.producer_epoch ?? 1;
|
|
2520
|
-
if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
|
|
2521
2558
|
this.host = host;
|
|
2522
|
-
this.relay = runtime.relay;
|
|
2523
|
-
this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
|
|
2524
|
-
this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
|
|
2525
|
-
this.installationRef = runtime.producer_installation_ref;
|
|
2526
|
-
this.epoch = epoch;
|
|
2527
2559
|
this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
|
|
2528
2560
|
}
|
|
2529
2561
|
drainOutbox() {
|
|
@@ -2533,116 +2565,145 @@ var HostProducerDrainCore = class {
|
|
|
2533
2565
|
}
|
|
2534
2566
|
async drainLocked() {
|
|
2535
2567
|
const read = await this.host.listEnvelopes();
|
|
2536
|
-
let
|
|
2568
|
+
let removed = 0;
|
|
2537
2569
|
let expired = 0;
|
|
2538
|
-
let retained = 0;
|
|
2539
2570
|
for (const envelope of read.entries) {
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
await this.host.removeEnvelope(envelope);
|
|
2543
|
-
expired += 1;
|
|
2544
|
-
continue;
|
|
2545
|
-
}
|
|
2546
|
-
const frames = await this.reserve(envelope);
|
|
2547
|
-
let complete = true;
|
|
2548
|
-
for (const frame of frames) {
|
|
2549
|
-
const outcome = await this.relay.admit(frame);
|
|
2550
|
-
if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
|
|
2551
|
-
complete = false;
|
|
2552
|
-
break;
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
if (!complete) {
|
|
2556
|
-
retained += 1;
|
|
2557
|
-
break;
|
|
2558
|
-
}
|
|
2571
|
+
if (Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) expired += 1;
|
|
2572
|
+
else removed += 1;
|
|
2559
2573
|
await this.host.removeEnvelope(envelope);
|
|
2560
|
-
await this.release(this.host.submissionRefOf(envelope));
|
|
2561
|
-
admitted += 1;
|
|
2562
2574
|
}
|
|
2563
|
-
return {
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2575
|
+
return { removed, expired, malformed: read.malformed };
|
|
2576
|
+
}
|
|
2577
|
+
};
|
|
2578
|
+
|
|
2579
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
2580
|
+
import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
|
|
2581
|
+
import { dirname as dirname2 } from "node:path";
|
|
2582
|
+
|
|
2583
|
+
// host/windows-rename-retry.ts
|
|
2584
|
+
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
2585
|
+
var RETRY_WINDOW_MS = 1e4;
|
|
2586
|
+
var RETRY_START_DELAY_MS = 100;
|
|
2587
|
+
var RETRY_MAX_DELAY_MS = 2e3;
|
|
2588
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["EBUSY", "EPERM", "EACCES"]);
|
|
2589
|
+
var WindowsRenameRetryError = class extends Error {
|
|
2590
|
+
code = "rename_unavailable";
|
|
2591
|
+
constructor(target, attempts, options = {}) {
|
|
2592
|
+
super(`Windows did not permit renaming into place within the bounded retry window: ${target} (after ${attempts} attempts). Wait for the process holding the path to release it, then rerun the same install or update command.`, options);
|
|
2593
|
+
this.name = "WindowsRenameRetryError";
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
function resolveRetrySchedule(tuning) {
|
|
2597
|
+
return {
|
|
2598
|
+
windowMs: tuning.windowMs ?? RETRY_WINDOW_MS,
|
|
2599
|
+
startDelayMs: tuning.startDelayMs ?? RETRY_START_DELAY_MS,
|
|
2600
|
+
maxDelayMs: tuning.maxDelayMs ?? RETRY_MAX_DELAY_MS,
|
|
2601
|
+
sleep: tuning.sleep ?? ((ms) => setTimeout2(ms)),
|
|
2602
|
+
now: tuning.now ?? (() => Date.now())
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
async function renameWithWindowsRetry(operation, tuning) {
|
|
2606
|
+
const schedule = resolveRetrySchedule(tuning);
|
|
2607
|
+
const deadline = schedule.now() + schedule.windowMs;
|
|
2608
|
+
let delayMs = schedule.startDelayMs;
|
|
2609
|
+
let attempts = 0;
|
|
2610
|
+
for (; ; ) {
|
|
2611
|
+
attempts += 1;
|
|
2595
2612
|
try {
|
|
2596
|
-
|
|
2597
|
-
return
|
|
2613
|
+
await operation();
|
|
2614
|
+
return;
|
|
2598
2615
|
} catch (error) {
|
|
2599
|
-
if (
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
}
|
|
2603
|
-
async writeState(state) {
|
|
2604
|
-
await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
|
|
2605
|
-
const temporary = `${this.statePath}.${process.pid}.new`;
|
|
2606
|
-
await writeFile(temporary, JSON.stringify(state), { mode: 384 });
|
|
2607
|
-
await rename2(temporary, this.statePath);
|
|
2608
|
-
}
|
|
2609
|
-
decodeState(value) {
|
|
2610
|
-
const label = this.host.producer_label;
|
|
2611
|
-
const fail = (code2, message) => this.host.fail(code2, message);
|
|
2612
|
-
if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
|
|
2613
|
-
throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
|
|
2614
|
-
{ name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
|
|
2615
|
-
{ name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
|
|
2616
|
-
])}).`);
|
|
2617
|
-
}
|
|
2618
|
-
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}`)}).`);
|
|
2619
|
-
const pending = {};
|
|
2620
|
-
const storedEpoch = Number(value.producer_epoch);
|
|
2621
|
-
for (const [submissionRef2, frames] of Object.entries(value.pending)) {
|
|
2622
|
-
if (!isHostSafeRef(submissionRef2) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
|
|
2623
|
-
pending[submissionRef2] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
|
|
2616
|
+
if (tuning.platform !== "win32" || !isRetryableRenameError(error)) throw error;
|
|
2617
|
+
await sleepWithinWindow(schedule, deadline, delayMs, () => new WindowsRenameRetryError(tuning.target, attempts, { cause: error }));
|
|
2618
|
+
delayMs = Math.min(delayMs * 2, schedule.maxDelayMs);
|
|
2624
2619
|
}
|
|
2625
|
-
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
|
|
2626
|
-
}
|
|
2627
|
-
decodeStoredFrame(value, storedEpoch) {
|
|
2628
|
-
const label = this.host.producer_label;
|
|
2629
|
-
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
2630
|
-
const encoded = Buffer.allocUnsafe(json.length + 4);
|
|
2631
|
-
encoded.writeUInt32BE(json.length, 0);
|
|
2632
|
-
json.copy(encoded, 4);
|
|
2633
|
-
const frame = decodeHostProducerFrame(encoded);
|
|
2634
|
-
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([
|
|
2635
|
-
{ name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
|
|
2636
|
-
{ name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
|
|
2637
|
-
{ name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
|
|
2638
|
-
])}).`);
|
|
2639
|
-
return frame;
|
|
2640
2620
|
}
|
|
2621
|
+
}
|
|
2622
|
+
async function sleepWithinWindow(schedule, deadline, delayMs, exhausted) {
|
|
2623
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2624
|
+
await schedule.sleep(Math.min(delayMs, deadline - schedule.now()));
|
|
2625
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2626
|
+
}
|
|
2627
|
+
function isRetryableRenameError(error) {
|
|
2628
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RETRYABLE_CODES.has(error.code);
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
2632
|
+
var DEFAULT_PORT = {
|
|
2633
|
+
platform: process.platform,
|
|
2634
|
+
mkdir: (path, options) => mkdir(path, options),
|
|
2635
|
+
lstat: (path) => lstat2(path),
|
|
2636
|
+
open_file: (path) => open2(path, "wx", 384),
|
|
2637
|
+
rename,
|
|
2638
|
+
unlink,
|
|
2639
|
+
open_directory: (path) => open2(path),
|
|
2640
|
+
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2641
2641
|
};
|
|
2642
|
-
function
|
|
2643
|
-
|
|
2642
|
+
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
2643
|
+
const parent = dirname2(target);
|
|
2644
|
+
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
2645
|
+
const parentStats = await port.lstat(parent);
|
|
2646
|
+
const expectedUid = typeof process.geteuid === "function" ? process.geteuid() : parentStats.uid;
|
|
2647
|
+
const ownerPrivate = port.platform === "win32" || isOwnedBy(parentStats.uid, expectedUid) && isPrivateFileMode(parentStats.mode, port.platform);
|
|
2648
|
+
if (!parentStats.isDirectory() || parentStats.isSymbolicLink() || !ownerPrivate) throw new TypeError(`Host Relay durable-state directory is unsafe: ${refusalFacts([{ name: "parent_stats", value: parentStats, expected: "an owner-private directory" }, { name: "platform", value: port.platform }])}.`);
|
|
2649
|
+
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
2650
|
+
let staged;
|
|
2651
|
+
const failures = [];
|
|
2652
|
+
try {
|
|
2653
|
+
staged = await port.open_file(temporary);
|
|
2654
|
+
await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
2655
|
+
await staged.sync();
|
|
2656
|
+
const closing = staged;
|
|
2657
|
+
staged = void 0;
|
|
2658
|
+
await closing.close();
|
|
2659
|
+
await renameWithWindowsRetry(() => port.rename(temporary, target), { ...port.rename_retry, platform: port.platform, target });
|
|
2660
|
+
await syncParentDirectory(parent, port);
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
failures.push(error);
|
|
2663
|
+
}
|
|
2664
|
+
await closeStaged(staged, failures);
|
|
2665
|
+
await removeTemporary(temporary, port, failures);
|
|
2666
|
+
throwFailures(failures);
|
|
2667
|
+
}
|
|
2668
|
+
async function syncParentDirectory(path, port) {
|
|
2669
|
+
if (port.platform === "win32") return;
|
|
2670
|
+
const handle = await port.open_directory(path);
|
|
2671
|
+
let failure;
|
|
2672
|
+
try {
|
|
2673
|
+
await handle.sync();
|
|
2674
|
+
} catch (error) {
|
|
2675
|
+
if (!directorySyncUnsupported(error)) failure = error;
|
|
2676
|
+
}
|
|
2677
|
+
try {
|
|
2678
|
+
await handle.close();
|
|
2679
|
+
} catch (error) {
|
|
2680
|
+
if (failure === void 0) failure = error;
|
|
2681
|
+
else failure = new AggregateError([failure, error], "Host Relay directory sync and close failed.");
|
|
2682
|
+
}
|
|
2683
|
+
if (failure !== void 0) throw failure;
|
|
2684
|
+
}
|
|
2685
|
+
async function closeStaged(staged, failures) {
|
|
2686
|
+
if (staged !== void 0) try {
|
|
2687
|
+
await staged.close();
|
|
2688
|
+
} catch (error) {
|
|
2689
|
+
failures.push(error);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
async function removeTemporary(path, port, failures) {
|
|
2693
|
+
try {
|
|
2694
|
+
await port.unlink(path);
|
|
2695
|
+
} catch (error) {
|
|
2696
|
+
if (errorCode(error) !== "ENOENT") failures.push(error);
|
|
2697
|
+
}
|
|
2644
2698
|
}
|
|
2645
|
-
function
|
|
2699
|
+
function throwFailures(failures) {
|
|
2700
|
+
if (failures.length === 1) throw failures[0];
|
|
2701
|
+
if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
|
|
2702
|
+
}
|
|
2703
|
+
function directorySyncUnsupported(error) {
|
|
2704
|
+
return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode(error) ?? "");
|
|
2705
|
+
}
|
|
2706
|
+
function errorCode(error) {
|
|
2646
2707
|
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2647
2708
|
}
|
|
2648
2709
|
|
|
@@ -2656,6 +2717,22 @@ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, produ
|
|
|
2656
2717
|
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2657
2718
|
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2658
2719
|
|
|
2720
|
+
// host/relay/src/relay-pipe-handshake.ts
|
|
2721
|
+
var RELAY_PIPE_FRAME_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2722
|
+
|
|
2723
|
+
// host/relay/src/relay-ipc.ts
|
|
2724
|
+
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2725
|
+
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
2726
|
+
"permission_denied",
|
|
2727
|
+
"socket_missing",
|
|
2728
|
+
"connection_refused",
|
|
2729
|
+
"closed_early",
|
|
2730
|
+
"response_invalid",
|
|
2731
|
+
"timed_out",
|
|
2732
|
+
"cancelled",
|
|
2733
|
+
"unavailable"
|
|
2734
|
+
]);
|
|
2735
|
+
|
|
2659
2736
|
// host/claude/src/claude-invocation-context.ts
|
|
2660
2737
|
function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
|
|
2661
2738
|
return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory, turn_generation_ref: turnGenerationRef });
|
|
@@ -2667,42 +2744,38 @@ function validateClaudeHostInvocationContext(value) {
|
|
|
2667
2744
|
validateContextTimes(value);
|
|
2668
2745
|
}
|
|
2669
2746
|
function contextShape(value) {
|
|
2670
|
-
return
|
|
2747
|
+
return record2(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["working_directory", "turn_generation_ref"]);
|
|
2671
2748
|
}
|
|
2672
2749
|
function validateIdentity(value) {
|
|
2673
2750
|
if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
|
|
2674
2751
|
}
|
|
2675
2752
|
function validateContextReferences(value) {
|
|
2676
|
-
if (!
|
|
2753
|
+
if (!safeRef2(value.prompt_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
2677
2754
|
}
|
|
2678
2755
|
function validateContextTimes(value) {
|
|
2679
|
-
if (!
|
|
2680
|
-
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)}`)}).`);
|
|
2756
|
+
if (!timestamp(value.expires_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
2681
2757
|
}
|
|
2682
2758
|
function validIdentity(value) {
|
|
2683
|
-
return
|
|
2759
|
+
return record2(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef2);
|
|
2684
2760
|
}
|
|
2685
2761
|
function optionalSafeRef(value) {
|
|
2686
|
-
return value === void 0 ||
|
|
2687
|
-
}
|
|
2688
|
-
function optionalTimestamp(value) {
|
|
2689
|
-
return value === void 0 || timestamp2(value);
|
|
2762
|
+
return value === void 0 || safeRef2(value);
|
|
2690
2763
|
}
|
|
2691
2764
|
function optionalDirectory(value) {
|
|
2692
2765
|
return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
|
|
2693
2766
|
}
|
|
2694
|
-
function
|
|
2767
|
+
function safeRef2(value) {
|
|
2695
2768
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2696
2769
|
}
|
|
2697
|
-
function
|
|
2770
|
+
function timestamp(value) {
|
|
2698
2771
|
return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
|
|
2699
2772
|
}
|
|
2700
|
-
function
|
|
2773
|
+
function record2(value) {
|
|
2701
2774
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2702
2775
|
}
|
|
2703
2776
|
|
|
2704
2777
|
// host/claude/src/claude-outbox.ts
|
|
2705
|
-
var
|
|
2778
|
+
var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2706
2779
|
var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
|
|
2707
2780
|
var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
|
|
2708
2781
|
var ClaudeOutboxStore = class {
|
|
@@ -2716,16 +2789,16 @@ var ClaudeOutboxStore = class {
|
|
|
2716
2789
|
constructor(directory) {
|
|
2717
2790
|
if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
|
|
2718
2791
|
this.directory = directory;
|
|
2719
|
-
this.envelopes =
|
|
2720
|
-
this.sessions =
|
|
2721
|
-
this.registrations =
|
|
2722
|
-
this.cursors =
|
|
2723
|
-
this.activeContexts =
|
|
2724
|
-
this.turnSelections =
|
|
2792
|
+
this.envelopes = join2(directory, "envelopes");
|
|
2793
|
+
this.sessions = join2(directory, "sessions");
|
|
2794
|
+
this.registrations = join2(directory, "transcript-registrations");
|
|
2795
|
+
this.cursors = join2(directory, "transcript-cursors");
|
|
2796
|
+
this.activeContexts = join2(directory, "active-contexts");
|
|
2797
|
+
this.turnSelections = join2(directory, "turn-selections");
|
|
2725
2798
|
}
|
|
2726
2799
|
async prepare() {
|
|
2727
2800
|
for (const path of [this.envelopes, this.sessions, this.registrations, this.cursors, this.activeContexts, this.turnSelections]) {
|
|
2728
|
-
await
|
|
2801
|
+
await mkdir2(path, { recursive: true, mode: 448 });
|
|
2729
2802
|
}
|
|
2730
2803
|
}
|
|
2731
2804
|
async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
|
|
@@ -2760,10 +2833,10 @@ var ClaudeOutboxStore = class {
|
|
|
2760
2833
|
* a missing one. Every other caller keeps the fail-closed read above.
|
|
2761
2834
|
*/
|
|
2762
2835
|
async readRawInvocationContext(hostSourceRef, hostSessionRef) {
|
|
2763
|
-
if (!
|
|
2836
|
+
if (!SAFE_REF2.test(hostSourceRef) || !SAFE_REF2.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
|
|
2764
2837
|
await this.prepare();
|
|
2765
2838
|
const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
|
|
2766
|
-
return
|
|
2839
|
+
return context;
|
|
2767
2840
|
}
|
|
2768
2841
|
async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
|
|
2769
2842
|
await this.prepare();
|
|
@@ -2771,9 +2844,9 @@ var ClaudeOutboxStore = class {
|
|
|
2771
2844
|
for (const name of await readdir(this.activeContexts)) {
|
|
2772
2845
|
if (!name.endsWith(".json")) continue;
|
|
2773
2846
|
try {
|
|
2774
|
-
const value = JSON.parse(await
|
|
2847
|
+
const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
|
|
2775
2848
|
validateClaudeHostInvocationContext(value);
|
|
2776
|
-
if (Date.parse(value.expires_at) > now.getTime()
|
|
2849
|
+
if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
|
|
2777
2850
|
} catch {
|
|
2778
2851
|
}
|
|
2779
2852
|
}
|
|
@@ -2791,7 +2864,7 @@ var ClaudeOutboxStore = class {
|
|
|
2791
2864
|
for (const name of await readdir(this.envelopes)) {
|
|
2792
2865
|
if (!name.endsWith(".json")) continue;
|
|
2793
2866
|
try {
|
|
2794
|
-
const value = JSON.parse(await
|
|
2867
|
+
const value = JSON.parse(await readFile(join2(this.envelopes, name), "utf8"));
|
|
2795
2868
|
validateEnvelope(value);
|
|
2796
2869
|
entries.push(value);
|
|
2797
2870
|
} catch {
|
|
@@ -2812,13 +2885,13 @@ var ClaudeOutboxStore = class {
|
|
|
2812
2885
|
async saveSession(snapshot) {
|
|
2813
2886
|
validateSession(snapshot);
|
|
2814
2887
|
await this.prepare();
|
|
2815
|
-
await durableAtomicReplace(
|
|
2888
|
+
await durableAtomicReplace(join2(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
|
|
2816
2889
|
}
|
|
2817
2890
|
async readSession(sourceRef, sessionRef) {
|
|
2818
|
-
if (!
|
|
2891
|
+
if (!SAFE_REF2.test(sourceRef) || !SAFE_REF2.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
|
|
2819
2892
|
await this.prepare();
|
|
2820
2893
|
try {
|
|
2821
|
-
const value = JSON.parse(await
|
|
2894
|
+
const value = JSON.parse(await readFile(join2(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
|
|
2822
2895
|
validateSession(value);
|
|
2823
2896
|
return value;
|
|
2824
2897
|
} catch (error) {
|
|
@@ -2853,7 +2926,7 @@ var ClaudeOutboxStore = class {
|
|
|
2853
2926
|
for (const name of await readdir(this.registrations)) {
|
|
2854
2927
|
if (!name.endsWith(".json")) continue;
|
|
2855
2928
|
try {
|
|
2856
|
-
const value = JSON.parse(await
|
|
2929
|
+
const value = JSON.parse(await readFile(join2(this.registrations, name), "utf8"));
|
|
2857
2930
|
validateRegistration(value);
|
|
2858
2931
|
registrations.push(value);
|
|
2859
2932
|
} catch {
|
|
@@ -2862,10 +2935,10 @@ var ClaudeOutboxStore = class {
|
|
|
2862
2935
|
return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
|
|
2863
2936
|
}
|
|
2864
2937
|
async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
|
|
2865
|
-
if (!
|
|
2938
|
+
if (!SAFE_REF2.test(hostSourceRef) || !SAFE_REF2.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
|
|
2866
2939
|
await this.prepare();
|
|
2867
2940
|
try {
|
|
2868
|
-
const value = JSON.parse(await
|
|
2941
|
+
const value = JSON.parse(await readFile(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
|
|
2869
2942
|
if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
|
|
2870
2943
|
return value;
|
|
2871
2944
|
} catch (error) {
|
|
@@ -2877,7 +2950,7 @@ var ClaudeOutboxStore = class {
|
|
|
2877
2950
|
validateRegistration(registration);
|
|
2878
2951
|
await this.prepare();
|
|
2879
2952
|
try {
|
|
2880
|
-
const value = JSON.parse(await
|
|
2953
|
+
const value = JSON.parse(await readFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
|
|
2881
2954
|
validateCursor(value);
|
|
2882
2955
|
return value;
|
|
2883
2956
|
} catch (error) {
|
|
@@ -2891,20 +2964,20 @@ var ClaudeOutboxStore = class {
|
|
|
2891
2964
|
await durableAtomicReplace(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), JSON.stringify(cursor));
|
|
2892
2965
|
}
|
|
2893
2966
|
envelopePath(ref) {
|
|
2894
|
-
return
|
|
2967
|
+
return join2(this.envelopes, `${cealRequestSha256(ref)}.json`);
|
|
2895
2968
|
}
|
|
2896
2969
|
registrationPath(sourceRef, sessionRef) {
|
|
2897
|
-
return
|
|
2970
|
+
return join2(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
|
|
2898
2971
|
}
|
|
2899
2972
|
turnSelectionPath(sourceRef, sessionRef) {
|
|
2900
|
-
return
|
|
2973
|
+
return join2(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
|
|
2901
2974
|
}
|
|
2902
2975
|
cursorPath(sourceRef, sessionRef) {
|
|
2903
|
-
return
|
|
2976
|
+
return join2(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
|
|
2904
2977
|
}
|
|
2905
2978
|
async readRegistrationFile(path) {
|
|
2906
2979
|
try {
|
|
2907
|
-
const value = JSON.parse(await
|
|
2980
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
2908
2981
|
validateRegistration(value);
|
|
2909
2982
|
return value;
|
|
2910
2983
|
} catch (error) {
|
|
@@ -2920,15 +2993,15 @@ var ClaudeOutboxStore = class {
|
|
|
2920
2993
|
}
|
|
2921
2994
|
}
|
|
2922
2995
|
activeContextPath(identity) {
|
|
2923
|
-
return
|
|
2996
|
+
return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
|
|
2924
2997
|
}
|
|
2925
2998
|
};
|
|
2926
2999
|
function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
|
|
2927
|
-
if (!
|
|
3000
|
+
if (!SAFE_REF2.test(hostSourceRef) || !SAFE_REF2.test(hostSessionRef) || !(now instanceof Date) || !Number.isFinite(now.getTime())) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
|
|
2928
3001
|
}
|
|
2929
3002
|
async function readProjectionFile(file) {
|
|
2930
3003
|
try {
|
|
2931
|
-
const context = JSON.parse(await
|
|
3004
|
+
const context = JSON.parse(await readFile(file, "utf8"));
|
|
2932
3005
|
validateClaudeHostInvocationContext(context);
|
|
2933
3006
|
return context;
|
|
2934
3007
|
} catch (error) {
|
|
@@ -2937,65 +3010,63 @@ async function readProjectionFile(file) {
|
|
|
2937
3010
|
throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
|
|
2938
3011
|
}
|
|
2939
3012
|
}
|
|
2940
|
-
function
|
|
2941
|
-
return typeof value === "string" &&
|
|
3013
|
+
function safeRef3(value) {
|
|
3014
|
+
return typeof value === "string" && SAFE_REF2.test(value);
|
|
2942
3015
|
}
|
|
2943
|
-
function
|
|
3016
|
+
function timestamp2(value) {
|
|
2944
3017
|
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
2945
3018
|
}
|
|
2946
|
-
function
|
|
3019
|
+
function record3(value) {
|
|
2947
3020
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2948
3021
|
}
|
|
2949
3022
|
function code(error) {
|
|
2950
|
-
return
|
|
3023
|
+
return record3(error) && typeof error.code === "string" ? error.code : null;
|
|
2951
3024
|
}
|
|
2952
3025
|
function validateEnvelope(value) {
|
|
2953
|
-
if (!
|
|
2954
|
-
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "
|
|
3026
|
+
if (!record3(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
|
|
3027
|
+
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"])) {
|
|
2955
3028
|
throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
|
|
2956
3029
|
}
|
|
2957
3030
|
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}`)}).`);
|
|
2958
3031
|
validateEnvelopeIdentity(value);
|
|
2959
3032
|
validateEnvelopeObservations(value.observations);
|
|
2960
3033
|
if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
|
|
2961
|
-
if (!
|
|
3034
|
+
if (!timestamp2(value.created_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
|
|
2962
3035
|
}
|
|
2963
3036
|
function validateEnvelopeIdentity(value) {
|
|
2964
3037
|
validateIdentity2(value.identity);
|
|
2965
|
-
if (!
|
|
2966
|
-
if (!
|
|
2967
|
-
if (!safeRef4(value.prompt_ref) || !safeRef4(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
|
|
2968
|
-
if (value.request_ref !== void 0 && !safeRef4(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
|
|
3038
|
+
if (!safeRef3(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
|
|
3039
|
+
if (!safeRef3(value.prompt_ref) || !safeRef3(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
|
|
2969
3040
|
}
|
|
2970
3041
|
function validateEnvelopeObservations(value) {
|
|
2971
3042
|
if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
|
|
2972
3043
|
try {
|
|
2973
|
-
for (const
|
|
3044
|
+
for (const observation of value) validateCealHostObservationDraft(observation);
|
|
2974
3045
|
} catch {
|
|
2975
3046
|
throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
|
|
2976
3047
|
}
|
|
2977
3048
|
}
|
|
2978
3049
|
function validateSession(value) {
|
|
2979
|
-
if (!
|
|
3050
|
+
if (!record3(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
|
|
2980
3051
|
if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) {
|
|
2981
3052
|
throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
|
|
2982
3053
|
}
|
|
2983
3054
|
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}`)}).`);
|
|
2984
|
-
if (!
|
|
3055
|
+
if (!safeRef3(value.host_session_ref) || !safeRef3(value.host_source_ref) || !timestamp2(value.observed_at) || !timestamp2(value.expires_at)) {
|
|
2985
3056
|
throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
|
|
2986
3057
|
}
|
|
2987
3058
|
}
|
|
2988
3059
|
function validateRegistration(value) {
|
|
2989
|
-
if (!
|
|
3060
|
+
if (!record3(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
|
|
2990
3061
|
if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "claude_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) {
|
|
2991
3062
|
throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
|
|
2992
3063
|
}
|
|
2993
3064
|
if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
|
|
2994
|
-
if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" &&
|
|
3065
|
+
if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) {
|
|
2995
3066
|
throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
|
|
2996
3067
|
}
|
|
2997
3068
|
validateRegistrationPaths(value.cwd, value.transcript_path);
|
|
2998
|
-
if (!
|
|
3069
|
+
if (!timestamp2(value.registered_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
|
|
2999
3070
|
}
|
|
3000
3071
|
function validateRegistrationPaths(cwd, transcriptPath) {
|
|
3001
3072
|
if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
|
|
@@ -3006,12 +3077,12 @@ function validateRegistrationPaths(cwd, transcriptPath) {
|
|
|
3006
3077
|
}
|
|
3007
3078
|
}
|
|
3008
3079
|
function validateCursor(value) {
|
|
3009
|
-
if (!
|
|
3080
|
+
if (!record3(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
|
|
3010
3081
|
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"])) {
|
|
3011
3082
|
throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
|
|
3012
3083
|
}
|
|
3013
3084
|
if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
|
|
3014
|
-
if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" &&
|
|
3085
|
+
if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) throw new ClaudeHostError("storage", "Claude transcript cursor references are malformed.");
|
|
3015
3086
|
if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
|
|
3016
3087
|
validateCursorPosition(value);
|
|
3017
3088
|
validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
|
|
@@ -3023,7 +3094,7 @@ function validateCursorPosition(value) {
|
|
|
3023
3094
|
if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
|
|
3024
3095
|
if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
|
|
3025
3096
|
if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
|
|
3026
|
-
if (value.status !== "active" && value.status !== "quarantined" || !
|
|
3097
|
+
if (value.status !== "active" && value.status !== "quarantined" || !timestamp2(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
|
|
3027
3098
|
}
|
|
3028
3099
|
function validateCursorTurn(openTurn, unattributed, sessionObserved) {
|
|
3029
3100
|
if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
|
|
@@ -3031,13 +3102,13 @@ function validateCursorTurn(openTurn, unattributed, sessionObserved) {
|
|
|
3031
3102
|
if (sessionObserved !== void 0 && typeof sessionObserved !== "boolean") throw new ClaudeHostError("storage", "Claude transcript cursor session marker is malformed.");
|
|
3032
3103
|
}
|
|
3033
3104
|
function validateSubagentCursors(value) {
|
|
3034
|
-
if (!
|
|
3105
|
+
if (!record3(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
|
|
3035
3106
|
for (const [name, entry] of Object.entries(value)) validateSubagentEntry(name, entry);
|
|
3036
3107
|
}
|
|
3037
3108
|
function validateSubagentEntry(name, entry) {
|
|
3038
3109
|
if (name.length === 0 || name.length > CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS || name.includes("/")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
|
|
3039
3110
|
if (name.includes("\0")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
|
|
3040
|
-
if (!
|
|
3111
|
+
if (!record3(entry) || !sameKeys(entry, ["offset", "anchor_start", "anchor_sha256"], ["device", "inode"]) || !nonNegativeInteger(entry.offset)) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
|
|
3041
3112
|
validateSubagentIdentity(entry);
|
|
3042
3113
|
}
|
|
3043
3114
|
function validateSubagentIdentity(entry) {
|
|
@@ -3053,9 +3124,9 @@ function validCursorPosition(value) {
|
|
|
3053
3124
|
return nonNegativeInteger(value.offset) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256);
|
|
3054
3125
|
}
|
|
3055
3126
|
function validOpenTurn(value) {
|
|
3056
|
-
if (!
|
|
3057
|
-
if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" &&
|
|
3058
|
-
if (!
|
|
3127
|
+
if (!record3(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;
|
|
3128
|
+
if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) return false;
|
|
3129
|
+
if (!timestamp2(value.started_at) || !timestamp2(value.last_observed_at)) return false;
|
|
3059
3130
|
if (!validMessageIds(value.message_ids)) return false;
|
|
3060
3131
|
if (!validTurnCounts(value.counts)) return false;
|
|
3061
3132
|
return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
|
|
@@ -3066,17 +3137,17 @@ function validMessageIds(value) {
|
|
|
3066
3137
|
return new Set(value).size === value.length;
|
|
3067
3138
|
}
|
|
3068
3139
|
function validUnattributed(value) {
|
|
3069
|
-
if (!
|
|
3140
|
+
if (!record3(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
|
|
3070
3141
|
if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
|
|
3071
|
-
if (!
|
|
3142
|
+
if (!timestamp2(value.last_observed_at)) return false;
|
|
3072
3143
|
return validTurnCounts(value);
|
|
3073
3144
|
}
|
|
3074
3145
|
function validTurnCounts(value) {
|
|
3075
|
-
if (!
|
|
3146
|
+
if (!record3(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
|
|
3076
3147
|
return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
|
|
3077
3148
|
}
|
|
3078
3149
|
function validateIdentity2(value) {
|
|
3079
|
-
if (!
|
|
3150
|
+
if (!record3(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) {
|
|
3080
3151
|
throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
|
|
3081
3152
|
}
|
|
3082
3153
|
}
|
|
@@ -3088,13 +3159,11 @@ function sameKeys(value, required, optional = []) {
|
|
|
3088
3159
|
}
|
|
3089
3160
|
|
|
3090
3161
|
// host/claude/src/claude-transcript-watcher.ts
|
|
3091
|
-
import {
|
|
3092
|
-
import {
|
|
3093
|
-
import { join as join4 } from "node:path";
|
|
3162
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
3163
|
+
import { join as join3 } from "node:path";
|
|
3094
3164
|
var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
|
|
3095
3165
|
var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
3096
3166
|
store;
|
|
3097
|
-
registerCurrentPrompt;
|
|
3098
3167
|
diagnostic;
|
|
3099
3168
|
constructor(options) {
|
|
3100
3169
|
super(options, {
|
|
@@ -3103,7 +3172,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3103
3172
|
clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
|
|
3104
3173
|
});
|
|
3105
3174
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3106
|
-
this.registerCurrentPrompt = options.register_current_prompt;
|
|
3107
3175
|
this.diagnostic = options.diagnostic ?? (() => void 0);
|
|
3108
3176
|
}
|
|
3109
3177
|
transcriptPathOf(registration) {
|
|
@@ -3116,13 +3184,15 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3116
3184
|
const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
|
|
3117
3185
|
const cursor = await this.preflight(registration, handle, size, device, inode);
|
|
3118
3186
|
if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
|
|
3119
|
-
if (cursor.status === "quarantined")
|
|
3187
|
+
if (cursor.status === "quarantined") {
|
|
3188
|
+
return { records: 0, enqueued: 0, quarantined: true };
|
|
3189
|
+
}
|
|
3120
3190
|
const draft = mutableCursor(cursor);
|
|
3121
3191
|
await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
|
|
3122
3192
|
return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
|
|
3123
3193
|
}
|
|
3124
3194
|
async scanFailed(handle, registration, error) {
|
|
3125
|
-
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
3195
|
+
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
|
|
3126
3196
|
if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
|
|
3127
3197
|
if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
|
|
3128
3198
|
await this.quarantineFailure(registration, handle);
|
|
@@ -3170,55 +3240,72 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3170
3240
|
async consumeMainChunk(registration, complete, draft) {
|
|
3171
3241
|
for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
|
|
3172
3242
|
draft.consumed += 1;
|
|
3173
|
-
const
|
|
3174
|
-
if (
|
|
3175
|
-
if (isUserTurnLine(
|
|
3176
|
-
else if (isAssistantUsageLine(
|
|
3177
|
-
else if (isAssistantLine(
|
|
3243
|
+
const record4 = parseTranscriptLine(line);
|
|
3244
|
+
if (record4 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
|
|
3245
|
+
if (isUserTurnLine(record4)) await this.openTurn(registration, draft, record4);
|
|
3246
|
+
else if (isAssistantUsageLine(record4)) this.accumulateUsage(draft, toUsage(record4.uuid, record4.timestamp, record4.message.usage));
|
|
3247
|
+
else if (isAssistantLine(record4) && record4.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
|
|
3248
|
+
else {
|
|
3249
|
+
const queued = decodeClaudeQueuedPromptRow(record4, registration.claude_session_id);
|
|
3250
|
+
if (queued !== null) await this.openQueuedTurn(registration, draft, queued);
|
|
3251
|
+
}
|
|
3178
3252
|
}
|
|
3179
3253
|
}
|
|
3180
|
-
async openTurn(registration, draft,
|
|
3181
|
-
this.
|
|
3182
|
-
|
|
3254
|
+
async openTurn(registration, draft, record4) {
|
|
3255
|
+
await this.openPromptTurn(registration, draft, { uuid: record4.uuid, timestamp: record4.timestamp, content: record4.message.content });
|
|
3256
|
+
}
|
|
3257
|
+
async openQueuedTurn(registration, draft, queued) {
|
|
3258
|
+
await this.openPromptTurn(registration, draft, { uuid: queued.turnId, timestamp: queued.timestamp, content: queued.content });
|
|
3259
|
+
}
|
|
3260
|
+
async openPromptTurn(registration, draft, prompt) {
|
|
3261
|
+
await this.closeTurn(registration, draft, prompt.timestamp);
|
|
3183
3262
|
const sessionRef = registration.host_session_ref;
|
|
3184
|
-
const turnRef = claudeLocalTurnRef(registration.claude_session_id,
|
|
3185
|
-
const inputRef = claudeLocalInputRef(registration.claude_session_id,
|
|
3186
|
-
const promptRef = hostProducerPromptRef(this.sha(
|
|
3263
|
+
const turnRef = claudeLocalTurnRef(registration.claude_session_id, prompt.uuid);
|
|
3264
|
+
const inputRef = claudeLocalInputRef(registration.claude_session_id, prompt.uuid);
|
|
3265
|
+
const promptRef = hostProducerPromptRef(this.sha(prompt.content));
|
|
3187
3266
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
|
|
3188
3267
|
const observations = [
|
|
3189
|
-
{ observation_ref: observationRef("transcript_user_text", inputRef,
|
|
3190
|
-
{ observation_ref: observationRef("transcript_turn_started", turnRef,
|
|
3268
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_user_text", inputRef, prompt.content), observation_kind: "prompt", observed_at: prompt.timestamp, value: { body_sha256: this.sha(prompt.content) } },
|
|
3269
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_turn_started", turnRef, prompt.uuid), observation_kind: "turn", observed_at: prompt.timestamp, value: { status: "started", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turnRef } }
|
|
3191
3270
|
];
|
|
3192
3271
|
if (!draft.session_observed) {
|
|
3193
3272
|
const session = await this.store.readSession(registration.host_source_ref, sessionRef);
|
|
3194
|
-
if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at:
|
|
3273
|
+
if (session !== null) observations.push({ observation_ref: observationRef(registration.host_source_ref, "transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: prompt.timestamp, value: session.value });
|
|
3195
3274
|
draft.session_observed = true;
|
|
3196
3275
|
}
|
|
3276
|
+
await this.persistOpenedTurnContext(draft, { uuid: prompt.uuid, timestamp: prompt.timestamp, message: { content: prompt.content } }, inputRef, turnRef, promptRef, identity, observations);
|
|
3277
|
+
}
|
|
3278
|
+
async persistOpenedTurnContext(draft, record4, inputRef, turnRef, promptRef, identity, observations) {
|
|
3279
|
+
const expiresAt = this.expire(this.outboxRetentionMs);
|
|
3197
3280
|
await this.store.putEnvelope({
|
|
3198
3281
|
schema_version: "ceal.claude_observation_outbox.v1",
|
|
3199
3282
|
identity,
|
|
3200
3283
|
host_input_ref: inputRef,
|
|
3201
|
-
input_kind: "human",
|
|
3202
3284
|
prompt_ref: promptRef,
|
|
3203
3285
|
observations,
|
|
3204
3286
|
origin_input: true,
|
|
3205
3287
|
submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
|
|
3206
|
-
created_at:
|
|
3207
|
-
expires_at:
|
|
3288
|
+
created_at: record4.timestamp,
|
|
3289
|
+
expires_at: expiresAt
|
|
3208
3290
|
});
|
|
3209
3291
|
draft.enqueued += 1;
|
|
3210
|
-
await this.
|
|
3292
|
+
await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
|
|
3293
|
+
this.setOpenedTurn(draft, record4, inputRef, turnRef, promptRef);
|
|
3294
|
+
}
|
|
3295
|
+
async activateOpenedTurnContext(identity, promptRef, expiresAt) {
|
|
3211
3296
|
const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
3212
3297
|
const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
|
|
3213
|
-
await this.activateContext(identity, promptRef, turnGenerationRef);
|
|
3298
|
+
await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
|
|
3299
|
+
}
|
|
3300
|
+
setOpenedTurn(draft, record4, inputRef, turnRef, promptRef) {
|
|
3214
3301
|
const folded = foldUnattributed(draft.unattributed);
|
|
3215
3302
|
draft.open_turn = {
|
|
3216
3303
|
turn_key: turnRef,
|
|
3217
|
-
user_uuid:
|
|
3304
|
+
user_uuid: record4.uuid,
|
|
3218
3305
|
prompt_ref: promptRef,
|
|
3219
3306
|
host_input_ref: inputRef,
|
|
3220
|
-
started_at:
|
|
3221
|
-
last_observed_at: folded?.last_observed_at ??
|
|
3307
|
+
started_at: record4.timestamp,
|
|
3308
|
+
last_observed_at: folded?.last_observed_at ?? record4.timestamp,
|
|
3222
3309
|
message_ids: folded?.message_ids ?? [],
|
|
3223
3310
|
counts: folded?.counts ?? zeroCounts()
|
|
3224
3311
|
};
|
|
@@ -3229,9 +3316,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3229
3316
|
draft.open_turn = void 0;
|
|
3230
3317
|
if (turn === void 0) return;
|
|
3231
3318
|
const digest2 = terminalDigest(turn);
|
|
3232
|
-
if (digest2
|
|
3233
|
-
|
|
3234
|
-
|
|
3319
|
+
if (digest2 !== turn.last_terminal_digest) {
|
|
3320
|
+
await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
|
|
3321
|
+
draft.enqueued += 1;
|
|
3322
|
+
}
|
|
3235
3323
|
}
|
|
3236
3324
|
accumulateUsage(draft, usage) {
|
|
3237
3325
|
const turn = draft.open_turn;
|
|
@@ -3265,6 +3353,8 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3265
3353
|
if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
|
|
3266
3354
|
throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
|
|
3267
3355
|
}
|
|
3356
|
+
const listed = new Set(names);
|
|
3357
|
+
for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
|
|
3268
3358
|
if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
|
|
3269
3359
|
this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
|
|
3270
3360
|
names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
|
|
@@ -3276,10 +3366,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3276
3366
|
return consumed;
|
|
3277
3367
|
}
|
|
3278
3368
|
async consumeSubagentFile(draft, directory, name) {
|
|
3279
|
-
const path =
|
|
3369
|
+
const path = join3(directory, name);
|
|
3280
3370
|
let handle;
|
|
3281
3371
|
try {
|
|
3282
|
-
handle = await
|
|
3372
|
+
handle = await openNoFollow(path);
|
|
3283
3373
|
const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
|
|
3284
3374
|
const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
|
|
3285
3375
|
const pinned = await this.preflightSubagent(entry, size, device, inode);
|
|
@@ -3307,10 +3397,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3307
3397
|
let consumed = 0;
|
|
3308
3398
|
for (const line of chunk.toString("utf8").split("\n").filter((value) => value.length > 0)) {
|
|
3309
3399
|
consumed += 1;
|
|
3310
|
-
const
|
|
3311
|
-
if (
|
|
3312
|
-
if (isAssistantUsageLine(
|
|
3313
|
-
else if (isAssistantLine(
|
|
3400
|
+
const record4 = parseTranscriptLine(line);
|
|
3401
|
+
if (record4 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
|
|
3402
|
+
if (isAssistantUsageLine(record4)) this.accumulateUsage(draft, toUsage(record4.uuid, record4.timestamp, record4.message.usage));
|
|
3403
|
+
else if (isAssistantLine(record4) && record4.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
|
|
3314
3404
|
}
|
|
3315
3405
|
return consumed;
|
|
3316
3406
|
}
|
|
@@ -3346,12 +3436,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3346
3436
|
schema_version: "ceal.claude_observation_outbox.v1",
|
|
3347
3437
|
identity,
|
|
3348
3438
|
host_input_ref: turn.host_input_ref,
|
|
3349
|
-
input_kind: "host_automatic",
|
|
3350
3439
|
prompt_ref: turn.prompt_ref,
|
|
3351
3440
|
observations: [
|
|
3352
|
-
{ 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 } },
|
|
3353
|
-
{ observation_ref: observationRef("transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue }
|
|
3354
|
-
{ observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
|
|
3441
|
+
{ observation_ref: observationRef(registration.host_source_ref, "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 } },
|
|
3442
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue }
|
|
3355
3443
|
],
|
|
3356
3444
|
origin_input: false,
|
|
3357
3445
|
submission_ref: submissionRef("terminal", turn.turn_key, digest2, turn.prompt_ref),
|
|
@@ -3379,27 +3467,9 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3379
3467
|
updated_at: this.stamp()
|
|
3380
3468
|
});
|
|
3381
3469
|
}
|
|
3382
|
-
async
|
|
3383
|
-
if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
|
|
3384
|
-
try {
|
|
3385
|
-
await this.registerCurrentPrompt({
|
|
3386
|
-
producer_id: "claude",
|
|
3387
|
-
producer_installation_ref: this.producerInstallationRef,
|
|
3388
|
-
local_session_ref: sessionRef,
|
|
3389
|
-
local_turn_ref: turnRef,
|
|
3390
|
-
prompt_ref: promptRef,
|
|
3391
|
-
current_prompt: prompt,
|
|
3392
|
-
context_expires_at: this.expire(this.outboxRetentionMs)
|
|
3393
|
-
});
|
|
3394
|
-
this.diagnostic("current_prompt_registered");
|
|
3395
|
-
} catch (error) {
|
|
3396
|
-
this.diagnostic("current_prompt_registration_failed");
|
|
3397
|
-
throw error;
|
|
3398
|
-
}
|
|
3399
|
-
}
|
|
3400
|
-
async activateContext(identity, promptRef, turnGenerationRef) {
|
|
3470
|
+
async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
|
|
3401
3471
|
try {
|
|
3402
|
-
await this.store.activateInvocationContext(identity, promptRef,
|
|
3472
|
+
await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
|
|
3403
3473
|
this.diagnostic("activation_recorded");
|
|
3404
3474
|
} catch (error) {
|
|
3405
3475
|
this.diagnostic("activation_failed");
|
|
@@ -3510,86 +3580,103 @@ function parseTranscriptLine(line) {
|
|
|
3510
3580
|
return null;
|
|
3511
3581
|
}
|
|
3512
3582
|
}
|
|
3513
|
-
function lineIdentity(
|
|
3514
|
-
if (typeof
|
|
3515
|
-
if (typeof
|
|
3516
|
-
return { uuid:
|
|
3583
|
+
function lineIdentity(record4) {
|
|
3584
|
+
if (typeof record4.uuid !== "string" || record4.uuid.length === 0 || record4.uuid.length > 256) return null;
|
|
3585
|
+
if (typeof record4.timestamp !== "string" || !Number.isFinite(Date.parse(record4.timestamp))) return null;
|
|
3586
|
+
return { uuid: record4.uuid, timestamp: record4.timestamp };
|
|
3517
3587
|
}
|
|
3518
|
-
function isUserTurnLine(
|
|
3519
|
-
if (!isTranscriptRecord(
|
|
3520
|
-
if (!isTranscriptRecord(
|
|
3521
|
-
if (
|
|
3522
|
-
const identity = lineIdentity(
|
|
3588
|
+
function isUserTurnLine(record4) {
|
|
3589
|
+
if (!isTranscriptRecord(record4) || record4.type !== "user") return false;
|
|
3590
|
+
if (!isTranscriptRecord(record4.message) || typeof record4.message.content !== "string") return false;
|
|
3591
|
+
if (record4.isMeta === true && (!isTranscriptRecord(record4.origin) || typeof record4.origin.kind !== "string")) return false;
|
|
3592
|
+
const identity = lineIdentity(record4);
|
|
3523
3593
|
if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
|
|
3524
3594
|
return true;
|
|
3525
3595
|
}
|
|
3526
|
-
function
|
|
3527
|
-
|
|
3528
|
-
if (error instanceof ClaudeHostError) {
|
|
3529
|
-
if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
|
|
3530
|
-
if (error.code === "payload_rejected") return "payload_rejected";
|
|
3531
|
-
if (error.code === "storage") return "read_error";
|
|
3532
|
-
}
|
|
3533
|
-
const code2 = transcriptErrorCode(error);
|
|
3534
|
-
if (code2 === "EFBIG") return "size_budget";
|
|
3535
|
-
if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
|
|
3536
|
-
return "other";
|
|
3537
|
-
}
|
|
3538
|
-
function isAssistantLine(record6) {
|
|
3539
|
-
return isTranscriptRecord(record6) && record6.type === "assistant" && isTranscriptRecord(record6.message);
|
|
3596
|
+
function isAssistantLine(record4) {
|
|
3597
|
+
return isTranscriptRecord(record4) && record4.type === "assistant" && isTranscriptRecord(record4.message);
|
|
3540
3598
|
}
|
|
3541
|
-
function isAssistantUsageLine(
|
|
3542
|
-
if (!isTranscriptRecord(
|
|
3543
|
-
if (!isAssistantLine(
|
|
3544
|
-
const identity = lineIdentity(
|
|
3545
|
-
if (identity === null || !isTranscriptRecord(
|
|
3599
|
+
function isAssistantUsageLine(record4) {
|
|
3600
|
+
if (!isTranscriptRecord(record4)) return false;
|
|
3601
|
+
if (!isAssistantLine(record4) || record4.message.usage === void 0) return false;
|
|
3602
|
+
const identity = lineIdentity(record4);
|
|
3603
|
+
if (identity === null || !isTranscriptRecord(record4.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
|
|
3546
3604
|
return true;
|
|
3547
3605
|
}
|
|
3548
3606
|
function subagentsDirectory(transcriptPath) {
|
|
3549
3607
|
const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
|
|
3550
|
-
return
|
|
3608
|
+
return join3(stem, "subagents");
|
|
3551
3609
|
}
|
|
3552
|
-
function observationRef(event, identity, value) {
|
|
3553
|
-
return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
|
|
3610
|
+
function observationRef(hostSourceRef, event, identity, value) {
|
|
3611
|
+
return cealHostObservationRef("claude", transcriptObjectSha256({ host_source_ref: hostSourceRef, event, identity, value }));
|
|
3554
3612
|
}
|
|
3555
3613
|
function submissionRef(kind, turn, digest2, prompt) {
|
|
3556
3614
|
return `claude-submission:${transcriptObjectSha256({ kind, turn, digest: digest2, prompt })}`;
|
|
3557
3615
|
}
|
|
3558
3616
|
async function ownedClaudeTranscriptFile(handle) {
|
|
3559
3617
|
const stats = await handle.stat();
|
|
3560
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
3561
3618
|
if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
|
|
3562
|
-
if (stats.uid
|
|
3619
|
+
if (process.platform !== "win32" && !isSelfOwned(stats.uid, process.platform, handle)) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
|
|
3563
3620
|
return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
|
|
3564
3621
|
}
|
|
3565
3622
|
function isNotDirectory(error) {
|
|
3566
3623
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
|
|
3567
3624
|
}
|
|
3625
|
+
function claudeTranscriptScanCause(handle, error) {
|
|
3626
|
+
if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
|
|
3627
|
+
const code2 = transcriptErrorCode(error);
|
|
3628
|
+
if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
|
|
3629
|
+
return `${handle === void 0 ? "open" : "scan"}:unknown`;
|
|
3630
|
+
}
|
|
3631
|
+
function claudeTranscriptScanFailureKind(error) {
|
|
3632
|
+
return transcriptScanFailureKind(error, (candidate) => {
|
|
3633
|
+
if (!(candidate instanceof ClaudeHostError)) return void 0;
|
|
3634
|
+
if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
|
|
3635
|
+
if (candidate.code === "payload_rejected") return "payload_rejected";
|
|
3636
|
+
if (candidate.code === "storage") return "read_error";
|
|
3637
|
+
return void 0;
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3568
3640
|
function isSymlinkTranscript(error) {
|
|
3569
3641
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
|
|
3570
3642
|
}
|
|
3571
|
-
function transcriptErrorCode(error) {
|
|
3572
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
3573
|
-
}
|
|
3574
3643
|
|
|
3575
3644
|
// host/claude/src/claude-adapter.ts
|
|
3576
|
-
import { createHash as
|
|
3577
|
-
import {
|
|
3578
|
-
import {
|
|
3645
|
+
import { createHash as createHash5, randomUUID } from "node:crypto";
|
|
3646
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
3647
|
+
import { constants as constants2 } from "node:fs";
|
|
3579
3648
|
import { homedir } from "node:os";
|
|
3580
3649
|
import { isAbsolute as isAbsolute2, join as join5, relative, resolve } from "node:path";
|
|
3650
|
+
|
|
3651
|
+
// host/claude/src/claude-runtime-paths.ts
|
|
3652
|
+
import { join as join4 } from "node:path";
|
|
3653
|
+
var CLAUDE_CONFIG_DIRECTORY_ENVIRONMENT = "CLAUDE_CONFIG_DIR";
|
|
3654
|
+
var CLAUDE_HOME_DEFAULT_DIRECTORY = ".claude";
|
|
3655
|
+
var CLAUDE_PROJECTS_DIRECTORY = "projects";
|
|
3656
|
+
function resolveClaudeConfigHome(environment, home) {
|
|
3657
|
+
return environment[CLAUDE_CONFIG_DIRECTORY_ENVIRONMENT] || join4(home, CLAUDE_HOME_DEFAULT_DIRECTORY);
|
|
3658
|
+
}
|
|
3659
|
+
function resolveClaudeProjectsRoot(environment, home) {
|
|
3660
|
+
return join4(resolveClaudeConfigHome(environment, home), CLAUDE_PROJECTS_DIRECTORY);
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
// host/claude/src/claude-adapter.ts
|
|
3581
3664
|
var ClaudeHookAdapter = class {
|
|
3582
3665
|
store;
|
|
3583
3666
|
now;
|
|
3584
3667
|
optionsSourceRef;
|
|
3585
3668
|
transcriptsRoot;
|
|
3586
3669
|
randomUuid;
|
|
3670
|
+
platform;
|
|
3671
|
+
custody;
|
|
3587
3672
|
constructor(options) {
|
|
3588
3673
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3589
3674
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3590
3675
|
this.optionsSourceRef = options.host_source_ref ?? "claude-host:local";
|
|
3591
3676
|
this.transcriptsRoot = options.claude_transcripts_root;
|
|
3592
3677
|
this.randomUuid = options.random_uuid ?? randomUUID;
|
|
3678
|
+
this.platform = options.platform ?? process.platform;
|
|
3679
|
+
this.custody = options.custody ?? {};
|
|
3593
3680
|
}
|
|
3594
3681
|
async handle(event, raw) {
|
|
3595
3682
|
let input;
|
|
@@ -3605,10 +3692,10 @@ var ClaudeHookAdapter = class {
|
|
|
3605
3692
|
}
|
|
3606
3693
|
}
|
|
3607
3694
|
async registerSession(input) {
|
|
3608
|
-
const defaultRoot =
|
|
3609
|
-
const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? defaultRoot);
|
|
3695
|
+
const defaultRoot = resolveClaudeProjectsRoot(process.env, homedir());
|
|
3696
|
+
const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? defaultRoot, this.platform);
|
|
3610
3697
|
const hostSessionRef = claudeLocalSessionRef(input.session_id);
|
|
3611
|
-
const observedAt =
|
|
3698
|
+
const observedAt = timestamp3(this.now);
|
|
3612
3699
|
const expiresAt = new Date(Date.parse(observedAt) + CLAUDE_DEFAULT_RETENTION_MS).toISOString();
|
|
3613
3700
|
const registration = {
|
|
3614
3701
|
schema_version: "ceal.claude_transcript_registration.v1",
|
|
@@ -3622,7 +3709,7 @@ var ClaudeHookAdapter = class {
|
|
|
3622
3709
|
};
|
|
3623
3710
|
const existing = (await this.store.listTranscriptRegistrations()).find((candidate) => candidate.host_source_ref === registration.host_source_ref && candidate.host_session_ref === registration.host_session_ref);
|
|
3624
3711
|
if (existing === void 0) {
|
|
3625
|
-
await this.store.writeTranscriptCursor((await captureClaudeTranscriptBaseline(registration, this.now)).cursor);
|
|
3712
|
+
await this.store.writeTranscriptCursor((await captureClaudeTranscriptBaseline(registration, this.now, void 0, this.platform, this.custody)).cursor);
|
|
3626
3713
|
}
|
|
3627
3714
|
await this.store.registerTranscript(registration);
|
|
3628
3715
|
const snapshot = {
|
|
@@ -3637,9 +3724,9 @@ var ClaudeHookAdapter = class {
|
|
|
3637
3724
|
return { event: input.event, disposition: "ignored", reason: "transcript_registered", local_session_ref: hostSessionRef };
|
|
3638
3725
|
}
|
|
3639
3726
|
async registerCurrentTurn(input) {
|
|
3640
|
-
const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ??
|
|
3727
|
+
const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? resolveClaudeProjectsRoot(process.env, homedir()), this.platform);
|
|
3641
3728
|
const hostSessionRef = claudeLocalSessionRef(input.session_id);
|
|
3642
|
-
const observedAt =
|
|
3729
|
+
const observedAt = timestamp3(this.now);
|
|
3643
3730
|
const expiresAt = new Date(Date.parse(observedAt) + CLAUDE_DEFAULT_RETENTION_MS).toISOString();
|
|
3644
3731
|
const registration = {
|
|
3645
3732
|
schema_version: "ceal.claude_transcript_registration.v1",
|
|
@@ -3651,13 +3738,13 @@ var ClaudeHookAdapter = class {
|
|
|
3651
3738
|
registered_at: observedAt,
|
|
3652
3739
|
expires_at: expiresAt
|
|
3653
3740
|
};
|
|
3654
|
-
const promptRef = hostProducerPromptRef(
|
|
3741
|
+
const promptRef = hostProducerPromptRef(createHash5("sha256").update(input.prompt, "utf8").digest("hex"));
|
|
3655
3742
|
const turnGenerationRef = this.randomUuid();
|
|
3656
|
-
const selection = { schema_version:
|
|
3657
|
-
await this.store.writeInvocationTurnSelection(selection);
|
|
3743
|
+
const selection = { schema_version: HOST_INVOCATION_TURN_SELECTION_SCHEMA, host_source_ref: this.optionsSourceRef, host_session_ref: hostSessionRef, prompt_ref: promptRef, turn_generation_ref: turnGenerationRef };
|
|
3658
3744
|
const existing = (await this.store.listTranscriptRegistrations()).find((candidate) => candidate.host_source_ref === registration.host_source_ref && candidate.host_session_ref === registration.host_session_ref);
|
|
3659
|
-
const baseline = await captureClaudeTranscriptBaseline(registration, this.now, input.prompt);
|
|
3745
|
+
const baseline = await captureClaudeTranscriptBaseline(registration, this.now, input.prompt, this.platform, this.custody);
|
|
3660
3746
|
const transcriptTurnRef = baseline.current_turn_ref;
|
|
3747
|
+
await this.store.writeInvocationTurnSelection(selection);
|
|
3661
3748
|
if (existing === void 0) await this.store.writeTranscriptCursor(baseline.cursor);
|
|
3662
3749
|
else await refreshCurrentTurnCursor(this.store, registration, baseline, promptRef, turnGenerationRef, transcriptTurnRef);
|
|
3663
3750
|
await this.store.registerTranscript(registration);
|
|
@@ -3667,12 +3754,12 @@ var ClaudeHookAdapter = class {
|
|
|
3667
3754
|
function sessionObservationValue(input) {
|
|
3668
3755
|
return { status: "started", host_kind: "claude", source: input.source, model: input.model ?? "unavailable", decoder_evidence_version: "claude-code-2.1.258" };
|
|
3669
3756
|
}
|
|
3670
|
-
function sourceBoundTranscriptPath(path, transcriptsRoot) {
|
|
3757
|
+
function sourceBoundTranscriptPath(path, transcriptsRoot, platform = process.platform) {
|
|
3671
3758
|
if (!isAbsolute2(path) || !isAbsolute2(transcriptsRoot)) throw new ClaudeHostError("configuration", "Claude transcript registration paths must be absolute.");
|
|
3672
3759
|
const transcript = resolve(path);
|
|
3673
3760
|
const root = resolve(transcriptsRoot);
|
|
3674
3761
|
const child = relative(root, transcript);
|
|
3675
|
-
if (child === "" || child === ".." || child.startsWith(`..${
|
|
3762
|
+
if (child === "" || child === ".." || child.startsWith(`..${platform === "win32" ? "\\" : "/"}`) || isAbsolute2(child)) {
|
|
3676
3763
|
throw new ClaudeHostError("invalid_input", "Claude transcript path is outside the configured transcripts root.");
|
|
3677
3764
|
}
|
|
3678
3765
|
return transcript;
|
|
@@ -3690,19 +3777,19 @@ async function refreshCurrentTurnCursor(store, registration, baseline, promptRef
|
|
|
3690
3777
|
host_turn_ref: transcriptTurnRef
|
|
3691
3778
|
}, promptRef, turnGenerationRef);
|
|
3692
3779
|
}
|
|
3693
|
-
async function captureClaudeTranscriptBaseline(registration, now, currentPrompt) {
|
|
3780
|
+
async function captureClaudeTranscriptBaseline(registration, now, currentPrompt, platform = process.platform, custody = {}) {
|
|
3694
3781
|
let handle;
|
|
3695
3782
|
try {
|
|
3696
|
-
handle = await
|
|
3783
|
+
handle = await openNoFollow(registration.transcript_path, constants2.O_RDONLY, void 0, platform, custody);
|
|
3697
3784
|
} catch (error) {
|
|
3698
3785
|
if (isNotFound(error)) return { cursor: initialCursor(registration, now) };
|
|
3699
3786
|
throw error;
|
|
3700
3787
|
}
|
|
3701
3788
|
try {
|
|
3702
|
-
const file = await inspectClaudeTranscript(handle);
|
|
3789
|
+
const file = await inspectClaudeTranscript(handle, platform);
|
|
3703
3790
|
const current = currentPrompt === void 0 ? void 0 : await currentClaudeUserLine(handle, file.size, registration.claude_session_id, currentPrompt);
|
|
3704
3791
|
const offset = current?.offset ?? file.size;
|
|
3705
|
-
const cursor = await makeClaudeBaselineCursor(handle, registration, now, file, offset);
|
|
3792
|
+
const cursor = await makeClaudeBaselineCursor(handle, registration, now, file, offset, platform, custody);
|
|
3706
3793
|
return {
|
|
3707
3794
|
cursor,
|
|
3708
3795
|
...current?.user === void 0 ? {} : {
|
|
@@ -3715,21 +3802,55 @@ async function captureClaudeTranscriptBaseline(registration, now, currentPrompt)
|
|
|
3715
3802
|
await handle?.close();
|
|
3716
3803
|
}
|
|
3717
3804
|
}
|
|
3718
|
-
async function inspectClaudeTranscript(handle) {
|
|
3805
|
+
async function inspectClaudeTranscript(handle, platform = process.platform) {
|
|
3719
3806
|
const stats = await handle.stat();
|
|
3720
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
3721
3807
|
if (!stats.isFile()) throw new ClaudeHostError("storage", "Claude transcript source is not a regular file.");
|
|
3722
|
-
if (stats.uid
|
|
3808
|
+
if (platform !== "win32" && !isSelfOwned(stats.uid, platform, handle)) throw new ClaudeHostError("storage", "Claude transcript source is owned by another user.");
|
|
3723
3809
|
return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
|
|
3724
3810
|
}
|
|
3725
3811
|
async function currentClaudeUserLine(handle, size, sessionId, prompt) {
|
|
3726
|
-
const
|
|
3727
|
-
if (
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3812
|
+
const recent = await readRecentClaudeTranscriptLines(handle, size);
|
|
3813
|
+
if (recent === null) return void 0;
|
|
3814
|
+
for (let index = recent.length - 1; index >= 0; index -= 1) {
|
|
3815
|
+
const line = recent[index];
|
|
3816
|
+
if (line === void 0) continue;
|
|
3817
|
+
const user = parseClaudeUserLine(line.text, sessionId);
|
|
3818
|
+
if (user?.content === prompt) return { offset: line.start, user };
|
|
3819
|
+
const queued = decodeClaudeQueuedPromptRow(parseClaudeJsonLine(line.text), sessionId);
|
|
3820
|
+
if (queued?.content === prompt) return { offset: line.start, user: { uuid: queued.turnId } };
|
|
3821
|
+
}
|
|
3822
|
+
const last = recent[recent.length - 1];
|
|
3823
|
+
if (last === void 0) return { offset: size };
|
|
3824
|
+
return { offset: last.complete ? size : last.start };
|
|
3825
|
+
}
|
|
3826
|
+
var CLAUDE_BASELINE_SCAN_BYTES = 512 * 1024;
|
|
3827
|
+
var CLAUDE_BASELINE_SCAN_LINES = 128;
|
|
3828
|
+
async function readRecentClaudeTranscriptLines(handle, size) {
|
|
3829
|
+
if (size === 0) return null;
|
|
3830
|
+
const start = Math.max(0, size - CLAUDE_BASELINE_SCAN_BYTES);
|
|
3831
|
+
const buffer = Buffer.alloc(size - start);
|
|
3832
|
+
await handle.read(buffer, 0, buffer.length, start);
|
|
3833
|
+
const lines = [];
|
|
3834
|
+
let lineStart = 0;
|
|
3835
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
3836
|
+
if (buffer[index] !== 10) continue;
|
|
3837
|
+
lines.push({ start: start + lineStart, text: buffer.subarray(lineStart, index).toString("utf8"), complete: true });
|
|
3838
|
+
lineStart = index + 1;
|
|
3839
|
+
}
|
|
3840
|
+
if (lineStart < buffer.length) lines.push({ start: start + lineStart, text: buffer.subarray(lineStart).toString("utf8"), complete: false });
|
|
3841
|
+
if (lines.length === 0) return null;
|
|
3842
|
+
const whole = start > 0 ? lines.slice(1) : lines;
|
|
3843
|
+
if (whole.length === 0) return null;
|
|
3844
|
+
return whole.slice(-CLAUDE_BASELINE_SCAN_LINES);
|
|
3845
|
+
}
|
|
3846
|
+
function parseClaudeJsonLine(line) {
|
|
3847
|
+
try {
|
|
3848
|
+
return JSON.parse(line);
|
|
3849
|
+
} catch {
|
|
3850
|
+
return null;
|
|
3851
|
+
}
|
|
3731
3852
|
}
|
|
3732
|
-
async function makeClaudeBaselineCursor(handle, registration, now, file, offset) {
|
|
3853
|
+
async function makeClaudeBaselineCursor(handle, registration, now, file, offset, platform, custody) {
|
|
3733
3854
|
const anchorStart = Math.max(0, offset - CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
|
|
3734
3855
|
const anchor = Buffer.alloc(offset - anchorStart);
|
|
3735
3856
|
if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, anchorStart);
|
|
@@ -3742,55 +3863,67 @@ async function makeClaudeBaselineCursor(handle, registration, now, file, offset)
|
|
|
3742
3863
|
inode: file.inode,
|
|
3743
3864
|
offset,
|
|
3744
3865
|
anchor_start: anchorStart,
|
|
3745
|
-
anchor_sha256:
|
|
3746
|
-
subagents:
|
|
3866
|
+
anchor_sha256: createHash5("sha256").update(anchor).digest("hex"),
|
|
3867
|
+
subagents: await readClaudeSubagentBaseline(registration.transcript_path, platform, custody),
|
|
3747
3868
|
status: "active",
|
|
3748
|
-
updated_at:
|
|
3869
|
+
updated_at: timestamp3(now)
|
|
3749
3870
|
};
|
|
3750
3871
|
}
|
|
3751
|
-
async function
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
const
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3872
|
+
async function readClaudeSubagentBaseline(transcriptPath, platform, custody) {
|
|
3873
|
+
const directory = subagentsDirectory(transcriptPath);
|
|
3874
|
+
let names;
|
|
3875
|
+
try {
|
|
3876
|
+
names = (await readdir3(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
|
|
3877
|
+
} catch (error) {
|
|
3878
|
+
if (isNotFound(error)) return {};
|
|
3879
|
+
throw new ClaudeHostError("storage", "subagent_baseline_unavailable");
|
|
3880
|
+
}
|
|
3881
|
+
const cursors = {};
|
|
3882
|
+
for (const name of names) {
|
|
3883
|
+
try {
|
|
3884
|
+
cursors[name] = await readClaudeSubagentSeed(directory, name, platform, custody);
|
|
3885
|
+
} catch {
|
|
3886
|
+
throw new ClaudeHostError("storage", "subagent_baseline_unavailable");
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
return cursors;
|
|
3890
|
+
}
|
|
3891
|
+
async function readClaudeSubagentSeed(directory, name, platform, custody) {
|
|
3892
|
+
let handle;
|
|
3893
|
+
try {
|
|
3894
|
+
handle = await openNoFollow(join5(directory, name), constants2.O_RDONLY, void 0, platform, custody);
|
|
3895
|
+
const stats = await handle.stat();
|
|
3896
|
+
if (!stats.isFile()) throw new ClaudeHostError("storage", "Claude subagent baseline source is not a regular file.");
|
|
3897
|
+
if (platform !== "win32" && !isSelfOwned(stats.uid, platform, handle)) throw new ClaudeHostError("storage", "Claude subagent baseline source is owned by another user.");
|
|
3898
|
+
return {
|
|
3899
|
+
device: String(stats.dev),
|
|
3900
|
+
inode: String(stats.ino),
|
|
3901
|
+
offset: stats.size,
|
|
3902
|
+
anchor_start: 0,
|
|
3903
|
+
anchor_sha256: createHash5("sha256").update(Buffer.alloc(0)).digest("hex")
|
|
3904
|
+
};
|
|
3905
|
+
} finally {
|
|
3906
|
+
await handle?.close();
|
|
3772
3907
|
}
|
|
3773
|
-
if (position === 0) return { start: 0, bytes: Buffer.concat(chunks), complete };
|
|
3774
|
-
return null;
|
|
3775
3908
|
}
|
|
3776
3909
|
function parseClaudeUserLine(line, sessionId) {
|
|
3777
3910
|
const row = parseClaudeJsonObject(line);
|
|
3778
|
-
if (row === null || row.type !== "user" || row.sessionId !== sessionId || typeof row.uuid !== "string" || row.uuid.length === 0 || !
|
|
3911
|
+
if (row === null || row.type !== "user" || row.sessionId !== sessionId || typeof row.uuid !== "string" || row.uuid.length === 0 || !isRecord3(row.message)) return null;
|
|
3779
3912
|
return typeof row.message.content === "string" ? { uuid: row.uuid, content: row.message.content } : null;
|
|
3780
3913
|
}
|
|
3781
3914
|
function parseClaudeJsonObject(line) {
|
|
3782
3915
|
try {
|
|
3783
3916
|
const value = JSON.parse(line);
|
|
3784
|
-
return
|
|
3917
|
+
return isRecord3(value) ? value : null;
|
|
3785
3918
|
} catch {
|
|
3786
3919
|
return null;
|
|
3787
3920
|
}
|
|
3788
3921
|
}
|
|
3789
|
-
function
|
|
3922
|
+
function isRecord3(value) {
|
|
3790
3923
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3791
3924
|
}
|
|
3792
3925
|
function isNotFound(error) {
|
|
3793
|
-
return
|
|
3926
|
+
return isRecord3(error) && error.code === "ENOENT";
|
|
3794
3927
|
}
|
|
3795
3928
|
function initialCursor(registration, now) {
|
|
3796
3929
|
return {
|
|
@@ -3800,13 +3933,13 @@ function initialCursor(registration, now) {
|
|
|
3800
3933
|
transcript_path: registration.transcript_path,
|
|
3801
3934
|
offset: 0,
|
|
3802
3935
|
anchor_start: 0,
|
|
3803
|
-
anchor_sha256:
|
|
3936
|
+
anchor_sha256: createHash5("sha256").update(Buffer.alloc(0)).digest("hex"),
|
|
3804
3937
|
subagents: {},
|
|
3805
3938
|
status: "active",
|
|
3806
|
-
updated_at:
|
|
3939
|
+
updated_at: timestamp3(now)
|
|
3807
3940
|
};
|
|
3808
3941
|
}
|
|
3809
|
-
function
|
|
3942
|
+
function timestamp3(now) {
|
|
3810
3943
|
const value = now();
|
|
3811
3944
|
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new TypeError("Claude Host clock is invalid.");
|
|
3812
3945
|
return value.toISOString();
|
|
@@ -3814,141 +3947,78 @@ function timestamp4(now) {
|
|
|
3814
3947
|
|
|
3815
3948
|
// host/claude/src/claude-producer.ts
|
|
3816
3949
|
var ClaudeProducer = class {
|
|
3817
|
-
producer_id = "claude";
|
|
3818
|
-
producer_label = "Claude";
|
|
3819
|
-
outbox_directory;
|
|
3820
3950
|
store;
|
|
3821
3951
|
core;
|
|
3822
3952
|
constructor(options) {
|
|
3823
3953
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3824
|
-
this.outbox_directory = this.store.directory;
|
|
3825
3954
|
this.core = new HostProducerDrainCore(this, options);
|
|
3826
3955
|
}
|
|
3827
3956
|
drainOutbox() {
|
|
3828
3957
|
return this.core.drainOutbox();
|
|
3829
3958
|
}
|
|
3830
|
-
fail(code2, message) {
|
|
3831
|
-
return new ClaudeHostError(code2, message);
|
|
3832
|
-
}
|
|
3833
3959
|
listEnvelopes() {
|
|
3834
3960
|
return this.store.listEnvelopes();
|
|
3835
3961
|
}
|
|
3836
3962
|
removeEnvelope(envelope) {
|
|
3837
3963
|
return this.store.removeEnvelope(envelope);
|
|
3838
3964
|
}
|
|
3839
|
-
submissionRefOf(envelope) {
|
|
3840
|
-
return envelope.submission_ref;
|
|
3841
|
-
}
|
|
3842
3965
|
expiresAtOf(envelope) {
|
|
3843
3966
|
return envelope.expires_at;
|
|
3844
3967
|
}
|
|
3845
|
-
createdAtOf(envelope) {
|
|
3846
|
-
return envelope.created_at;
|
|
3847
|
-
}
|
|
3848
|
-
async mapEnvelope(envelope) {
|
|
3849
|
-
const prompt = observation(envelope, "prompt");
|
|
3850
|
-
if (prompt !== void 0) return this.mapInput(envelope, prompt);
|
|
3851
|
-
return [mapTerminal(envelope)];
|
|
3852
|
-
}
|
|
3853
|
-
async mapInput(envelope, prompt) {
|
|
3854
|
-
const drafts = [];
|
|
3855
|
-
const session = observation(envelope, "session");
|
|
3856
|
-
if (session !== void 0) {
|
|
3857
|
-
const registrations = await this.store.listTranscriptRegistrations();
|
|
3858
|
-
const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
|
|
3859
|
-
const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
|
|
3860
|
-
drafts.push({
|
|
3861
|
-
frame_kind: "session_started",
|
|
3862
|
-
payload: compact({
|
|
3863
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3864
|
-
working_directory_ref: claudeWorkspaceRef(directoryIdentity),
|
|
3865
|
-
model: recordText(session.value, "model"),
|
|
3866
|
-
observations: [session]
|
|
3867
|
-
})
|
|
3868
|
-
});
|
|
3869
|
-
}
|
|
3870
|
-
const turn = observation(envelope, "turn");
|
|
3871
|
-
if (turn !== void 0) drafts.push({
|
|
3872
|
-
frame_kind: "turn_started",
|
|
3873
|
-
payload: compact({
|
|
3874
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3875
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3876
|
-
observations: [turn]
|
|
3877
|
-
})
|
|
3878
|
-
});
|
|
3879
|
-
if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
|
|
3880
|
-
drafts.push({
|
|
3881
|
-
frame_kind: "user_input",
|
|
3882
|
-
payload: compact({
|
|
3883
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3884
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3885
|
-
local_input_ref: envelope.host_input_ref,
|
|
3886
|
-
input_kind: envelope.input_kind,
|
|
3887
|
-
origin_input: envelope.origin_input,
|
|
3888
|
-
observations: [prompt],
|
|
3889
|
-
context_expires_at: envelope.expires_at
|
|
3890
|
-
})
|
|
3891
|
-
});
|
|
3892
|
-
return drafts;
|
|
3893
|
-
}
|
|
3894
3968
|
};
|
|
3895
|
-
function mapTerminal(envelope) {
|
|
3896
|
-
const usage = envelope.observations.filter((item) => ["token", "cost"].includes(item.observation_kind));
|
|
3897
|
-
return {
|
|
3898
|
-
frame_kind: "turn_terminal",
|
|
3899
|
-
payload: compact({
|
|
3900
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3901
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3902
|
-
terminal_state: "completed",
|
|
3903
|
-
usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
|
|
3904
|
-
observations: envelope.observations
|
|
3905
|
-
})
|
|
3906
|
-
};
|
|
3907
|
-
}
|
|
3908
|
-
function observation(envelope, kind) {
|
|
3909
|
-
return envelope.observations.find((item) => item.observation_kind === kind);
|
|
3910
|
-
}
|
|
3911
|
-
function localTurnRef(envelope) {
|
|
3912
|
-
return envelope.identity.host_turn_ref;
|
|
3913
|
-
}
|
|
3914
|
-
function compact(value) {
|
|
3915
|
-
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
|
|
3916
|
-
}
|
|
3917
|
-
function asRecord(value) {
|
|
3918
|
-
return isRecord3(value) ? value : void 0;
|
|
3919
|
-
}
|
|
3920
|
-
function recordText(value, key) {
|
|
3921
|
-
const record6 = asRecord(value);
|
|
3922
|
-
return typeof record6?.[key] === "string" ? record6[key] : void 0;
|
|
3923
|
-
}
|
|
3924
|
-
function isRecord3(value) {
|
|
3925
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3926
|
-
}
|
|
3927
3969
|
|
|
3928
3970
|
// host/service-log.ts
|
|
3929
|
-
|
|
3971
|
+
import { copyFile, mkdir as mkdir3, stat, truncate, appendFile } from "node:fs/promises";
|
|
3972
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, posix, win32 as win322 } from "node:path";
|
|
3973
|
+
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
3974
|
+
function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
|
|
3975
|
+
const line = `${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {}, ...event === "watcher_scan_failed" && cause !== void 0 ? { cause } : {} })}
|
|
3976
|
+
`;
|
|
3930
3977
|
try {
|
|
3931
|
-
|
|
3932
|
-
`);
|
|
3978
|
+
(sink ?? defaultHostServiceDiagnosticSink)(line);
|
|
3933
3979
|
} catch {
|
|
3934
3980
|
}
|
|
3935
3981
|
}
|
|
3982
|
+
function defaultHostServiceDiagnosticSink(line) {
|
|
3983
|
+
writeHostServiceLogLine(line);
|
|
3984
|
+
process.stderr.write(line);
|
|
3985
|
+
}
|
|
3936
3986
|
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
3987
|
+
function writeHostServiceLogLine(line, environment = process.env) {
|
|
3988
|
+
if (process.platform !== "win32") return;
|
|
3989
|
+
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
3990
|
+
if (path === void 0 || !win322.isAbsolute(path) || win322.normalize(path) !== path) return;
|
|
3991
|
+
void appendHostServiceLogLine(path, line).catch(() => void 0);
|
|
3992
|
+
}
|
|
3993
|
+
async function appendHostServiceLogLine(path, line) {
|
|
3994
|
+
if (!isAbsolute3(path)) return;
|
|
3995
|
+
await ensureHostServiceLogDirectory(path);
|
|
3996
|
+
await appendFile(path, line);
|
|
3997
|
+
}
|
|
3937
3998
|
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
3999
|
+
var hostServiceLogDirectories = /* @__PURE__ */ new Map();
|
|
4000
|
+
function ensureHostServiceLogDirectory(path) {
|
|
4001
|
+
const directory = dirname3(path);
|
|
4002
|
+
const existing = hostServiceLogDirectories.get(directory);
|
|
4003
|
+
if (existing !== void 0) return existing;
|
|
4004
|
+
const pending = mkdir3(directory, { recursive: true, mode: 448 }).then(() => void 0).catch((error) => {
|
|
4005
|
+
hostServiceLogDirectories.delete(directory);
|
|
4006
|
+
throw error;
|
|
4007
|
+
});
|
|
4008
|
+
hostServiceLogDirectories.set(directory, pending);
|
|
4009
|
+
return pending;
|
|
4010
|
+
}
|
|
3938
4011
|
|
|
3939
4012
|
// host/claude/src/claude-producer-runtime.ts
|
|
3940
4013
|
async function drainClaudeProducerOnce(options) {
|
|
3941
|
-
const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
|
|
3942
4014
|
const scan = await new ClaudeTranscriptWatcher({
|
|
3943
4015
|
outbox_dir: options.outbox_dir,
|
|
3944
|
-
|
|
3945
|
-
register_current_prompt: (input) => promptPort.register(input),
|
|
3946
|
-
diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("claude", event, failure_kind)
|
|
4016
|
+
diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
|
|
3947
4017
|
}).scanOnce().catch((error) => {
|
|
3948
|
-
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
4018
|
+
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
|
|
3949
4019
|
throw error;
|
|
3950
4020
|
});
|
|
3951
|
-
const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir,
|
|
4021
|
+
const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir, ...options.now === void 0 ? {} : { now: options.now } });
|
|
3952
4022
|
return { scanned: scan.enqueued, ...await producer.drainOutbox() };
|
|
3953
4023
|
}
|
|
3954
4024
|
|
|
@@ -3957,16 +4027,16 @@ import { homedir as homedir2 } from "node:os";
|
|
|
3957
4027
|
import { join as join6 } from "node:path";
|
|
3958
4028
|
|
|
3959
4029
|
// host/producer-service.ts
|
|
3960
|
-
import { posix as
|
|
4030
|
+
import { posix as posix4, win32 as win325 } from "node:path";
|
|
3961
4031
|
|
|
3962
4032
|
// host/runtime-source-paths.ts
|
|
3963
|
-
import { createHash as
|
|
3964
|
-
import { posix as
|
|
4033
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
4034
|
+
import { posix as posix3, win32 as win324 } from "node:path";
|
|
3965
4035
|
|
|
3966
4036
|
// host/absolute-path.ts
|
|
3967
|
-
import { posix, win32 } from "node:path";
|
|
4037
|
+
import { posix as posix2, win32 as win323 } from "node:path";
|
|
3968
4038
|
function isAbsoluteNormalizedNonRootPath(value, flavor = process.platform, allowRoot = false) {
|
|
3969
|
-
const pathApi = flavor === "win32" || flavor === "windows" ?
|
|
4039
|
+
const pathApi = flavor === "win32" || flavor === "windows" ? win323 : posix2;
|
|
3970
4040
|
return typeof value === "string" && value.length <= 4096 && !value.includes("\0") && pathApi.isAbsolute(value) && pathApi.normalize(value) === value && (allowRoot || pathApi.parse(value).root !== value);
|
|
3971
4041
|
}
|
|
3972
4042
|
|
|
@@ -3978,28 +4048,23 @@ function hostSourceUuid(sourceRef) {
|
|
|
3978
4048
|
function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platform) {
|
|
3979
4049
|
const sourceId = hostSourceUuid(sourceRef);
|
|
3980
4050
|
if (!isAbsoluteNormalizedNonRootPath(stateRoot, platform) || sourceId === null) throw new TypeError("Host runtime source identity is invalid.");
|
|
3981
|
-
const path = platform === "win32" ?
|
|
4051
|
+
const path = platform === "win32" ? win324 : posix3;
|
|
3982
4052
|
const sourcesRoot = path.join(stateRoot, "sources");
|
|
3983
4053
|
const sourceRoot = path.join(sourcesRoot, sourceId);
|
|
3984
4054
|
const runtimeRoot = path.join(stateRoot, "run");
|
|
3985
|
-
const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${
|
|
3986
|
-
assertHostRuntimeIpcLength(endpoint, platform);
|
|
4055
|
+
const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash6("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
|
|
3987
4056
|
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 };
|
|
3988
4057
|
}
|
|
3989
|
-
function assertHostRuntimeIpcLength(endpoint, platform) {
|
|
3990
|
-
if (platform === "win32") return;
|
|
3991
|
-
const maximum = platform === "linux" ? 107 : 103;
|
|
3992
|
-
if (Buffer.byteLength(endpoint, "utf8") > maximum) throw Object.assign(new TypeError(`ipc_endpoint_too_long: Host IPC path exceeds ${maximum} bytes; use a shorter Host state root.`), { code: "ipc_endpoint_too_long" });
|
|
3993
|
-
}
|
|
3994
4058
|
function assertHostProducerSourcePaths(stateRoot, config, producer, platform = process.platform) {
|
|
3995
4059
|
const paths = hostRuntimeSourcePaths(stateRoot, config.host_source_ref, platform);
|
|
3996
|
-
if (config.outbox_dir !== (producer === "codex" ? paths.codexOutbox : paths.claudeOutbox) || config.relay_endpoint !== paths.endpoint) throw new TypeError("Host producer outbox and IPC must belong to its configured source.");
|
|
4060
|
+
if (config.outbox_dir !== (producer === "codex" ? paths.codexOutbox : paths.claudeOutbox) || platform !== "win32" && config.relay_endpoint !== paths.endpoint) throw new TypeError("Host producer outbox and IPC must belong to its configured source.");
|
|
4061
|
+
return paths.endpoint;
|
|
3997
4062
|
}
|
|
3998
4063
|
|
|
3999
4064
|
// host/producer-service.ts
|
|
4000
|
-
import { constants } from "node:fs";
|
|
4001
|
-
import { lstat as
|
|
4002
|
-
var
|
|
4065
|
+
import { constants as constants3 } from "node:fs";
|
|
4066
|
+
import { lstat as lstat3 } from "node:fs/promises";
|
|
4067
|
+
var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
4003
4068
|
var HOST_PRODUCER_CONFIG_FLAG = "--ceal-host-config";
|
|
4004
4069
|
var HostProducerServiceError = class extends Error {
|
|
4005
4070
|
code;
|
|
@@ -4010,18 +4075,19 @@ var HostProducerServiceError = class extends Error {
|
|
|
4010
4075
|
}
|
|
4011
4076
|
};
|
|
4012
4077
|
var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
|
|
4078
|
+
var HOST_PRODUCER_CONFIG_ENDPOINT_KEY = "relay_endpoint";
|
|
4013
4079
|
var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
|
|
4014
4080
|
function hostProducerExtraPath(extras, key) {
|
|
4015
4081
|
const value = extras[key];
|
|
4016
|
-
if (value === void 0) throw hostProducerError("invalid_config");
|
|
4082
|
+
if (value === void 0) throw hostProducerError("invalid_config", refusalFact(key, "missing", "an absolute path"));
|
|
4017
4083
|
return value;
|
|
4018
4084
|
}
|
|
4019
4085
|
function parseHostProducerConfigFlag(args, platform = process.platform) {
|
|
4020
4086
|
return parseProducerHostConfigFlag(args, HOST_PRODUCER_CONFIG_FLAG, platform, hostProducerError);
|
|
4021
4087
|
}
|
|
4022
|
-
async function readHostProducerConfig(descriptor, configPath, platform = process.platform) {
|
|
4023
|
-
const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError), platform);
|
|
4024
|
-
const path = platform === "win32" ?
|
|
4088
|
+
async function readHostProducerConfig(descriptor, configPath, platform = process.platform, custody = {}) {
|
|
4089
|
+
const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError, custody), platform);
|
|
4090
|
+
const path = platform === "win32" ? win325 : posix4;
|
|
4025
4091
|
const root = path.resolve(config.outbox_dir, "../../../../..");
|
|
4026
4092
|
try {
|
|
4027
4093
|
assertHostProducerSourcePaths(root, config, descriptor.producer_id, platform);
|
|
@@ -4047,15 +4113,31 @@ function parseProducerHostConfigFlag(args, flag, platform, error) {
|
|
|
4047
4113
|
assertProducerAbsolutePath(value, platform, "config", error);
|
|
4048
4114
|
return value;
|
|
4049
4115
|
}
|
|
4050
|
-
async function readProducerConfigText(configPath, platform, error) {
|
|
4051
|
-
assertProducerAbsolutePath(configPath, platform, "config", error);
|
|
4116
|
+
async function readProducerConfigText(configPath, platform, error, custody) {
|
|
4117
|
+
assertProducerAbsolutePath(configPath, process.platform, "config", error);
|
|
4118
|
+
if (platform === "win32") {
|
|
4119
|
+
let bytes;
|
|
4120
|
+
try {
|
|
4121
|
+
bytes = await readPrivateFileWithSecurefs(configPath, CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES, custody);
|
|
4122
|
+
} catch (cause) {
|
|
4123
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "not_private") throw error("config_not_private");
|
|
4124
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "too_large") throw error("invalid_config", refusalFact("config_bytes", "over_limit", `at most ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
|
|
4125
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "reparse_point") throw error("invalid_config");
|
|
4126
|
+
throw error("config_unavailable");
|
|
4127
|
+
}
|
|
4128
|
+
if (bytes.byteLength === 0) throw error("invalid_config", refusalFact("config_bytes", 0, `between 1 and ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
|
|
4129
|
+
try {
|
|
4130
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
4131
|
+
} catch {
|
|
4132
|
+
throw error("invalid_config");
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4052
4135
|
const pathStat = await configPathStat(configPath, error);
|
|
4053
4136
|
validateConfigFile(pathStat, platform, error);
|
|
4054
|
-
const
|
|
4055
|
-
const handle = await openConfigFile(configPath, constants.O_RDONLY | noFollow, error);
|
|
4137
|
+
const handle = await openConfigFile(configPath, platform, error, custody);
|
|
4056
4138
|
try {
|
|
4057
4139
|
const opened = await handle.stat();
|
|
4058
|
-
validateConfigFile(opened, platform, error);
|
|
4140
|
+
validateConfigFile(opened, platform, error, handle);
|
|
4059
4141
|
validateOpenedFile(opened, pathStat, platform, error);
|
|
4060
4142
|
return await readBoundedConfig(handle, opened.size, error);
|
|
4061
4143
|
} finally {
|
|
@@ -4063,8 +4145,9 @@ async function readProducerConfigText(configPath, platform, error) {
|
|
|
4063
4145
|
}
|
|
4064
4146
|
}
|
|
4065
4147
|
function decodeProducerConfigCore(text, options) {
|
|
4148
|
+
const ownership = producerConfigKeyOwnership(options.keys, options.platform);
|
|
4066
4149
|
const value = parseCanonicalJson(text, options.error);
|
|
4067
|
-
if (!isRecord4(value) || !hasConfigKeys(value,
|
|
4150
|
+
if (!isRecord4(value) || !hasConfigKeys(value, ownership.required, ownership.tolerated) || value.schema_version !== options.schema) throw options.error("invalid_config", refusalFacts(producerShapeFacts(value, { schema: options.schema, keys: ownership.required, tolerated: ownership.tolerated })));
|
|
4068
4151
|
const hostSourceRef = typeof value.host_source_ref === "string" ? value.host_source_ref : "";
|
|
4069
4152
|
const installationRef = typeof value.producer_installation_ref === "string" ? value.producer_installation_ref : "";
|
|
4070
4153
|
const producerEpoch = validateIdentity3(hostSourceRef, installationRef, value.producer_epoch, options.error);
|
|
@@ -4082,14 +4165,21 @@ function decodeProducerConfigCore(text, options) {
|
|
|
4082
4165
|
producer_installation_ref: installationRef,
|
|
4083
4166
|
producer_epoch: producerEpoch,
|
|
4084
4167
|
outbox_dir: value.outbox_dir,
|
|
4085
|
-
relay_endpoint: value
|
|
4168
|
+
relay_endpoint: producerConfigEndpoint(value),
|
|
4086
4169
|
reconcile_interval_ms: intervals[0],
|
|
4087
4170
|
retry_backoff_ms: intervals[1],
|
|
4088
4171
|
ipc_timeout_ms: intervals[2]
|
|
4089
4172
|
};
|
|
4090
4173
|
}
|
|
4174
|
+
function producerConfigKeyOwnership(keys, platform) {
|
|
4175
|
+
if (platform !== "win32") return { required: keys, tolerated: [] };
|
|
4176
|
+
return { required: keys.filter((key) => key !== HOST_PRODUCER_CONFIG_ENDPOINT_KEY), tolerated: [HOST_PRODUCER_CONFIG_ENDPOINT_KEY] };
|
|
4177
|
+
}
|
|
4178
|
+
function producerConfigEndpoint(value) {
|
|
4179
|
+
return typeof value.relay_endpoint === "string" ? value.relay_endpoint : "";
|
|
4180
|
+
}
|
|
4091
4181
|
function assertProducerRelayEndpoint(value, platform, error) {
|
|
4092
|
-
if (platform === "win32"
|
|
4182
|
+
if (platform === "win32") return;
|
|
4093
4183
|
assertProducerAbsolutePath(value, platform, "runtime", error);
|
|
4094
4184
|
}
|
|
4095
4185
|
function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
@@ -4097,26 +4187,27 @@ function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
|
4097
4187
|
}
|
|
4098
4188
|
async function configPathStat(configPath, error) {
|
|
4099
4189
|
try {
|
|
4100
|
-
return await
|
|
4190
|
+
return await lstat3(configPath);
|
|
4101
4191
|
} catch {
|
|
4102
4192
|
throw error("config_unavailable");
|
|
4103
4193
|
}
|
|
4104
4194
|
}
|
|
4105
|
-
function validateConfigFile(value, platform, error) {
|
|
4195
|
+
function validateConfigFile(value, platform, error, handle) {
|
|
4106
4196
|
if (!value.isFile() || value.isSymbolicLink()) throw error("invalid_config", refusalFacts([{ name: "config_is_file", value: value.isFile(), expected: "true" }, { name: "config_is_symlink", value: value.isSymbolicLink(), expected: "false" }]));
|
|
4107
4197
|
if (value.size <= 0 || value.size > CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES) throw error("invalid_config", refusalFact("config_bytes", value.size, `between 1 and ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
|
|
4108
|
-
if (
|
|
4198
|
+
if (!isSelfOwned(value.uid, platform, handle) || !isPrivateFileMode(value.mode, platform, handle)) throw error("config_not_private", refusalFacts([{ name: "platform", value: platform, expected: "a platform with verifiable owner and private-mode facts" }, { name: "config_uid", value: value.uid, expected: "the current user" }, { name: "config_mode", value: value.mode, expected: "private to its owner" }]));
|
|
4109
4199
|
}
|
|
4110
|
-
async function openConfigFile(configPath,
|
|
4200
|
+
async function openConfigFile(configPath, platform, error, custody) {
|
|
4111
4201
|
try {
|
|
4112
|
-
return await
|
|
4113
|
-
} catch {
|
|
4202
|
+
return await openNoFollow(configPath, constants3.O_RDONLY, void 0, platform, custody);
|
|
4203
|
+
} catch (error_) {
|
|
4204
|
+
if (error_ instanceof SecureFileCustodyError) throw error(error_.reason === "not_private" ? "config_not_private" : "invalid_config");
|
|
4114
4205
|
throw error("config_unavailable");
|
|
4115
4206
|
}
|
|
4116
4207
|
}
|
|
4117
4208
|
function validateOpenedFile(opened, pathStat, platform, error) {
|
|
4118
4209
|
if (!opened.isFile()) throw error("config_unavailable", refusalFacts([{ name: "opened_is_file", value: opened.isFile(), expected: "true" }, { name: "opened_mode", value: opened.mode.toString(8), expected: "a regular file mode" }]));
|
|
4119
|
-
if (
|
|
4210
|
+
if (opened.dev !== pathStat.dev || opened.ino !== pathStat.ino) throw error("config_unavailable", refusalFacts([{ name: "platform", value: platform, expected: "the same file the path named" }, { name: "opened_device", value: opened.dev, expected: String(pathStat.dev) }, { name: "opened_inode", value: opened.ino, expected: String(pathStat.ino) }]));
|
|
4120
4211
|
}
|
|
4121
4212
|
async function readBoundedConfig(handle, expectedBytes, error) {
|
|
4122
4213
|
const buffer = Buffer.allocUnsafe(expectedBytes + 1);
|
|
@@ -4148,25 +4239,50 @@ function firstDifferenceIndex(left, right) {
|
|
|
4148
4239
|
for (let index = 0; index < shared; index += 1) if (left[index] !== right[index]) return index;
|
|
4149
4240
|
return shared;
|
|
4150
4241
|
}
|
|
4242
|
+
function producerShapeFacts(value, options) {
|
|
4243
|
+
if (!isRecord4(value)) return [{ name: "config", value: "wrong_type", expected: "a record" }];
|
|
4244
|
+
const tolerated = options.tolerated ?? [];
|
|
4245
|
+
const allowed = [...options.keys, ...tolerated, ...Object.keys(HOST_PRODUCER_TIMING_DEFAULTS)];
|
|
4246
|
+
const facts = [
|
|
4247
|
+
...Object.keys(value).filter((key) => !allowed.includes(key)).map((key) => ({ name: key, value: "unknown", expected: `one of ${[...options.keys, ...tolerated].join(", ")}` })),
|
|
4248
|
+
...options.keys.filter((key) => !Object.hasOwn(value, key)).map((key) => ({ name: key, value: "missing", expected: "present" }))
|
|
4249
|
+
];
|
|
4250
|
+
if (Object.hasOwn(value, "schema_version") && value.schema_version !== options.schema) facts.push({ name: "schema_version", value: "wrong_type", expected: options.schema });
|
|
4251
|
+
return facts;
|
|
4252
|
+
}
|
|
4151
4253
|
function validateIdentity3(sourceRef, installationRef, epoch, error) {
|
|
4152
|
-
if (!
|
|
4254
|
+
if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(installationRef) || !positive(epoch, 2147483647)) throw error("invalid_config", refusalFacts(producerIdentityFacts(sourceRef, installationRef, epoch)));
|
|
4153
4255
|
return epoch;
|
|
4154
4256
|
}
|
|
4257
|
+
function producerIdentityFacts(sourceRef, installationRef, epoch) {
|
|
4258
|
+
const facts = [];
|
|
4259
|
+
if (!SAFE_REF3.test(sourceRef)) facts.push({ name: "host_source_ref", value: "wrong_type", expected: "a safe reference" });
|
|
4260
|
+
if (!SAFE_REF3.test(installationRef)) facts.push({ name: "producer_installation_ref", value: "wrong_type", expected: "a safe reference" });
|
|
4261
|
+
if (!positive(epoch, 2147483647)) facts.push({ name: "producer_epoch", value: "wrong_type", expected: "a positive integer" });
|
|
4262
|
+
return facts;
|
|
4263
|
+
}
|
|
4155
4264
|
function validateIntervals(reconcile, retry, timeout, error) {
|
|
4156
|
-
if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config");
|
|
4265
|
+
if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config", refusalFacts(producerIntervalFacts(reconcile, retry, timeout)));
|
|
4157
4266
|
return [reconcile, retry, timeout];
|
|
4158
4267
|
}
|
|
4159
|
-
function
|
|
4160
|
-
|
|
4268
|
+
function producerIntervalFacts(reconcile, retry, timeout) {
|
|
4269
|
+
const facts = [];
|
|
4270
|
+
if (!boundedMilliseconds(reconcile)) facts.push({ name: "reconcile_interval_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
4271
|
+
if (!boundedMilliseconds(retry)) facts.push({ name: "retry_backoff_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
4272
|
+
if (!boundedMilliseconds(timeout)) facts.push({ name: "ipc_timeout_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
4273
|
+
return facts;
|
|
4274
|
+
}
|
|
4275
|
+
function hasConfigKeys(value, required, tolerated = []) {
|
|
4276
|
+
return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || tolerated.includes(key) || Object.hasOwn(HOST_PRODUCER_TIMING_DEFAULTS, key));
|
|
4161
4277
|
}
|
|
4162
4278
|
function isRecord4(value) {
|
|
4163
4279
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4164
4280
|
}
|
|
4165
|
-
function
|
|
4281
|
+
function positive(value, maximum) {
|
|
4166
4282
|
return Number.isSafeInteger(value) && Number(value) > 0 && Number(value) <= maximum;
|
|
4167
4283
|
}
|
|
4168
4284
|
function boundedMilliseconds(value) {
|
|
4169
|
-
return
|
|
4285
|
+
return positive(value, 6e4) && Number(value) >= 25;
|
|
4170
4286
|
}
|
|
4171
4287
|
|
|
4172
4288
|
// host/claude/src/claude-producer-service.ts
|
|
@@ -4178,11 +4294,7 @@ var CLAUDE_PRODUCER_DESCRIPTOR = {
|
|
|
4178
4294
|
extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
|
|
4179
4295
|
config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
|
|
4180
4296
|
runtime_options: (config) => Object.freeze({
|
|
4181
|
-
outbox_dir: config.outbox_dir
|
|
4182
|
-
producer_installation_ref: config.producer_installation_ref,
|
|
4183
|
-
producer_epoch: config.producer_epoch,
|
|
4184
|
-
relay_endpoint: config.relay_endpoint,
|
|
4185
|
-
timeout_ms: config.ipc_timeout_ms
|
|
4297
|
+
outbox_dir: config.outbox_dir
|
|
4186
4298
|
}),
|
|
4187
4299
|
drain: drainClaudeProducerOnce
|
|
4188
4300
|
};
|
|
@@ -4199,11 +4311,11 @@ function claudeHookEventFromArgs(args) {
|
|
|
4199
4311
|
async function runClaudeHook(event, stdin, options) {
|
|
4200
4312
|
try {
|
|
4201
4313
|
const result = await runClaudeHookFromStdin(event, stdin, options);
|
|
4202
|
-
if (result.disposition === "rejected") writeHostServiceDiagnostic("claude", "hook_failed");
|
|
4203
|
-
else if (result.reason === "transcript_registered") writeHostServiceDiagnostic("claude", "registration_written");
|
|
4314
|
+
if (result.disposition === "rejected") writeHostServiceDiagnostic("claude", "hook_failed", void 0, void 0, options.diagnostic_sink);
|
|
4315
|
+
else if (result.reason === "transcript_registered") writeHostServiceDiagnostic("claude", "registration_written", void 0, void 0, options.diagnostic_sink);
|
|
4204
4316
|
return 0;
|
|
4205
4317
|
} catch {
|
|
4206
|
-
writeHostServiceDiagnostic("claude", "hook_failed");
|
|
4318
|
+
writeHostServiceDiagnostic("claude", "hook_failed", void 0, void 0, options.diagnostic_sink);
|
|
4207
4319
|
return 0;
|
|
4208
4320
|
}
|
|
4209
4321
|
}
|
|
@@ -4226,8 +4338,9 @@ async function installedClaudeHookRunnerOptions(args) {
|
|
|
4226
4338
|
Object.defineProperty(options, "host_source_ref", { value: config.host_source_ref, enumerable: false });
|
|
4227
4339
|
return options;
|
|
4228
4340
|
}
|
|
4229
|
-
function standaloneClaudeHookRunnerOptions() {
|
|
4230
|
-
|
|
4341
|
+
function standaloneClaudeHookRunnerOptions(overrides) {
|
|
4342
|
+
const claudeHome = resolveClaudeConfigHome(process.env, homedir2());
|
|
4343
|
+
return { outbox_dir: overrides?.outbox_dir ?? join6(claudeHome, ".ceal", "claude-observation-outbox"), claude_transcripts_root: resolveClaudeProjectsRoot(process.env, homedir2()) };
|
|
4231
4344
|
}
|
|
4232
4345
|
function hasHostConfigArgument(args) {
|
|
4233
4346
|
return args.some((argument) => argument === HOST_PRODUCER_CONFIG_FLAG || argument.startsWith(`${HOST_PRODUCER_CONFIG_FLAG}=`));
|
|
@@ -4244,8 +4357,11 @@ async function readStdin(stdin) {
|
|
|
4244
4357
|
return Buffer.concat(chunks).toString("utf8");
|
|
4245
4358
|
}
|
|
4246
4359
|
|
|
4360
|
+
// host/local-capability-descriptor.ts
|
|
4361
|
+
var HOST_LOCAL_CAPABILITY_AUTHENTICATION_SUFFIX = Object.freeze(["authenticate", "--client-nonce"]);
|
|
4362
|
+
|
|
4247
4363
|
// host/client-doctor.ts
|
|
4248
|
-
import { execFile } from "node:child_process";
|
|
4364
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
4249
4365
|
import { promisify } from "node:util";
|
|
4250
4366
|
|
|
4251
4367
|
// host/invocation-authentication-client-command.ts
|
|
@@ -4256,7 +4372,7 @@ var DOCTOR_FLAGS = Object.freeze({
|
|
|
4256
4372
|
});
|
|
4257
4373
|
|
|
4258
4374
|
// host/client-doctor.ts
|
|
4259
|
-
var execFileAsync = promisify(
|
|
4375
|
+
var execFileAsync = promisify(execFile2);
|
|
4260
4376
|
|
|
4261
4377
|
// host/claude/src/claude-installer.ts
|
|
4262
4378
|
var ACTIVE_CLAUDE_HOOK_EVENTS = new Set(CLAUDE_HOOK_EVENTS);
|