@corca-org/ceal-host-linux-arm64 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 +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
|
@@ -29,16 +29,34 @@ function isPlainRecord(value) {
|
|
|
29
29
|
return prototype === Object.prototype || prototype === null;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
|
|
32
35
|
// packages/ceal-client-protocol/client-wire-contract.json
|
|
33
36
|
var client_wire_contract_default = {
|
|
34
37
|
schema_version: "ceal.client_wire_contract.v1",
|
|
38
|
+
doctor: {
|
|
39
|
+
schema_version: "ceal.doctor.v1"
|
|
40
|
+
},
|
|
41
|
+
client_commands: {
|
|
42
|
+
doctor: ["doctor"],
|
|
43
|
+
version: ["version"],
|
|
44
|
+
access_group_list: ["access-group", "list"]
|
|
45
|
+
},
|
|
46
|
+
client_environment: {
|
|
47
|
+
environment_prefix: "CEAL",
|
|
48
|
+
context_override_suffixes: ["GATEWAY_ORIGIN", "GATEWAY_TRANSPORT_ORIGIN", "ACCESS_TOKEN", "CONTEXT_STORE_PATH"]
|
|
49
|
+
},
|
|
35
50
|
canonical_json: {
|
|
36
51
|
ordering: "utf16_code_unit_ascending"
|
|
37
52
|
},
|
|
38
53
|
artifact_descriptor: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
operation_contract_bundle_schema_name: "ceal.operation_contract_bundle",
|
|
55
|
+
operation_contract_bundle_fields: ["schema_version", "catalog_revision", "operations", "failure_guidance"],
|
|
56
|
+
schema_name: "ceal.verified_artifact_descriptor",
|
|
57
|
+
fields: ["schema_version", "kind", "media_type", "byte_count", "sha256", "binding"],
|
|
58
|
+
binding_schema_name: "ceal.verified_artifact_binding",
|
|
59
|
+
binding_fields: ["schema_version", "kind", "catalog_revision"],
|
|
42
60
|
kind: "verified_artifact",
|
|
43
61
|
binding_kind: "catalog",
|
|
44
62
|
media_type: "application/json",
|
|
@@ -54,14 +72,16 @@ var client_wire_contract_default = {
|
|
|
54
72
|
length: 72,
|
|
55
73
|
pattern: "^catalog:[a-f0-9]{64}$"
|
|
56
74
|
},
|
|
57
|
-
envelope_max_bytes:
|
|
75
|
+
envelope_max_bytes: 407
|
|
58
76
|
},
|
|
59
77
|
command_request: {
|
|
60
|
-
|
|
78
|
+
schema_name: "ceal.command_request",
|
|
79
|
+
fields: ["schema_version", "argv", "operation", "client_nonce", "context_token"],
|
|
61
80
|
absolute_transport_max_bytes: 65536
|
|
62
81
|
},
|
|
63
82
|
command_resolution: {
|
|
64
|
-
|
|
83
|
+
schema_name: "ceal.gateway_command_resolution",
|
|
84
|
+
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"],
|
|
65
85
|
authority: {
|
|
66
86
|
anonymous: {
|
|
67
87
|
state: "anonymous",
|
|
@@ -101,7 +121,8 @@ var client_wire_contract_default = {
|
|
|
101
121
|
}
|
|
102
122
|
},
|
|
103
123
|
command_response: {
|
|
104
|
-
|
|
124
|
+
schema_name: "ceal.command_response",
|
|
125
|
+
fields: ["schema_version", "exit_code", "document", "operation_id", "visible_output_max_bytes", "local_delivery", "verified_artifact"],
|
|
105
126
|
descriptor_field: "verified_artifact",
|
|
106
127
|
required_fields: [
|
|
107
128
|
"schema_version",
|
|
@@ -125,29 +146,41 @@ var client_wire_contract_default = {
|
|
|
125
146
|
"required_action_sequence",
|
|
126
147
|
"withheld"
|
|
127
148
|
],
|
|
128
|
-
prompt_field: "prompt",
|
|
129
|
-
prompt_schema_version: "ceal.prompt.v1",
|
|
130
|
-
focused_prompt_document_fields: [
|
|
131
|
-
"catalog_revision",
|
|
132
|
-
"prompt"
|
|
133
|
-
],
|
|
134
|
-
focused_prompt_continuation_document_fields: [
|
|
135
|
-
"catalog_revision",
|
|
136
|
-
"remaining_work",
|
|
137
|
-
"prompt"
|
|
138
|
-
],
|
|
139
149
|
discovery_command: "discovery",
|
|
140
|
-
|
|
150
|
+
discovery_manifest_schema_name: "ceal.discovery_manifest",
|
|
151
|
+
discovery_manifest_fields: ["schema_version", "skill", "authority_scope", "connector_kinds", "personal_connector_kinds", "guide_markdown", "renderer_version", "digest"],
|
|
141
152
|
discovery_manifest_document_fields: [
|
|
142
153
|
"discovery_manifest"
|
|
143
154
|
],
|
|
144
155
|
exit_code_max: 125,
|
|
145
156
|
bare_document_max_bytes: 65536,
|
|
146
|
-
complete_max_bytes:
|
|
157
|
+
complete_max_bytes: 66046
|
|
158
|
+
},
|
|
159
|
+
host_invocation_authentication: {
|
|
160
|
+
request_schema_name: "ceal.host_invocation_authentication_request",
|
|
161
|
+
request_fields: [
|
|
162
|
+
"schema_version",
|
|
163
|
+
"client_nonce"
|
|
164
|
+
],
|
|
165
|
+
result_schema_name: "ceal.host_invocation_authentication_result",
|
|
166
|
+
result_fields: [
|
|
167
|
+
"schema_version",
|
|
168
|
+
"context_token",
|
|
169
|
+
"expires_at"
|
|
170
|
+
],
|
|
171
|
+
client_nonce: {
|
|
172
|
+
character_class_source: "A-Za-z0-9_-",
|
|
173
|
+
encoded_characters: 43
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
gateway_host_invocation_context_request: {
|
|
177
|
+
schema_name: "ceal.gateway_host_invocation_context_request",
|
|
178
|
+
fields: ["schema_version", "client_nonce", "context_expires_at", "request_work_context", "current_prompt", "visible_output_max_bytes"]
|
|
147
179
|
},
|
|
148
180
|
resource_stream: {
|
|
149
181
|
path: "/api/ceal/v2/resources",
|
|
150
|
-
|
|
182
|
+
request_schema_name: "ceal.resource_stream_request",
|
|
183
|
+
request_fields: ["schema_version", "resource_ref", "client_nonce", "context_token"],
|
|
151
184
|
ref_pattern: "^resource:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
152
185
|
media_type_pattern: "^[a-z0-9][a-z0-9!#$&^_.+-]{0,63}/[a-z0-9][a-z0-9!#$&^_.+-]{0,63}$",
|
|
153
186
|
filename_max_bytes: 512,
|
|
@@ -164,92 +197,6 @@ var client_wire_contract_default = {
|
|
|
164
197
|
}
|
|
165
198
|
};
|
|
166
199
|
|
|
167
|
-
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
168
|
-
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
169
|
-
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
170
|
-
var commandResolutionAuthority = commandResolution.authority;
|
|
171
|
-
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
172
|
-
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = commandResolution.schema_version;
|
|
173
|
-
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
174
|
-
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
175
|
-
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
176
|
-
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
177
|
-
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = artifact.operation_contract_bundle_schema_version;
|
|
178
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = artifact.schema_version;
|
|
179
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = artifact.binding_schema_version;
|
|
180
|
-
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
181
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
182
|
-
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
183
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
184
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
185
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
186
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
187
|
-
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
188
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
189
|
-
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = request.schema_version;
|
|
190
|
-
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
191
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
192
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_FIELD = response.prompt_field;
|
|
193
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_SCHEMA_VERSION = response.prompt_schema_version;
|
|
194
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
195
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.bare_document_fields, response.prompt_field]);
|
|
196
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_document_fields]);
|
|
197
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_CONTINUATION_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_continuation_document_fields]);
|
|
198
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
199
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = response.discovery_manifest_schema_version;
|
|
200
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
201
|
-
var CEAL_COMMAND_RESPONSE_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
202
|
-
|
|
203
|
-
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
204
|
-
function vocabulary(words) {
|
|
205
|
-
return Object.freeze(words);
|
|
206
|
-
}
|
|
207
|
-
function isVocabularyWord(words, value) {
|
|
208
|
-
return words.some((word) => word === value);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
212
|
-
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
213
|
-
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
214
|
-
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
215
|
-
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
216
|
-
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"]);
|
|
217
|
-
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
218
|
-
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
219
|
-
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
220
|
-
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
221
|
-
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
222
|
-
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
223
|
-
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
224
|
-
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
225
|
-
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
226
|
-
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
227
|
-
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
228
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
229
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
230
|
-
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
231
|
-
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
232
|
-
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
233
|
-
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
234
|
-
"audited_call_pending",
|
|
235
|
-
"continuation_read_pending",
|
|
236
|
-
"continuation_write_pending",
|
|
237
|
-
"provider_pacing_pending",
|
|
238
|
-
"corpus_replay_pending",
|
|
239
|
-
"corpus_append_pending",
|
|
240
|
-
"corpus_fence_pending",
|
|
241
|
-
"corpus_content_pending",
|
|
242
|
-
"corpus_transaction_pending"
|
|
243
|
-
]);
|
|
244
|
-
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
245
|
-
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
246
|
-
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
247
|
-
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
248
|
-
"connector_ref",
|
|
249
|
-
"connector_revision",
|
|
250
|
-
"policy_decision_revision"
|
|
251
|
-
]);
|
|
252
|
-
|
|
253
200
|
// packages/ceal-client-protocol/src/operation-next-actions.ts
|
|
254
201
|
var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
|
|
255
202
|
var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
|
|
@@ -316,7 +263,10 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
|
|
|
316
263
|
"follow_required_action_sequence",
|
|
317
264
|
// The command needs caller-owned local materialization (for example an
|
|
318
265
|
// @path file) before the Gateway can receive the argument.
|
|
319
|
-
"materialize_local_argument"
|
|
266
|
+
"materialize_local_argument",
|
|
267
|
+
// A peer's derived schema identifier changed with its field set; install or
|
|
268
|
+
// upgrade the CLI and Host Relay to the Gateway's release before retrying.
|
|
269
|
+
"upgrade_ceal_client"
|
|
320
270
|
];
|
|
321
271
|
var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
|
|
322
272
|
var NON_REMEDY_NEXT_ACTION_VALUES = [
|
|
@@ -429,47 +379,56 @@ var CEAL_OPERATION_WITHHELD_NEXT_ACTIONS = Object.freeze({
|
|
|
429
379
|
personal_authorization_required: "connect_personal_account"
|
|
430
380
|
});
|
|
431
381
|
|
|
432
|
-
// packages/ceal-client-protocol/src/
|
|
433
|
-
|
|
382
|
+
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
383
|
+
function vocabulary(words) {
|
|
384
|
+
return Object.freeze(words);
|
|
385
|
+
}
|
|
386
|
+
function isVocabularyWord(words, value) {
|
|
387
|
+
return words.some((word) => word === value);
|
|
388
|
+
}
|
|
434
389
|
|
|
435
|
-
// packages/ceal-client-protocol/src/
|
|
436
|
-
var
|
|
437
|
-
var
|
|
438
|
-
var
|
|
439
|
-
var
|
|
440
|
-
var
|
|
441
|
-
var
|
|
442
|
-
var
|
|
443
|
-
var
|
|
444
|
-
var
|
|
445
|
-
var
|
|
446
|
-
var
|
|
447
|
-
var
|
|
448
|
-
var
|
|
449
|
-
var
|
|
450
|
-
var
|
|
451
|
-
var
|
|
452
|
-
var
|
|
453
|
-
var
|
|
454
|
-
var
|
|
455
|
-
var
|
|
456
|
-
var
|
|
457
|
-
var
|
|
458
|
-
var
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
"
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
390
|
+
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
391
|
+
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
392
|
+
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
393
|
+
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
394
|
+
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
395
|
+
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"]);
|
|
396
|
+
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
397
|
+
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
398
|
+
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
399
|
+
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
400
|
+
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
401
|
+
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
402
|
+
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
403
|
+
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
404
|
+
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
405
|
+
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
406
|
+
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
407
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
408
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
409
|
+
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
410
|
+
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
411
|
+
var CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION = "ceal.gateway_call_result.v1";
|
|
412
|
+
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
413
|
+
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
414
|
+
"audited_call_pending",
|
|
415
|
+
"continuation_read_pending",
|
|
416
|
+
"continuation_write_pending",
|
|
417
|
+
"provider_pacing_pending",
|
|
418
|
+
"corpus_replay_pending",
|
|
419
|
+
"corpus_append_pending",
|
|
420
|
+
"corpus_fence_pending",
|
|
421
|
+
"corpus_content_pending",
|
|
422
|
+
"corpus_transaction_pending"
|
|
423
|
+
]);
|
|
424
|
+
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
425
|
+
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
426
|
+
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
427
|
+
var CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION = "ceal.gateway_audit_call_detail.v1";
|
|
428
|
+
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
429
|
+
"connector_ref",
|
|
430
|
+
"connector_revision",
|
|
431
|
+
"policy_decision_revision"
|
|
473
432
|
]);
|
|
474
433
|
|
|
475
434
|
// packages/ceal-client-protocol/src/gateway-validation-primitives.ts
|
|
@@ -609,6 +568,11 @@ function isCealJsonRecord(value) {
|
|
|
609
568
|
}
|
|
610
569
|
var CEAL_VALIDATION_STATUSES = vocabulary(["not_applicable", "valid", "invalid"]);
|
|
611
570
|
var CEAL_PARSE_STATUSES = vocabulary(["not_applicable", "parsed", "parse_failed"]);
|
|
571
|
+
function hasExactKeys(value, expected) {
|
|
572
|
+
const actual = Object.keys(value).sort();
|
|
573
|
+
const wanted = [...expected].sort();
|
|
574
|
+
return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]);
|
|
575
|
+
}
|
|
612
576
|
function invalidByContext(options) {
|
|
613
577
|
if (options.forbidAuthorityKeys) invalidRequest();
|
|
614
578
|
invalidResponse();
|
|
@@ -683,51 +647,6 @@ function cut(text) {
|
|
|
683
647
|
return text.length <= REFUSAL_VALUE_MAX_CHARS ? text : `${text.slice(0, REFUSAL_VALUE_MAX_CHARS)}...(+${text.length - REFUSAL_VALUE_MAX_CHARS} chars)`;
|
|
684
648
|
}
|
|
685
649
|
|
|
686
|
-
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
687
|
-
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
688
|
-
|
|
689
|
-
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
690
|
-
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
691
|
-
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
692
|
-
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
693
|
-
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
694
|
-
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
695
|
-
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
696
|
-
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
697
|
-
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
698
|
-
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
699
|
-
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
700
|
-
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
701
|
-
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
702
|
-
|
|
703
|
-
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
704
|
-
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
705
|
-
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
706
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
707
|
-
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
708
|
-
}
|
|
709
|
-
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
713
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
714
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
715
|
-
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
716
|
-
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
717
|
-
max_depth: 8,
|
|
718
|
-
max_properties: 96,
|
|
719
|
-
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
720
|
-
max_alternatives: 8,
|
|
721
|
-
max_enum_values: 64,
|
|
722
|
-
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
723
|
-
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
724
|
-
max_description_characters: 1024,
|
|
725
|
-
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
726
|
-
max_pattern_characters: 256,
|
|
727
|
-
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
728
|
-
max_literal_nodes: 512
|
|
729
|
-
});
|
|
730
|
-
|
|
731
650
|
// packages/ceal-client-protocol/src/operation-validation-primitives.ts
|
|
732
651
|
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;
|
|
733
652
|
var CEAL_CALENDAR_DATE_PATTERN_SOURCE = "^\\d{4}-\\d{2}-\\d{2}$";
|
|
@@ -795,38 +714,366 @@ function requireIsoTimestamp(value, message) {
|
|
|
795
714
|
if (!isCealIsoTimestamp(value)) throw new TypeError(message);
|
|
796
715
|
}
|
|
797
716
|
|
|
798
|
-
// packages/ceal-client-protocol/src/
|
|
799
|
-
var
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
var
|
|
803
|
-
var
|
|
804
|
-
var
|
|
805
|
-
var
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
717
|
+
// packages/ceal-client-protocol/src/failure-guidance-table.ts
|
|
718
|
+
var CEAL_FAILURE_REDACTION_CLASSES = vocabulary(["model_safe", "withheld"]);
|
|
719
|
+
var CEAL_GATEWAY_STARTING_FAILURE_CODE = "gateway_starting";
|
|
720
|
+
var CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE = "bootstrap_absent";
|
|
721
|
+
var CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE = "bootstrap_unavailable";
|
|
722
|
+
var LOCAL_COMMAND_INVALID = "The local command or argument is invalid.";
|
|
723
|
+
var LOCAL_TRANSPORT_INCOMPLETE = "The local Ceal transport could not complete; report this as a gap.";
|
|
724
|
+
var GATEWAY_TRANSPORT_UNAVAILABLE = "The Gateway transport is unavailable; ask an administrator to restore Gateway availability.";
|
|
725
|
+
var HOST_AUTHENTICATION_UNAVAILABLE = "The Host could not authenticate this invocation; report this as a gap.";
|
|
726
|
+
var SESSION_CONTEXT_UNAVAILABLE = "The local Gateway session context is unavailable.";
|
|
727
|
+
function guidance(message, next_action, http_status, retryable = false, redaction_class = "model_safe") {
|
|
728
|
+
return Object.freeze({ message, next_action, http_status, retryable, redaction_class });
|
|
729
|
+
}
|
|
730
|
+
var CEAL_FAILURE_GUIDANCE_TABLE = Object.freeze({
|
|
731
|
+
invalid_command: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
732
|
+
invalid_argument: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
733
|
+
command_not_found: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 404),
|
|
734
|
+
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),
|
|
735
|
+
local_route_unavailable: guidance("The requested local route is unavailable or its descriptor is invalid; report this as a gap.", "answer_with_gap", 404),
|
|
736
|
+
local_route_ambiguous: guidance("The requested local route is ambiguous; no local executable was selected. Report this as a gap.", "answer_with_gap", 400),
|
|
737
|
+
local_execution_unavailable: guidance("The selected local route executable is unavailable; report this as a gap.", "answer_with_gap", 500),
|
|
738
|
+
local_pickup_failed: guidance("The Host Request pickup failed; the Gateway command was not sent. Report this as a gap.", "answer_with_gap", 502),
|
|
739
|
+
invocation_authentication_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
740
|
+
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),
|
|
741
|
+
gateway_origin_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
742
|
+
access_token_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
743
|
+
context_pair_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
744
|
+
context_not_selected: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
745
|
+
context_store_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
746
|
+
context_store_conflict: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
747
|
+
refresh_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
748
|
+
refresh_expired: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
749
|
+
refresh_replayed: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
750
|
+
refresh_revoked: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
751
|
+
refresh_recovery_unavailable: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
752
|
+
refresh_custody_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
753
|
+
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),
|
|
754
|
+
local_output_exists: guidance("The selected local output path already exists; nothing was written to it.", "choose_new_output_path", 400),
|
|
755
|
+
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),
|
|
756
|
+
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),
|
|
757
|
+
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),
|
|
758
|
+
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),
|
|
759
|
+
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),
|
|
760
|
+
response_encoding_failed: guidance("The local response could not be encoded; report this as a gap.", "answer_with_gap", 500),
|
|
761
|
+
request_timeout: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 504),
|
|
762
|
+
request_failed: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 502),
|
|
763
|
+
gateway_unavailable: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 503, true),
|
|
764
|
+
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),
|
|
765
|
+
// `gateway_starting` is the one retryable envelope the serving gate answers on every route while starting
|
|
766
|
+
// (`cli/internal/client/gateway_startup_wait.go` re-drives it); the sentence is the client default that renders
|
|
767
|
+
// when the wait ends still unserved, moved from the `localFailureText` default branch.
|
|
768
|
+
[CEAL_GATEWAY_STARTING_FAILURE_CODE]: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503, true),
|
|
769
|
+
gateway_internal_error: guidance("The Gateway failed without exposing internal state; report this as a gap.", "answer_with_gap", 500),
|
|
770
|
+
// The serving gate emits this code with no message; the sentence is the owner-normalized form of the
|
|
771
|
+
// `gateway_internal_error` guidance (decided with the operator 2026-10-13).
|
|
772
|
+
internal_error: guidance("The Gateway failed without exposing internal state.", "answer_with_gap", 500),
|
|
773
|
+
// Owned by the Gateway canonical adapter (`gateway/core/src/gateway-canonical-operation-http-adapter.ts`):
|
|
774
|
+
// the client variant of this sentence in `localFailureText` was retired when the client started reading this table.
|
|
775
|
+
request_body_too_large: guidance("The Gateway request body exceeds its bounded limit.", "reduce_request_size", 413),
|
|
776
|
+
// Restored verbatim from the retired `localFailureText` `unknown_outcome`
|
|
777
|
+
// case: the outcome is unknown, so a read may be retried while a write
|
|
778
|
+
// must receipt-check first. Collapsing both to "do not repeat the call"
|
|
779
|
+
// forbids the safe read retry the sentence exists to allow.
|
|
780
|
+
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),
|
|
781
|
+
// Restored verbatim from the retired `localFailureText`
|
|
782
|
+
// `refresh_unknown_outcome` case: a refresh that ends unknown leaves the
|
|
783
|
+
// session context unavailable, so the caller repairs or adopts the
|
|
784
|
+
// session instead of reporting a transport gap.
|
|
785
|
+
refresh_unknown_outcome: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
786
|
+
request_cancelled: guidance("The caller cancelled the request before it completed.", "retry", 400, true),
|
|
787
|
+
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),
|
|
788
|
+
denied: guidance("An administrator denied this device's activation request; ask an administrator whether to reconsider it.", "ask_admin", 403),
|
|
789
|
+
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),
|
|
790
|
+
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),
|
|
791
|
+
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),
|
|
792
|
+
recovery_required: guidance("The Gateway could not safely complete the activation transaction automatically; ask an administrator to recover this transaction.", "ask_admin", 500),
|
|
793
|
+
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),
|
|
794
|
+
// Activation commit codes rendered through the `localFailureText` default before phase 2b; `retryable` is moved from the
|
|
795
|
+
// emitting sites in `cli/internal/client/gateway_installation_activation.go` and `activation.go:483`.
|
|
796
|
+
activation_state_commit_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500, true),
|
|
797
|
+
activation_response_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502, true),
|
|
798
|
+
activation_rejected: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
799
|
+
activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
800
|
+
activation_binding: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
801
|
+
activation_crypto: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
802
|
+
activation_state_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
803
|
+
// Long-tail local codes rendered through the `localFailureText` default before phase 2b; sentences moved from that branch.
|
|
804
|
+
invalid_response: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
805
|
+
invalid_configuration: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
806
|
+
activation_state_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
807
|
+
activation_code_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
808
|
+
activation_code_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
809
|
+
activation_code_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 404),
|
|
810
|
+
bootstrap_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
811
|
+
bootstrap_not_active: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
812
|
+
bootstrap_unauthorized: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
813
|
+
// The Gateway owner-bootstrap store file was never created, so the Gateway
|
|
814
|
+
// holds no owner bootstrap to exchange (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
815
|
+
// `absent`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:162`);
|
|
816
|
+
// the sentence is the owner-normalized form of that store error. Non-retryable:
|
|
817
|
+
// repeating the exchange cannot create the store; only an administrator
|
|
818
|
+
// placing a bootstrap (fresh gateway setup) changes the outcome.
|
|
819
|
+
[CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE]: guidance("The Gateway holds no owner bootstrap to exchange; report this as a gap.", "answer_with_gap", 503),
|
|
820
|
+
// The Gateway owner-bootstrap store file exists but cannot be read, so the
|
|
821
|
+
// exchange cannot proceed (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
822
|
+
// `unavailable`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:163`);
|
|
823
|
+
// the sentence is the owner-normalized form of that store error. Retryable:
|
|
824
|
+
// unlike `absent`, a later exchange may succeed once the store is readable.
|
|
825
|
+
[CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE]: guidance("The Gateway owner bootstrap store is unavailable; retry the exchange.", "retry", 503, true),
|
|
826
|
+
context_selection_ambiguous: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
827
|
+
session_authority_unverified: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
828
|
+
session_authority_mismatch: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
829
|
+
local_credential_lost: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
830
|
+
local_capability_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
831
|
+
host_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
832
|
+
host_activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
833
|
+
doctor_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
834
|
+
admin_sign_in_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
835
|
+
// `invalid_request` renders through the client default today (local encode/path faults); Gateway-answered
|
|
836
|
+
// envelopes carry the Gateway message verbatim and never read this sentence.
|
|
837
|
+
invalid_request: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
838
|
+
// Gateway canonical-route codes, sentences moved from their minting adapters; statuses moved from the
|
|
839
|
+
// `valid*Failure` decoder maps in `cli/internal/client/gateway_api.go` and `transport.go`.
|
|
840
|
+
invalid_json: guidance("The Gateway request body must be valid JSON.", "answer_with_gap", 400),
|
|
841
|
+
// Per-route variants name their route; the table holds the one stable guidance and the route's `allow`
|
|
842
|
+
// header stays the measured fact (decided with the operator 2026-10-13).
|
|
843
|
+
method_not_allowed: guidance("The HTTP method is not allowed for this Gateway route.", "answer_with_gap", 405),
|
|
844
|
+
request_target_too_large: guidance("The Gateway request target exceeds its bounded limit.", "reduce_request_size", 414),
|
|
845
|
+
route_not_found: guidance("The Gateway route is not present.", "answer_with_gap", 404),
|
|
846
|
+
artifact_unavailable: guidance("The Gateway artifact is unavailable.", "answer_with_gap", 503),
|
|
847
|
+
discovery_unavailable: guidance("The Gateway discovery manifest is unavailable.", "answer_with_gap", 503),
|
|
848
|
+
serialization_failed: guidance("The Gateway response could not be serialized.", "answer_with_gap", 500),
|
|
849
|
+
request_header_too_large: guidance("A consumed Gateway request header exceeds its bounded limit.", "answer_with_gap", 431),
|
|
850
|
+
request_work_context_missing: guidance("The Gateway command requires a Request work context.", "answer_with_gap", 400),
|
|
851
|
+
request_work_context_invalid: guidance("The Gateway Request work context encoding is invalid.", "answer_with_gap", 400),
|
|
852
|
+
request_work_context_refused: guidance("The Host-only discovery manifest takes no Request work context.", "answer_with_gap", 400),
|
|
853
|
+
// Moved from `gateway/src/operations/http-composition.ts` (`unauthorized`) and `command-runtime.ts:827`
|
|
854
|
+
// (`artifact_stale`); both travel with Gateway messages verbatim online, so these sentences are the
|
|
855
|
+
// owner fallback guidance. `unauthorized` re-authenticates through the session, hence its action.
|
|
856
|
+
unauthorized: guidance("Unauthorized.", "repair_or_adopt_session", 401),
|
|
857
|
+
artifact_stale: guidance("The requested contract artifact is not the current catalog artifact.", "answer_with_gap", 409),
|
|
858
|
+
// Host mint classifications. The CLI emits `invocation_authentication_unavailable` with the classification
|
|
859
|
+
// in its detail fact; rows without their own sentence share that code's moved sentence, while `next_action`
|
|
860
|
+
// is moved from `canonicalHostAuthenticationNextActions` (generated from `CEAL_HOST_AUTHENTICATION_POLICY`)
|
|
861
|
+
// and `retryable` from `hostAuthenticationFailureForCause` (`failure.nextAction == "retry"`). The three rows
|
|
862
|
+
// with their own sentences carry the retired policy remedies as Gateway-owned guidance. Statuses are the
|
|
863
|
+
// Gateway-owned mapping decided with the operator 2026-10-13. This covers every wire cause the maps at
|
|
864
|
+
// `host/invocation-authentication-command.ts:217-242` emit, through their policy classifications.
|
|
865
|
+
host_arguments_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 400),
|
|
866
|
+
host_configuration_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
867
|
+
host_session_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
868
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
869
|
+
// missing exact-turn fact names the fresh Host turn, not a reinstall.
|
|
870
|
+
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),
|
|
871
|
+
host_invocation_context_stale: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
872
|
+
host_invocation_context_expired: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
873
|
+
host_source_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 403),
|
|
874
|
+
host_registration_credential_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 403),
|
|
875
|
+
host_mint_gateway_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 503),
|
|
876
|
+
host_mint_gateway_rate_limited: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 429),
|
|
877
|
+
host_mint_gateway_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
878
|
+
host_mint_gateway_response_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
879
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
880
|
+
// producer-frame fault names the administrator repair step.
|
|
881
|
+
host_producer_frame_invalid: guidance("Ask an administrator to inspect and repair the producer frame, then start a fresh Host turn.", "ask_admin", 400),
|
|
882
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
883
|
+
// sandboxed Relay socket names the run-alone retry.
|
|
884
|
+
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),
|
|
885
|
+
host_relay_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 503),
|
|
886
|
+
host_mint_transient_retryable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 503, true),
|
|
887
|
+
host_mint_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
888
|
+
host_relay_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
889
|
+
host_refusal_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500)
|
|
890
|
+
});
|
|
891
|
+
function cealFailureGuidanceForCode(code2) {
|
|
892
|
+
const entry = CEAL_FAILURE_GUIDANCE_TABLE[code2];
|
|
893
|
+
if (entry === void 0) throw new TypeError(`Failure guidance code is not guided: ${code2}.`);
|
|
894
|
+
return entry;
|
|
895
|
+
}
|
|
896
|
+
var FAILURE_GUIDANCE_ENTRY_KEYS = Object.freeze(["message", "next_action", "http_status", "retryable", "redaction_class"]);
|
|
811
897
|
|
|
812
|
-
// packages/ceal-client-protocol/src/
|
|
813
|
-
var
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
"
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
"
|
|
829
|
-
"
|
|
898
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
899
|
+
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
900
|
+
var CEAL_CLIENT_DOCTOR_SCHEMA_VERSION = CEAL_CLIENT_WIRE_CONTRACT.doctor.schema_version;
|
|
901
|
+
var clientCommands = CEAL_CLIENT_WIRE_CONTRACT.client_commands;
|
|
902
|
+
var CEAL_CLIENT_DOCTOR_COMMAND = Object.freeze([...clientCommands.doctor]);
|
|
903
|
+
var CEAL_CLIENT_VERSION_COMMAND = Object.freeze([...clientCommands.version]);
|
|
904
|
+
var CEAL_CLIENT_ACCESS_GROUP_LIST_COMMAND = Object.freeze([...clientCommands.access_group_list]);
|
|
905
|
+
function deriveCealClientWireSchemaVersion(schema) {
|
|
906
|
+
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))) {
|
|
907
|
+
throw new TypeError("Client wire schema name or field set is invalid.");
|
|
908
|
+
}
|
|
909
|
+
const fields = schema.fields.filter((field) => field !== "schema_version").sort();
|
|
910
|
+
const hash8 = createHash("sha256").update(cealCanonicalJson(fields), "utf8").digest("hex").slice(0, 8);
|
|
911
|
+
return `${schema.schema_name}.v1.${hash8}`;
|
|
912
|
+
}
|
|
913
|
+
var CealClientWireContractMismatchError = class extends Error {
|
|
914
|
+
code = "contract_mismatch";
|
|
915
|
+
next_action = cealFailureGuidanceForCode("contract_mismatch").next_action;
|
|
916
|
+
constructor(expected, actual) {
|
|
917
|
+
super(`The peer wire contract ${actual} does not match this client contract ${expected}.`);
|
|
918
|
+
this.name = "CealClientWireContractMismatchError";
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
function requireCealClientWireSchemaVersion(value, expected) {
|
|
922
|
+
if (typeof value === "string" && value !== expected) throw new CealClientWireContractMismatchError(expected, value);
|
|
923
|
+
}
|
|
924
|
+
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
925
|
+
var commandResolutionAuthority = commandResolution.authority;
|
|
926
|
+
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
927
|
+
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(commandResolution);
|
|
928
|
+
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
929
|
+
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
930
|
+
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
931
|
+
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
932
|
+
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.operation_contract_bundle_schema_name, fields: artifact.operation_contract_bundle_fields });
|
|
933
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(artifact);
|
|
934
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.binding_schema_name, fields: artifact.binding_fields });
|
|
935
|
+
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
936
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
937
|
+
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
938
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
939
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
940
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
941
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
942
|
+
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
943
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
944
|
+
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(request);
|
|
945
|
+
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
946
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
947
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
948
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
949
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: response.discovery_manifest_schema_name, fields: response.discovery_manifest_fields });
|
|
950
|
+
var CEAL_COMMAND_RESPONSE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(response);
|
|
951
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
952
|
+
var CEAL_COMMAND_RESPONSE_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
953
|
+
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 });
|
|
954
|
+
var CEAL_RESOURCE_STREAM_REQUEST_FIELDS = Object.freeze([...CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_fields]);
|
|
955
|
+
|
|
956
|
+
// packages/ceal-client-protocol/src/operation-withheld-cause.ts
|
|
957
|
+
var CEAL_OPERATION_WITHHELD_CAUSES = vocabulary(["provider_permission", "access_level", "personal_authorization_required"]);
|
|
958
|
+
|
|
959
|
+
// packages/ceal-client-protocol/src/operation-contract-types.ts
|
|
960
|
+
var CEAL_JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
961
|
+
var CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION = "ceal.operation_descriptor.v1";
|
|
962
|
+
var CEAL_OPERATION_ERROR_SCHEMA_VERSION = "ceal.operation_error.v1";
|
|
963
|
+
var CEAL_ARTIFACT_STAGE_TERMINALS = vocabulary(["chunk_accepted", "artifact_ready", "idempotency_replayed"]);
|
|
964
|
+
var CEAL_RETRY_AFTER_MAX_MS = 60 * 60 * 1e3;
|
|
965
|
+
var CEAL_JSON_SCHEMA_TYPES = vocabulary(["object", "array", "string", "integer", "number", "boolean", "null"]);
|
|
966
|
+
var OBSERVATION_POSTURES = vocabulary(["direct_readback", "approved_no_readback", "operator_mediated", "excluded"]);
|
|
967
|
+
var EFFECT_DOMAINS = vocabulary(["none", "gateway_state", "provider", "external_world"]);
|
|
968
|
+
var SAFETY_CLASSES = vocabulary(["routine", "sensitive", "high_impact"]);
|
|
969
|
+
var IDEMPOTENCY_CLASSES = vocabulary(["idempotent", "conditionally_idempotent", "non_idempotent"]);
|
|
970
|
+
var REVERSIBILITY_CLASSES = vocabulary(["none", "reversible", "compensatable", "irreversible"]);
|
|
971
|
+
var COMPENSATION_OWNERS = vocabulary(["none", "connector", "operator"]);
|
|
972
|
+
var CANCELLATION_BOUNDARIES = vocabulary(["before_admission", "before_handoff", "after_handoff_reconcile"]);
|
|
973
|
+
var HANDOFF_CLASSES = vocabulary(["not_applicable", "submission", "effect"]);
|
|
974
|
+
var AUTHORITATIVE_READBACK_CLASSES = vocabulary(["required", "unavailable"]);
|
|
975
|
+
var PROVIDER_READBACK_CLASSES = vocabulary(["required", "not_available", "not_applicable"]);
|
|
976
|
+
var RECONCILIATION_CLASSES = vocabulary(["none", "automatic", "operator"]);
|
|
977
|
+
var NO_READBACK_APPROVAL_CLASSES = vocabulary(["not_applicable", "explicit", "operator"]);
|
|
978
|
+
var REPLAY_PREREQUISITES = vocabulary(["never", "fresh_admission", "reconciled_non_application", "operator_approval"]);
|
|
979
|
+
var UNKNOWN_OUTCOMES = vocabulary(["reconcile", "operator_action", "excluded"]);
|
|
980
|
+
var RECOVERY_OWNERS = vocabulary(["gateway", "connector", "operator"]);
|
|
981
|
+
var CEAL_OPERATION_ADMISSION_STATES = vocabulary(["proposed", "refused", "admitted"]);
|
|
982
|
+
var CEAL_OPERATION_EXECUTION_STATES = vocabulary(["not_started", "started", "completed"]);
|
|
983
|
+
var CEAL_OPERATION_PROVIDER_HANDOFF_STATES = vocabulary(["not_offered", "offered", "accepted", "rejected", "unknown"]);
|
|
984
|
+
var CEAL_OPERATION_EFFECT_STATES = vocabulary(["none", "applied", "failed", "unknown", "compensated"]);
|
|
985
|
+
var CEAL_OPERATION_OBSERVATION_STATES = vocabulary(["not_started", "not_required", "pending", "observed", "unavailable"]);
|
|
986
|
+
var CEAL_OPERATION_RECOVERY_KINDS = vocabulary(["none", "reconcile", "compensate", "operator_action"]);
|
|
987
|
+
var CEAL_OPERATION_RECOVERY_STATUSES = vocabulary(["not_started", "active", "completed"]);
|
|
988
|
+
var CEAL_OPERATION_PROVIDER_READBACK_AVAILABILITY = vocabulary(["not_started", "not_applicable", "not_available", "pending", "available"]);
|
|
989
|
+
var CEAL_OPERATION_OFFERED_HANDOFF_STATES = vocabulary(["accepted", "rejected", "unknown"]);
|
|
990
|
+
var CEAL_OPERATION_RECEIPT_AUTHORITY_KEYS = Object.freeze(["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]);
|
|
991
|
+
var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
|
|
992
|
+
"approval_not_required_by_policy",
|
|
993
|
+
"interactive_pending",
|
|
994
|
+
"interactive_granted",
|
|
995
|
+
"interactive_denied",
|
|
996
|
+
"interactive_expired",
|
|
997
|
+
"interactive_unavailable"
|
|
998
|
+
]);
|
|
999
|
+
|
|
1000
|
+
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
1001
|
+
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
1002
|
+
|
|
1003
|
+
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
1004
|
+
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
1005
|
+
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
1006
|
+
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
1007
|
+
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
1008
|
+
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
1009
|
+
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
1010
|
+
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
1011
|
+
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
1012
|
+
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
1013
|
+
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
1014
|
+
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
1015
|
+
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
1016
|
+
|
|
1017
|
+
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
1018
|
+
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
1019
|
+
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
1020
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
1021
|
+
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
1022
|
+
}
|
|
1023
|
+
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
1027
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
1028
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
1029
|
+
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
1030
|
+
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
1031
|
+
max_depth: 8,
|
|
1032
|
+
max_properties: 96,
|
|
1033
|
+
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
1034
|
+
max_alternatives: 8,
|
|
1035
|
+
max_enum_values: 64,
|
|
1036
|
+
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
1037
|
+
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
1038
|
+
max_description_characters: 1024,
|
|
1039
|
+
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
1040
|
+
max_pattern_characters: 256,
|
|
1041
|
+
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
1042
|
+
max_literal_nodes: 512
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
// packages/ceal-client-protocol/src/operation-json-schema.ts
|
|
1046
|
+
var JSON_DESCRIPTION_CONTROL_CHARACTER = new RegExp("\\p{Cc}", "u");
|
|
1047
|
+
|
|
1048
|
+
// packages/ceal-client-protocol/src/operation-contract-validation.ts
|
|
1049
|
+
var CEAL_OPERATION_AUTHORITY_POLICY_KEYS = Object.freeze(["access_group", "instance_binding", "target", "admission_recheck"]);
|
|
1050
|
+
var MAX_OPERATION_INPUT_RESOLVER_BINDINGS = 96;
|
|
1051
|
+
var CEAL_OPERATION_DESCRIPTOR_REQUIRED_ENVELOPE_KEYS = Object.freeze(["schema_version", "id", "input_contract", "result_contract", "policy"]);
|
|
1052
|
+
var CEAL_OPERATION_DESCRIPTOR_OPTIONAL_ENVELOPE_KEYS = Object.freeze(["input_resolution", "summary"]);
|
|
1053
|
+
|
|
1054
|
+
// packages/ceal-client-protocol/src/operation-catalog.ts
|
|
1055
|
+
var CEAL_OPERATION_CATALOG_SCHEMA_VERSION = "ceal.catalog.v3";
|
|
1056
|
+
var CLIENT_AUDIENCES = Object.freeze(["ceal", "cealctl", "agent"]);
|
|
1057
|
+
var CLIENT_AUDIENCE_SET = new Set(CLIENT_AUDIENCES);
|
|
1058
|
+
|
|
1059
|
+
// packages/ceal-client-protocol/src/schema-literals.ts
|
|
1060
|
+
var CEAL_SCHEMA_LITERALS = [
|
|
1061
|
+
"ceal.activation_transport.v1",
|
|
1062
|
+
CEAL_OPERATION_ACCESS_SCHEMA_VERSION,
|
|
1063
|
+
CEAL_OPERATION_CATALOG_SCHEMA_VERSION,
|
|
1064
|
+
"ceal.client_refresh_request.v1",
|
|
1065
|
+
"ceal.client_refresh_result.v1",
|
|
1066
|
+
"ceal.client_revoke_request.v1",
|
|
1067
|
+
"ceal.client_revoke_result.v1",
|
|
1068
|
+
CEAL_COMMAND_REQUEST_SCHEMA_VERSION,
|
|
1069
|
+
"ceal.device_enrollment_challenge.v1",
|
|
1070
|
+
"ceal.device_enrollment_challenge_request.v1",
|
|
1071
|
+
"ceal.device_enrollment_hpke_aad.v1",
|
|
1072
|
+
"ceal.device_enrollment_hpke_info.v1",
|
|
1073
|
+
"ceal.device_enrollment_poll.v1",
|
|
1074
|
+
"ceal.device_enrollment_poll_result.v1",
|
|
1075
|
+
"ceal.device_enrollment_proof.v1",
|
|
1076
|
+
"ceal.device_enrollment_start.v1",
|
|
830
1077
|
"ceal.device_enrollment_start_result.v1",
|
|
831
1078
|
"ceal.effect_approval.v1",
|
|
832
1079
|
"ceal.effect_approval_confirmation.v1",
|
|
@@ -836,12 +1083,12 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
836
1083
|
"ceal.enrollment_result.v1",
|
|
837
1084
|
"ceal.first_activation_causal_join.v1",
|
|
838
1085
|
"ceal.gateway_announcement_policy.v1",
|
|
839
|
-
|
|
1086
|
+
CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION,
|
|
840
1087
|
"ceal.gateway_audit_event.v1",
|
|
841
1088
|
"ceal.gateway_audit_readback.v1",
|
|
842
1089
|
"ceal.gateway_authorization_snapshot.v1",
|
|
843
1090
|
"ceal.gateway_cache_origin.v1",
|
|
844
|
-
|
|
1091
|
+
CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION,
|
|
845
1092
|
CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION,
|
|
846
1093
|
"ceal.gateway_connector_route_failure.v1",
|
|
847
1094
|
"ceal.gateway_discovery.v1",
|
|
@@ -866,7 +1113,7 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
866
1113
|
"ceal.host_observation.v1",
|
|
867
1114
|
"ceal.host_registration_request.v1",
|
|
868
1115
|
"ceal.host_registration_result.v1",
|
|
869
|
-
|
|
1116
|
+
CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION,
|
|
870
1117
|
"ceal.operation_error.v1",
|
|
871
1118
|
"ceal.operation_failure.v1",
|
|
872
1119
|
"ceal.operation_failure.compact.v1",
|
|
@@ -974,11 +1221,22 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
|
|
|
974
1221
|
var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
|
|
975
1222
|
var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
|
|
976
1223
|
|
|
1224
|
+
// packages/ceal-client-protocol/src/client-environment-generated.ts
|
|
1225
|
+
var CEAL_CLIENT_CONTEXT_OVERRIDE_ENVIRONMENTS = Object.freeze(["CEAL_GATEWAY_ORIGIN", "CEAL_GATEWAY_TRANSPORT_ORIGIN", "CEAL_ACCESS_TOKEN", "CEAL_CONTEXT_STORE_PATH"]);
|
|
1226
|
+
|
|
1227
|
+
// packages/ceal-client-protocol/src/client-process-contract.ts
|
|
1228
|
+
var CEAL_CLIENT_VERSION_SCHEMA_SUFFIX = ".version.v1";
|
|
1229
|
+
var CEAL_CLIENT_VERSION_SCHEMA_VERSION = `ceal${CEAL_CLIENT_VERSION_SCHEMA_SUFFIX}`;
|
|
1230
|
+
|
|
977
1231
|
// packages/ceal-client-protocol/src/bare-document.ts
|
|
978
1232
|
var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_fields;
|
|
979
1233
|
var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
|
|
980
1234
|
|
|
1235
|
+
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1236
|
+
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1237
|
+
|
|
981
1238
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
1239
|
+
var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
|
|
982
1240
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
983
1241
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
984
1242
|
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"]);
|
|
@@ -991,6 +1249,8 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
|
|
|
991
1249
|
"resource_created",
|
|
992
1250
|
"review_submitted",
|
|
993
1251
|
"comment_created",
|
|
1252
|
+
"reaction_created",
|
|
1253
|
+
"ceal_write_completed",
|
|
994
1254
|
"event_start",
|
|
995
1255
|
"all_day_start",
|
|
996
1256
|
"file_created",
|
|
@@ -1025,6 +1285,10 @@ var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
|
|
|
1025
1285
|
"github.review.submitted",
|
|
1026
1286
|
"github.issue_comment.created",
|
|
1027
1287
|
"github.review_comment.created",
|
|
1288
|
+
"github.reaction.created",
|
|
1289
|
+
"github.reaction.deleted",
|
|
1290
|
+
"github.pull_request.review_requested",
|
|
1291
|
+
"github.pull_request.review_request_removed",
|
|
1028
1292
|
"calendar.organized",
|
|
1029
1293
|
"calendar.accepted_invite",
|
|
1030
1294
|
"calendar.recorded",
|
|
@@ -1046,6 +1310,10 @@ var CEAL_ACTIVITY_EVENT_TIMESTAMP_BASIS_MAP = Object.freeze({
|
|
|
1046
1310
|
"github.pull_request.opened": "resource_created",
|
|
1047
1311
|
"github.issue.edited": "resource_modified",
|
|
1048
1312
|
"github.pull_request.edited": "resource_modified",
|
|
1313
|
+
"github.reaction.created": "reaction_created",
|
|
1314
|
+
"github.reaction.deleted": "ceal_write_completed",
|
|
1315
|
+
"github.pull_request.review_requested": "ceal_write_completed",
|
|
1316
|
+
"github.pull_request.review_request_removed": "ceal_write_completed",
|
|
1049
1317
|
"calendar.organized": "resource_created",
|
|
1050
1318
|
"drive.file.created": "file_created",
|
|
1051
1319
|
"drive.file.modified": "file_modified_by_me",
|
|
@@ -1062,12 +1330,14 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1062
1330
|
next: { required: true, timestamp: false },
|
|
1063
1331
|
kind: { required: true, timestamp: false },
|
|
1064
1332
|
matched_kinds: { required: false, timestamp: false },
|
|
1333
|
+
title_match: { required: false, timestamp: false },
|
|
1065
1334
|
title: { required: true, timestamp: false },
|
|
1066
1335
|
container_label: { required: true, timestamp: false },
|
|
1067
1336
|
container_ref: { required: false, timestamp: false },
|
|
1068
1337
|
source_url: { required: false, timestamp: false },
|
|
1069
1338
|
source: { required: true, timestamp: false },
|
|
1070
1339
|
owner: { required: false, timestamp: false },
|
|
1340
|
+
actor_kind: { required: false, timestamp: false },
|
|
1071
1341
|
counterparts: { required: false, timestamp: false },
|
|
1072
1342
|
attachments: { required: false, timestamp: false },
|
|
1073
1343
|
created_at: { required: false, timestamp: true },
|
|
@@ -1075,9 +1345,18 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1075
1345
|
modified_at: { required: false, timestamp: true },
|
|
1076
1346
|
modified_at_local: { required: false, timestamp: true },
|
|
1077
1347
|
viewed_by_me_at: { required: false, timestamp: true },
|
|
1348
|
+
modified_by_me_at: { required: false, timestamp: true },
|
|
1078
1349
|
own_ceal_write: { required: false, timestamp: false },
|
|
1079
1350
|
owned_by_me: { required: false, timestamp: false },
|
|
1351
|
+
mime_type: { required: false, timestamp: false },
|
|
1352
|
+
size_bytes: { required: false, timestamp: false },
|
|
1353
|
+
version: { required: false, timestamp: false },
|
|
1354
|
+
trashed: { required: false, timestamp: false },
|
|
1080
1355
|
visibility: { required: false, timestamp: false },
|
|
1356
|
+
archived: { required: false, timestamp: false },
|
|
1357
|
+
tag: { required: false, timestamp: false },
|
|
1358
|
+
labels: { required: false, timestamp: false },
|
|
1359
|
+
labels_incomplete: { required: false, timestamp: false },
|
|
1081
1360
|
start: { required: false, timestamp: false },
|
|
1082
1361
|
end: { required: false, timestamp: false },
|
|
1083
1362
|
status: { required: false, timestamp: false },
|
|
@@ -1150,18 +1429,29 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
|
|
|
1150
1429
|
// The serving gate answers this while the Gateway is still starting and the
|
|
1151
1430
|
// refresh was never attempted; the same attempt stays retryable. It is never
|
|
1152
1431
|
// stored by the session owner, only answered at the serving edge.
|
|
1153
|
-
|
|
1432
|
+
CEAL_GATEWAY_STARTING_FAILURE_CODE
|
|
1433
|
+
]);
|
|
1434
|
+
var CEAL_CLIENT_REVOKE_FAILURE_CODES = vocabulary([
|
|
1435
|
+
"refresh_invalid",
|
|
1436
|
+
"refresh_expired",
|
|
1437
|
+
"refresh_replayed",
|
|
1438
|
+
"refresh_revoked"
|
|
1154
1439
|
]);
|
|
1440
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX = "ceal_personal_";
|
|
1441
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX = "ceal_refresh_";
|
|
1442
|
+
var PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN = "[A-Za-z0-9_-]{43}";
|
|
1443
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1444
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1155
1445
|
|
|
1156
1446
|
// packages/ceal-client-protocol/src/request-validation-primitives.ts
|
|
1157
|
-
import { createHash } from "node:crypto";
|
|
1447
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1158
1448
|
var DIGEST = /^[a-f0-9]{64}$/u;
|
|
1159
1449
|
var CEAL_SHA256_DIGEST_PATTERN = DIGEST.source;
|
|
1160
1450
|
function isCealDigest(value) {
|
|
1161
1451
|
return typeof value === "string" && DIGEST.test(value);
|
|
1162
1452
|
}
|
|
1163
1453
|
function cealRequestSha256(value) {
|
|
1164
|
-
return
|
|
1454
|
+
return createHash2("sha256").update(cealCanonicalJson(value), "utf8").digest("hex");
|
|
1165
1455
|
}
|
|
1166
1456
|
function operationRecord(value, message) {
|
|
1167
1457
|
requireOperationRecord(value, message);
|
|
@@ -1177,40 +1467,71 @@ function requirePrefixedRef(value, prefix, message) {
|
|
|
1177
1467
|
// packages/ceal-client-protocol/src/request-authority.ts
|
|
1178
1468
|
var CEAL_REQUEST_SCHEMA_VERSION = "ceal.request.v1";
|
|
1179
1469
|
var CEAL_REQUEST_ARTIFACT_OWNERS = vocabulary(["gateway", "host", "connector"]);
|
|
1180
|
-
var
|
|
1470
|
+
var CEAL_HOST_INVOCATION_IDENTITY_KEYS = vocabulary(["host_session_ref", "host_turn_ref", "host_source_ref"]);
|
|
1181
1471
|
var CEAL_REQUEST_ARTIFACT_RETENTION_CLASSES = vocabulary(["request", "source", "result"]);
|
|
1182
1472
|
var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
|
|
1183
|
-
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"
|
|
1473
|
+
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"]);
|
|
1474
|
+
var CEAL_REQUEST_COMMON_PROPERTIES = Object.freeze({
|
|
1475
|
+
schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
|
|
1476
|
+
ceal_request_ref: { type: "string", minLength: 1, maxLength: 128, pattern: "^ceal-request:[A-Za-z0-9][A-Za-z0-9._:-]{0,114}$" },
|
|
1477
|
+
created_at: { type: "string", minLength: 1, maxLength: 64 },
|
|
1478
|
+
authority: {
|
|
1479
|
+
type: "object",
|
|
1480
|
+
maxProperties: 5,
|
|
1481
|
+
additionalProperties: false,
|
|
1482
|
+
properties: {
|
|
1483
|
+
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1484
|
+
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1485
|
+
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1486
|
+
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1487
|
+
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1488
|
+
},
|
|
1489
|
+
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
var CEAL_REQUEST_HOST_PROPERTIES = Object.freeze({
|
|
1493
|
+
prompt_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1494
|
+
host_session_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1495
|
+
host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1496
|
+
host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1497
|
+
host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1498
|
+
});
|
|
1499
|
+
var CEAL_REQUEST_CALLER_PROPERTIES = Object.freeze({
|
|
1500
|
+
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." },
|
|
1501
|
+
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." }
|
|
1502
|
+
});
|
|
1503
|
+
var CEAL_REQUEST_OPTIONAL_PROPERTIES = Object.freeze({
|
|
1504
|
+
requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
|
|
1505
|
+
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." }
|
|
1506
|
+
});
|
|
1507
|
+
var CEAL_REQUEST_HOST_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, ...CEAL_HOST_INVOCATION_IDENTITY_KEYS, "prompt_ref"]);
|
|
1508
|
+
var CEAL_REQUEST_CALLER_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, "caller_call_group_ref", "caller_call_input_sha256"]);
|
|
1184
1509
|
var CEAL_REQUEST_JSON_SCHEMA = {
|
|
1185
1510
|
type: "object",
|
|
1186
|
-
|
|
1511
|
+
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.",
|
|
1512
|
+
maxProperties: 13,
|
|
1187
1513
|
additionalProperties: false,
|
|
1188
1514
|
properties: {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1515
|
+
...CEAL_REQUEST_COMMON_PROPERTIES,
|
|
1516
|
+
...CEAL_REQUEST_HOST_PROPERTIES,
|
|
1517
|
+
...CEAL_REQUEST_CALLER_PROPERTIES,
|
|
1518
|
+
...CEAL_REQUEST_OPTIONAL_PROPERTIES
|
|
1519
|
+
},
|
|
1520
|
+
required: CEAL_REQUEST_KEYS,
|
|
1521
|
+
anyOf: [
|
|
1522
|
+
{
|
|
1193
1523
|
type: "object",
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1198
|
-
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1199
|
-
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1200
|
-
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1201
|
-
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1202
|
-
},
|
|
1203
|
-
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1524
|
+
required: CEAL_REQUEST_HOST_REQUIRED_KEYS,
|
|
1525
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_HOST_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1526
|
+
additionalProperties: false
|
|
1204
1527
|
},
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
},
|
|
1213
|
-
required: CEAL_REQUEST_KEYS
|
|
1528
|
+
{
|
|
1529
|
+
type: "object",
|
|
1530
|
+
required: CEAL_REQUEST_CALLER_REQUIRED_KEYS,
|
|
1531
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_CALLER_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1532
|
+
additionalProperties: false
|
|
1533
|
+
}
|
|
1534
|
+
]
|
|
1214
1535
|
};
|
|
1215
1536
|
|
|
1216
1537
|
// packages/ceal-client-protocol/src/request-operands.ts
|
|
@@ -1321,10 +1642,6 @@ var CEAL_REQUEST_RUN_BATCH_JSON_SCHEMA = {
|
|
|
1321
1642
|
};
|
|
1322
1643
|
|
|
1323
1644
|
// packages/ceal-client-protocol/src/host-observation.ts
|
|
1324
|
-
var CEAL_HOST_INPUT_KINDS = vocabulary(["human", "host_automatic"]);
|
|
1325
|
-
function isCealHostInputKind(value) {
|
|
1326
|
-
return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
|
|
1327
|
-
}
|
|
1328
1645
|
var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
|
|
1329
1646
|
var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
|
|
1330
1647
|
var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
|
|
@@ -1377,7 +1694,7 @@ function validateCealHostObservationDraft(value) {
|
|
|
1377
1694
|
assertHostObservationValue(record5.value, record5.observation_kind);
|
|
1378
1695
|
}
|
|
1379
1696
|
var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
|
|
1380
|
-
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "
|
|
1697
|
+
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "timing"]);
|
|
1381
1698
|
function isCealHostPromptObservationValue(value) {
|
|
1382
1699
|
return isOperationRecord(value) && Object.keys(value).length === 1 && isCealDigest(value.body_sha256);
|
|
1383
1700
|
}
|
|
@@ -1500,17 +1817,128 @@ var CEAL_REQUEST_GRAPH_NODE_PREPARE_READINESS_JSON_SCHEMA = {
|
|
|
1500
1817
|
};
|
|
1501
1818
|
|
|
1502
1819
|
// packages/ceal-client-protocol/src/host-invocation-authentication.ts
|
|
1503
|
-
var
|
|
1504
|
-
var
|
|
1505
|
-
var
|
|
1506
|
-
var
|
|
1507
|
-
var
|
|
1508
|
-
var
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1820
|
+
var hostInvocationAuthenticationContract = CEAL_CLIENT_WIRE_CONTRACT.host_invocation_authentication;
|
|
1821
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.request_schema_name, fields: hostInvocationAuthenticationContract.request_fields });
|
|
1822
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.request_fields]);
|
|
1823
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.result_schema_name, fields: hostInvocationAuthenticationContract.result_fields });
|
|
1824
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.result_fields]);
|
|
1825
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_ENVIRONMENT = "CEAL_HOST_AUTHENTICATION_BUDGET_MS";
|
|
1826
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE = "^(?:0|[1-9][0-9]{0,8})$";
|
|
1827
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN = new RegExp(CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE, "u");
|
|
1828
|
+
var CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA = "ceal.gateway_host_invocation_context_request.v2";
|
|
1829
|
+
var CEAL_HOST_INVOCATION_CONTEXT_REQUEST_MAX_BYTES = 16 * 1024;
|
|
1830
|
+
var CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES = 2 * 1024;
|
|
1831
|
+
var CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_MESSAGES = 32;
|
|
1832
|
+
var HOST_CONTEXT_EXPIRY_MAX_CHARS = 64;
|
|
1833
|
+
var CEAL_HOST_INVOCATION_CONTEXT_VISIBLE_OUTPUT_MAX_BYTES = CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES;
|
|
1834
|
+
var CEAL_HOST_CONTEXT_TOKEN_PREFIX = "ceal_context_";
|
|
1835
|
+
function boundCealHostInvocationHumanMessageWindow(newestFirst, sourceTruncated = false) {
|
|
1836
|
+
const messages = [];
|
|
1837
|
+
let truncated = sourceTruncated || newestFirst.length > CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_MESSAGES;
|
|
1838
|
+
for (const candidate of newestFirst.slice(0, CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_MESSAGES)) {
|
|
1839
|
+
const next = [...messages, candidate];
|
|
1840
|
+
if (new TextEncoder().encode(JSON.stringify(next)).byteLength > CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES) {
|
|
1841
|
+
truncated = true;
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
messages.push(Object.freeze({ host_turn_ref: candidate.host_turn_ref, host_input_ref: candidate.host_input_ref, text: candidate.text }));
|
|
1845
|
+
}
|
|
1846
|
+
return Object.freeze({ messages: Object.freeze(messages), truncated });
|
|
1847
|
+
}
|
|
1848
|
+
function decodeCealHostInvocationContextRequest(value) {
|
|
1849
|
+
if (!isCealJsonRecord(value)) throw invalidInvocationContextRequest();
|
|
1850
|
+
requireCealClientWireSchemaVersion(value.schema_version, CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA);
|
|
1851
|
+
if (!hasInvocationContextRequestKeys(value)) throw invalidInvocationContextRequest();
|
|
1852
|
+
const clientNonce = requireClientNonce(value.client_nonce);
|
|
1853
|
+
const expiresAt = requireContextExpiration(value.context_expires_at);
|
|
1854
|
+
const hostBinding = requireHostBinding(value.host_binding);
|
|
1855
|
+
const messageWindow = requireHumanMessageWindow(value.human_message_window);
|
|
1856
|
+
const lastRequestRef = optionalSafeReference(value.last_seen_request_ref);
|
|
1857
|
+
const visibleOutputMaxBytes = optionalVisibleOutputBudget(value.visible_output_max_bytes);
|
|
1858
|
+
return Object.freeze({
|
|
1859
|
+
schema_version: CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA,
|
|
1860
|
+
client_nonce: clientNonce,
|
|
1861
|
+
context_expires_at: expiresAt,
|
|
1862
|
+
host_binding: Object.freeze({ ...hostBinding }),
|
|
1863
|
+
human_message_window: messageWindow,
|
|
1864
|
+
...lastRequestRef === void 0 ? {} : { last_seen_request_ref: lastRequestRef },
|
|
1865
|
+
...visibleOutputMaxBytes === void 0 ? {} : { visible_output_max_bytes: visibleOutputMaxBytes }
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
function hasInvocationContextRequestKeys(value) {
|
|
1869
|
+
const required = ["schema_version", "client_nonce", "context_expires_at", "host_binding", "human_message_window"];
|
|
1870
|
+
const optional = ["last_seen_request_ref", "visible_output_max_bytes"];
|
|
1871
|
+
const optionalKeys = Object.keys(value).filter((key) => optional.includes(key));
|
|
1872
|
+
return hasExactKeys(value, [...required, ...optionalKeys]) && required.every((key) => Object.hasOwn(value, key)) && value.schema_version === CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA;
|
|
1873
|
+
}
|
|
1874
|
+
function requireClientNonce(value) {
|
|
1875
|
+
if (typeof value !== "string" || !CEAL_HOST_CLIENT_NONCE_PATTERN.test(value)) throw invalidInvocationContextRequest();
|
|
1876
|
+
return value;
|
|
1877
|
+
}
|
|
1878
|
+
function requireContextExpiration(value) {
|
|
1879
|
+
if (typeof value !== "string" || value.length > HOST_CONTEXT_EXPIRY_MAX_CHARS || !Number.isFinite(Date.parse(value))) throw invalidInvocationContextRequest();
|
|
1880
|
+
return value;
|
|
1881
|
+
}
|
|
1882
|
+
function requireHostBinding(value) {
|
|
1883
|
+
if (!isCealJsonRecord(value) || !exactOptionalKeys(value, ["host_source_ref", "host_session_ref", "host_turn_ref"], []) || !safeReference(value.host_source_ref) || !safeReference(value.host_session_ref) || !safeReference(value.host_turn_ref)) throw invalidInvocationContextRequest();
|
|
1884
|
+
return { host_source_ref: value.host_source_ref, host_session_ref: value.host_session_ref, host_turn_ref: value.host_turn_ref };
|
|
1885
|
+
}
|
|
1886
|
+
function requireHumanMessageWindow(value) {
|
|
1887
|
+
if (!isCealJsonRecord(value) || !exactOptionalKeys(value, ["messages", "truncated"], []) || !Array.isArray(value.messages) || value.messages.length > CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_MESSAGES || typeof value.truncated !== "boolean") throw invalidInvocationContextRequest();
|
|
1888
|
+
const messages = value.messages.map(decodeHumanMessage);
|
|
1889
|
+
const bytes = new TextEncoder().encode(JSON.stringify(messages)).byteLength;
|
|
1890
|
+
if (bytes > CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES) throw invalidInvocationContextRequest();
|
|
1891
|
+
return Object.freeze({ messages: Object.freeze(messages), truncated: value.truncated });
|
|
1892
|
+
}
|
|
1893
|
+
function decodeHumanMessage(candidate) {
|
|
1894
|
+
if (!isCealJsonRecord(candidate) || !exactOptionalKeys(candidate, ["host_turn_ref", "host_input_ref", "text"], []) || !safeReference(candidate.host_turn_ref) || !safeReference(candidate.host_input_ref) || typeof candidate.text !== "string") throw invalidInvocationContextRequest();
|
|
1895
|
+
return Object.freeze({ host_turn_ref: candidate.host_turn_ref, host_input_ref: candidate.host_input_ref, text: candidate.text });
|
|
1896
|
+
}
|
|
1897
|
+
function optionalSafeReference(value) {
|
|
1898
|
+
if (value === void 0) return void 0;
|
|
1899
|
+
if (!safeReference(value)) throw invalidInvocationContextRequest();
|
|
1900
|
+
return value;
|
|
1901
|
+
}
|
|
1902
|
+
function safeReference(value) {
|
|
1903
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
1904
|
+
}
|
|
1905
|
+
function exactOptionalKeys(value, required, optional = []) {
|
|
1906
|
+
const keys = Object.keys(value);
|
|
1907
|
+
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
1908
|
+
}
|
|
1909
|
+
function optionalVisibleOutputBudget(value) {
|
|
1910
|
+
if (value === void 0) return void 0;
|
|
1911
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > CEAL_HOST_INVOCATION_CONTEXT_VISIBLE_OUTPUT_MAX_BYTES) throw invalidInvocationContextRequest();
|
|
1912
|
+
return value;
|
|
1913
|
+
}
|
|
1914
|
+
function invalidInvocationContextRequest() {
|
|
1915
|
+
return new TypeError("The Host invocation context request is invalid.");
|
|
1916
|
+
}
|
|
1917
|
+
function deriveCealHostInvocationNonceContract(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1918
|
+
const clientNonce = contract.host_invocation_authentication.client_nonce;
|
|
1919
|
+
return {
|
|
1920
|
+
encoded_characters: clientNonce.encoded_characters,
|
|
1921
|
+
client_nonce_pattern_source: `^[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`,
|
|
1922
|
+
context_token_pattern_source: `^${CEAL_HOST_CONTEXT_TOKEN_PREFIX}[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
var hostNonceContract = deriveCealHostInvocationNonceContract();
|
|
1926
|
+
var CEAL_HOST_NONCE_ENCODED_CHARACTERS = hostNonceContract.encoded_characters;
|
|
1927
|
+
var CEAL_HOST_CLIENT_NONCE_PATTERN = new RegExp(hostNonceContract.client_nonce_pattern_source, "u");
|
|
1928
|
+
var CEAL_HOST_CONTEXT_TOKEN_PATTERN = new RegExp(hostNonceContract.context_token_pattern_source, "u");
|
|
1929
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = deriveCealHostInvocationAuthenticationResultMaxBytes();
|
|
1930
|
+
function deriveCealHostInvocationAuthenticationResultMaxBytes(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1931
|
+
const hostAuthentication = contract.host_invocation_authentication;
|
|
1932
|
+
const values = {
|
|
1933
|
+
schema_version: deriveCealClientWireSchemaVersion({ schema_name: hostAuthentication.result_schema_name, fields: hostAuthentication.result_fields }),
|
|
1934
|
+
context_token: `${CEAL_HOST_CONTEXT_TOKEN_PREFIX}${"A".repeat(hostAuthentication.client_nonce.encoded_characters)}`,
|
|
1935
|
+
expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
|
|
1936
|
+
};
|
|
1937
|
+
const fields = hostAuthentication.result_fields;
|
|
1938
|
+
if (fields.some((field) => !Object.hasOwn(values, field))) throw new TypeError("Host invocation result fields have no bounded sample value.");
|
|
1939
|
+
return new TextEncoder().encode(`${JSON.stringify(Object.fromEntries(fields.map((field) => [field, values[field]])))}
|
|
1513
1940
|
`).byteLength;
|
|
1941
|
+
}
|
|
1514
1942
|
var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
|
|
1515
1943
|
"request_authority_unavailable",
|
|
1516
1944
|
"request_missing",
|
|
@@ -1534,6 +1962,7 @@ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
|
|
|
1534
1962
|
"host_credential_refused",
|
|
1535
1963
|
"gateway_unavailable",
|
|
1536
1964
|
"gateway_rate_limited",
|
|
1965
|
+
"contract_mismatch",
|
|
1537
1966
|
"gateway_refused",
|
|
1538
1967
|
"gateway_response_invalid",
|
|
1539
1968
|
"producer_frame_invalid",
|
|
@@ -1548,19 +1977,23 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1548
1977
|
invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
|
|
1549
1978
|
options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
|
|
1550
1979
|
session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1551
|
-
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "
|
|
1552
|
-
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "
|
|
1553
|
-
no_binding
|
|
1554
|
-
|
|
1555
|
-
|
|
1980
|
+
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "retry" }),
|
|
1981
|
+
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1982
|
+
// `no_binding` now means the producer has no real current Host session. The
|
|
1983
|
+
// Host Relay never uses it for an uncreated Gateway Request; there is no
|
|
1984
|
+
// useful retry until the consumer is running inside a Host session.
|
|
1985
|
+
no_binding: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1986
|
+
context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1987
|
+
context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "retry" }),
|
|
1556
1988
|
host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
|
|
1557
1989
|
host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
|
|
1558
1990
|
gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
|
|
1559
1991
|
gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
|
|
1992
|
+
contract_mismatch: Object.freeze({ classification: "contract_mismatch", next_action: "upgrade_ceal_client" }),
|
|
1560
1993
|
gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
|
|
1561
1994
|
gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
|
|
1562
|
-
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin"
|
|
1563
|
-
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox"
|
|
1995
|
+
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin" }),
|
|
1996
|
+
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox" }),
|
|
1564
1997
|
relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
|
|
1565
1998
|
transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
|
|
1566
1999
|
mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
|
|
@@ -1570,21 +2003,33 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1570
2003
|
function isCealHostAuthenticationCause(value) {
|
|
1571
2004
|
return isVocabularyWord(CEAL_HOST_AUTHENTICATION_CAUSES, value);
|
|
1572
2005
|
}
|
|
2006
|
+
function cealHostAuthenticationCauseForExchangeFailure(failure2) {
|
|
2007
|
+
if (failure2 === "permission_denied") return "relay_permission_denied";
|
|
2008
|
+
if (failure2 === "timed_out") return "transient_retryable";
|
|
2009
|
+
return "relay_unavailable";
|
|
2010
|
+
}
|
|
1573
2011
|
function decodeCealHostInvocationAuthenticationRequest(value) {
|
|
1574
|
-
if (!isCealJsonRecord(value)
|
|
2012
|
+
if (!isCealJsonRecord(value)) throw new TypeError("The Host invocation authentication request is invalid.");
|
|
2013
|
+
requireCealClientWireSchemaVersion(value.schema_version, CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA);
|
|
2014
|
+
if (!exactKeys(value, CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_FIELDS) || typeof value.client_nonce !== "string" || !CEAL_HOST_CLIENT_NONCE_PATTERN.test(value.client_nonce)) {
|
|
1575
2015
|
throw new TypeError("The Host invocation authentication request is invalid.");
|
|
1576
2016
|
}
|
|
1577
2017
|
return Object.freeze({ schema_version: CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA, client_nonce: value.client_nonce });
|
|
1578
2018
|
}
|
|
1579
2019
|
function decodeCealHostInvocationAuthenticationResult(value, now = /* @__PURE__ */ new Date()) {
|
|
1580
|
-
|
|
2020
|
+
return decodeHostInvocationAuthenticationResult(value, CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_FIELDS, now);
|
|
2021
|
+
}
|
|
2022
|
+
function decodeHostInvocationAuthenticationResult(value, fields, now) {
|
|
2023
|
+
if (!isCealJsonRecord(value)) throw new TypeError("The Host invocation authentication result is invalid.");
|
|
2024
|
+
requireCealClientWireSchemaVersion(value.schema_version, CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA);
|
|
2025
|
+
if (!exactKeys(value, fields) || typeof value.context_token !== "string" || !CEAL_HOST_CONTEXT_TOKEN_PATTERN.test(value.context_token) || typeof value.expires_at !== "string" || !validFutureTimestamp(value.expires_at, now)) {
|
|
1581
2026
|
throw new TypeError("The Host invocation authentication result is invalid.");
|
|
1582
2027
|
}
|
|
1583
2028
|
return Object.freeze({ schema_version: CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA, context_token: value.context_token, expires_at: value.expires_at });
|
|
1584
2029
|
}
|
|
1585
2030
|
function validFutureTimestamp(value, now) {
|
|
1586
|
-
const
|
|
1587
|
-
return Number.isFinite(
|
|
2031
|
+
const timestamp3 = Date.parse(value);
|
|
2032
|
+
return Number.isFinite(timestamp3) && new Date(timestamp3).toISOString() === value && timestamp3 > now.getTime();
|
|
1588
2033
|
}
|
|
1589
2034
|
function exactKeys(value, expected) {
|
|
1590
2035
|
const keys = Object.keys(value);
|
|
@@ -1598,9 +2043,6 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
|
1598
2043
|
"hook_invoked",
|
|
1599
2044
|
"registration_written",
|
|
1600
2045
|
"hook_failed",
|
|
1601
|
-
"watcher_turn_observed",
|
|
1602
|
-
"current_prompt_registered",
|
|
1603
|
-
"current_prompt_registration_failed",
|
|
1604
2046
|
"activation_recorded",
|
|
1605
2047
|
"activation_failed",
|
|
1606
2048
|
"watcher_scan_failed",
|
|
@@ -1623,6 +2065,9 @@ var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "un
|
|
|
1623
2065
|
var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
|
|
1624
2066
|
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
|
|
1625
2067
|
|
|
2068
|
+
// packages/ceal-client-protocol/src/discovery-manifest.ts
|
|
2069
|
+
var DISCOVERY_MANIFEST_KEYS = CEAL_CLIENT_WIRE_CONTRACT.command_response.discovery_manifest_fields;
|
|
2070
|
+
|
|
1626
2071
|
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1627
2072
|
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1628
2073
|
|
|
@@ -1681,6 +2126,7 @@ var ADMIN_UNAVAILABLE_REASONS = vocabulary([
|
|
|
1681
2126
|
"legacy",
|
|
1682
2127
|
"invalid_observation",
|
|
1683
2128
|
"redacted",
|
|
2129
|
+
"organization_setting_disabled",
|
|
1684
2130
|
"owner_unavailable",
|
|
1685
2131
|
"unit_unknown",
|
|
1686
2132
|
"retention_gap",
|
|
@@ -1710,15 +2156,13 @@ var ADMIN_APPROVAL_DISPOSITIONS = CEAL_OPERATION_APPROVAL_DISPOSITIONS;
|
|
|
1710
2156
|
var ADMIN_APPROVAL_POSTURES = vocabulary([...ADMIN_APPROVAL_DISPOSITIONS, "not_applicable"]);
|
|
1711
2157
|
var ADMIN_LINEAGE_NODE_KINDS = vocabulary(["source_read", "synthesis_preview", "approval", "effect", "readback"]);
|
|
1712
2158
|
|
|
1713
|
-
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1714
|
-
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1715
|
-
|
|
1716
2159
|
// packages/ceal-client-protocol/src/device-enrollment.ts
|
|
1717
2160
|
var CEAL_DEVICE_ENROLLMENT_POLL_FAILURE_CODES = Object.freeze(["unsupported_feature", "recovery_required", "expired", "denied"]);
|
|
1718
2161
|
var POLL_RESULT_KEYS = Object.freeze(["retry_after_ms", "schema_version", "status"]);
|
|
1719
2162
|
|
|
1720
2163
|
// packages/ceal-client-protocol/src/personal-client-session.ts
|
|
1721
2164
|
var REFRESH_FAILURE_CODES = new Set(CEAL_REFRESH_FAILURE_CODES);
|
|
2165
|
+
var REVOKE_FAILURE_CODES = new Set(CEAL_CLIENT_REVOKE_FAILURE_CODES);
|
|
1722
2166
|
|
|
1723
2167
|
// packages/ceal-client-protocol/src/index.ts
|
|
1724
2168
|
var MAX_REQUEST_BYTES = 32 * 1024;
|
|
@@ -1813,16 +2257,9 @@ function hostClientProbeRefusal(probe, subject) {
|
|
|
1813
2257
|
import { performance } from "node:perf_hooks";
|
|
1814
2258
|
|
|
1815
2259
|
// host/relay/src/producer-frame.ts
|
|
1816
|
-
var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
|
|
1817
|
-
var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
|
|
1818
2260
|
var HOST_PRODUCER_FRAME_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
1819
|
-
var
|
|
1820
|
-
var PRODUCERS = /* @__PURE__ */ new Set(["codex", "claude"]);
|
|
1821
|
-
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "user_input", "turn_terminal", "producer_status"];
|
|
2261
|
+
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "turn_terminal", "producer_status"];
|
|
1822
2262
|
var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
|
|
1823
|
-
function isHostProducerFrameKind(value) {
|
|
1824
|
-
return typeof value === "string" && FRAME_KINDS.has(value);
|
|
1825
|
-
}
|
|
1826
2263
|
var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
1827
2264
|
"invalid_frame_encoding",
|
|
1828
2265
|
"invalid_frame_length",
|
|
@@ -1832,515 +2269,217 @@ var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
|
1832
2269
|
"invalid_producer_identity",
|
|
1833
2270
|
"frame_too_large"
|
|
1834
2271
|
];
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2272
|
+
|
|
2273
|
+
// host/relay/src/host-producer-core.ts
|
|
2274
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2275
|
+
|
|
2276
|
+
// host/secure-file.ts
|
|
2277
|
+
import { execFile } from "node:child_process";
|
|
2278
|
+
import { constants } from "node:fs";
|
|
2279
|
+
import { lstat, open } from "node:fs/promises";
|
|
2280
|
+
import { dirname, win32 } from "node:path";
|
|
2281
|
+
import { fileURLToPath } from "node:url";
|
|
2282
|
+
var SecureFileCustodyError = class extends Error {
|
|
2283
|
+
name = "SecureFileCustodyError";
|
|
2284
|
+
reason;
|
|
2285
|
+
constructor(reason, message) {
|
|
1838
2286
|
super(message);
|
|
1839
|
-
this.
|
|
2287
|
+
this.reason = reason;
|
|
2288
|
+
}
|
|
2289
|
+
get code() {
|
|
2290
|
+
if (this.reason === "reparse_point") return "ELOOP";
|
|
2291
|
+
if (this.reason === "not_found") return "ENOENT";
|
|
2292
|
+
if (this.reason === "too_large") return "secure_file_too_large";
|
|
2293
|
+
if (this.reason === "already_exists") return "EEXIST";
|
|
2294
|
+
return this.reason === "not_private" ? "secure_file_not_private" : "secure_file_unavailable";
|
|
1840
2295
|
}
|
|
1841
2296
|
};
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
return
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
const
|
|
1858
|
-
const text = new TextDecoder("utf-8", { fatal: true });
|
|
1859
|
-
let value;
|
|
2297
|
+
var WINDOWS_SECUREFS_REFUSAL_SCHEMA = "ceal.host_securefs_refusal.v1";
|
|
2298
|
+
var WINDOWS_SECUREFS_MAX_RANDOM_FILE_BYTES = 4096;
|
|
2299
|
+
var WINDOWS_SECUREFS_TIMEOUT_MS = 1e4;
|
|
2300
|
+
var WINDOWS_SECUREFS_DIAGNOSTIC_BYTES = 4096;
|
|
2301
|
+
function secureOpenFlags(flags, platform) {
|
|
2302
|
+
if (platform === "win32") return flags;
|
|
2303
|
+
const nofollow = constants.O_NOFOLLOW;
|
|
2304
|
+
if (typeof nofollow === "number" && nofollow !== 0) return flags | nofollow;
|
|
2305
|
+
throw new Error("Secure file reads require O_NOFOLLOW on this platform.");
|
|
2306
|
+
}
|
|
2307
|
+
async function openNoFollow(path, flags = constants.O_RDONLY, mode, platform = process.platform, _custody = {}) {
|
|
2308
|
+
if (platform !== "win32") return await open(path, secureOpenFlags(flags, platform), mode);
|
|
2309
|
+
return await openWindowsIdentityScreened(path, flags, mode);
|
|
2310
|
+
}
|
|
2311
|
+
async function openWindowsIdentityScreened(path, flags, mode) {
|
|
2312
|
+
const handle = await open(path, flags, mode);
|
|
1860
2313
|
try {
|
|
1861
|
-
|
|
2314
|
+
const first = await handle.stat();
|
|
2315
|
+
if (!first.isFile()) throw new SecureFileCustodyError("not_private", "The Windows Host file is not a regular file.");
|
|
2316
|
+
await assertWindowsLinkFree(path, first);
|
|
2317
|
+
await assertWindowsIdentityStable(handle, first, path);
|
|
2318
|
+
return handle;
|
|
1862
2319
|
} catch (error) {
|
|
1863
|
-
|
|
1864
|
-
throw
|
|
2320
|
+
await handle.close();
|
|
2321
|
+
throw error;
|
|
1865
2322
|
}
|
|
1866
|
-
validateFrame(value);
|
|
1867
|
-
return value;
|
|
1868
2323
|
}
|
|
1869
|
-
function
|
|
1870
|
-
|
|
2324
|
+
async function assertWindowsLinkFree(path, first) {
|
|
2325
|
+
const entry = await lstat(path);
|
|
2326
|
+
if (entry.isSymbolicLink() || entry.dev !== first.dev || entry.ino !== first.ino) {
|
|
2327
|
+
throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
async function assertWindowsIdentityStable(handle, first, path) {
|
|
2331
|
+
const second = await handle.stat();
|
|
2332
|
+
if (second.dev !== first.dev || second.ino !== first.ino || second.size !== first.size || second.mtimeMs !== first.mtimeMs) {
|
|
2333
|
+
throw new SecureFileCustodyError("unavailable", "The Windows Host file changed while its identity was checked.");
|
|
2334
|
+
}
|
|
2335
|
+
const entry = await lstat(path);
|
|
2336
|
+
if (entry.isSymbolicLink()) throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2337
|
+
if (entry.dev !== second.dev || entry.ino !== second.ino) throw new SecureFileCustodyError("unavailable", "The Windows Host file path no longer names the opened file.");
|
|
2338
|
+
}
|
|
2339
|
+
async function readPrivateFileWithSecurefs(path, maxBytes, custody = {}) {
|
|
2340
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) throw new TypeError("The securefs read bound must be a positive safe integer.");
|
|
2341
|
+
const result = await runWindowsSecurefs(["internal", "securefs", "read-private-file", path, String(maxBytes)], maxBytes, custody);
|
|
2342
|
+
if (result.exit_code === 0 && result.stderr.length === 0 && result.stdout.length <= maxBytes) return result.stdout;
|
|
2343
|
+
if (result.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned bytes with a refusal.");
|
|
2344
|
+
if (result.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command exceeded its output contract.");
|
|
2345
|
+
throw securefsRefusal(result.stderr);
|
|
2346
|
+
}
|
|
2347
|
+
async function createRandomPrivateFileWithSecurefs(path, byteLength2, custody = {}) {
|
|
2348
|
+
if (!Number.isSafeInteger(byteLength2) || byteLength2 < 1 || byteLength2 > WINDOWS_SECUREFS_MAX_RANDOM_FILE_BYTES) throw new TypeError(`The securefs random-file size must be between 1 and ${WINDOWS_SECUREFS_MAX_RANDOM_FILE_BYTES} bytes.`);
|
|
2349
|
+
const result = await runWindowsSecurefs(["internal", "securefs", "create-random-private-file", path, String(byteLength2)], 0, custody);
|
|
2350
|
+
if (result.exit_code === 0 && result.stdout.length === 0 && result.stderr.length === 0) return;
|
|
2351
|
+
if (result.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned unexpected creation bytes.");
|
|
2352
|
+
if (result.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an invalid creation result.");
|
|
2353
|
+
throw securefsRefusal(result.stderr);
|
|
2354
|
+
}
|
|
2355
|
+
async function runWindowsSecurefs(args, maxBytes, custody) {
|
|
2356
|
+
if (custody.run_securefs_command !== void 0) return await custody.run_securefs_command(args);
|
|
2357
|
+
const executable = custody.securefs_executable ?? resolveInstalledHostExecutable();
|
|
2358
|
+
const processArgs = [...custody.securefs_args_prefix ?? [], ...args];
|
|
2359
|
+
return await new Promise((resolve, reject) => {
|
|
2360
|
+
execFile(executable, processArgs, { encoding: "buffer", timeout: WINDOWS_SECUREFS_TIMEOUT_MS, maxBuffer: maxBytes + WINDOWS_SECUREFS_DIAGNOSTIC_BYTES }, (error, stdout, stderr) => {
|
|
2361
|
+
const exitCode = error === null ? 0 : typeof error.code === "number" ? error.code : null;
|
|
2362
|
+
if (exitCode === null) {
|
|
2363
|
+
reject(new SecureFileCustodyError("unavailable", "The installed Windows Host securefs command could not run."));
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
resolve({ exit_code: exitCode, stdout: Buffer.from(stdout), stderr: Buffer.from(stderr) });
|
|
2367
|
+
});
|
|
2368
|
+
});
|
|
1871
2369
|
}
|
|
1872
|
-
function
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
2370
|
+
function securefsRefusal(stderr) {
|
|
2371
|
+
let value;
|
|
2372
|
+
try {
|
|
2373
|
+
value = JSON.parse(stderr.toString("utf8"));
|
|
2374
|
+
} catch {
|
|
2375
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an unreadable refusal.");
|
|
2376
|
+
}
|
|
2377
|
+
if (!isRecord(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.");
|
|
2378
|
+
if (value.code === "reparse_point") return new SecureFileCustodyError("reparse_point", "The Windows Host securefs file is a reparse point.");
|
|
2379
|
+
if (value.code === "not_found") return new SecureFileCustodyError("not_found", "The Windows Host securefs file was not found.");
|
|
2380
|
+
if (value.code === "too_large") return new SecureFileCustodyError("too_large", "The Windows Host securefs file exceeds its read bound.");
|
|
2381
|
+
if (value.code === "already_exists") return new SecureFileCustodyError("already_exists", "The Windows Host securefs target already exists.");
|
|
2382
|
+
if (value.code === "not_private") return new SecureFileCustodyError("not_private", "The Windows Host securefs file is not private to the current user.");
|
|
2383
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command refused the file read.");
|
|
1879
2384
|
}
|
|
1880
|
-
function
|
|
1881
|
-
const
|
|
1882
|
-
|
|
2385
|
+
function resolveInstalledHostExecutable() {
|
|
2386
|
+
const privateRuntimeRoot = dirname(fileURLToPath(import.meta.url));
|
|
2387
|
+
return win32.join(privateRuntimeRoot, "..", "..", "bin", "ceal-host.exe");
|
|
1883
2388
|
}
|
|
1884
|
-
function
|
|
1885
|
-
return
|
|
2389
|
+
function isRecord(value) {
|
|
2390
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1886
2391
|
}
|
|
1887
|
-
function
|
|
1888
|
-
return
|
|
2392
|
+
function isOwnedBy(uid, expectedUid) {
|
|
2393
|
+
return Number.isSafeInteger(uid) && Number.isSafeInteger(expectedUid) && uid === expectedUid;
|
|
1889
2394
|
}
|
|
1890
|
-
function
|
|
1891
|
-
|
|
2395
|
+
function isSelfOwned(uid, platform = process.platform, _handle) {
|
|
2396
|
+
if (platform === "win32" || typeof process.geteuid !== "function") return false;
|
|
2397
|
+
return isOwnedBy(uid, process.geteuid());
|
|
1892
2398
|
}
|
|
1893
|
-
function
|
|
1894
|
-
|
|
2399
|
+
function isPrivateFileMode(mode, platform = process.platform, _handle) {
|
|
2400
|
+
if (platform === "win32") return false;
|
|
2401
|
+
return (mode & 63) === 0;
|
|
1895
2402
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
2403
|
+
|
|
2404
|
+
// host/relay/src/host-producer-core.ts
|
|
2405
|
+
var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
|
|
2406
|
+
function isHostTranscriptCursorReason(value) {
|
|
2407
|
+
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
1898
2408
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
2409
|
+
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2410
|
+
var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
|
|
2411
|
+
var HOST_INVOCATION_TRANSCRIPT_TAIL_MAX_BYTES = 1024 * 1024;
|
|
2412
|
+
async function readBoundedHostTranscriptTail(path, maximumBytes = HOST_INVOCATION_TRANSCRIPT_TAIL_MAX_BYTES) {
|
|
2413
|
+
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1 || maximumBytes > HOST_INVOCATION_TRANSCRIPT_TAIL_MAX_BYTES) throw new TypeError("Host transcript window byte bound is invalid.");
|
|
2414
|
+
const handle = await openNoFollow(path);
|
|
2415
|
+
try {
|
|
2416
|
+
const stats = await handle.stat();
|
|
2417
|
+
if (!stats.isFile() || !isSelfOwned(stats.uid)) throw new TypeError("Host transcript source is not an owner-owned regular file.");
|
|
2418
|
+
const truncated = stats.size > maximumBytes;
|
|
2419
|
+
const start = Math.max(0, stats.size - maximumBytes);
|
|
2420
|
+
const bytes = await readTranscriptBytes(handle, start, stats.size - start);
|
|
2421
|
+
return { bytes: truncated ? dropPartialLeadingTranscriptRecord(bytes) : bytes, truncated };
|
|
2422
|
+
} finally {
|
|
2423
|
+
await handle.close();
|
|
2424
|
+
}
|
|
1901
2425
|
}
|
|
1902
|
-
function
|
|
1903
|
-
|
|
2426
|
+
async function readTranscriptBytes(handle, start, length) {
|
|
2427
|
+
const output = Buffer.alloc(Math.max(0, length));
|
|
2428
|
+
let offset = 0;
|
|
2429
|
+
while (offset < output.length) {
|
|
2430
|
+
const { bytesRead } = await handle.read(output, offset, output.length - offset, start + offset);
|
|
2431
|
+
if (bytesRead === 0) break;
|
|
2432
|
+
offset += bytesRead;
|
|
2433
|
+
}
|
|
2434
|
+
return output.subarray(0, offset);
|
|
1904
2435
|
}
|
|
1905
|
-
function
|
|
1906
|
-
|
|
2436
|
+
function dropPartialLeadingTranscriptRecord(bytes) {
|
|
2437
|
+
if (bytes.length === 0) return bytes;
|
|
2438
|
+
const newline = bytes.indexOf(10);
|
|
2439
|
+
return newline < 0 ? Buffer.alloc(0) : bytes.subarray(newline + 1);
|
|
1907
2440
|
}
|
|
1908
|
-
function
|
|
1909
|
-
|
|
2441
|
+
async function readRegisteredHostInvocationHumanMessageWindow(listRegistrations, identity, decodeLine) {
|
|
2442
|
+
try {
|
|
2443
|
+
const registration = (await listRegistrations()).find((candidate) => candidate.host_source_ref === identity.host_source_ref && candidate.host_session_ref === identity.host_session_ref && Date.parse(candidate.expires_at) > Date.now());
|
|
2444
|
+
if (registration === void 0) throw new TypeError("context_unavailable");
|
|
2445
|
+
return await readHostInvocationHumanMessageWindow(registration.transcript_path, (line) => decodeLine(line, registration));
|
|
2446
|
+
} catch {
|
|
2447
|
+
throw new TypeError("context_unavailable");
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
async function readHostInvocationHumanMessageWindow(path, decodeLine) {
|
|
2451
|
+
const tail = await readBoundedHostTranscriptTail(path);
|
|
2452
|
+
const lines = new TextDecoder("utf-8", { fatal: true }).decode(tail.bytes).split("\n");
|
|
2453
|
+
const incompleteTail = lines.at(-1) !== "";
|
|
2454
|
+
if (incompleteTail) lines.pop();
|
|
2455
|
+
let truncated = tail.truncated || incompleteTail;
|
|
2456
|
+
const newestFirst = [];
|
|
2457
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2458
|
+
for (const line of lines.reverse()) {
|
|
2459
|
+
if (line.length === 0) continue;
|
|
2460
|
+
const message = decodeLine(line);
|
|
2461
|
+
if (message === "malformed") {
|
|
2462
|
+
truncated = true;
|
|
2463
|
+
continue;
|
|
2464
|
+
}
|
|
2465
|
+
if (message === null || seen.has(message.host_input_ref)) continue;
|
|
2466
|
+
seen.add(message.host_input_ref);
|
|
2467
|
+
newestFirst.push(message);
|
|
2468
|
+
}
|
|
2469
|
+
return boundCealHostInvocationHumanMessageWindow(newestFirst, truncated);
|
|
1910
2470
|
}
|
|
1911
|
-
function
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
if (!Array.isArray(value) || value.length > 32) return false;
|
|
1920
|
-
return value.every((item) => {
|
|
1921
|
-
try {
|
|
1922
|
-
validateCealHostObservationDraft(item);
|
|
1923
|
-
return true;
|
|
1924
|
-
} catch {
|
|
1925
|
-
return false;
|
|
1926
|
-
}
|
|
1927
|
-
});
|
|
1928
|
-
}
|
|
1929
|
-
function jsonValue(value, depth = 0) {
|
|
1930
|
-
if (depth > 16) return false;
|
|
1931
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1932
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
1933
|
-
if (Array.isArray(value)) return value.length <= 256 && value.every((item) => jsonValue(item, depth + 1));
|
|
1934
|
-
return record(value) && Object.keys(value).length <= 256 && Object.entries(value).every(([key, item]) => key.length > 0 && key.length <= 128 && jsonValue(item, depth + 1));
|
|
1935
|
-
}
|
|
1936
|
-
function record(value) {
|
|
1937
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1938
|
-
}
|
|
1939
|
-
function safeRef(value) {
|
|
1940
|
-
return typeof value === "string" && SAFE_REF2.test(value);
|
|
1941
|
-
}
|
|
1942
|
-
function positive(value) {
|
|
1943
|
-
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
1944
|
-
}
|
|
1945
|
-
function timestamp(value) {
|
|
1946
|
-
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
1947
|
-
}
|
|
1948
|
-
function hostProducerFrameKind(value) {
|
|
1949
|
-
return isHostProducerFrameKind(value);
|
|
1950
|
-
}
|
|
1951
|
-
function exactKeys2(value, keys) {
|
|
1952
|
-
return JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
|
|
1953
|
-
}
|
|
1954
|
-
function exactOptional(value, required, optional) {
|
|
1955
|
-
const keys = Object.keys(value);
|
|
1956
|
-
return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
// host/relay/src/host-producer-core.ts
|
|
1960
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
1961
|
-
import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
|
|
1962
|
-
import { mkdir as mkdir2, open as open2, readFile, rename as rename2, writeFile } from "node:fs/promises";
|
|
1963
|
-
import { join as join2 } from "node:path";
|
|
1964
|
-
|
|
1965
|
-
// host/relay/src/relay-ipc.ts
|
|
1966
|
-
import { createConnection, createServer } from "node:net";
|
|
1967
|
-
|
|
1968
|
-
// host/relay/src/durable-atomic-replacement.ts
|
|
1969
|
-
import { O_RDONLY } from "node:constants";
|
|
1970
|
-
import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
|
|
1971
|
-
import { dirname } from "node:path";
|
|
1972
|
-
var DEFAULT_PORT = {
|
|
1973
|
-
platform: process.platform,
|
|
1974
|
-
mkdir: (path, options) => mkdir(path, options),
|
|
1975
|
-
lstat: (path) => lstat(path),
|
|
1976
|
-
open_file: (path) => open(path, "wx", 384),
|
|
1977
|
-
rename,
|
|
1978
|
-
unlink,
|
|
1979
|
-
open_directory: (path) => open(path, O_RDONLY),
|
|
1980
|
-
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
1981
|
-
};
|
|
1982
|
-
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
1983
|
-
const parent = dirname(target);
|
|
1984
|
-
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
1985
|
-
const parentStats = await port.lstat(parent);
|
|
1986
|
-
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 }])}.`);
|
|
1987
|
-
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
1988
|
-
let staged;
|
|
1989
|
-
const failures = [];
|
|
1990
|
-
try {
|
|
1991
|
-
staged = await port.open_file(temporary);
|
|
1992
|
-
await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
1993
|
-
await staged.sync();
|
|
1994
|
-
const closing = staged;
|
|
1995
|
-
staged = void 0;
|
|
1996
|
-
await closing.close();
|
|
1997
|
-
await port.rename(temporary, target);
|
|
1998
|
-
await syncParentDirectory(parent, port);
|
|
1999
|
-
} catch (error) {
|
|
2000
|
-
failures.push(error);
|
|
2001
|
-
}
|
|
2002
|
-
await closeStaged(staged, failures);
|
|
2003
|
-
await removeTemporary(temporary, port, failures);
|
|
2004
|
-
throwFailures(failures);
|
|
2005
|
-
}
|
|
2006
|
-
async function syncParentDirectory(path, port) {
|
|
2007
|
-
if (port.platform === "win32") return;
|
|
2008
|
-
const handle = await port.open_directory(path);
|
|
2009
|
-
let failure2;
|
|
2010
|
-
try {
|
|
2011
|
-
await handle.sync();
|
|
2012
|
-
} catch (error) {
|
|
2013
|
-
if (!directorySyncUnsupported(error)) failure2 = error;
|
|
2014
|
-
}
|
|
2015
|
-
try {
|
|
2016
|
-
await handle.close();
|
|
2017
|
-
} catch (error) {
|
|
2018
|
-
if (failure2 === void 0) failure2 = error;
|
|
2019
|
-
else failure2 = new AggregateError([failure2, error], "Host Relay directory sync and close failed.");
|
|
2020
|
-
}
|
|
2021
|
-
if (failure2 !== void 0) throw failure2;
|
|
2022
|
-
}
|
|
2023
|
-
function ownerPrivate(stats, platform) {
|
|
2024
|
-
if (platform === "win32") return true;
|
|
2025
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
2026
|
-
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
2027
|
-
}
|
|
2028
|
-
async function closeStaged(staged, failures) {
|
|
2029
|
-
if (staged !== void 0) try {
|
|
2030
|
-
await staged.close();
|
|
2031
|
-
} catch (error) {
|
|
2032
|
-
failures.push(error);
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
async function removeTemporary(path, port, failures) {
|
|
2036
|
-
try {
|
|
2037
|
-
await port.unlink(path);
|
|
2038
|
-
} catch (error) {
|
|
2039
|
-
if (errorCode2(error) !== "ENOENT") failures.push(error);
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
function throwFailures(failures) {
|
|
2043
|
-
if (failures.length === 1) throw failures[0];
|
|
2044
|
-
if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
|
|
2045
|
-
}
|
|
2046
|
-
function directorySyncUnsupported(error) {
|
|
2047
|
-
return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode2(error) ?? "");
|
|
2048
|
-
}
|
|
2049
|
-
function errorCode2(error) {
|
|
2050
|
-
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
// host/relay/src/relay-request-binding-store.ts
|
|
2054
|
-
var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
2055
|
-
|
|
2056
|
-
// host/relay/src/current-prompt-custody.ts
|
|
2057
|
-
var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
|
|
2058
|
-
|
|
2059
|
-
// host/relay/src/relay-ipc.ts
|
|
2060
|
-
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
2061
|
-
"schema_version",
|
|
2062
|
-
"producer_id",
|
|
2063
|
-
"producer_installation_ref",
|
|
2064
|
-
"local_session_ref",
|
|
2065
|
-
"local_turn_ref",
|
|
2066
|
-
"local_turn_aliases",
|
|
2067
|
-
"prompt_ref",
|
|
2068
|
-
"request_ref",
|
|
2069
|
-
"bound_at",
|
|
2070
|
-
"host_source_ref",
|
|
2071
|
-
"host_session_ref",
|
|
2072
|
-
"host_turn_ref",
|
|
2073
|
-
"host_input_ref"
|
|
2074
|
-
]);
|
|
2075
|
-
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
2076
|
-
var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v1";
|
|
2077
|
-
var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v1";
|
|
2078
|
-
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
2079
|
-
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
2080
|
-
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2081
|
-
var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
|
|
2082
|
-
var HOST_RELAY_QUERY_RESPONSE_MAX_BYTES = 8192;
|
|
2083
|
-
var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
2084
|
-
"context_stale",
|
|
2085
|
-
"context_expired",
|
|
2086
|
-
"host_source_refused",
|
|
2087
|
-
"host_credential_refused",
|
|
2088
|
-
"gateway_unavailable",
|
|
2089
|
-
"gateway_refused",
|
|
2090
|
-
"gateway_response_invalid",
|
|
2091
|
-
"gateway_rate_limited",
|
|
2092
|
-
"producer_frame_invalid",
|
|
2093
|
-
"deadline",
|
|
2094
|
-
"cancelled",
|
|
2095
|
-
"mint_failed",
|
|
2096
|
-
"relay_failed",
|
|
2097
|
-
"request_authority_unavailable",
|
|
2098
|
-
"request_missing",
|
|
2099
|
-
"authority_mismatch",
|
|
2100
|
-
"host_source_mismatch",
|
|
2101
|
-
"host_session_mismatch",
|
|
2102
|
-
"host_turn_mismatch",
|
|
2103
|
-
"host_input_mismatch",
|
|
2104
|
-
"host_prompt_mismatch"
|
|
2105
|
-
]);
|
|
2106
|
-
var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
|
|
2107
|
-
"context_stale",
|
|
2108
|
-
"context_expired",
|
|
2109
|
-
"host_source_refused",
|
|
2110
|
-
"host_credential_refused",
|
|
2111
|
-
"gateway_unavailable",
|
|
2112
|
-
"gateway_rate_limited",
|
|
2113
|
-
"gateway_refused",
|
|
2114
|
-
"gateway_response_invalid",
|
|
2115
|
-
"producer_frame_invalid"
|
|
2116
|
-
]);
|
|
2117
|
-
var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
|
|
2118
|
-
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
2119
|
-
"permission_denied",
|
|
2120
|
-
"socket_missing",
|
|
2121
|
-
"connection_refused",
|
|
2122
|
-
"closed_early",
|
|
2123
|
-
"response_invalid",
|
|
2124
|
-
"timed_out",
|
|
2125
|
-
"cancelled",
|
|
2126
|
-
"unavailable"
|
|
2127
|
-
]);
|
|
2128
|
-
var HostRelayIpcError = class extends Error {
|
|
2129
|
-
code;
|
|
2130
|
-
/** Set only when the client-side socket exchange itself failed. */
|
|
2131
|
-
failure;
|
|
2132
|
-
constructor(code2, message = code2, failure2) {
|
|
2133
|
-
super(message);
|
|
2134
|
-
this.code = code2;
|
|
2135
|
-
this.failure = failure2;
|
|
2136
|
-
}
|
|
2137
|
-
};
|
|
2138
|
-
function hostRelayExchangeFailure(error) {
|
|
2139
|
-
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
2140
|
-
}
|
|
2141
|
-
function socketErrorFailure(error) {
|
|
2142
|
-
switch (errorCode3(error)) {
|
|
2143
|
-
case "EACCES":
|
|
2144
|
-
case "EPERM":
|
|
2145
|
-
return "permission_denied";
|
|
2146
|
-
case "ENOENT":
|
|
2147
|
-
return "socket_missing";
|
|
2148
|
-
case "ECONNREFUSED":
|
|
2149
|
-
return "connection_refused";
|
|
2150
|
-
default:
|
|
2151
|
-
return "unavailable";
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
var HostRelayInvocationContextPort = class {
|
|
2155
|
-
endpoint;
|
|
2156
|
-
constructor(endpoint) {
|
|
2157
|
-
this.endpoint = endpoint;
|
|
2158
|
-
}
|
|
2159
|
-
mintInvocationContext(input, signal) {
|
|
2160
|
-
return queryHostRelayInvocationContext(this.endpoint, input, signal);
|
|
2161
|
-
}
|
|
2162
|
-
};
|
|
2163
|
-
var HostRelayIpcAdmissionPort = class {
|
|
2164
|
-
endpoint;
|
|
2165
|
-
timeout_ms;
|
|
2166
|
-
constructor(endpoint, timeoutMs = 5e3) {
|
|
2167
|
-
this.endpoint = endpoint;
|
|
2168
|
-
this.timeout_ms = timeoutMs;
|
|
2169
|
-
}
|
|
2170
|
-
async admit(frame) {
|
|
2171
|
-
const response2 = await submitHostProducerFrame(this.endpoint, frame, this.timeout_ms);
|
|
2172
|
-
if (!relayAdmissionCode(response2.code)) throw new HostRelayIpcError(response2.code);
|
|
2173
|
-
return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
|
|
2174
|
-
}
|
|
2175
|
-
};
|
|
2176
|
-
var HostRelayCurrentPromptPort = class {
|
|
2177
|
-
endpoint;
|
|
2178
|
-
timeout_ms;
|
|
2179
|
-
constructor(endpoint, timeoutMs = 5e3) {
|
|
2180
|
-
this.endpoint = endpoint;
|
|
2181
|
-
this.timeout_ms = timeoutMs;
|
|
2182
|
-
}
|
|
2183
|
-
async register(input) {
|
|
2184
|
-
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
|
|
2185
|
-
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2186
|
-
if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
|
|
2187
|
-
}
|
|
2188
|
-
};
|
|
2189
|
-
function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
|
|
2190
|
-
return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
|
|
2191
|
-
}
|
|
2192
|
-
function queryHostRelayInvocationContext(endpoint, query, signal) {
|
|
2193
|
-
return exchange(endpoint, encodeInvocationContextQuery(query), void 0, decodeInvocationContextResponseBuffer, signal);
|
|
2194
|
-
}
|
|
2195
|
-
function exchange(endpoint, encoded, timeoutMs, decode, signal) {
|
|
2196
|
-
return new Promise((resolve, reject) => {
|
|
2197
|
-
const socket = createConnection(endpoint);
|
|
2198
|
-
let buffer = Buffer.alloc(0);
|
|
2199
|
-
let settled = false;
|
|
2200
|
-
const aborted = () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC was cancelled.", "cancelled"));
|
|
2201
|
-
const finish = (error, response2) => {
|
|
2202
|
-
if (settled) return;
|
|
2203
|
-
settled = true;
|
|
2204
|
-
signal?.removeEventListener("abort", aborted);
|
|
2205
|
-
socket.destroy();
|
|
2206
|
-
if (error !== null) reject(error);
|
|
2207
|
-
else if (response2 !== void 0) resolve(response2);
|
|
2208
|
-
};
|
|
2209
|
-
if (timeoutMs !== void 0) socket.setTimeout(timeoutMs, () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC timed out.", "timed_out")));
|
|
2210
|
-
if (signal?.aborted === true) {
|
|
2211
|
-
aborted();
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
signal?.addEventListener("abort", aborted, { once: true });
|
|
2215
|
-
socket.once("error", (error) => {
|
|
2216
|
-
const failure2 = socketErrorFailure(error);
|
|
2217
|
-
finish(new HostRelayIpcError("relay_unavailable", `Host Relay IPC is unavailable: ${failure2}.`, failure2));
|
|
2218
|
-
});
|
|
2219
|
-
socket.once("connect", () => socket.write(encoded));
|
|
2220
|
-
socket.on("data", (chunk) => {
|
|
2221
|
-
try {
|
|
2222
|
-
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
|
2223
|
-
const decoded = decode(buffer);
|
|
2224
|
-
if (decoded !== null) finish(null, decoded);
|
|
2225
|
-
} catch (error) {
|
|
2226
|
-
finish(new HostRelayIpcError("relay_unavailable", error instanceof Error ? error.message : "Host Relay IPC response is invalid.", "response_invalid"));
|
|
2227
|
-
}
|
|
2228
|
-
});
|
|
2229
|
-
socket.once("end", () => {
|
|
2230
|
-
if (!settled) finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC closed without a response.", "closed_early"));
|
|
2231
|
-
});
|
|
2232
|
-
});
|
|
2233
|
-
}
|
|
2234
|
-
function decodeHostRelayIpcResponse(input) {
|
|
2235
|
-
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}`)}.`);
|
|
2236
|
-
const length = input.readUInt32BE(0);
|
|
2237
|
-
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}` }])}.`);
|
|
2238
|
-
let value;
|
|
2239
|
-
try {
|
|
2240
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
|
|
2241
|
-
} catch {
|
|
2242
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response is invalid.");
|
|
2243
|
-
}
|
|
2244
|
-
if (!validResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response shape is invalid.");
|
|
2245
|
-
return value;
|
|
2246
|
-
}
|
|
2247
|
-
function relayAdmissionCode(value) {
|
|
2248
|
-
return value === "admitted" || value === "already_admitted" || value === "producer_epoch_stale" || value === "sequence_gap" || value === "frame_identity_conflict" || value === "producer_queue_full" || value === "relay_queue_full" || value === "producer_already_connected" || value === "invalid_producer_identity";
|
|
2249
|
-
}
|
|
2250
|
-
function validResponse(value) {
|
|
2251
|
-
return record2(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;
|
|
2252
|
-
}
|
|
2253
|
-
function hostRelayIpcCode(value) {
|
|
2254
|
-
return typeof value === "string" && ["admitted", "already_admitted", "producer_epoch_stale", "sequence_gap", "frame_identity_conflict", "producer_queue_full", "relay_queue_full", "invalid_frame_encoding", "invalid_frame_length", "frame_truncated", "unsupported_frame_schema", "invalid_frame_shape", "invalid_producer_identity", "frame_too_large", "producer_already_connected", "relay_unavailable"].includes(value);
|
|
2255
|
-
}
|
|
2256
|
-
function nonnegative(value) {
|
|
2257
|
-
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
2258
|
-
}
|
|
2259
|
-
function record2(value) {
|
|
2260
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2261
|
-
}
|
|
2262
|
-
function decodeResponseBuffer(buffer) {
|
|
2263
|
-
if (buffer.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2264
|
-
const length = buffer.readUInt32BE(0);
|
|
2265
|
-
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}`)}.`);
|
|
2266
|
-
if (buffer.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2267
|
-
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}` }])}.`);
|
|
2268
|
-
return decodeHostRelayIpcResponse(buffer);
|
|
2269
|
-
}
|
|
2270
|
-
function decodeInvocationContextResponseBuffer(buffer) {
|
|
2271
|
-
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_QUERY_RESPONSE_MAX_BYTES);
|
|
2272
|
-
if (value === null) return null;
|
|
2273
|
-
if (!validInvocationContextResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay invocation context response is invalid.");
|
|
2274
|
-
return value;
|
|
2275
|
-
}
|
|
2276
|
-
function decodeCurrentPromptResponseBuffer(buffer) {
|
|
2277
|
-
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
|
|
2278
|
-
if (value === null) return null;
|
|
2279
|
-
if (!record2(value) || !exactKeys3(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "current_prompt_unavailable") throw new HostRelayIpcError("relay_unavailable");
|
|
2280
|
-
return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
|
|
2281
|
-
}
|
|
2282
|
-
function encodeInvocationContextQuery(query) {
|
|
2283
|
-
const value = { schema_version: HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA, ...query };
|
|
2284
|
-
if (!validInvocationContextQueryRequest(value)) throw new HostRelayIpcError("invalid_frame_shape");
|
|
2285
|
-
return encodeLengthPrefixed(value);
|
|
2286
|
-
}
|
|
2287
|
-
function validInvocationContextQueryRequest(value) {
|
|
2288
|
-
return validQueryIdentity(value, HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) && exactKeys3(value, ["schema_version", "producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref", "client_nonce"], ["visible_output_max_bytes"]) && typeof value.client_nonce === "string" && CEAL_HOST_CLIENT_NONCE_PATTERN.test(value.client_nonce) && (value.visible_output_max_bytes === void 0 || validVisibleOutputBudget(value.visible_output_max_bytes));
|
|
2289
|
-
}
|
|
2290
|
-
function validQueryIdentity(value, schema) {
|
|
2291
|
-
return record2(value) && value.schema_version === schema && (value.producer_id === "codex" || value.producer_id === "claude") && safeRef2(value.producer_installation_ref) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref);
|
|
2292
|
-
}
|
|
2293
|
-
function validVisibleOutputBudget(value) {
|
|
2294
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES;
|
|
2295
|
-
}
|
|
2296
|
-
function validInvocationContextResponse(value) {
|
|
2297
|
-
if (!record2(value) || value.schema_version !== HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA) return false;
|
|
2298
|
-
if (value.status === "no_binding") return Object.keys(value).length === 2;
|
|
2299
|
-
if (value.status === "refused") return Object.keys(value).length === 3 && isVocabularyWord(HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS, value.reason);
|
|
2300
|
-
if (value.status !== "ready" || Object.keys(value).length !== 3) return false;
|
|
2301
|
-
try {
|
|
2302
|
-
decodeCealHostInvocationAuthenticationResult(value.result);
|
|
2303
|
-
return true;
|
|
2304
|
-
} catch {
|
|
2305
|
-
return false;
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
function decodeLengthPrefixedJson(input, maximum) {
|
|
2309
|
-
if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2310
|
-
const length = input.readUInt32BE(0);
|
|
2311
|
-
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}`)}.`);
|
|
2312
|
-
if (input.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2313
|
-
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}` }])}.`);
|
|
2314
|
-
try {
|
|
2315
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
|
|
2316
|
-
} catch {
|
|
2317
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC message is invalid.");
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
function encodeLengthPrefixed(value) {
|
|
2321
|
-
const body = Buffer.from(JSON.stringify(value), "utf8");
|
|
2322
|
-
const output = Buffer.allocUnsafe(body.length + HOST_RELAY_IPC_LENGTH_BYTES);
|
|
2323
|
-
output.writeUInt32BE(body.length, 0);
|
|
2324
|
-
body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
|
|
2325
|
-
return output;
|
|
2326
|
-
}
|
|
2327
|
-
function exactKeys3(value, required, optional = []) {
|
|
2328
|
-
const keys = Object.keys(value);
|
|
2329
|
-
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2330
|
-
}
|
|
2331
|
-
function safeRef2(value) {
|
|
2332
|
-
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2333
|
-
}
|
|
2334
|
-
function errorCode3(error) {
|
|
2335
|
-
return record2(error) && typeof error.code === "string" ? error.code : null;
|
|
2471
|
+
function transcriptScanFailureKind(error, adapterFailureKind) {
|
|
2472
|
+
if (transcriptErrorCode(error) === "ELOOP") return "symlink";
|
|
2473
|
+
const adapter = adapterFailureKind(error);
|
|
2474
|
+
if (adapter !== void 0) return adapter;
|
|
2475
|
+
const code2 = transcriptErrorCode(error);
|
|
2476
|
+
if (code2 === "EFBIG") return "size_budget";
|
|
2477
|
+
if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
|
|
2478
|
+
return "other";
|
|
2336
2479
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
|
|
2340
|
-
function isHostTranscriptCursorReason(value) {
|
|
2341
|
-
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2480
|
+
function transcriptErrorCode(error) {
|
|
2481
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
2342
2482
|
}
|
|
2343
|
-
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2344
2483
|
var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
|
|
2345
2484
|
async function readTranscriptCompleteChunk(handle, offset, size) {
|
|
2346
2485
|
const available = Math.max(0, size - offset);
|
|
@@ -2385,10 +2524,10 @@ async function transcriptCursorBoundsReason(handle, cursor, size) {
|
|
|
2385
2524
|
return null;
|
|
2386
2525
|
}
|
|
2387
2526
|
function transcriptSha256(value) {
|
|
2388
|
-
return
|
|
2527
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
2389
2528
|
}
|
|
2390
2529
|
function transcriptObjectSha256(value) {
|
|
2391
|
-
return
|
|
2530
|
+
return createHash3("sha256").update(JSON.stringify(value), "utf8").digest("hex");
|
|
2392
2531
|
}
|
|
2393
2532
|
function isTranscriptRecord(value) {
|
|
2394
2533
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2457,11 +2596,10 @@ var HostTranscriptWatcher = class {
|
|
|
2457
2596
|
async openTranscript(registration, scan, handlers) {
|
|
2458
2597
|
let handle;
|
|
2459
2598
|
try {
|
|
2460
|
-
handle = await
|
|
2599
|
+
handle = await openNoFollow(this.transcriptPathOf(registration));
|
|
2461
2600
|
return await scan(handle);
|
|
2462
2601
|
} catch (error) {
|
|
2463
2602
|
if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
|
|
2464
|
-
if (hostRelayExchangeFailure(error) !== void 0) throw error;
|
|
2465
2603
|
return handlers.failed(handle, error);
|
|
2466
2604
|
} finally {
|
|
2467
2605
|
await handle?.close();
|
|
@@ -2507,29 +2645,12 @@ var HostTranscriptWatcher = class {
|
|
|
2507
2645
|
return transcriptCursorBoundsReason(handle, cursor, size);
|
|
2508
2646
|
}
|
|
2509
2647
|
};
|
|
2510
|
-
var HOST_PRODUCER_STATE_IDENTITY = {
|
|
2511
|
-
codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
|
|
2512
|
-
claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
|
|
2513
|
-
};
|
|
2514
2648
|
var HostProducerDrainCore = class {
|
|
2515
2649
|
host;
|
|
2516
|
-
relay;
|
|
2517
|
-
statePath;
|
|
2518
|
-
stateSchema;
|
|
2519
|
-
installationRef;
|
|
2520
|
-
epoch;
|
|
2521
2650
|
now;
|
|
2522
2651
|
tail = Promise.resolve();
|
|
2523
2652
|
constructor(host, runtime) {
|
|
2524
|
-
if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
|
|
2525
|
-
const epoch = runtime.producer_epoch ?? 1;
|
|
2526
|
-
if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
|
|
2527
2653
|
this.host = host;
|
|
2528
|
-
this.relay = runtime.relay;
|
|
2529
|
-
this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
|
|
2530
|
-
this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
|
|
2531
|
-
this.installationRef = runtime.producer_installation_ref;
|
|
2532
|
-
this.epoch = epoch;
|
|
2533
2654
|
this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
|
|
2534
2655
|
}
|
|
2535
2656
|
drainOutbox() {
|
|
@@ -2539,156 +2660,681 @@ var HostProducerDrainCore = class {
|
|
|
2539
2660
|
}
|
|
2540
2661
|
async drainLocked() {
|
|
2541
2662
|
const read = await this.host.listEnvelopes();
|
|
2542
|
-
let
|
|
2663
|
+
let removed = 0;
|
|
2543
2664
|
let expired = 0;
|
|
2544
|
-
let retained = 0;
|
|
2545
2665
|
for (const envelope of read.entries) {
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2666
|
+
if (Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) expired += 1;
|
|
2667
|
+
else removed += 1;
|
|
2668
|
+
await this.host.removeEnvelope(envelope);
|
|
2669
|
+
}
|
|
2670
|
+
return { removed, expired, malformed: read.malformed };
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
|
|
2674
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
2675
|
+
import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
|
|
2676
|
+
import { dirname as dirname2 } from "node:path";
|
|
2677
|
+
|
|
2678
|
+
// host/windows-rename-retry.ts
|
|
2679
|
+
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
2680
|
+
var RETRY_WINDOW_MS = 1e4;
|
|
2681
|
+
var RETRY_START_DELAY_MS = 100;
|
|
2682
|
+
var RETRY_MAX_DELAY_MS = 2e3;
|
|
2683
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["EBUSY", "EPERM", "EACCES"]);
|
|
2684
|
+
var WindowsRenameRetryError = class extends Error {
|
|
2685
|
+
code = "rename_unavailable";
|
|
2686
|
+
constructor(target, attempts, options = {}) {
|
|
2687
|
+
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);
|
|
2688
|
+
this.name = "WindowsRenameRetryError";
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
function resolveRetrySchedule(tuning) {
|
|
2692
|
+
return {
|
|
2693
|
+
windowMs: tuning.windowMs ?? RETRY_WINDOW_MS,
|
|
2694
|
+
startDelayMs: tuning.startDelayMs ?? RETRY_START_DELAY_MS,
|
|
2695
|
+
maxDelayMs: tuning.maxDelayMs ?? RETRY_MAX_DELAY_MS,
|
|
2696
|
+
sleep: tuning.sleep ?? ((ms) => setTimeout2(ms)),
|
|
2697
|
+
now: tuning.now ?? (() => Date.now())
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
async function renameWithWindowsRetry(operation, tuning) {
|
|
2701
|
+
const schedule = resolveRetrySchedule(tuning);
|
|
2702
|
+
const deadline = schedule.now() + schedule.windowMs;
|
|
2703
|
+
let delayMs = schedule.startDelayMs;
|
|
2704
|
+
let attempts = 0;
|
|
2705
|
+
for (; ; ) {
|
|
2706
|
+
attempts += 1;
|
|
2707
|
+
try {
|
|
2708
|
+
await operation();
|
|
2709
|
+
return;
|
|
2710
|
+
} catch (error) {
|
|
2711
|
+
if (tuning.platform !== "win32" || !isRetryableRenameError(error)) throw error;
|
|
2712
|
+
await sleepWithinWindow(schedule, deadline, delayMs, () => new WindowsRenameRetryError(tuning.target, attempts, { cause: error }));
|
|
2713
|
+
delayMs = Math.min(delayMs * 2, schedule.maxDelayMs);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
async function sleepWithinWindow(schedule, deadline, delayMs, exhausted) {
|
|
2718
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2719
|
+
await schedule.sleep(Math.min(delayMs, deadline - schedule.now()));
|
|
2720
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2721
|
+
}
|
|
2722
|
+
function isRetryableRenameError(error) {
|
|
2723
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RETRYABLE_CODES.has(error.code);
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
2727
|
+
var DEFAULT_PORT = {
|
|
2728
|
+
platform: process.platform,
|
|
2729
|
+
mkdir: (path, options) => mkdir(path, options),
|
|
2730
|
+
lstat: (path) => lstat2(path),
|
|
2731
|
+
open_file: (path) => open2(path, "wx", 384),
|
|
2732
|
+
rename,
|
|
2733
|
+
unlink,
|
|
2734
|
+
open_directory: (path) => open2(path),
|
|
2735
|
+
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2736
|
+
};
|
|
2737
|
+
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
2738
|
+
const parent = dirname2(target);
|
|
2739
|
+
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
2740
|
+
const parentStats = await port.lstat(parent);
|
|
2741
|
+
const expectedUid = typeof process.geteuid === "function" ? process.geteuid() : parentStats.uid;
|
|
2742
|
+
const ownerPrivate = port.platform === "win32" || isOwnedBy(parentStats.uid, expectedUid) && isPrivateFileMode(parentStats.mode, port.platform);
|
|
2743
|
+
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 }])}.`);
|
|
2744
|
+
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
2745
|
+
let staged;
|
|
2746
|
+
const failures = [];
|
|
2747
|
+
try {
|
|
2748
|
+
staged = await port.open_file(temporary);
|
|
2749
|
+
await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
2750
|
+
await staged.sync();
|
|
2751
|
+
const closing = staged;
|
|
2752
|
+
staged = void 0;
|
|
2753
|
+
await closing.close();
|
|
2754
|
+
await renameWithWindowsRetry(() => port.rename(temporary, target), { ...port.rename_retry, platform: port.platform, target });
|
|
2755
|
+
await syncParentDirectory(parent, port);
|
|
2756
|
+
} catch (error) {
|
|
2757
|
+
failures.push(error);
|
|
2758
|
+
}
|
|
2759
|
+
await closeStaged(staged, failures);
|
|
2760
|
+
await removeTemporary(temporary, port, failures);
|
|
2761
|
+
throwFailures(failures);
|
|
2762
|
+
}
|
|
2763
|
+
async function syncParentDirectory(path, port) {
|
|
2764
|
+
if (port.platform === "win32") return;
|
|
2765
|
+
const handle = await port.open_directory(path);
|
|
2766
|
+
let failure2;
|
|
2767
|
+
try {
|
|
2768
|
+
await handle.sync();
|
|
2769
|
+
} catch (error) {
|
|
2770
|
+
if (!directorySyncUnsupported(error)) failure2 = error;
|
|
2771
|
+
}
|
|
2772
|
+
try {
|
|
2773
|
+
await handle.close();
|
|
2774
|
+
} catch (error) {
|
|
2775
|
+
if (failure2 === void 0) failure2 = error;
|
|
2776
|
+
else failure2 = new AggregateError([failure2, error], "Host Relay directory sync and close failed.");
|
|
2777
|
+
}
|
|
2778
|
+
if (failure2 !== void 0) throw failure2;
|
|
2779
|
+
}
|
|
2780
|
+
async function closeStaged(staged, failures) {
|
|
2781
|
+
if (staged !== void 0) try {
|
|
2782
|
+
await staged.close();
|
|
2783
|
+
} catch (error) {
|
|
2784
|
+
failures.push(error);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
async function removeTemporary(path, port, failures) {
|
|
2788
|
+
try {
|
|
2789
|
+
await port.unlink(path);
|
|
2790
|
+
} catch (error) {
|
|
2791
|
+
if (errorCode2(error) !== "ENOENT") failures.push(error);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
function throwFailures(failures) {
|
|
2795
|
+
if (failures.length === 1) throw failures[0];
|
|
2796
|
+
if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
|
|
2797
|
+
}
|
|
2798
|
+
function directorySyncUnsupported(error) {
|
|
2799
|
+
return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode2(error) ?? "");
|
|
2800
|
+
}
|
|
2801
|
+
function errorCode2(error) {
|
|
2802
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
// host/relay/src/relay-quarantine-store.ts
|
|
2806
|
+
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2807
|
+
var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
|
|
2808
|
+
var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
|
|
2809
|
+
|
|
2810
|
+
// host/relay/src/relay-admission-store.ts
|
|
2811
|
+
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
2812
|
+
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2813
|
+
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2814
|
+
|
|
2815
|
+
// host/relay/src/relay-ipc.ts
|
|
2816
|
+
import { createConnection, createServer } from "node:net";
|
|
2817
|
+
|
|
2818
|
+
// host/relay/src/relay-pipe-handshake.ts
|
|
2819
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
2820
|
+
import { constants as constants2 } from "node:fs";
|
|
2821
|
+
import { link, unlink as unlink2 } from "node:fs/promises";
|
|
2822
|
+
import { dirname as dirname3, join as join2 } from "node:path";
|
|
2823
|
+
var RELAY_PIPE_HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
2824
|
+
var RELAY_PIPE_KEY_BYTES = 32;
|
|
2825
|
+
var RELAY_PIPE_KEY_FILENAME = "relay-pipe.key";
|
|
2826
|
+
var RELAY_PIPE_NONCE_BYTES = 32;
|
|
2827
|
+
var RELAY_PIPE_NONCE_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
|
|
2828
|
+
var RELAY_PIPE_HELLO_SCHEMA = "ceal.host_relay_pipe_hello.v1";
|
|
2829
|
+
var RELAY_PIPE_CHALLENGE_SCHEMA = "ceal.host_relay_pipe_challenge.v1";
|
|
2830
|
+
var RELAY_PIPE_RESPONSE_SCHEMA = "ceal.host_relay_pipe_response.v1";
|
|
2831
|
+
var RELAY_PIPE_FRAME_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2832
|
+
var RELAY_PIPE_FRAME_MAX_BYTES = 1024;
|
|
2833
|
+
var RelayPipeHandshakeError = class extends Error {
|
|
2834
|
+
name = "RelayPipeHandshakeError";
|
|
2835
|
+
code;
|
|
2836
|
+
constructor(code2, message) {
|
|
2837
|
+
super(message);
|
|
2838
|
+
this.code = code2;
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
function relayPipeStateRootForOutboxDir(outboxDir) {
|
|
2842
|
+
return join2(dirname3(dirname3(dirname3(outboxDir))), "relay");
|
|
2843
|
+
}
|
|
2844
|
+
function relayPipeKeyPath(relayStateRoot) {
|
|
2845
|
+
return join2(relayStateRoot, RELAY_PIPE_KEY_FILENAME);
|
|
2846
|
+
}
|
|
2847
|
+
async function loadRelayPipeKey(relayStateRoot, platform = process.platform, custody = {}) {
|
|
2848
|
+
const path = relayPipeKeyPath(relayStateRoot);
|
|
2849
|
+
if (platform === "win32") return await loadWindowsRelayPipeKey(path, custody);
|
|
2850
|
+
const existing = await readRelayPipeKey(path, platform, custody);
|
|
2851
|
+
if (existing !== null) return existing;
|
|
2852
|
+
return await createPosixRelayPipeKey(path, platform, custody);
|
|
2853
|
+
}
|
|
2854
|
+
async function loadWindowsRelayPipeKey(path, custody) {
|
|
2855
|
+
const existing = await readRelayPipeKey(path, "win32", custody);
|
|
2856
|
+
if (existing !== null) return existing;
|
|
2857
|
+
try {
|
|
2858
|
+
await createRandomPrivateFileWithSecurefs(path, RELAY_PIPE_KEY_BYTES, custody);
|
|
2859
|
+
} catch (error) {
|
|
2860
|
+
if (errorCode3(error) === "EEXIST") return await readWinnerPipeKey(path, "win32", custody);
|
|
2861
|
+
throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}; delete it and restart the Host Relay.`);
|
|
2862
|
+
}
|
|
2863
|
+
return await readWinnerPipeKey(path, "win32", custody);
|
|
2864
|
+
}
|
|
2865
|
+
async function createPosixRelayPipeKey(path, platform, custody) {
|
|
2866
|
+
const key = randomBytes(RELAY_PIPE_KEY_BYTES);
|
|
2867
|
+
const staging = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
2868
|
+
let handle;
|
|
2869
|
+
try {
|
|
2870
|
+
handle = await openNoFollow(staging, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 384);
|
|
2871
|
+
} catch (error) {
|
|
2872
|
+
if (errorCode3(error) !== "EEXIST") throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
2873
|
+
return await readWinnerPipeKey(path, platform, custody);
|
|
2874
|
+
}
|
|
2875
|
+
try {
|
|
2876
|
+
const stats = await handle.stat();
|
|
2877
|
+
if (!stats.isFile() || !isSelfOwned(stats.uid) || !isPrivateFileMode(stats.mode)) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} is not a private regular file owned by the current user.`);
|
|
2878
|
+
await handle.writeFile(key);
|
|
2879
|
+
await handle.sync();
|
|
2880
|
+
} finally {
|
|
2881
|
+
await handle.close();
|
|
2882
|
+
}
|
|
2883
|
+
try {
|
|
2884
|
+
await link(staging, path);
|
|
2885
|
+
} catch (error) {
|
|
2886
|
+
if (errorCode3(error) !== "EEXIST") throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
2887
|
+
return await readWinnerPipeKey(path, platform, custody);
|
|
2888
|
+
} finally {
|
|
2889
|
+
await unlink2(staging).catch(() => void 0);
|
|
2890
|
+
}
|
|
2891
|
+
return Buffer.from(key);
|
|
2892
|
+
}
|
|
2893
|
+
async function readWinnerPipeKey(path, platform, custody) {
|
|
2894
|
+
const winner = await readRelayPipeKey(path, platform, custody);
|
|
2895
|
+
if (winner === null) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
2896
|
+
return winner;
|
|
2897
|
+
}
|
|
2898
|
+
async function readRelayPipeKey(path, platform, custody) {
|
|
2899
|
+
if (platform === "win32") return await readWindowsRelayPipeKey(path, custody);
|
|
2900
|
+
return await readPosixRelayPipeKey(path);
|
|
2901
|
+
}
|
|
2902
|
+
async function readWindowsRelayPipeKey(path, custody) {
|
|
2903
|
+
let key;
|
|
2904
|
+
try {
|
|
2905
|
+
key = await readPrivateFileWithSecurefs(path, RELAY_PIPE_KEY_BYTES, custody);
|
|
2906
|
+
} catch (error) {
|
|
2907
|
+
if (errorCode3(error) === "ENOENT" || errorCode3(error) === "ELOOP") return null;
|
|
2908
|
+
throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}; delete it and restart the Host Relay.`);
|
|
2909
|
+
}
|
|
2910
|
+
if (key.byteLength !== RELAY_PIPE_KEY_BYTES) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} has ${key.byteLength} bytes; want ${RELAY_PIPE_KEY_BYTES}. Delete the file and restart the Host Relay.`);
|
|
2911
|
+
return Buffer.from(key);
|
|
2912
|
+
}
|
|
2913
|
+
async function readPosixRelayPipeKey(path) {
|
|
2914
|
+
let handle;
|
|
2915
|
+
try {
|
|
2916
|
+
handle = await openNoFollow(path);
|
|
2917
|
+
} catch (error) {
|
|
2918
|
+
if (errorCode3(error) === "ENOENT" || errorCode3(error) === "ELOOP") return null;
|
|
2919
|
+
throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
2920
|
+
}
|
|
2921
|
+
try {
|
|
2922
|
+
const stats = await handle.stat();
|
|
2923
|
+
if (!stats.isFile()) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} is not a regular file; delete it and restart the Host Relay.`);
|
|
2924
|
+
if (!isSelfOwned(stats.uid) || !isPrivateFileMode(stats.mode)) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} is not a private file owned by the current user; delete it and restart the Host Relay.`);
|
|
2925
|
+
if (stats.size !== RELAY_PIPE_KEY_BYTES) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} has ${stats.size} bytes; want ${RELAY_PIPE_KEY_BYTES}. Delete the file and restart the Host Relay.`);
|
|
2926
|
+
return await handle.readFile();
|
|
2927
|
+
} finally {
|
|
2928
|
+
await handle.close();
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
function createPipeClientNonce() {
|
|
2932
|
+
return randomBytes(RELAY_PIPE_NONCE_BYTES).toString("base64url");
|
|
2933
|
+
}
|
|
2934
|
+
function pipeProof(key, role, firstNonce, secondNonce) {
|
|
2935
|
+
return createHmac("sha256", key).update(`${role}\0${firstNonce}\0${secondNonce}`, "utf8").digest("hex");
|
|
2936
|
+
}
|
|
2937
|
+
function createPipeServerProof(key, clientNonce, serverNonce) {
|
|
2938
|
+
return pipeProof(key, "server", clientNonce, serverNonce);
|
|
2939
|
+
}
|
|
2940
|
+
function createPipeClientProof(key, serverNonce, clientNonce) {
|
|
2941
|
+
return pipeProof(key, "client", serverNonce, clientNonce);
|
|
2942
|
+
}
|
|
2943
|
+
function encodePipeFrame(value) {
|
|
2944
|
+
const body = Buffer.from(JSON.stringify(value), "utf8");
|
|
2945
|
+
const output = Buffer.allocUnsafe(body.length + RELAY_PIPE_FRAME_BYTES);
|
|
2946
|
+
output.writeUInt32BE(body.length, 0);
|
|
2947
|
+
body.copy(output, RELAY_PIPE_FRAME_BYTES);
|
|
2948
|
+
return output;
|
|
2949
|
+
}
|
|
2950
|
+
function decodePipeFrame(buffer) {
|
|
2951
|
+
if (buffer.length < RELAY_PIPE_FRAME_BYTES) return null;
|
|
2952
|
+
const length = buffer.readUInt32BE(0);
|
|
2953
|
+
if (length === 0 || length > RELAY_PIPE_FRAME_MAX_BYTES) throw new RelayPipeHandshakeError("pipe_handshake_invalid", `Host Relay pipe handshake frame declares ${length} bytes; want between 1 and ${RELAY_PIPE_FRAME_MAX_BYTES}.`);
|
|
2954
|
+
if (buffer.length < length + RELAY_PIPE_FRAME_BYTES) return null;
|
|
2955
|
+
let value;
|
|
2956
|
+
try {
|
|
2957
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(RELAY_PIPE_FRAME_BYTES, length + RELAY_PIPE_FRAME_BYTES)));
|
|
2958
|
+
} catch {
|
|
2959
|
+
throw new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake frame is not JSON.");
|
|
2960
|
+
}
|
|
2961
|
+
return { value, rest: buffer.subarray(length + RELAY_PIPE_FRAME_BYTES) };
|
|
2962
|
+
}
|
|
2963
|
+
function validNonce(value) {
|
|
2964
|
+
return typeof value === "string" && RELAY_PIPE_NONCE_PATTERN.test(value);
|
|
2965
|
+
}
|
|
2966
|
+
function writePipeFrame(socket, frame) {
|
|
2967
|
+
if (socket.write(frame)) return Promise.resolve();
|
|
2968
|
+
return new Promise((resolve, reject) => {
|
|
2969
|
+
socket.once("drain", resolve);
|
|
2970
|
+
socket.once("error", reject);
|
|
2971
|
+
socket.once("close", () => reject(new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake connection closed.")));
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
function readPipeFrames() {
|
|
2975
|
+
let buffer = Buffer.alloc(0);
|
|
2976
|
+
let pending = null;
|
|
2977
|
+
const feed = (chunk) => {
|
|
2978
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
2979
|
+
if (pending === null) return;
|
|
2980
|
+
let frame;
|
|
2981
|
+
try {
|
|
2982
|
+
frame = decodePipeFrame(buffer);
|
|
2983
|
+
} catch (error) {
|
|
2984
|
+
const waiting2 = pending;
|
|
2985
|
+
pending = null;
|
|
2986
|
+
waiting2.reject(error instanceof Error ? error : new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake frame is invalid."));
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
if (frame === null) return;
|
|
2990
|
+
buffer = frame.rest;
|
|
2991
|
+
const waiting = pending;
|
|
2992
|
+
pending = null;
|
|
2993
|
+
waiting.resolve({ value: frame.value, rest: Buffer.alloc(0) });
|
|
2994
|
+
};
|
|
2995
|
+
const waiter = {
|
|
2996
|
+
wait: () => new Promise((resolve, reject) => {
|
|
2997
|
+
let frame;
|
|
2998
|
+
try {
|
|
2999
|
+
frame = decodePipeFrame(buffer);
|
|
3000
|
+
} catch (error) {
|
|
3001
|
+
reject(error instanceof Error ? error : new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake frame is invalid."));
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
if (frame !== null) {
|
|
3005
|
+
buffer = frame.rest;
|
|
3006
|
+
resolve({ value: frame.value, rest: Buffer.alloc(0) });
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
pending = { resolve, reject };
|
|
3010
|
+
}),
|
|
3011
|
+
cancel: (error) => {
|
|
3012
|
+
if (pending !== null) {
|
|
3013
|
+
const waiting = pending;
|
|
3014
|
+
pending = null;
|
|
3015
|
+
waiting.reject(error);
|
|
2551
3016
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
return { waiter, feed, remainder: () => buffer };
|
|
3020
|
+
}
|
|
3021
|
+
function attachHandshakeReader(socket, waiter, feed) {
|
|
3022
|
+
const onData = (chunk) => feed(Buffer.from(chunk));
|
|
3023
|
+
const onError = (error) => waiter.cancel(error);
|
|
3024
|
+
const onClose = () => waiter.cancel(new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake connection closed."));
|
|
3025
|
+
socket.on("data", onData);
|
|
3026
|
+
socket.once("error", onError);
|
|
3027
|
+
socket.once("close", onClose);
|
|
3028
|
+
return () => {
|
|
3029
|
+
socket.removeListener("data", onData);
|
|
3030
|
+
socket.removeListener("error", onError);
|
|
3031
|
+
socket.removeListener("close", onClose);
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
function handshakeTimeout(timeoutMs) {
|
|
3035
|
+
let timer;
|
|
3036
|
+
const done = new Promise((_resolve, reject) => {
|
|
3037
|
+
timer = setTimeout(() => reject(new RelayPipeHandshakeError("pipe_handshake_timeout", `Host Relay pipe handshake stalled past ${timeoutMs} ms; dropping the connection.`)), timeoutMs);
|
|
3038
|
+
timer.unref?.();
|
|
3039
|
+
});
|
|
3040
|
+
return { done, clear: () => clearTimeout(timer) };
|
|
3041
|
+
}
|
|
3042
|
+
async function conductPipeClientHandshake(socket, key, timeoutMs = RELAY_PIPE_HANDSHAKE_TIMEOUT_MS) {
|
|
3043
|
+
const bound = handshakeTimeout(timeoutMs);
|
|
3044
|
+
const { waiter, feed } = readPipeFrames();
|
|
3045
|
+
const detach = attachHandshakeReader(socket, waiter, feed);
|
|
3046
|
+
try {
|
|
3047
|
+
const clientNonce = createPipeClientNonce();
|
|
3048
|
+
await Promise.race([writePipeFrame(socket, encodePipeFrame({ schema_version: RELAY_PIPE_HELLO_SCHEMA, client_nonce: clientNonce })), bound.done]);
|
|
3049
|
+
const challenge = await Promise.race([waiter.wait(), bound.done]);
|
|
3050
|
+
if (!isRecord2(challenge.value) || challenge.value.schema_version !== RELAY_PIPE_CHALLENGE_SCHEMA || !validNonce(challenge.value.server_nonce) || typeof challenge.value.server_proof !== "string") {
|
|
3051
|
+
throw new RelayPipeHandshakeError("pipe_server_unproven", "Host Relay pipe server proof is invalid; aborting before sending any request.");
|
|
3052
|
+
}
|
|
3053
|
+
const serverNonce = challenge.value.server_nonce;
|
|
3054
|
+
const expected = createPipeServerProof(key, clientNonce, serverNonce);
|
|
3055
|
+
if (!timingSafeEqualHex(expected, challenge.value.server_proof)) {
|
|
3056
|
+
throw new RelayPipeHandshakeError("pipe_server_unproven", "Host Relay pipe server proof failed; aborting before sending any request.");
|
|
3057
|
+
}
|
|
3058
|
+
const clientProof = createPipeClientProof(key, serverNonce, clientNonce);
|
|
3059
|
+
await Promise.race([writePipeFrame(socket, encodePipeFrame({ schema_version: RELAY_PIPE_RESPONSE_SCHEMA, client_proof: clientProof })), bound.done]);
|
|
3060
|
+
} finally {
|
|
3061
|
+
bound.clear();
|
|
3062
|
+
detach();
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
function timingSafeEqualHex(left, right) {
|
|
3066
|
+
if (left.length !== right.length) return false;
|
|
3067
|
+
let diff = 0;
|
|
3068
|
+
for (let index = 0; index < left.length; index += 1) diff |= left.charCodeAt(index) ^ right.charCodeAt(index);
|
|
3069
|
+
return diff === 0;
|
|
3070
|
+
}
|
|
3071
|
+
function isRecord2(value) {
|
|
3072
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3073
|
+
}
|
|
3074
|
+
function errorCode3(error) {
|
|
3075
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
// host/relay/src/relay-ipc.ts
|
|
3079
|
+
var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v2";
|
|
3080
|
+
var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v2";
|
|
3081
|
+
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
3082
|
+
var HOST_RELAY_QUERY_RESPONSE_MAX_BYTES = 8192;
|
|
3083
|
+
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
3084
|
+
"permission_denied",
|
|
3085
|
+
"socket_missing",
|
|
3086
|
+
"connection_refused",
|
|
3087
|
+
"closed_early",
|
|
3088
|
+
"response_invalid",
|
|
3089
|
+
"timed_out",
|
|
3090
|
+
"cancelled",
|
|
3091
|
+
"unavailable"
|
|
3092
|
+
]);
|
|
3093
|
+
var HostRelayIpcError = class extends Error {
|
|
3094
|
+
code;
|
|
3095
|
+
/** Set only when the client-side socket exchange itself failed. */
|
|
3096
|
+
failure;
|
|
3097
|
+
constructor(code2, message = code2, failure2) {
|
|
3098
|
+
super(message);
|
|
3099
|
+
this.code = code2;
|
|
3100
|
+
this.failure = failure2;
|
|
3101
|
+
}
|
|
3102
|
+
};
|
|
3103
|
+
function hostRelayExchangeFailure(error) {
|
|
3104
|
+
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
3105
|
+
}
|
|
3106
|
+
function socketErrorFailure(error) {
|
|
3107
|
+
switch (errorCode4(error)) {
|
|
3108
|
+
case "EACCES":
|
|
3109
|
+
case "EPERM":
|
|
3110
|
+
return "permission_denied";
|
|
3111
|
+
case "ENOENT":
|
|
3112
|
+
return "socket_missing";
|
|
3113
|
+
case "ECONNREFUSED":
|
|
3114
|
+
return "connection_refused";
|
|
3115
|
+
default:
|
|
3116
|
+
return "unavailable";
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
var HostRelayInvocationContextPort = class {
|
|
3120
|
+
endpoint;
|
|
3121
|
+
pipe_key;
|
|
3122
|
+
timeout_ms;
|
|
3123
|
+
constructor(endpoint, pipeKey, timeoutMs = 5e3) {
|
|
3124
|
+
this.endpoint = endpoint;
|
|
3125
|
+
this.pipe_key = pipeKey;
|
|
3126
|
+
this.timeout_ms = timeoutMs;
|
|
3127
|
+
}
|
|
3128
|
+
mintInvocationContext(input, signal) {
|
|
3129
|
+
return queryHostRelayInvocationContext(this.endpoint, input, this.timeout_ms, signal, this.pipe_key);
|
|
3130
|
+
}
|
|
3131
|
+
};
|
|
3132
|
+
function queryHostRelayInvocationContext(endpoint, query, timeoutMs, signal, pipeKey) {
|
|
3133
|
+
return exchange(endpoint, encodeInvocationContextQuery(query), timeoutMs, decodeInvocationContextResponseBuffer, signal, pipeKey);
|
|
3134
|
+
}
|
|
3135
|
+
function exchange(endpoint, encoded, timeoutMs, decode, signal, pipeKey) {
|
|
3136
|
+
return new Promise((resolve, reject) => {
|
|
3137
|
+
const socket = createConnection(endpoint);
|
|
3138
|
+
let buffer = Buffer.alloc(0);
|
|
3139
|
+
let settled = false;
|
|
3140
|
+
const aborted = () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC was cancelled.", "cancelled"));
|
|
3141
|
+
const finish = (error, response2) => {
|
|
3142
|
+
if (settled) return;
|
|
3143
|
+
settled = true;
|
|
3144
|
+
signal?.removeEventListener("abort", aborted);
|
|
3145
|
+
socket.destroy();
|
|
3146
|
+
if (error !== null) reject(error);
|
|
3147
|
+
else if (response2 !== void 0) resolve(response2);
|
|
3148
|
+
};
|
|
3149
|
+
if (timeoutMs !== void 0) socket.setTimeout(timeoutMs, () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC timed out.", "timed_out")));
|
|
3150
|
+
if (signal?.aborted === true) {
|
|
3151
|
+
aborted();
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
3155
|
+
socket.once("error", (error) => {
|
|
3156
|
+
const failure2 = socketErrorFailure(error);
|
|
3157
|
+
finish(new HostRelayIpcError("relay_unavailable", `Host Relay IPC is unavailable: ${failure2}.`, failure2));
|
|
3158
|
+
});
|
|
3159
|
+
socket.once("connect", () => {
|
|
3160
|
+
void beginExchange();
|
|
3161
|
+
});
|
|
3162
|
+
async function beginExchange() {
|
|
3163
|
+
try {
|
|
3164
|
+
if (process.platform === "win32") {
|
|
3165
|
+
if (pipeKey === void 0) throw new HostRelayIpcError("relay_unavailable", "Host Relay pipe key is unavailable; refusing to send any request.");
|
|
3166
|
+
await conductPipeClientHandshake(socket, pipeKey, RELAY_PIPE_HANDSHAKE_TIMEOUT_MS);
|
|
2559
3167
|
}
|
|
3168
|
+
} catch (error) {
|
|
3169
|
+
finish(error instanceof HostRelayIpcError ? error : new HostRelayIpcError("relay_unavailable", error instanceof Error ? error.message : "Host Relay pipe handshake failed.", "closed_early"));
|
|
3170
|
+
return;
|
|
2560
3171
|
}
|
|
2561
|
-
if (
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
const drafts = await this.host.mapEnvelope(envelope);
|
|
2580
|
-
const frames = drafts.map((draft, offset) => ({
|
|
2581
|
-
schema_version: "ceal.host_producer_frame.v1",
|
|
2582
|
-
producer_id: this.host.producer_id,
|
|
2583
|
-
producer_installation_ref: this.installationRef,
|
|
2584
|
-
producer_epoch: this.epoch,
|
|
2585
|
-
sequence: state.next_sequence + offset,
|
|
2586
|
-
frame_kind: draft.frame_kind,
|
|
2587
|
-
occurred_at: this.host.createdAtOf(envelope),
|
|
2588
|
-
payload: draft.payload
|
|
2589
|
-
}));
|
|
2590
|
-
await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef2]: frames } });
|
|
2591
|
-
return frames;
|
|
2592
|
-
}
|
|
2593
|
-
async release(submissionRef2) {
|
|
2594
|
-
const state = await this.readState();
|
|
2595
|
-
if (state.pending[submissionRef2] === void 0) return;
|
|
2596
|
-
const pending = { ...state.pending };
|
|
2597
|
-
delete pending[submissionRef2];
|
|
2598
|
-
await this.writeState({ ...state, pending });
|
|
2599
|
-
}
|
|
2600
|
-
async readState() {
|
|
2601
|
-
try {
|
|
2602
|
-
const value = JSON.parse(await readFile(this.statePath, "utf8"));
|
|
2603
|
-
return this.decodeState(value);
|
|
2604
|
-
} catch (error) {
|
|
2605
|
-
if (errorCode4(error) !== "ENOENT") throw error;
|
|
2606
|
-
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
|
|
3172
|
+
if (settled) return;
|
|
3173
|
+
socket.once("error", (error) => {
|
|
3174
|
+
const failure2 = socketErrorFailure(error);
|
|
3175
|
+
finish(new HostRelayIpcError("relay_unavailable", `Host Relay IPC is unavailable: ${failure2}.`, failure2));
|
|
3176
|
+
});
|
|
3177
|
+
socket.on("data", (chunk) => {
|
|
3178
|
+
try {
|
|
3179
|
+
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
|
3180
|
+
const decoded = decode(buffer);
|
|
3181
|
+
if (decoded !== null) finish(null, decoded);
|
|
3182
|
+
} catch (error) {
|
|
3183
|
+
finish(new HostRelayIpcError("relay_unavailable", error instanceof Error ? error.message : "Host Relay IPC response is invalid.", "response_invalid"));
|
|
3184
|
+
}
|
|
3185
|
+
});
|
|
3186
|
+
socket.once("end", () => {
|
|
3187
|
+
if (!settled) finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC closed without a response.", "closed_early"));
|
|
3188
|
+
});
|
|
3189
|
+
socket.write(encoded);
|
|
2607
3190
|
}
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
function record(value) {
|
|
3194
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3195
|
+
}
|
|
3196
|
+
function decodeInvocationContextResponseBuffer(buffer) {
|
|
3197
|
+
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_QUERY_RESPONSE_MAX_BYTES);
|
|
3198
|
+
if (value === null) return null;
|
|
3199
|
+
if (!validInvocationContextResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay invocation context response is invalid.");
|
|
3200
|
+
return value;
|
|
3201
|
+
}
|
|
3202
|
+
function encodeInvocationContextQuery(query) {
|
|
3203
|
+
const value = { schema_version: HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA, ...query };
|
|
3204
|
+
if (!validInvocationContextQueryRequest(value)) throw new HostRelayIpcError("invalid_frame_shape");
|
|
3205
|
+
return encodeLengthPrefixed(value);
|
|
3206
|
+
}
|
|
3207
|
+
function validInvocationContextQueryRequest(value) {
|
|
3208
|
+
if (!validQueryEnvelope(value)) return false;
|
|
3209
|
+
try {
|
|
3210
|
+
decodeCealHostInvocationContextRequest({
|
|
3211
|
+
schema_version: CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA,
|
|
3212
|
+
client_nonce: value.client_nonce,
|
|
3213
|
+
context_expires_at: value.context_expires_at,
|
|
3214
|
+
host_binding: value.host_binding,
|
|
3215
|
+
human_message_window: value.human_message_window,
|
|
3216
|
+
...value.visible_output_max_bytes === void 0 ? {} : { visible_output_max_bytes: value.visible_output_max_bytes }
|
|
3217
|
+
});
|
|
3218
|
+
return true;
|
|
3219
|
+
} catch {
|
|
3220
|
+
return false;
|
|
2608
3221
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
{ name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
|
|
2643
|
-
{ name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
|
|
2644
|
-
])}).`);
|
|
2645
|
-
return frame;
|
|
3222
|
+
}
|
|
3223
|
+
function validQueryEnvelope(value) {
|
|
3224
|
+
if (!validQueryIdentity(value, HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) || !exactKeys2(value, ["schema_version", "producer_id", "producer_installation_ref", "client_nonce", "context_expires_at", "host_binding", "human_message_window"], ["visible_output_max_bytes"])) return false;
|
|
3225
|
+
return typeof value.client_nonce === "string" && CEAL_HOST_CLIENT_NONCE_PATTERN.test(value.client_nonce) && typeof value.context_expires_at === "string" && (value.visible_output_max_bytes === void 0 || validVisibleOutputBudget(value.visible_output_max_bytes));
|
|
3226
|
+
}
|
|
3227
|
+
function validQueryIdentity(value, schema) {
|
|
3228
|
+
return record(value) && value.schema_version === schema && (value.producer_id === "codex" || value.producer_id === "claude") && safeRef(value.producer_installation_ref);
|
|
3229
|
+
}
|
|
3230
|
+
function validVisibleOutputBudget(value) {
|
|
3231
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES;
|
|
3232
|
+
}
|
|
3233
|
+
function validInvocationContextResponse(value) {
|
|
3234
|
+
if (!record(value) || value.schema_version !== HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA) return false;
|
|
3235
|
+
return value.status === "refused" ? validInvocationContextRefusal(value) : validReadyInvocationContext(value);
|
|
3236
|
+
}
|
|
3237
|
+
function validInvocationContextRefusal(value) {
|
|
3238
|
+
return exactKeys2(value, ["schema_version", "status", "reason"]) && isCealHostAuthenticationCause(value.reason) && value.reason !== "no_binding";
|
|
3239
|
+
}
|
|
3240
|
+
function validReadyInvocationContext(value) {
|
|
3241
|
+
if (value.status !== "ready" || !exactKeys2(value, ["schema_version", "status", "request_ref", "result"]) || !safeRef(value.request_ref) || !value.request_ref.startsWith("ceal-request:") || !record(value.result)) return false;
|
|
3242
|
+
const result = value.result;
|
|
3243
|
+
return exactKeys2(result, ["schema_version", "context_token", "expires_at"]) && result.schema_version === CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA && typeof result.context_token === "string" && CEAL_HOST_CONTEXT_TOKEN_PATTERN.test(result.context_token) && typeof result.expires_at === "string" && Number.isFinite(Date.parse(result.expires_at));
|
|
3244
|
+
}
|
|
3245
|
+
function decodeLengthPrefixedJson(input, maximum) {
|
|
3246
|
+
if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
3247
|
+
const length = input.readUInt32BE(0);
|
|
3248
|
+
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}`)}.`);
|
|
3249
|
+
if (input.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
3250
|
+
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}` }])}.`);
|
|
3251
|
+
try {
|
|
3252
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
|
|
3253
|
+
} catch {
|
|
3254
|
+
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC message is invalid.");
|
|
2646
3255
|
}
|
|
2647
|
-
}
|
|
2648
|
-
function
|
|
2649
|
-
|
|
3256
|
+
}
|
|
3257
|
+
function encodeLengthPrefixed(value) {
|
|
3258
|
+
const body = Buffer.from(JSON.stringify(value), "utf8");
|
|
3259
|
+
const output = Buffer.allocUnsafe(body.length + HOST_RELAY_IPC_LENGTH_BYTES);
|
|
3260
|
+
output.writeUInt32BE(body.length, 0);
|
|
3261
|
+
body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
|
|
3262
|
+
return output;
|
|
3263
|
+
}
|
|
3264
|
+
function exactKeys2(value, required, optional = []) {
|
|
3265
|
+
const keys = Object.keys(value);
|
|
3266
|
+
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
3267
|
+
}
|
|
3268
|
+
function safeRef(value) {
|
|
3269
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2650
3270
|
}
|
|
2651
3271
|
function errorCode4(error) {
|
|
2652
|
-
return
|
|
3272
|
+
return record(error) && typeof error.code === "string" ? error.code : null;
|
|
2653
3273
|
}
|
|
2654
3274
|
|
|
2655
|
-
// host/relay/src/relay-quarantine-store.ts
|
|
2656
|
-
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2657
|
-
var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
|
|
2658
|
-
var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
|
|
2659
|
-
|
|
2660
|
-
// host/relay/src/relay-admission-store.ts
|
|
2661
|
-
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
2662
|
-
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2663
|
-
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2664
|
-
|
|
2665
3275
|
// host/service-log.ts
|
|
2666
|
-
|
|
3276
|
+
import { copyFile, mkdir as mkdir2, stat, truncate, appendFile } from "node:fs/promises";
|
|
3277
|
+
import { dirname as dirname4, isAbsolute, posix, win32 as win322 } from "node:path";
|
|
3278
|
+
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
3279
|
+
function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
|
|
3280
|
+
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 } : {} })}
|
|
3281
|
+
`;
|
|
2667
3282
|
try {
|
|
2668
|
-
|
|
2669
|
-
`);
|
|
3283
|
+
(sink ?? defaultHostServiceDiagnosticSink)(line);
|
|
2670
3284
|
} catch {
|
|
2671
3285
|
}
|
|
2672
3286
|
}
|
|
3287
|
+
function defaultHostServiceDiagnosticSink(line) {
|
|
3288
|
+
writeHostServiceLogLine(line);
|
|
3289
|
+
process.stderr.write(line);
|
|
3290
|
+
}
|
|
2673
3291
|
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
3292
|
+
function writeHostServiceLogLine(line, environment = process.env) {
|
|
3293
|
+
if (process.platform !== "win32") return;
|
|
3294
|
+
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
3295
|
+
if (path === void 0 || !win322.isAbsolute(path) || win322.normalize(path) !== path) return;
|
|
3296
|
+
void appendHostServiceLogLine(path, line).catch(() => void 0);
|
|
3297
|
+
}
|
|
3298
|
+
async function appendHostServiceLogLine(path, line) {
|
|
3299
|
+
if (!isAbsolute(path)) return;
|
|
3300
|
+
await ensureHostServiceLogDirectory(path);
|
|
3301
|
+
await appendFile(path, line);
|
|
3302
|
+
}
|
|
2674
3303
|
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
3304
|
+
var hostServiceLogDirectories = /* @__PURE__ */ new Map();
|
|
3305
|
+
function ensureHostServiceLogDirectory(path) {
|
|
3306
|
+
const directory = dirname4(path);
|
|
3307
|
+
const existing = hostServiceLogDirectories.get(directory);
|
|
3308
|
+
if (existing !== void 0) return existing;
|
|
3309
|
+
const pending = mkdir2(directory, { recursive: true, mode: 448 }).then(() => void 0).catch((error) => {
|
|
3310
|
+
hostServiceLogDirectories.delete(directory);
|
|
3311
|
+
throw error;
|
|
3312
|
+
});
|
|
3313
|
+
hostServiceLogDirectories.set(directory, pending);
|
|
3314
|
+
return pending;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
// host/local-capability-descriptor.ts
|
|
3318
|
+
var HOST_LOCAL_CAPABILITY_AUTHENTICATION_SUFFIX = Object.freeze(["authenticate", "--client-nonce"]);
|
|
2675
3319
|
|
|
2676
3320
|
// host/invocation-authentication-command.ts
|
|
2677
|
-
var HOST_INVOCATION_AUTHENTICATION_COMMAND_PREFIX = ["authenticate", "--client-nonce"];
|
|
2678
|
-
var HOST_AUTHENTICATION_BUDGET_ENVIRONMENT = "CEAL_HOST_AUTHENTICATION_BUDGET_MS";
|
|
2679
3321
|
var HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS = 2e4;
|
|
2680
3322
|
var CLAUDE_CONTEXT_ACTIVATION_WAIT_MAX_MS = HOST_CONTEXT_ACTIVATION_WAIT_MAX_MS;
|
|
2681
3323
|
var HOST_AUTHENTICATION_COMPLETION_RESERVE_MS = 5e3;
|
|
2682
3324
|
var INVOCATION_CONTEXT_POLL_INTERVAL_MS = 250;
|
|
2683
3325
|
var REFUSAL_REASON_PATTERN = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
2684
3326
|
function installedHostInvocationAuthenticationCommandOptions(config, contextBudgetMs, maxContextWaitMs) {
|
|
2685
|
-
|
|
3327
|
+
let pipeKey;
|
|
3328
|
+
const loadPipeKey = () => {
|
|
3329
|
+
if (pipeKey === void 0) pipeKey = process.platform === "win32" ? loadRelayPipeKey(relayPipeStateRootForOutboxDir(config.outbox_dir)) : Promise.resolve(void 0);
|
|
3330
|
+
return pipeKey;
|
|
3331
|
+
};
|
|
2686
3332
|
return {
|
|
2687
3333
|
host_source_ref: config.host_source_ref,
|
|
2688
3334
|
outbox_dir: config.outbox_dir,
|
|
2689
3335
|
producer_installation_ref: config.producer_installation_ref,
|
|
2690
3336
|
context_activation_wait_ms: boundedContextActivationWait(contextBudgetMs, config.ipc_timeout_ms, maxContextWaitMs),
|
|
2691
|
-
mint_invocation_context: (input) =>
|
|
3337
|
+
mint_invocation_context: async (input) => new HostRelayInvocationContextPort(config.relay_endpoint, await loadPipeKey(), config.ipc_timeout_ms).mintInvocationContext(input)
|
|
2692
3338
|
};
|
|
2693
3339
|
}
|
|
2694
3340
|
async function runHostInvocationAuthenticationCommand(descriptor, args, options) {
|
|
@@ -2709,26 +3355,31 @@ function authenticationRefusalReason(error) {
|
|
|
2709
3355
|
return isCealHostAuthenticationCause(message) ? message : "host_refusal_invalid";
|
|
2710
3356
|
}
|
|
2711
3357
|
async function authenticateHostInvocation(descriptor, input, options) {
|
|
2712
|
-
if (input.length !==
|
|
3358
|
+
if (input.length !== HOST_LOCAL_CAPABILITY_AUTHENTICATION_SUFFIX.length + 1 || input[0] !== HOST_LOCAL_CAPABILITY_AUTHENTICATION_SUFFIX[0] || input[1] !== HOST_LOCAL_CAPABILITY_AUTHENTICATION_SUFFIX[1]) throw new TypeError("invalid_arguments");
|
|
2713
3359
|
const request2 = decodeCealHostInvocationAuthenticationRequest({ schema_version: CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA, client_nonce: input[2] });
|
|
2714
3360
|
const config = validateOptions(options);
|
|
2715
3361
|
const now = config.now ?? (() => /* @__PURE__ */ new Date());
|
|
2716
3362
|
const { store, current } = await currentInvocation(descriptor, config, now);
|
|
2717
3363
|
if (config.mint_invocation_context === void 0) throw new TypeError("relay_failed");
|
|
2718
|
-
const query =
|
|
2719
|
-
producer_id: descriptor.producer_id,
|
|
2720
|
-
producer_installation_ref: config.producer_installation_ref,
|
|
2721
|
-
local_session_ref: current.identity.host_session_ref,
|
|
2722
|
-
local_turn_ref: current.identity.host_turn_ref,
|
|
2723
|
-
client_nonce: request2.client_nonce,
|
|
2724
|
-
...await visibleOutputBudget(descriptor)
|
|
2725
|
-
};
|
|
3364
|
+
const query = await invocationMintQuery(descriptor, store, current, request2.client_nonce, config);
|
|
2726
3365
|
const response2 = await mintedResult(config.mint_invocation_context, query);
|
|
2727
3366
|
const completedAt = now();
|
|
2728
3367
|
const rechecked = await store.readInvocationContext(current.identity.host_source_ref, current.identity.host_session_ref, completedAt);
|
|
2729
3368
|
if (rechecked === null || !sameTurn(current, rechecked)) throw new TypeError("context_replaced");
|
|
2730
3369
|
return decodeCealHostInvocationAuthenticationResult(response2.result, completedAt);
|
|
2731
3370
|
}
|
|
3371
|
+
async function invocationMintQuery(descriptor, store, current, clientNonce, options) {
|
|
3372
|
+
const readWindow = options.read_human_message_window?.(current.identity) ?? descriptor.read_human_message_window?.({ store, identity: current.identity }) ?? Promise.resolve({ messages: [], truncated: false });
|
|
3373
|
+
return {
|
|
3374
|
+
producer_id: descriptor.producer_id,
|
|
3375
|
+
producer_installation_ref: options.producer_installation_ref,
|
|
3376
|
+
client_nonce: clientNonce,
|
|
3377
|
+
context_expires_at: current.expires_at,
|
|
3378
|
+
host_binding: { ...current.identity },
|
|
3379
|
+
human_message_window: await readWindow,
|
|
3380
|
+
...await visibleOutputBudget(descriptor)
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
2732
3383
|
async function visibleOutputBudget(descriptor) {
|
|
2733
3384
|
const bytes = await descriptor.resolve_visible_output_max_bytes?.();
|
|
2734
3385
|
return bytes === void 0 ? {} : { visible_output_max_bytes: bytes };
|
|
@@ -2738,30 +3389,15 @@ async function mintedResult(mint, query) {
|
|
|
2738
3389
|
try {
|
|
2739
3390
|
response2 = await mint(query);
|
|
2740
3391
|
} catch (error) {
|
|
2741
|
-
throw new TypeError(
|
|
3392
|
+
throw new TypeError(cealHostAuthenticationCauseForExchangeFailure(hostRelayExchangeFailure(error) ?? "unavailable"));
|
|
2742
3393
|
}
|
|
2743
|
-
if (response2.status
|
|
2744
|
-
if (response2.status !== "ready") throw new TypeError("no_binding");
|
|
3394
|
+
if (response2.status !== "ready") throw new TypeError(isCealHostAuthenticationCause(response2.reason) && response2.reason !== "no_binding" ? response2.reason : "host_refusal_invalid");
|
|
2745
3395
|
return response2;
|
|
2746
3396
|
}
|
|
2747
|
-
function mintRefusalCause(reason) {
|
|
2748
|
-
if (isDirectMintRefusalCause(reason)) return reason;
|
|
2749
|
-
if (reason === "deadline" || reason === "cancelled") return "transient_retryable";
|
|
2750
|
-
if (reason === "mint_failed" || reason === "relay_failed") return reason;
|
|
2751
|
-
return isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, reason) ? "context_stale" : "host_refusal_invalid";
|
|
2752
|
-
}
|
|
2753
|
-
function isDirectMintRefusalCause(reason) {
|
|
2754
|
-
return isVocabularyWord(HOST_RELAY_DIRECT_WIRE_MINT_REASONS, reason);
|
|
2755
|
-
}
|
|
2756
|
-
function exchangeFailureCause(failure2) {
|
|
2757
|
-
if (failure2 === "permission_denied") return "relay_permission_denied";
|
|
2758
|
-
if (failure2 === "timed_out") return "transient_retryable";
|
|
2759
|
-
return "relay_unavailable";
|
|
2760
|
-
}
|
|
2761
3397
|
async function currentInvocation(descriptor, config, now) {
|
|
2762
3398
|
const store = descriptor.create_store(config.outbox_dir);
|
|
2763
3399
|
const identity = resolveIdentity(descriptor, config);
|
|
2764
|
-
if (identity.status
|
|
3400
|
+
if (identity.status === "session_unavailable") throw new TypeError("no_binding");
|
|
2765
3401
|
const waitBudgetMs = config.context_activation_wait_ms ?? 0;
|
|
2766
3402
|
const monotonicNow = config.monotonic_now ?? (() => performance.now());
|
|
2767
3403
|
const deadline = monotonicNow() + waitBudgetMs;
|
|
@@ -2769,36 +3405,36 @@ async function currentInvocation(descriptor, config, now) {
|
|
|
2769
3405
|
let waitEngaged = false;
|
|
2770
3406
|
while (true) {
|
|
2771
3407
|
if (!firstCheck && monotonicNow() >= deadline) {
|
|
2772
|
-
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs);
|
|
3408
|
+
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
|
|
2773
3409
|
throw new TypeError("context_unavailable");
|
|
2774
3410
|
}
|
|
2775
3411
|
firstCheck = false;
|
|
2776
3412
|
const observedAt = now();
|
|
2777
3413
|
const current = await readLiveSessionContext(store, identity.identity.host_source_ref, identity.identity.host_session_ref, observedAt);
|
|
2778
3414
|
if (current !== null) {
|
|
2779
|
-
recordActivationWaitCompletion(descriptor.producer_id, waitEngaged);
|
|
3415
|
+
recordActivationWaitCompletion(descriptor.producer_id, waitEngaged, config.diagnostic_sink);
|
|
2780
3416
|
return { store, current };
|
|
2781
3417
|
}
|
|
2782
3418
|
const remainingMs = deadline - monotonicNow();
|
|
2783
3419
|
if (remainingMs <= 0) {
|
|
2784
|
-
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs);
|
|
3420
|
+
recordActivationWaitExhaustion(descriptor.producer_id, waitEngaged, waitBudgetMs, config.diagnostic_sink);
|
|
2785
3421
|
throw new TypeError("context_unavailable");
|
|
2786
3422
|
}
|
|
2787
|
-
recordActivationWaitEngagement(descriptor.producer_id, waitEngaged);
|
|
3423
|
+
recordActivationWaitEngagement(descriptor.producer_id, waitEngaged, config.diagnostic_sink);
|
|
2788
3424
|
waitEngaged = true;
|
|
2789
3425
|
const intervalMs = Math.min(INVOCATION_CONTEXT_POLL_INTERVAL_MS, remainingMs);
|
|
2790
3426
|
await (config.wait ?? waitForContextActivation)(intervalMs);
|
|
2791
3427
|
}
|
|
2792
3428
|
}
|
|
2793
|
-
function recordActivationWaitExhaustion(producer, engaged, budgetMs) {
|
|
2794
|
-
if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted");
|
|
2795
|
-
else writeHostServiceDiagnostic(producer, "activation_wait_skipped");
|
|
3429
|
+
function recordActivationWaitExhaustion(producer, engaged, budgetMs, sink) {
|
|
3430
|
+
if (engaged || budgetMs > 0) writeHostServiceDiagnostic(producer, "activation_wait_exhausted", void 0, void 0, sink);
|
|
3431
|
+
else writeHostServiceDiagnostic(producer, "activation_wait_skipped", void 0, void 0, sink);
|
|
2796
3432
|
}
|
|
2797
|
-
function recordActivationWaitCompletion(producer, engaged) {
|
|
2798
|
-
if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed");
|
|
3433
|
+
function recordActivationWaitCompletion(producer, engaged, sink) {
|
|
3434
|
+
if (engaged) writeHostServiceDiagnostic(producer, "activation_wait_completed", void 0, void 0, sink);
|
|
2799
3435
|
}
|
|
2800
|
-
function recordActivationWaitEngagement(producer, engaged) {
|
|
2801
|
-
if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged");
|
|
3436
|
+
function recordActivationWaitEngagement(producer, engaged, sink) {
|
|
3437
|
+
if (!engaged) writeHostServiceDiagnostic(producer, "activation_wait_engaged", void 0, void 0, sink);
|
|
2802
3438
|
}
|
|
2803
3439
|
async function readLiveSessionContext(store, hostSourceRef, hostSessionRef, now) {
|
|
2804
3440
|
const current = await store.readInvocationContext(hostSourceRef, hostSessionRef, now);
|
|
@@ -2825,9 +3461,9 @@ async function resolveOptions(descriptor, args, options) {
|
|
|
2825
3461
|
};
|
|
2826
3462
|
}
|
|
2827
3463
|
function authenticationBudgetMsFromEnvironment() {
|
|
2828
|
-
const value = process.env[
|
|
3464
|
+
const value = process.env[CEAL_HOST_AUTHENTICATION_BUDGET_ENVIRONMENT];
|
|
2829
3465
|
if (value === void 0) return 0;
|
|
2830
|
-
if (
|
|
3466
|
+
if (!CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN.test(value)) throw new TypeError("invalid_arguments");
|
|
2831
3467
|
return Number(value);
|
|
2832
3468
|
}
|
|
2833
3469
|
function boundedContextActivationWait(contextBudgetMs, relayTimeoutMs, maxContextWaitMs) {
|
|
@@ -2864,16 +3500,16 @@ function writeResult(value, sink) {
|
|
|
2864
3500
|
}
|
|
2865
3501
|
|
|
2866
3502
|
// host/producer-service.ts
|
|
2867
|
-
import { posix as
|
|
3503
|
+
import { posix as posix4, win32 as win325 } from "node:path";
|
|
2868
3504
|
|
|
2869
3505
|
// host/runtime-source-paths.ts
|
|
2870
|
-
import { createHash as
|
|
2871
|
-
import { posix as
|
|
3506
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3507
|
+
import { posix as posix3, win32 as win324 } from "node:path";
|
|
2872
3508
|
|
|
2873
3509
|
// host/absolute-path.ts
|
|
2874
|
-
import { posix, win32 } from "node:path";
|
|
3510
|
+
import { posix as posix2, win32 as win323 } from "node:path";
|
|
2875
3511
|
function isAbsoluteNormalizedNonRootPath(value, flavor = process.platform, allowRoot = false) {
|
|
2876
|
-
const pathApi = flavor === "win32" || flavor === "windows" ?
|
|
3512
|
+
const pathApi = flavor === "win32" || flavor === "windows" ? win323 : posix2;
|
|
2877
3513
|
return typeof value === "string" && value.length <= 4096 && !value.includes("\0") && pathApi.isAbsolute(value) && pathApi.normalize(value) === value && (allowRoot || pathApi.parse(value).root !== value);
|
|
2878
3514
|
}
|
|
2879
3515
|
|
|
@@ -2885,28 +3521,23 @@ function hostSourceUuid(sourceRef) {
|
|
|
2885
3521
|
function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platform) {
|
|
2886
3522
|
const sourceId = hostSourceUuid(sourceRef);
|
|
2887
3523
|
if (!isAbsoluteNormalizedNonRootPath(stateRoot, platform) || sourceId === null) throw new TypeError("Host runtime source identity is invalid.");
|
|
2888
|
-
const path = platform === "win32" ?
|
|
3524
|
+
const path = platform === "win32" ? win324 : posix3;
|
|
2889
3525
|
const sourcesRoot = path.join(stateRoot, "sources");
|
|
2890
3526
|
const sourceRoot = path.join(sourcesRoot, sourceId);
|
|
2891
3527
|
const runtimeRoot = path.join(stateRoot, "run");
|
|
2892
|
-
const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${
|
|
2893
|
-
assertHostRuntimeIpcLength(endpoint, platform);
|
|
3528
|
+
const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash4("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
|
|
2894
3529
|
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 };
|
|
2895
3530
|
}
|
|
2896
|
-
function assertHostRuntimeIpcLength(endpoint, platform) {
|
|
2897
|
-
if (platform === "win32") return;
|
|
2898
|
-
const maximum = platform === "linux" ? 107 : 103;
|
|
2899
|
-
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" });
|
|
2900
|
-
}
|
|
2901
3531
|
function assertHostProducerSourcePaths(stateRoot, config, producer, platform = process.platform) {
|
|
2902
3532
|
const paths = hostRuntimeSourcePaths(stateRoot, config.host_source_ref, platform);
|
|
2903
|
-
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.");
|
|
3533
|
+
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.");
|
|
3534
|
+
return paths.endpoint;
|
|
2904
3535
|
}
|
|
2905
3536
|
|
|
2906
3537
|
// host/producer-service.ts
|
|
2907
|
-
import { constants } from "node:fs";
|
|
2908
|
-
import { lstat as
|
|
2909
|
-
var
|
|
3538
|
+
import { constants as constants3 } from "node:fs";
|
|
3539
|
+
import { lstat as lstat3 } from "node:fs/promises";
|
|
3540
|
+
var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2910
3541
|
var HOST_PRODUCER_CONFIG_FLAG = "--ceal-host-config";
|
|
2911
3542
|
var HostProducerServiceError = class extends Error {
|
|
2912
3543
|
code;
|
|
@@ -2917,18 +3548,19 @@ var HostProducerServiceError = class extends Error {
|
|
|
2917
3548
|
}
|
|
2918
3549
|
};
|
|
2919
3550
|
var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
|
|
3551
|
+
var HOST_PRODUCER_CONFIG_ENDPOINT_KEY = "relay_endpoint";
|
|
2920
3552
|
var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
|
|
2921
3553
|
function hostProducerExtraPath(extras, key) {
|
|
2922
3554
|
const value = extras[key];
|
|
2923
|
-
if (value === void 0) throw hostProducerError("invalid_config");
|
|
3555
|
+
if (value === void 0) throw hostProducerError("invalid_config", refusalFact(key, "missing", "an absolute path"));
|
|
2924
3556
|
return value;
|
|
2925
3557
|
}
|
|
2926
3558
|
function parseHostProducerConfigFlag(args, platform = process.platform) {
|
|
2927
3559
|
return parseProducerHostConfigFlag(args, HOST_PRODUCER_CONFIG_FLAG, platform, hostProducerError);
|
|
2928
3560
|
}
|
|
2929
|
-
async function readHostProducerConfig(descriptor, configPath, platform = process.platform) {
|
|
2930
|
-
const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError), platform);
|
|
2931
|
-
const path = platform === "win32" ?
|
|
3561
|
+
async function readHostProducerConfig(descriptor, configPath, platform = process.platform, custody = {}) {
|
|
3562
|
+
const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError, custody), platform);
|
|
3563
|
+
const path = platform === "win32" ? win325 : posix4;
|
|
2932
3564
|
const root = path.resolve(config.outbox_dir, "../../../../..");
|
|
2933
3565
|
try {
|
|
2934
3566
|
assertHostProducerSourcePaths(root, config, descriptor.producer_id, platform);
|
|
@@ -2954,15 +3586,31 @@ function parseProducerHostConfigFlag(args, flag, platform, error) {
|
|
|
2954
3586
|
assertProducerAbsolutePath(value, platform, "config", error);
|
|
2955
3587
|
return value;
|
|
2956
3588
|
}
|
|
2957
|
-
async function readProducerConfigText(configPath, platform, error) {
|
|
2958
|
-
assertProducerAbsolutePath(configPath, platform, "config", error);
|
|
3589
|
+
async function readProducerConfigText(configPath, platform, error, custody) {
|
|
3590
|
+
assertProducerAbsolutePath(configPath, process.platform, "config", error);
|
|
3591
|
+
if (platform === "win32") {
|
|
3592
|
+
let bytes;
|
|
3593
|
+
try {
|
|
3594
|
+
bytes = await readPrivateFileWithSecurefs(configPath, CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES, custody);
|
|
3595
|
+
} catch (cause) {
|
|
3596
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "not_private") throw error("config_not_private");
|
|
3597
|
+
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}`));
|
|
3598
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "reparse_point") throw error("invalid_config");
|
|
3599
|
+
throw error("config_unavailable");
|
|
3600
|
+
}
|
|
3601
|
+
if (bytes.byteLength === 0) throw error("invalid_config", refusalFact("config_bytes", 0, `between 1 and ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
|
|
3602
|
+
try {
|
|
3603
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
3604
|
+
} catch {
|
|
3605
|
+
throw error("invalid_config");
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
2959
3608
|
const pathStat = await configPathStat(configPath, error);
|
|
2960
3609
|
validateConfigFile(pathStat, platform, error);
|
|
2961
|
-
const
|
|
2962
|
-
const handle = await openConfigFile(configPath, constants.O_RDONLY | noFollow, error);
|
|
3610
|
+
const handle = await openConfigFile(configPath, platform, error, custody);
|
|
2963
3611
|
try {
|
|
2964
3612
|
const opened = await handle.stat();
|
|
2965
|
-
validateConfigFile(opened, platform, error);
|
|
3613
|
+
validateConfigFile(opened, platform, error, handle);
|
|
2966
3614
|
validateOpenedFile(opened, pathStat, platform, error);
|
|
2967
3615
|
return await readBoundedConfig(handle, opened.size, error);
|
|
2968
3616
|
} finally {
|
|
@@ -2970,8 +3618,9 @@ async function readProducerConfigText(configPath, platform, error) {
|
|
|
2970
3618
|
}
|
|
2971
3619
|
}
|
|
2972
3620
|
function decodeProducerConfigCore(text, options) {
|
|
3621
|
+
const ownership = producerConfigKeyOwnership(options.keys, options.platform);
|
|
2973
3622
|
const value = parseCanonicalJson(text, options.error);
|
|
2974
|
-
if (!
|
|
3623
|
+
if (!isRecord3(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 })));
|
|
2975
3624
|
const hostSourceRef = typeof value.host_source_ref === "string" ? value.host_source_ref : "";
|
|
2976
3625
|
const installationRef = typeof value.producer_installation_ref === "string" ? value.producer_installation_ref : "";
|
|
2977
3626
|
const producerEpoch = validateIdentity(hostSourceRef, installationRef, value.producer_epoch, options.error);
|
|
@@ -2989,14 +3638,21 @@ function decodeProducerConfigCore(text, options) {
|
|
|
2989
3638
|
producer_installation_ref: installationRef,
|
|
2990
3639
|
producer_epoch: producerEpoch,
|
|
2991
3640
|
outbox_dir: value.outbox_dir,
|
|
2992
|
-
relay_endpoint: value
|
|
3641
|
+
relay_endpoint: producerConfigEndpoint(value),
|
|
2993
3642
|
reconcile_interval_ms: intervals[0],
|
|
2994
3643
|
retry_backoff_ms: intervals[1],
|
|
2995
3644
|
ipc_timeout_ms: intervals[2]
|
|
2996
3645
|
};
|
|
2997
3646
|
}
|
|
3647
|
+
function producerConfigKeyOwnership(keys, platform) {
|
|
3648
|
+
if (platform !== "win32") return { required: keys, tolerated: [] };
|
|
3649
|
+
return { required: keys.filter((key) => key !== HOST_PRODUCER_CONFIG_ENDPOINT_KEY), tolerated: [HOST_PRODUCER_CONFIG_ENDPOINT_KEY] };
|
|
3650
|
+
}
|
|
3651
|
+
function producerConfigEndpoint(value) {
|
|
3652
|
+
return typeof value.relay_endpoint === "string" ? value.relay_endpoint : "";
|
|
3653
|
+
}
|
|
2998
3654
|
function assertProducerRelayEndpoint(value, platform, error) {
|
|
2999
|
-
if (platform === "win32"
|
|
3655
|
+
if (platform === "win32") return;
|
|
3000
3656
|
assertProducerAbsolutePath(value, platform, "runtime", error);
|
|
3001
3657
|
}
|
|
3002
3658
|
function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
@@ -3004,26 +3660,27 @@ function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
|
3004
3660
|
}
|
|
3005
3661
|
async function configPathStat(configPath, error) {
|
|
3006
3662
|
try {
|
|
3007
|
-
return await
|
|
3663
|
+
return await lstat3(configPath);
|
|
3008
3664
|
} catch {
|
|
3009
3665
|
throw error("config_unavailable");
|
|
3010
3666
|
}
|
|
3011
3667
|
}
|
|
3012
|
-
function validateConfigFile(value, platform, error) {
|
|
3668
|
+
function validateConfigFile(value, platform, error, handle) {
|
|
3013
3669
|
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" }]));
|
|
3014
3670
|
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}`));
|
|
3015
|
-
if (
|
|
3671
|
+
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" }]));
|
|
3016
3672
|
}
|
|
3017
|
-
async function openConfigFile(configPath,
|
|
3673
|
+
async function openConfigFile(configPath, platform, error, custody) {
|
|
3018
3674
|
try {
|
|
3019
|
-
return await
|
|
3020
|
-
} catch {
|
|
3675
|
+
return await openNoFollow(configPath, constants3.O_RDONLY, void 0, platform, custody);
|
|
3676
|
+
} catch (error_) {
|
|
3677
|
+
if (error_ instanceof SecureFileCustodyError) throw error(error_.reason === "not_private" ? "config_not_private" : "invalid_config");
|
|
3021
3678
|
throw error("config_unavailable");
|
|
3022
3679
|
}
|
|
3023
3680
|
}
|
|
3024
3681
|
function validateOpenedFile(opened, pathStat, platform, error) {
|
|
3025
3682
|
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" }]));
|
|
3026
|
-
if (
|
|
3683
|
+
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) }]));
|
|
3027
3684
|
}
|
|
3028
3685
|
async function readBoundedConfig(handle, expectedBytes, error) {
|
|
3029
3686
|
const buffer = Buffer.allocUnsafe(expectedBytes + 1);
|
|
@@ -3055,29 +3712,54 @@ function firstDifferenceIndex(left, right) {
|
|
|
3055
3712
|
for (let index = 0; index < shared; index += 1) if (left[index] !== right[index]) return index;
|
|
3056
3713
|
return shared;
|
|
3057
3714
|
}
|
|
3715
|
+
function producerShapeFacts(value, options) {
|
|
3716
|
+
if (!isRecord3(value)) return [{ name: "config", value: "wrong_type", expected: "a record" }];
|
|
3717
|
+
const tolerated = options.tolerated ?? [];
|
|
3718
|
+
const allowed = [...options.keys, ...tolerated, ...Object.keys(HOST_PRODUCER_TIMING_DEFAULTS)];
|
|
3719
|
+
const facts = [
|
|
3720
|
+
...Object.keys(value).filter((key) => !allowed.includes(key)).map((key) => ({ name: key, value: "unknown", expected: `one of ${[...options.keys, ...tolerated].join(", ")}` })),
|
|
3721
|
+
...options.keys.filter((key) => !Object.hasOwn(value, key)).map((key) => ({ name: key, value: "missing", expected: "present" }))
|
|
3722
|
+
];
|
|
3723
|
+
if (Object.hasOwn(value, "schema_version") && value.schema_version !== options.schema) facts.push({ name: "schema_version", value: "wrong_type", expected: options.schema });
|
|
3724
|
+
return facts;
|
|
3725
|
+
}
|
|
3058
3726
|
function validateIdentity(sourceRef, installationRef, epoch, error) {
|
|
3059
|
-
if (!
|
|
3727
|
+
if (!SAFE_REF2.test(sourceRef) || !SAFE_REF2.test(installationRef) || !positive(epoch, 2147483647)) throw error("invalid_config", refusalFacts(producerIdentityFacts(sourceRef, installationRef, epoch)));
|
|
3060
3728
|
return epoch;
|
|
3061
3729
|
}
|
|
3730
|
+
function producerIdentityFacts(sourceRef, installationRef, epoch) {
|
|
3731
|
+
const facts = [];
|
|
3732
|
+
if (!SAFE_REF2.test(sourceRef)) facts.push({ name: "host_source_ref", value: "wrong_type", expected: "a safe reference" });
|
|
3733
|
+
if (!SAFE_REF2.test(installationRef)) facts.push({ name: "producer_installation_ref", value: "wrong_type", expected: "a safe reference" });
|
|
3734
|
+
if (!positive(epoch, 2147483647)) facts.push({ name: "producer_epoch", value: "wrong_type", expected: "a positive integer" });
|
|
3735
|
+
return facts;
|
|
3736
|
+
}
|
|
3062
3737
|
function validateIntervals(reconcile, retry, timeout, error) {
|
|
3063
|
-
if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config");
|
|
3738
|
+
if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config", refusalFacts(producerIntervalFacts(reconcile, retry, timeout)));
|
|
3064
3739
|
return [reconcile, retry, timeout];
|
|
3065
3740
|
}
|
|
3066
|
-
function
|
|
3067
|
-
|
|
3741
|
+
function producerIntervalFacts(reconcile, retry, timeout) {
|
|
3742
|
+
const facts = [];
|
|
3743
|
+
if (!boundedMilliseconds(reconcile)) facts.push({ name: "reconcile_interval_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
3744
|
+
if (!boundedMilliseconds(retry)) facts.push({ name: "retry_backoff_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
3745
|
+
if (!boundedMilliseconds(timeout)) facts.push({ name: "ipc_timeout_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
3746
|
+
return facts;
|
|
3068
3747
|
}
|
|
3069
|
-
function
|
|
3748
|
+
function hasConfigKeys(value, required, tolerated = []) {
|
|
3749
|
+
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));
|
|
3750
|
+
}
|
|
3751
|
+
function isRecord3(value) {
|
|
3070
3752
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3071
3753
|
}
|
|
3072
|
-
function
|
|
3754
|
+
function positive(value, maximum) {
|
|
3073
3755
|
return Number.isSafeInteger(value) && Number(value) > 0 && Number(value) <= maximum;
|
|
3074
3756
|
}
|
|
3075
3757
|
function boundedMilliseconds(value) {
|
|
3076
|
-
return
|
|
3758
|
+
return positive(value, 6e4) && Number(value) >= 25;
|
|
3077
3759
|
}
|
|
3078
3760
|
|
|
3079
3761
|
// host/claude/src/claude-decoder.ts
|
|
3080
|
-
import { createHash as
|
|
3762
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3081
3763
|
|
|
3082
3764
|
// host/claude/src/claude-types.ts
|
|
3083
3765
|
var CLAUDE_SESSION_ID_ENVIRONMENT = "CLAUDE_CODE_SESSION_ID";
|
|
@@ -3108,6 +3790,76 @@ var ClaudeHostError = class extends Error {
|
|
|
3108
3790
|
|
|
3109
3791
|
// host/claude/src/claude-decoder.ts
|
|
3110
3792
|
var MAX_HOOK_BYTES = 256 * 1024;
|
|
3793
|
+
function decodeClaudeQueuedPromptRow(row, sessionId) {
|
|
3794
|
+
if (!record2(row)) return null;
|
|
3795
|
+
if (row.type === "user") return decodeArrayTextPrompt(row, sessionId);
|
|
3796
|
+
if (row.type === "attachment") return decodeQueuedCommandPrompt(row, sessionId);
|
|
3797
|
+
if (row.type === "queue-operation") return decodeQueueEnqueuePrompt(row, sessionId);
|
|
3798
|
+
return null;
|
|
3799
|
+
}
|
|
3800
|
+
function decodeClaudeHumanMessageRow(row, sessionId) {
|
|
3801
|
+
if (!isClaudeNativeHumanMessageRow(row)) return decodeClaudeQueuedPromptRow(row, sessionId);
|
|
3802
|
+
return decodeClaudeNativeHumanMessage(row, sessionId);
|
|
3803
|
+
}
|
|
3804
|
+
function isClaudeNativeHumanMessageRow(row) {
|
|
3805
|
+
return record2(row) && row.type === "user" && record2(row.message) && typeof row.message.content === "string";
|
|
3806
|
+
}
|
|
3807
|
+
function decodeClaudeNativeHumanMessage(row, sessionId) {
|
|
3808
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
3809
|
+
if (row.isMeta === true && (!record2(row.origin) || typeof row.origin.kind !== "string")) return null;
|
|
3810
|
+
const identity = queuedRowIdentity(row);
|
|
3811
|
+
if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript human message identity is malformed.");
|
|
3812
|
+
const message = row.message;
|
|
3813
|
+
if (!record2(message) || typeof message.content !== "string") return null;
|
|
3814
|
+
return { turnId: identity.uuid, timestamp: identity.timestamp, content: message.content };
|
|
3815
|
+
}
|
|
3816
|
+
function decodeArrayTextPrompt(row, sessionId) {
|
|
3817
|
+
if (!record2(row.message) || !Array.isArray(row.message.content)) return null;
|
|
3818
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
3819
|
+
if (row.isMeta === true && (!record2(row.origin) || typeof row.origin.kind !== "string")) return null;
|
|
3820
|
+
const texts = arrayTextBlocks(row.message.content);
|
|
3821
|
+
if (texts === null) return null;
|
|
3822
|
+
const identity = queuedRowIdentity(row);
|
|
3823
|
+
if (identity === null) return null;
|
|
3824
|
+
return { turnId: identity.uuid, timestamp: identity.timestamp, content: texts.join("") };
|
|
3825
|
+
}
|
|
3826
|
+
function arrayTextBlocks(content) {
|
|
3827
|
+
const texts = [];
|
|
3828
|
+
for (const block of content) {
|
|
3829
|
+
if (!record2(block) || block.type !== "text" || typeof block.text !== "string") return null;
|
|
3830
|
+
texts.push(block.text);
|
|
3831
|
+
}
|
|
3832
|
+
return texts.length === 0 ? null : texts;
|
|
3833
|
+
}
|
|
3834
|
+
function decodeQueuedCommandPrompt(row, sessionId) {
|
|
3835
|
+
if (!record2(row.attachment) || row.attachment.type !== "queued_command" || row.attachment.commandMode !== "prompt") return null;
|
|
3836
|
+
if (typeof row.attachment.prompt !== "string" || row.attachment.prompt.length === 0) return null;
|
|
3837
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
3838
|
+
const identity = queuedRowIdentity(row);
|
|
3839
|
+
if (identity === null) return null;
|
|
3840
|
+
return { turnId: identity.uuid, timestamp: identity.timestamp, content: row.attachment.prompt };
|
|
3841
|
+
}
|
|
3842
|
+
function decodeQueueEnqueuePrompt(row, sessionId) {
|
|
3843
|
+
if (row.operation !== "enqueue" || typeof row.content !== "string" || row.content.length === 0) return null;
|
|
3844
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
3845
|
+
if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
|
|
3846
|
+
return { turnId: queueEnqueueTurnId(sessionId, row.timestamp, row.content), timestamp: row.timestamp, content: row.content };
|
|
3847
|
+
}
|
|
3848
|
+
function sessionMatches(row, sessionId) {
|
|
3849
|
+
const hasSessionId = Object.hasOwn(row, "sessionId");
|
|
3850
|
+
const hasSnakeSessionId = Object.hasOwn(row, "session_id");
|
|
3851
|
+
return (hasSessionId || hasSnakeSessionId) && (!hasSessionId || row.sessionId === sessionId) && (!hasSnakeSessionId || row.session_id === sessionId);
|
|
3852
|
+
}
|
|
3853
|
+
function queuedRowIdentity(row) {
|
|
3854
|
+
if (typeof row.uuid !== "string" || row.uuid.length === 0 || row.uuid.length > 256) return null;
|
|
3855
|
+
if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
|
|
3856
|
+
return { uuid: row.uuid, timestamp: row.timestamp };
|
|
3857
|
+
}
|
|
3858
|
+
function queueEnqueueTurnId(sessionId, timestamp3, content) {
|
|
3859
|
+
return `queue-enqueue:${createHash5("sha256").update(`${sessionId}
|
|
3860
|
+
${timestamp3}
|
|
3861
|
+
${content}`, "utf8").digest("hex").slice(0, 32)}`;
|
|
3862
|
+
}
|
|
3111
3863
|
function claudeLocalSessionRef(sessionId) {
|
|
3112
3864
|
return `claude-session:${digest(sessionId)}`;
|
|
3113
3865
|
}
|
|
@@ -3117,25 +3869,25 @@ function claudeLocalTurnRef(sessionId, turnId) {
|
|
|
3117
3869
|
function claudeLocalInputRef(sessionId, turnId) {
|
|
3118
3870
|
return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
|
|
3119
3871
|
}
|
|
3120
|
-
function claudeWorkspaceRef(cwd) {
|
|
3121
|
-
return `workspace:${digest(cwd)}`;
|
|
3122
|
-
}
|
|
3123
3872
|
function digest(value) {
|
|
3124
|
-
return
|
|
3873
|
+
return createHash5("sha256").update(value, "utf8").digest("hex");
|
|
3874
|
+
}
|
|
3875
|
+
function record2(value) {
|
|
3876
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3125
3877
|
}
|
|
3126
3878
|
|
|
3127
3879
|
// host/claude/src/claude-outbox.ts
|
|
3128
|
-
import { mkdir as mkdir3, readdir, readFile
|
|
3129
|
-
import { isAbsolute, join as join3 } from "node:path";
|
|
3880
|
+
import { mkdir as mkdir3, readdir, readFile, unlink as unlink3 } from "node:fs/promises";
|
|
3881
|
+
import { isAbsolute as isAbsolute2, join as join3 } from "node:path";
|
|
3130
3882
|
|
|
3131
3883
|
// host/invocation-context.ts
|
|
3132
|
-
var
|
|
3884
|
+
var HOST_INVOCATION_TURN_SELECTION_SCHEMA = "ceal.host_invocation_turn_selection.v1";
|
|
3133
3885
|
var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
|
|
3134
3886
|
function invocationContextKey(identity) {
|
|
3135
3887
|
return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
|
|
3136
3888
|
}
|
|
3137
3889
|
function hasHostInvocationIdentityKeys(value) {
|
|
3138
|
-
return
|
|
3890
|
+
return exactKeys3(value, CEAL_HOST_INVOCATION_IDENTITY_KEYS);
|
|
3139
3891
|
}
|
|
3140
3892
|
function hasHostInvocationContextKeys(value, optional = []) {
|
|
3141
3893
|
return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
|
|
@@ -3161,12 +3913,9 @@ function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
|
|
|
3161
3913
|
return Object.freeze(context);
|
|
3162
3914
|
}
|
|
3163
3915
|
function isHostInvocationTurnSelection(value) {
|
|
3164
|
-
return
|
|
3165
|
-
}
|
|
3166
|
-
function invocationTurnSelectionMatches(context, selection) {
|
|
3167
|
-
return selection === null || context !== null && selection.host_source_ref === context.identity.host_source_ref && selection.host_session_ref === context.identity.host_session_ref && selection.prompt_ref === context.prompt_ref && selection.turn_generation_ref === context.turn_generation_ref;
|
|
3916
|
+
return isRecord4(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) => safeRef2(value[key]));
|
|
3168
3917
|
}
|
|
3169
|
-
function
|
|
3918
|
+
function exactKeys3(value, keys) {
|
|
3170
3919
|
const actual = Object.keys(value);
|
|
3171
3920
|
return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
|
|
3172
3921
|
}
|
|
@@ -3175,10 +3924,10 @@ function requiredKeys(value, required, optional) {
|
|
|
3175
3924
|
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
3176
3925
|
return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
|
|
3177
3926
|
}
|
|
3178
|
-
function
|
|
3927
|
+
function isRecord4(value) {
|
|
3179
3928
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3180
3929
|
}
|
|
3181
|
-
function
|
|
3930
|
+
function safeRef2(value) {
|
|
3182
3931
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
3183
3932
|
}
|
|
3184
3933
|
|
|
@@ -3193,34 +3942,30 @@ function validateClaudeHostInvocationContext(value) {
|
|
|
3193
3942
|
validateContextTimes(value);
|
|
3194
3943
|
}
|
|
3195
3944
|
function contextShape(value) {
|
|
3196
|
-
return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["
|
|
3945
|
+
return record3(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["working_directory", "turn_generation_ref"]);
|
|
3197
3946
|
}
|
|
3198
3947
|
function validateIdentity2(value) {
|
|
3199
3948
|
if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
|
|
3200
3949
|
}
|
|
3201
3950
|
function validateContextReferences(value) {
|
|
3202
|
-
if (!
|
|
3951
|
+
if (!safeRef3(value.prompt_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
3203
3952
|
}
|
|
3204
3953
|
function validateContextTimes(value) {
|
|
3205
|
-
if (!
|
|
3206
|
-
if (typeof value.bound_at === "string" && Date.parse(value.bound_at) > Date.parse(String(value.expires_at))) throw new ClaudeHostError("storage", `Claude Host invocation context has invalid binding time (${refusalFact("bound_at", value.bound_at, `at or before ${String(value.expires_at)}`)}).`);
|
|
3954
|
+
if (!timestamp(value.expires_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
3207
3955
|
}
|
|
3208
3956
|
function validIdentity(value) {
|
|
3209
|
-
return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(
|
|
3957
|
+
return record3(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef3);
|
|
3210
3958
|
}
|
|
3211
3959
|
function optionalSafeRef(value) {
|
|
3212
|
-
return value === void 0 ||
|
|
3213
|
-
}
|
|
3214
|
-
function optionalTimestamp(value) {
|
|
3215
|
-
return value === void 0 || timestamp2(value);
|
|
3960
|
+
return value === void 0 || safeRef3(value);
|
|
3216
3961
|
}
|
|
3217
3962
|
function optionalDirectory(value) {
|
|
3218
3963
|
return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
|
|
3219
3964
|
}
|
|
3220
|
-
function
|
|
3965
|
+
function safeRef3(value) {
|
|
3221
3966
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
3222
3967
|
}
|
|
3223
|
-
function
|
|
3968
|
+
function timestamp(value) {
|
|
3224
3969
|
return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
|
|
3225
3970
|
}
|
|
3226
3971
|
function record3(value) {
|
|
@@ -3228,7 +3973,7 @@ function record3(value) {
|
|
|
3228
3973
|
}
|
|
3229
3974
|
|
|
3230
3975
|
// host/claude/src/claude-outbox.ts
|
|
3231
|
-
var
|
|
3976
|
+
var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
3232
3977
|
var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
|
|
3233
3978
|
var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
|
|
3234
3979
|
var ClaudeOutboxStore = class {
|
|
@@ -3286,10 +4031,10 @@ var ClaudeOutboxStore = class {
|
|
|
3286
4031
|
* a missing one. Every other caller keeps the fail-closed read above.
|
|
3287
4032
|
*/
|
|
3288
4033
|
async readRawInvocationContext(hostSourceRef, hostSessionRef) {
|
|
3289
|
-
if (!
|
|
4034
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
|
|
3290
4035
|
await this.prepare();
|
|
3291
4036
|
const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
|
|
3292
|
-
return
|
|
4037
|
+
return context;
|
|
3293
4038
|
}
|
|
3294
4039
|
async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
|
|
3295
4040
|
await this.prepare();
|
|
@@ -3297,9 +4042,9 @@ var ClaudeOutboxStore = class {
|
|
|
3297
4042
|
for (const name of await readdir(this.activeContexts)) {
|
|
3298
4043
|
if (!name.endsWith(".json")) continue;
|
|
3299
4044
|
try {
|
|
3300
|
-
const value = JSON.parse(await
|
|
4045
|
+
const value = JSON.parse(await readFile(join3(this.activeContexts, name), "utf8"));
|
|
3301
4046
|
validateClaudeHostInvocationContext(value);
|
|
3302
|
-
if (Date.parse(value.expires_at) > now.getTime()
|
|
4047
|
+
if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
|
|
3303
4048
|
} catch {
|
|
3304
4049
|
}
|
|
3305
4050
|
}
|
|
@@ -3317,7 +4062,7 @@ var ClaudeOutboxStore = class {
|
|
|
3317
4062
|
for (const name of await readdir(this.envelopes)) {
|
|
3318
4063
|
if (!name.endsWith(".json")) continue;
|
|
3319
4064
|
try {
|
|
3320
|
-
const value = JSON.parse(await
|
|
4065
|
+
const value = JSON.parse(await readFile(join3(this.envelopes, name), "utf8"));
|
|
3321
4066
|
validateEnvelope(value);
|
|
3322
4067
|
entries.push(value);
|
|
3323
4068
|
} catch {
|
|
@@ -3330,7 +4075,7 @@ var ClaudeOutboxStore = class {
|
|
|
3330
4075
|
async removeEnvelope(entry) {
|
|
3331
4076
|
validateEnvelope(entry);
|
|
3332
4077
|
try {
|
|
3333
|
-
await
|
|
4078
|
+
await unlink3(this.envelopePath(entry.submission_ref));
|
|
3334
4079
|
} catch (error) {
|
|
3335
4080
|
if (code(error) !== "ENOENT") throw error;
|
|
3336
4081
|
}
|
|
@@ -3341,10 +4086,10 @@ var ClaudeOutboxStore = class {
|
|
|
3341
4086
|
await durableAtomicReplace(join3(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
|
|
3342
4087
|
}
|
|
3343
4088
|
async readSession(sourceRef, sessionRef) {
|
|
3344
|
-
if (!
|
|
4089
|
+
if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
|
|
3345
4090
|
await this.prepare();
|
|
3346
4091
|
try {
|
|
3347
|
-
const value = JSON.parse(await
|
|
4092
|
+
const value = JSON.parse(await readFile(join3(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
|
|
3348
4093
|
validateSession(value);
|
|
3349
4094
|
return value;
|
|
3350
4095
|
} catch (error) {
|
|
@@ -3379,7 +4124,7 @@ var ClaudeOutboxStore = class {
|
|
|
3379
4124
|
for (const name of await readdir(this.registrations)) {
|
|
3380
4125
|
if (!name.endsWith(".json")) continue;
|
|
3381
4126
|
try {
|
|
3382
|
-
const value = JSON.parse(await
|
|
4127
|
+
const value = JSON.parse(await readFile(join3(this.registrations, name), "utf8"));
|
|
3383
4128
|
validateRegistration(value);
|
|
3384
4129
|
registrations.push(value);
|
|
3385
4130
|
} catch {
|
|
@@ -3388,10 +4133,10 @@ var ClaudeOutboxStore = class {
|
|
|
3388
4133
|
return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
|
|
3389
4134
|
}
|
|
3390
4135
|
async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
|
|
3391
|
-
if (!
|
|
4136
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
|
|
3392
4137
|
await this.prepare();
|
|
3393
4138
|
try {
|
|
3394
|
-
const value = JSON.parse(await
|
|
4139
|
+
const value = JSON.parse(await readFile(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
|
|
3395
4140
|
if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
|
|
3396
4141
|
return value;
|
|
3397
4142
|
} catch (error) {
|
|
@@ -3403,7 +4148,7 @@ var ClaudeOutboxStore = class {
|
|
|
3403
4148
|
validateRegistration(registration);
|
|
3404
4149
|
await this.prepare();
|
|
3405
4150
|
try {
|
|
3406
|
-
const value = JSON.parse(await
|
|
4151
|
+
const value = JSON.parse(await readFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
|
|
3407
4152
|
validateCursor(value);
|
|
3408
4153
|
return value;
|
|
3409
4154
|
} catch (error) {
|
|
@@ -3430,7 +4175,7 @@ var ClaudeOutboxStore = class {
|
|
|
3430
4175
|
}
|
|
3431
4176
|
async readRegistrationFile(path) {
|
|
3432
4177
|
try {
|
|
3433
|
-
const value = JSON.parse(await
|
|
4178
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
3434
4179
|
validateRegistration(value);
|
|
3435
4180
|
return value;
|
|
3436
4181
|
} catch (error) {
|
|
@@ -3440,7 +4185,7 @@ var ClaudeOutboxStore = class {
|
|
|
3440
4185
|
}
|
|
3441
4186
|
async removeStateFile(path) {
|
|
3442
4187
|
try {
|
|
3443
|
-
await
|
|
4188
|
+
await unlink3(path);
|
|
3444
4189
|
} catch (error) {
|
|
3445
4190
|
if (code(error) !== "ENOENT") throw error;
|
|
3446
4191
|
}
|
|
@@ -3450,11 +4195,11 @@ var ClaudeOutboxStore = class {
|
|
|
3450
4195
|
}
|
|
3451
4196
|
};
|
|
3452
4197
|
function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
|
|
3453
|
-
if (!
|
|
4198
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef) || !(now instanceof Date) || !Number.isFinite(now.getTime())) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
|
|
3454
4199
|
}
|
|
3455
4200
|
async function readProjectionFile(file) {
|
|
3456
4201
|
try {
|
|
3457
|
-
const context = JSON.parse(await
|
|
4202
|
+
const context = JSON.parse(await readFile(file, "utf8"));
|
|
3458
4203
|
validateClaudeHostInvocationContext(context);
|
|
3459
4204
|
return context;
|
|
3460
4205
|
} catch (error) {
|
|
@@ -3463,10 +4208,10 @@ async function readProjectionFile(file) {
|
|
|
3463
4208
|
throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
|
|
3464
4209
|
}
|
|
3465
4210
|
}
|
|
3466
|
-
function
|
|
3467
|
-
return typeof value === "string" &&
|
|
4211
|
+
function safeRef4(value) {
|
|
4212
|
+
return typeof value === "string" && SAFE_REF3.test(value);
|
|
3468
4213
|
}
|
|
3469
|
-
function
|
|
4214
|
+
function timestamp2(value) {
|
|
3470
4215
|
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
3471
4216
|
}
|
|
3472
4217
|
function record4(value) {
|
|
@@ -3477,26 +4222,24 @@ function code(error) {
|
|
|
3477
4222
|
}
|
|
3478
4223
|
function validateEnvelope(value) {
|
|
3479
4224
|
if (!record4(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
|
|
3480
|
-
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "
|
|
4225
|
+
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"])) {
|
|
3481
4226
|
throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
|
|
3482
4227
|
}
|
|
3483
4228
|
if (value.schema_version !== CLAUDE_OUTBOX_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude outbox entry is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_OUTBOX_SCHEMA_VERSION}`)}).`);
|
|
3484
4229
|
validateEnvelopeIdentity(value);
|
|
3485
4230
|
validateEnvelopeObservations(value.observations);
|
|
3486
4231
|
if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
|
|
3487
|
-
if (!
|
|
4232
|
+
if (!timestamp2(value.created_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
|
|
3488
4233
|
}
|
|
3489
4234
|
function validateEnvelopeIdentity(value) {
|
|
3490
4235
|
validateIdentity3(value.identity);
|
|
3491
|
-
if (!
|
|
3492
|
-
if (!
|
|
3493
|
-
if (!safeRef5(value.prompt_ref) || !safeRef5(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
|
|
3494
|
-
if (value.request_ref !== void 0 && !safeRef5(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
|
|
4236
|
+
if (!safeRef4(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
|
|
4237
|
+
if (!safeRef4(value.prompt_ref) || !safeRef4(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
|
|
3495
4238
|
}
|
|
3496
4239
|
function validateEnvelopeObservations(value) {
|
|
3497
4240
|
if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
|
|
3498
4241
|
try {
|
|
3499
|
-
for (const
|
|
4242
|
+
for (const observation of value) validateCealHostObservationDraft(observation);
|
|
3500
4243
|
} catch {
|
|
3501
4244
|
throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
|
|
3502
4245
|
}
|
|
@@ -3507,7 +4250,7 @@ function validateSession(value) {
|
|
|
3507
4250
|
throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
|
|
3508
4251
|
}
|
|
3509
4252
|
if (value.schema_version !== CLAUDE_SESSION_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude session snapshot is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_SESSION_SCHEMA_VERSION}`)}).`);
|
|
3510
|
-
if (!
|
|
4253
|
+
if (!safeRef4(value.host_session_ref) || !safeRef4(value.host_source_ref) || !timestamp2(value.observed_at) || !timestamp2(value.expires_at)) {
|
|
3511
4254
|
throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
|
|
3512
4255
|
}
|
|
3513
4256
|
}
|
|
@@ -3517,14 +4260,14 @@ function validateRegistration(value) {
|
|
|
3517
4260
|
throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
|
|
3518
4261
|
}
|
|
3519
4262
|
if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
|
|
3520
|
-
if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" &&
|
|
4263
|
+
if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
|
|
3521
4264
|
throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
|
|
3522
4265
|
}
|
|
3523
4266
|
validateRegistrationPaths(value.cwd, value.transcript_path);
|
|
3524
|
-
if (!
|
|
4267
|
+
if (!timestamp2(value.registered_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
|
|
3525
4268
|
}
|
|
3526
4269
|
function validateRegistrationPaths(cwd, transcriptPath) {
|
|
3527
|
-
if (cwd !== void 0 && (typeof cwd !== "string" || !
|
|
4270
|
+
if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute2(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
|
|
3528
4271
|
throw new ClaudeHostError("storage", "Claude transcript registration working directory is malformed.");
|
|
3529
4272
|
}
|
|
3530
4273
|
if (typeof transcriptPath !== "string" || transcriptPath.length === 0 || transcriptPath.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS) {
|
|
@@ -3537,7 +4280,7 @@ function validateCursor(value) {
|
|
|
3537
4280
|
throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
|
|
3538
4281
|
}
|
|
3539
4282
|
if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
|
|
3540
|
-
if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" &&
|
|
4283
|
+
if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) throw new ClaudeHostError("storage", "Claude transcript cursor references are malformed.");
|
|
3541
4284
|
if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
|
|
3542
4285
|
validateCursorPosition(value);
|
|
3543
4286
|
validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
|
|
@@ -3549,7 +4292,7 @@ function validateCursorPosition(value) {
|
|
|
3549
4292
|
if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
|
|
3550
4293
|
if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
|
|
3551
4294
|
if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
|
|
3552
|
-
if (value.status !== "active" && value.status !== "quarantined" || !
|
|
4295
|
+
if (value.status !== "active" && value.status !== "quarantined" || !timestamp2(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
|
|
3553
4296
|
}
|
|
3554
4297
|
function validateCursorTurn(openTurn, unattributed, sessionObserved) {
|
|
3555
4298
|
if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
|
|
@@ -3580,8 +4323,8 @@ function validCursorPosition(value) {
|
|
|
3580
4323
|
}
|
|
3581
4324
|
function validOpenTurn(value) {
|
|
3582
4325
|
if (!record4(value) || !sameKeys(value, ["turn_key", "user_uuid", "prompt_ref", "host_input_ref", "started_at", "last_observed_at", "message_ids", "counts"], ["last_terminal_digest"])) return false;
|
|
3583
|
-
if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" &&
|
|
3584
|
-
if (!
|
|
4326
|
+
if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) return false;
|
|
4327
|
+
if (!timestamp2(value.started_at) || !timestamp2(value.last_observed_at)) return false;
|
|
3585
4328
|
if (!validMessageIds(value.message_ids)) return false;
|
|
3586
4329
|
if (!validTurnCounts(value.counts)) return false;
|
|
3587
4330
|
return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
|
|
@@ -3594,7 +4337,7 @@ function validMessageIds(value) {
|
|
|
3594
4337
|
function validUnattributed(value) {
|
|
3595
4338
|
if (!record4(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
|
|
3596
4339
|
if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
|
|
3597
|
-
if (!
|
|
4340
|
+
if (!timestamp2(value.last_observed_at)) return false;
|
|
3598
4341
|
return validTurnCounts(value);
|
|
3599
4342
|
}
|
|
3600
4343
|
function validTurnCounts(value) {
|
|
@@ -3602,7 +4345,7 @@ function validTurnCounts(value) {
|
|
|
3602
4345
|
return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
|
|
3603
4346
|
}
|
|
3604
4347
|
function validateIdentity3(value) {
|
|
3605
|
-
if (!record4(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" &&
|
|
4348
|
+
if (!record4(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
|
|
3606
4349
|
throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
|
|
3607
4350
|
}
|
|
3608
4351
|
}
|
|
@@ -3615,125 +4358,32 @@ function sameKeys(value, required, optional = []) {
|
|
|
3615
4358
|
|
|
3616
4359
|
// host/claude/src/claude-producer.ts
|
|
3617
4360
|
var ClaudeProducer = class {
|
|
3618
|
-
producer_id = "claude";
|
|
3619
|
-
producer_label = "Claude";
|
|
3620
|
-
outbox_directory;
|
|
3621
4361
|
store;
|
|
3622
4362
|
core;
|
|
3623
4363
|
constructor(options) {
|
|
3624
4364
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3625
|
-
this.outbox_directory = this.store.directory;
|
|
3626
4365
|
this.core = new HostProducerDrainCore(this, options);
|
|
3627
4366
|
}
|
|
3628
4367
|
drainOutbox() {
|
|
3629
4368
|
return this.core.drainOutbox();
|
|
3630
4369
|
}
|
|
3631
|
-
fail(code2, message) {
|
|
3632
|
-
return new ClaudeHostError(code2, message);
|
|
3633
|
-
}
|
|
3634
4370
|
listEnvelopes() {
|
|
3635
4371
|
return this.store.listEnvelopes();
|
|
3636
4372
|
}
|
|
3637
4373
|
removeEnvelope(envelope) {
|
|
3638
4374
|
return this.store.removeEnvelope(envelope);
|
|
3639
4375
|
}
|
|
3640
|
-
submissionRefOf(envelope) {
|
|
3641
|
-
return envelope.submission_ref;
|
|
3642
|
-
}
|
|
3643
4376
|
expiresAtOf(envelope) {
|
|
3644
4377
|
return envelope.expires_at;
|
|
3645
4378
|
}
|
|
3646
|
-
createdAtOf(envelope) {
|
|
3647
|
-
return envelope.created_at;
|
|
3648
|
-
}
|
|
3649
|
-
async mapEnvelope(envelope) {
|
|
3650
|
-
const prompt = observation(envelope, "prompt");
|
|
3651
|
-
if (prompt !== void 0) return this.mapInput(envelope, prompt);
|
|
3652
|
-
return [mapTerminal(envelope)];
|
|
3653
|
-
}
|
|
3654
|
-
async mapInput(envelope, prompt) {
|
|
3655
|
-
const drafts = [];
|
|
3656
|
-
const session = observation(envelope, "session");
|
|
3657
|
-
if (session !== void 0) {
|
|
3658
|
-
const registrations = await this.store.listTranscriptRegistrations();
|
|
3659
|
-
const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
|
|
3660
|
-
const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
|
|
3661
|
-
drafts.push({
|
|
3662
|
-
frame_kind: "session_started",
|
|
3663
|
-
payload: compact({
|
|
3664
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3665
|
-
working_directory_ref: claudeWorkspaceRef(directoryIdentity),
|
|
3666
|
-
model: recordText(session.value, "model"),
|
|
3667
|
-
observations: [session]
|
|
3668
|
-
})
|
|
3669
|
-
});
|
|
3670
|
-
}
|
|
3671
|
-
const turn = observation(envelope, "turn");
|
|
3672
|
-
if (turn !== void 0) drafts.push({
|
|
3673
|
-
frame_kind: "turn_started",
|
|
3674
|
-
payload: compact({
|
|
3675
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3676
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3677
|
-
observations: [turn]
|
|
3678
|
-
})
|
|
3679
|
-
});
|
|
3680
|
-
if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
|
|
3681
|
-
drafts.push({
|
|
3682
|
-
frame_kind: "user_input",
|
|
3683
|
-
payload: compact({
|
|
3684
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3685
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3686
|
-
local_input_ref: envelope.host_input_ref,
|
|
3687
|
-
input_kind: envelope.input_kind,
|
|
3688
|
-
origin_input: envelope.origin_input,
|
|
3689
|
-
observations: [prompt],
|
|
3690
|
-
context_expires_at: envelope.expires_at
|
|
3691
|
-
})
|
|
3692
|
-
});
|
|
3693
|
-
return drafts;
|
|
3694
|
-
}
|
|
3695
4379
|
};
|
|
3696
|
-
function mapTerminal(envelope) {
|
|
3697
|
-
const usage = envelope.observations.filter((item) => ["token", "cost"].includes(item.observation_kind));
|
|
3698
|
-
return {
|
|
3699
|
-
frame_kind: "turn_terminal",
|
|
3700
|
-
payload: compact({
|
|
3701
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3702
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3703
|
-
terminal_state: "completed",
|
|
3704
|
-
usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
|
|
3705
|
-
observations: envelope.observations
|
|
3706
|
-
})
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3709
|
-
function observation(envelope, kind) {
|
|
3710
|
-
return envelope.observations.find((item) => item.observation_kind === kind);
|
|
3711
|
-
}
|
|
3712
|
-
function localTurnRef(envelope) {
|
|
3713
|
-
return envelope.identity.host_turn_ref;
|
|
3714
|
-
}
|
|
3715
|
-
function compact(value) {
|
|
3716
|
-
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
|
|
3717
|
-
}
|
|
3718
|
-
function asRecord(value) {
|
|
3719
|
-
return isRecord3(value) ? value : void 0;
|
|
3720
|
-
}
|
|
3721
|
-
function recordText(value, key) {
|
|
3722
|
-
const record5 = asRecord(value);
|
|
3723
|
-
return typeof record5?.[key] === "string" ? record5[key] : void 0;
|
|
3724
|
-
}
|
|
3725
|
-
function isRecord3(value) {
|
|
3726
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3727
|
-
}
|
|
3728
4380
|
|
|
3729
4381
|
// host/claude/src/claude-transcript-watcher.ts
|
|
3730
|
-
import {
|
|
3731
|
-
import { open as open4, readdir as readdir2 } from "node:fs/promises";
|
|
4382
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
3732
4383
|
import { join as join4 } from "node:path";
|
|
3733
4384
|
var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
|
|
3734
4385
|
var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
3735
4386
|
store;
|
|
3736
|
-
registerCurrentPrompt;
|
|
3737
4387
|
diagnostic;
|
|
3738
4388
|
constructor(options) {
|
|
3739
4389
|
super(options, {
|
|
@@ -3742,7 +4392,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3742
4392
|
clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
|
|
3743
4393
|
});
|
|
3744
4394
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3745
|
-
this.registerCurrentPrompt = options.register_current_prompt;
|
|
3746
4395
|
this.diagnostic = options.diagnostic ?? (() => void 0);
|
|
3747
4396
|
}
|
|
3748
4397
|
transcriptPathOf(registration) {
|
|
@@ -3755,13 +4404,15 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3755
4404
|
const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
|
|
3756
4405
|
const cursor = await this.preflight(registration, handle, size, device, inode);
|
|
3757
4406
|
if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
|
|
3758
|
-
if (cursor.status === "quarantined")
|
|
4407
|
+
if (cursor.status === "quarantined") {
|
|
4408
|
+
return { records: 0, enqueued: 0, quarantined: true };
|
|
4409
|
+
}
|
|
3759
4410
|
const draft = mutableCursor(cursor);
|
|
3760
4411
|
await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
|
|
3761
4412
|
return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
|
|
3762
4413
|
}
|
|
3763
4414
|
async scanFailed(handle, registration, error) {
|
|
3764
|
-
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
4415
|
+
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
|
|
3765
4416
|
if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
|
|
3766
4417
|
if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
|
|
3767
4418
|
await this.quarantineFailure(registration, handle);
|
|
@@ -3814,42 +4465,59 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3814
4465
|
if (isUserTurnLine(record5)) await this.openTurn(registration, draft, record5);
|
|
3815
4466
|
else if (isAssistantUsageLine(record5)) this.accumulateUsage(draft, toUsage(record5.uuid, record5.timestamp, record5.message.usage));
|
|
3816
4467
|
else if (isAssistantLine(record5) && record5.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
|
|
4468
|
+
else {
|
|
4469
|
+
const queued = decodeClaudeQueuedPromptRow(record5, registration.claude_session_id);
|
|
4470
|
+
if (queued !== null) await this.openQueuedTurn(registration, draft, queued);
|
|
4471
|
+
}
|
|
3817
4472
|
}
|
|
3818
4473
|
}
|
|
3819
4474
|
async openTurn(registration, draft, record5) {
|
|
3820
|
-
this.
|
|
3821
|
-
|
|
4475
|
+
await this.openPromptTurn(registration, draft, { uuid: record5.uuid, timestamp: record5.timestamp, content: record5.message.content });
|
|
4476
|
+
}
|
|
4477
|
+
async openQueuedTurn(registration, draft, queued) {
|
|
4478
|
+
await this.openPromptTurn(registration, draft, { uuid: queued.turnId, timestamp: queued.timestamp, content: queued.content });
|
|
4479
|
+
}
|
|
4480
|
+
async openPromptTurn(registration, draft, prompt) {
|
|
4481
|
+
await this.closeTurn(registration, draft, prompt.timestamp);
|
|
3822
4482
|
const sessionRef = registration.host_session_ref;
|
|
3823
|
-
const turnRef = claudeLocalTurnRef(registration.claude_session_id,
|
|
3824
|
-
const inputRef = claudeLocalInputRef(registration.claude_session_id,
|
|
3825
|
-
const promptRef = hostProducerPromptRef(this.sha(
|
|
4483
|
+
const turnRef = claudeLocalTurnRef(registration.claude_session_id, prompt.uuid);
|
|
4484
|
+
const inputRef = claudeLocalInputRef(registration.claude_session_id, prompt.uuid);
|
|
4485
|
+
const promptRef = hostProducerPromptRef(this.sha(prompt.content));
|
|
3826
4486
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
|
|
3827
4487
|
const observations = [
|
|
3828
|
-
{ observation_ref: observationRef("transcript_user_text", inputRef,
|
|
3829
|
-
{ observation_ref: observationRef("transcript_turn_started", turnRef,
|
|
4488
|
+
{ 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) } },
|
|
4489
|
+
{ 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 } }
|
|
3830
4490
|
];
|
|
3831
4491
|
if (!draft.session_observed) {
|
|
3832
4492
|
const session = await this.store.readSession(registration.host_source_ref, sessionRef);
|
|
3833
|
-
if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at:
|
|
4493
|
+
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 });
|
|
3834
4494
|
draft.session_observed = true;
|
|
3835
4495
|
}
|
|
4496
|
+
await this.persistOpenedTurnContext(draft, { uuid: prompt.uuid, timestamp: prompt.timestamp, message: { content: prompt.content } }, inputRef, turnRef, promptRef, identity, observations);
|
|
4497
|
+
}
|
|
4498
|
+
async persistOpenedTurnContext(draft, record5, inputRef, turnRef, promptRef, identity, observations) {
|
|
4499
|
+
const expiresAt = this.expire(this.outboxRetentionMs);
|
|
3836
4500
|
await this.store.putEnvelope({
|
|
3837
4501
|
schema_version: "ceal.claude_observation_outbox.v1",
|
|
3838
4502
|
identity,
|
|
3839
4503
|
host_input_ref: inputRef,
|
|
3840
|
-
input_kind: "human",
|
|
3841
4504
|
prompt_ref: promptRef,
|
|
3842
4505
|
observations,
|
|
3843
4506
|
origin_input: true,
|
|
3844
4507
|
submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
|
|
3845
4508
|
created_at: record5.timestamp,
|
|
3846
|
-
expires_at:
|
|
4509
|
+
expires_at: expiresAt
|
|
3847
4510
|
});
|
|
3848
4511
|
draft.enqueued += 1;
|
|
3849
|
-
await this.
|
|
4512
|
+
await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
|
|
4513
|
+
this.setOpenedTurn(draft, record5, inputRef, turnRef, promptRef);
|
|
4514
|
+
}
|
|
4515
|
+
async activateOpenedTurnContext(identity, promptRef, expiresAt) {
|
|
3850
4516
|
const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
3851
4517
|
const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
|
|
3852
|
-
await this.activateContext(identity, promptRef, turnGenerationRef);
|
|
4518
|
+
await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
|
|
4519
|
+
}
|
|
4520
|
+
setOpenedTurn(draft, record5, inputRef, turnRef, promptRef) {
|
|
3853
4521
|
const folded = foldUnattributed(draft.unattributed);
|
|
3854
4522
|
draft.open_turn = {
|
|
3855
4523
|
turn_key: turnRef,
|
|
@@ -3868,9 +4536,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3868
4536
|
draft.open_turn = void 0;
|
|
3869
4537
|
if (turn === void 0) return;
|
|
3870
4538
|
const digest2 = terminalDigest(turn);
|
|
3871
|
-
if (digest2
|
|
3872
|
-
|
|
3873
|
-
|
|
4539
|
+
if (digest2 !== turn.last_terminal_digest) {
|
|
4540
|
+
await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
|
|
4541
|
+
draft.enqueued += 1;
|
|
4542
|
+
}
|
|
3874
4543
|
}
|
|
3875
4544
|
accumulateUsage(draft, usage) {
|
|
3876
4545
|
const turn = draft.open_turn;
|
|
@@ -3904,6 +4573,8 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3904
4573
|
if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
|
|
3905
4574
|
throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
|
|
3906
4575
|
}
|
|
4576
|
+
const listed = new Set(names);
|
|
4577
|
+
for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
|
|
3907
4578
|
if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
|
|
3908
4579
|
this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
|
|
3909
4580
|
names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
|
|
@@ -3918,7 +4589,7 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3918
4589
|
const path = join4(directory, name);
|
|
3919
4590
|
let handle;
|
|
3920
4591
|
try {
|
|
3921
|
-
handle = await
|
|
4592
|
+
handle = await openNoFollow(path);
|
|
3922
4593
|
const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
|
|
3923
4594
|
const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
|
|
3924
4595
|
const pinned = await this.preflightSubagent(entry, size, device, inode);
|
|
@@ -3985,12 +4656,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3985
4656
|
schema_version: "ceal.claude_observation_outbox.v1",
|
|
3986
4657
|
identity,
|
|
3987
4658
|
host_input_ref: turn.host_input_ref,
|
|
3988
|
-
input_kind: "host_automatic",
|
|
3989
4659
|
prompt_ref: turn.prompt_ref,
|
|
3990
4660
|
observations: [
|
|
3991
|
-
{ observation_ref: observationRef("transcript_terminal", turn.turn_key, digest2), observation_kind: "turn", observed_at: observedAt, value: { status: "stopped", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turn.turn_key } },
|
|
3992
|
-
{ observation_ref: observationRef("transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue }
|
|
3993
|
-
{ observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
|
|
4661
|
+
{ 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 } },
|
|
4662
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue }
|
|
3994
4663
|
],
|
|
3995
4664
|
origin_input: false,
|
|
3996
4665
|
submission_ref: submissionRef("terminal", turn.turn_key, digest2, turn.prompt_ref),
|
|
@@ -4018,27 +4687,9 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
4018
4687
|
updated_at: this.stamp()
|
|
4019
4688
|
});
|
|
4020
4689
|
}
|
|
4021
|
-
async
|
|
4022
|
-
if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
|
|
4023
|
-
try {
|
|
4024
|
-
await this.registerCurrentPrompt({
|
|
4025
|
-
producer_id: "claude",
|
|
4026
|
-
producer_installation_ref: this.producerInstallationRef,
|
|
4027
|
-
local_session_ref: sessionRef,
|
|
4028
|
-
local_turn_ref: turnRef,
|
|
4029
|
-
prompt_ref: promptRef,
|
|
4030
|
-
current_prompt: prompt,
|
|
4031
|
-
context_expires_at: this.expire(this.outboxRetentionMs)
|
|
4032
|
-
});
|
|
4033
|
-
this.diagnostic("current_prompt_registered");
|
|
4034
|
-
} catch (error) {
|
|
4035
|
-
this.diagnostic("current_prompt_registration_failed");
|
|
4036
|
-
throw error;
|
|
4037
|
-
}
|
|
4038
|
-
}
|
|
4039
|
-
async activateContext(identity, promptRef, turnGenerationRef) {
|
|
4690
|
+
async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
|
|
4040
4691
|
try {
|
|
4041
|
-
await this.store.activateInvocationContext(identity, promptRef,
|
|
4692
|
+
await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
|
|
4042
4693
|
this.diagnostic("activation_recorded");
|
|
4043
4694
|
} catch (error) {
|
|
4044
4695
|
this.diagnostic("activation_failed");
|
|
@@ -4162,18 +4813,6 @@ function isUserTurnLine(record5) {
|
|
|
4162
4813
|
if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
|
|
4163
4814
|
return true;
|
|
4164
4815
|
}
|
|
4165
|
-
function claudeTranscriptScanFailureKind(error) {
|
|
4166
|
-
if (isSymlinkTranscript(error)) return "symlink";
|
|
4167
|
-
if (error instanceof ClaudeHostError) {
|
|
4168
|
-
if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
|
|
4169
|
-
if (error.code === "payload_rejected") return "payload_rejected";
|
|
4170
|
-
if (error.code === "storage") return "read_error";
|
|
4171
|
-
}
|
|
4172
|
-
const code2 = transcriptErrorCode(error);
|
|
4173
|
-
if (code2 === "EFBIG") return "size_budget";
|
|
4174
|
-
if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
|
|
4175
|
-
return "other";
|
|
4176
|
-
}
|
|
4177
4816
|
function isAssistantLine(record5) {
|
|
4178
4817
|
return isTranscriptRecord(record5) && record5.type === "assistant" && isTranscriptRecord(record5.message);
|
|
4179
4818
|
}
|
|
@@ -4188,42 +4827,50 @@ function subagentsDirectory(transcriptPath) {
|
|
|
4188
4827
|
const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
|
|
4189
4828
|
return join4(stem, "subagents");
|
|
4190
4829
|
}
|
|
4191
|
-
function observationRef(event, identity, value) {
|
|
4192
|
-
return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
|
|
4830
|
+
function observationRef(hostSourceRef, event, identity, value) {
|
|
4831
|
+
return cealHostObservationRef("claude", transcriptObjectSha256({ host_source_ref: hostSourceRef, event, identity, value }));
|
|
4193
4832
|
}
|
|
4194
4833
|
function submissionRef(kind, turn, digest2, prompt) {
|
|
4195
4834
|
return `claude-submission:${transcriptObjectSha256({ kind, turn, digest: digest2, prompt })}`;
|
|
4196
4835
|
}
|
|
4197
4836
|
async function ownedClaudeTranscriptFile(handle) {
|
|
4198
4837
|
const stats = await handle.stat();
|
|
4199
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
4200
4838
|
if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
|
|
4201
|
-
if (stats.uid
|
|
4839
|
+
if (process.platform !== "win32" && !isSelfOwned(stats.uid, process.platform, handle)) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
|
|
4202
4840
|
return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
|
|
4203
4841
|
}
|
|
4204
4842
|
function isNotDirectory(error) {
|
|
4205
4843
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
|
|
4206
4844
|
}
|
|
4845
|
+
function claudeTranscriptScanCause(handle, error) {
|
|
4846
|
+
if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
|
|
4847
|
+
const code2 = transcriptErrorCode(error);
|
|
4848
|
+
if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
|
|
4849
|
+
return `${handle === void 0 ? "open" : "scan"}:unknown`;
|
|
4850
|
+
}
|
|
4851
|
+
function claudeTranscriptScanFailureKind(error) {
|
|
4852
|
+
return transcriptScanFailureKind(error, (candidate) => {
|
|
4853
|
+
if (!(candidate instanceof ClaudeHostError)) return void 0;
|
|
4854
|
+
if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
|
|
4855
|
+
if (candidate.code === "payload_rejected") return "payload_rejected";
|
|
4856
|
+
if (candidate.code === "storage") return "read_error";
|
|
4857
|
+
return void 0;
|
|
4858
|
+
});
|
|
4859
|
+
}
|
|
4207
4860
|
function isSymlinkTranscript(error) {
|
|
4208
4861
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
|
|
4209
4862
|
}
|
|
4210
|
-
function transcriptErrorCode(error) {
|
|
4211
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
4212
|
-
}
|
|
4213
4863
|
|
|
4214
4864
|
// host/claude/src/claude-producer-runtime.ts
|
|
4215
4865
|
async function drainClaudeProducerOnce(options) {
|
|
4216
|
-
const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
|
|
4217
4866
|
const scan = await new ClaudeTranscriptWatcher({
|
|
4218
4867
|
outbox_dir: options.outbox_dir,
|
|
4219
|
-
|
|
4220
|
-
register_current_prompt: (input) => promptPort.register(input),
|
|
4221
|
-
diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("claude", event, failure_kind)
|
|
4868
|
+
diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
|
|
4222
4869
|
}).scanOnce().catch((error) => {
|
|
4223
|
-
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
4870
|
+
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
|
|
4224
4871
|
throw error;
|
|
4225
4872
|
});
|
|
4226
|
-
const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir,
|
|
4873
|
+
const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir, ...options.now === void 0 ? {} : { now: options.now } });
|
|
4227
4874
|
return { scanned: scan.enqueued, ...await producer.drainOutbox() };
|
|
4228
4875
|
}
|
|
4229
4876
|
|
|
@@ -4236,19 +4883,25 @@ var CLAUDE_PRODUCER_DESCRIPTOR = {
|
|
|
4236
4883
|
extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
|
|
4237
4884
|
config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
|
|
4238
4885
|
runtime_options: (config) => Object.freeze({
|
|
4239
|
-
outbox_dir: config.outbox_dir
|
|
4240
|
-
producer_installation_ref: config.producer_installation_ref,
|
|
4241
|
-
producer_epoch: config.producer_epoch,
|
|
4242
|
-
relay_endpoint: config.relay_endpoint,
|
|
4243
|
-
timeout_ms: config.ipc_timeout_ms
|
|
4886
|
+
outbox_dir: config.outbox_dir
|
|
4244
4887
|
}),
|
|
4245
4888
|
drain: drainClaudeProducerOnce
|
|
4246
4889
|
};
|
|
4247
4890
|
|
|
4248
4891
|
// host/claude/src/claude-visible-output-budget.ts
|
|
4249
|
-
import { readFile as
|
|
4892
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
4250
4893
|
import { homedir } from "node:os";
|
|
4894
|
+
import { join as join6 } from "node:path";
|
|
4895
|
+
|
|
4896
|
+
// host/claude/src/claude-runtime-paths.ts
|
|
4251
4897
|
import { join as join5 } from "node:path";
|
|
4898
|
+
var CLAUDE_CONFIG_DIRECTORY_ENVIRONMENT = "CLAUDE_CONFIG_DIR";
|
|
4899
|
+
var CLAUDE_HOME_DEFAULT_DIRECTORY = ".claude";
|
|
4900
|
+
function resolveClaudeConfigHome(environment, home) {
|
|
4901
|
+
return environment[CLAUDE_CONFIG_DIRECTORY_ENVIRONMENT] || join5(home, CLAUDE_HOME_DEFAULT_DIRECTORY);
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
// host/claude/src/claude-visible-output-budget.ts
|
|
4252
4905
|
var CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS = 3e4;
|
|
4253
4906
|
var CLAUDE_MAX_INLINE_OUTPUT_MAX_CHARS = 128e3;
|
|
4254
4907
|
async function resolveClaudeVisibleOutputMaxBytes(environment = process.env, workingDirectory = process.cwd()) {
|
|
@@ -4258,17 +4911,17 @@ async function resolveClaudeVisibleOutputMaxBytes(environment = process.env, wor
|
|
|
4258
4911
|
return Math.min(bashReadback ?? CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS, CLAUDE_DEFAULT_BASH_OUTPUT_MAX_CHARS);
|
|
4259
4912
|
}
|
|
4260
4913
|
async function projectOrUserInlineLimit(environment, workingDirectory) {
|
|
4261
|
-
const configHome = environment
|
|
4262
|
-
const userLimit = await settingsInlineLimit(
|
|
4263
|
-
const projectLimit = await settingsInlineLimit(
|
|
4264
|
-
const localLimit = await settingsInlineLimit(
|
|
4914
|
+
const configHome = resolveClaudeConfigHome(environment, homedir());
|
|
4915
|
+
const userLimit = await settingsInlineLimit(join6(configHome, "settings.json"));
|
|
4916
|
+
const projectLimit = await settingsInlineLimit(join6(workingDirectory, ".claude", "settings.json"));
|
|
4917
|
+
const localLimit = await settingsInlineLimit(join6(workingDirectory, ".claude", "settings.local.json"));
|
|
4265
4918
|
const candidate = localLimit ?? projectLimit ?? userLimit;
|
|
4266
4919
|
return candidate === void 0 ? void 0 : Math.min(candidate, CLAUDE_MAX_INLINE_OUTPUT_MAX_CHARS);
|
|
4267
4920
|
}
|
|
4268
4921
|
async function settingsInlineLimit(path) {
|
|
4269
4922
|
try {
|
|
4270
|
-
const parsed = JSON.parse(await
|
|
4271
|
-
if (!
|
|
4923
|
+
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
4924
|
+
if (!isRecord5(parsed)) return void 0;
|
|
4272
4925
|
return positiveInteger(parsed.bashOutputMaxChars);
|
|
4273
4926
|
} catch {
|
|
4274
4927
|
return void 0;
|
|
@@ -4279,7 +4932,7 @@ function positiveInteger(value) {
|
|
|
4279
4932
|
const parsed = typeof normalized === "string" && normalized.length > 0 ? Number(normalized) : normalized;
|
|
4280
4933
|
return typeof parsed === "number" && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
4281
4934
|
}
|
|
4282
|
-
function
|
|
4935
|
+
function isRecord5(value) {
|
|
4283
4936
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4284
4937
|
}
|
|
4285
4938
|
|
|
@@ -4293,23 +4946,83 @@ var CLAUDE_INVOCATION_AUTHENTICATION_DESCRIPTOR = {
|
|
|
4293
4946
|
config_flag: HOST_PRODUCER_CONFIG_FLAG,
|
|
4294
4947
|
parse_config_flag: parseHostProducerConfigFlag,
|
|
4295
4948
|
read_producer_config: (configPath) => readHostProducerConfig(CLAUDE_PRODUCER_DESCRIPTOR, configPath),
|
|
4296
|
-
resolve_visible_output_max_bytes: () => resolveClaudeVisibleOutputMaxBytes()
|
|
4949
|
+
resolve_visible_output_max_bytes: () => resolveClaudeVisibleOutputMaxBytes(),
|
|
4950
|
+
read_human_message_window: readClaudeInvocationHumanMessageWindow
|
|
4297
4951
|
};
|
|
4298
4952
|
function runClaudeInvocationAuthenticationCommand(args, options) {
|
|
4299
4953
|
return runHostInvocationAuthenticationCommand(CLAUDE_INVOCATION_AUTHENTICATION_DESCRIPTOR, args, options);
|
|
4300
4954
|
}
|
|
4955
|
+
async function readClaudeInvocationHumanMessageWindow(input) {
|
|
4956
|
+
return readRegisteredHostInvocationHumanMessageWindow(() => input.store.listTranscriptRegistrations(), input.identity, (line, registration) => {
|
|
4957
|
+
let row;
|
|
4958
|
+
try {
|
|
4959
|
+
row = JSON.parse(line);
|
|
4960
|
+
} catch {
|
|
4961
|
+
return "malformed";
|
|
4962
|
+
}
|
|
4963
|
+
const message = decodeClaudeHumanMessageRow(row, registration.claude_session_id);
|
|
4964
|
+
return message === null ? null : {
|
|
4965
|
+
host_turn_ref: claudeLocalTurnRef(registration.claude_session_id, message.turnId),
|
|
4966
|
+
host_input_ref: claudeLocalInputRef(registration.claude_session_id, message.turnId),
|
|
4967
|
+
text: message.content
|
|
4968
|
+
};
|
|
4969
|
+
});
|
|
4970
|
+
}
|
|
4301
4971
|
|
|
4302
4972
|
// host/client-doctor.ts
|
|
4303
|
-
import { createHash as
|
|
4304
|
-
import { execFile } from "node:child_process";
|
|
4305
|
-
import { access, open as
|
|
4306
|
-
import { delimiter, isAbsolute as
|
|
4973
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
4974
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
4975
|
+
import { access, open as open3, readFile as readFile3, realpath, stat as stat2 } from "node:fs/promises";
|
|
4976
|
+
import { delimiter, isAbsolute as isAbsolute3, join as join7 } from "node:path";
|
|
4307
4977
|
import { promisify } from "node:util";
|
|
4308
|
-
|
|
4978
|
+
|
|
4979
|
+
// host/command-vector.ts
|
|
4980
|
+
import { win32 as win326 } from "node:path";
|
|
4981
|
+
var MAX_COMMAND_BYTES = 4096;
|
|
4982
|
+
function formatHostCommand(vector, platform) {
|
|
4983
|
+
return formatWith(vector, platform === "windows" ? quoteWindowsArgument : quotePosixArgument);
|
|
4984
|
+
}
|
|
4985
|
+
function hostScriptLauncherInvocation(launcherPath, args, platform = process.platform, environment = process.env) {
|
|
4986
|
+
if (/\.(?:cjs|js|mjs)$/iu.test(launcherPath)) return { executable: process.execPath, args: [launcherPath, ...args] };
|
|
4987
|
+
if (platform === "win32" && /\.(?:bat|cmd)$/iu.test(launcherPath)) {
|
|
4988
|
+
return {
|
|
4989
|
+
executable: environment.ComSpec ?? "cmd.exe",
|
|
4990
|
+
args: ["/d", "/s", "/c", `call ${formatHostCommand({ executable: launcherPath, args }, "windows")}`],
|
|
4991
|
+
windowsVerbatimArguments: true
|
|
4992
|
+
};
|
|
4993
|
+
}
|
|
4994
|
+
if (platform === "win32" && /\.ps1$/iu.test(launcherPath)) {
|
|
4995
|
+
return {
|
|
4996
|
+
executable: win326.join(environment.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
4997
|
+
args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-File", launcherPath, ...args]
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
return { executable: launcherPath, args };
|
|
5001
|
+
}
|
|
5002
|
+
function formatWith(vector, quote) {
|
|
5003
|
+
const command = [vector.executable, ...vector.args].map(quote).join(" ");
|
|
5004
|
+
const commandBytes = Buffer.byteLength(command, "utf8");
|
|
5005
|
+
if (commandBytes > MAX_COMMAND_BYTES) throw new TypeError(`Host command vector is too large (${refusalFact("bytes", commandBytes, `at most ${MAX_COMMAND_BYTES} UTF-8 bytes`)}).`);
|
|
5006
|
+
return command;
|
|
5007
|
+
}
|
|
5008
|
+
function quotePosixArgument(value) {
|
|
5009
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
5010
|
+
}
|
|
5011
|
+
function quoteWindowsArgument(value) {
|
|
5012
|
+
if (value.includes("%") || value.includes("!")) throw new TypeError("Host command vector contains unsafe Windows expansion syntax.");
|
|
5013
|
+
return quoteWindowsExecArgument(value);
|
|
5014
|
+
}
|
|
5015
|
+
function quoteWindowsExecArgument(value) {
|
|
5016
|
+
const escaped = value.replace(/(\\*)"/gu, '$1$1\\"').replace(/(\\+)$/gu, "$1$1");
|
|
5017
|
+
return `"${escaped}"`;
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
// host/client-doctor.ts
|
|
5021
|
+
var execFileAsync = promisify(execFile2);
|
|
4309
5022
|
var GIT_OID = /^[a-f0-9]{40}$/u;
|
|
4310
5023
|
var SHA2562 = /^[a-f0-9]{64}$/u;
|
|
4311
|
-
var
|
|
4312
|
-
var CEAL_ENVIRONMENT_OVERRIDES =
|
|
5024
|
+
var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
|
|
5025
|
+
var CEAL_ENVIRONMENT_OVERRIDES = CEAL_CLIENT_CONTEXT_OVERRIDE_ENVIRONMENTS;
|
|
4313
5026
|
async function doctorClient(options = {}, component) {
|
|
4314
5027
|
const environment = canonicalClientEnvironment(options.environment ?? process.env);
|
|
4315
5028
|
const run = options.run ?? executeCealCommand;
|
|
@@ -4346,7 +5059,7 @@ async function inspectExecutable(options, environment, component) {
|
|
|
4346
5059
|
return { ok: true, value: executable };
|
|
4347
5060
|
}
|
|
4348
5061
|
async function inspectVersion(options, executable, environment, run, component) {
|
|
4349
|
-
const probe = hostClientProbe(await safeRun(run, executable.path,
|
|
5062
|
+
const probe = hostClientProbe(await safeRun(run, executable.path, CEAL_CLIENT_VERSION_COMMAND, environment), (result) => versionIdentity(commandDocument(result)));
|
|
4350
5063
|
if (probe.kind !== "value") return probeFailure(probe, "version", component, options);
|
|
4351
5064
|
if (options.expected_source_commit !== void 0 && probe.value.source_commit !== options.expected_source_commit) return inspectedFailure("ceal_identity_mismatch", `The Ceal executable visible to the ${component} Host did not return the expected source identity (${refusalFact("source_commit", probe.value.source_commit, `expected ${options.expected_source_commit}`)}).`, `Install the exact candidate, restart ${component}, and verify \`ceal version\` from that Host.`);
|
|
4352
5065
|
return { ok: true, value: probe.value };
|
|
@@ -4363,8 +5076,8 @@ function probeCode(code2) {
|
|
|
4363
5076
|
return "ceal_output_unreadable";
|
|
4364
5077
|
}
|
|
4365
5078
|
async function inspectCanonicalContext(options, executable, environment, run, component) {
|
|
4366
|
-
const probe = hostClientProbe(await safeRun(run, executable.path,
|
|
4367
|
-
if (probe.kind === "unreachable") return probeFailure(probe, "
|
|
5079
|
+
const probe = hostClientProbe(await safeRun(run, executable.path, CEAL_CLIENT_ACCESS_GROUP_LIST_COMMAND, environment), (result) => selectedContext(commandDocument(result)));
|
|
5080
|
+
if (probe.kind === "unreachable") return probeFailure(probe, CEAL_CLIENT_ACCESS_GROUP_LIST_COMMAND.join(" "), component, options);
|
|
4368
5081
|
if (probe.kind !== "value" || options.expected_instance_ref !== void 0 && probe.value.instance_ref !== options.expected_instance_ref) return inspectedFailure("canonical_context_unavailable", `The ${component} Host cannot select the intended authenticated context from Ceal's canonical user configuration (${refusalFacts([{ name: "probe_kind", value: probe.kind, expected: "value" }, { name: "instance_ref", value: probe.kind === "value" ? probe.value.instance_ref : "unavailable", expected: options.expected_instance_ref ?? "a selected context" }])}).`, `Run \`ceal session adopt --email <email>\` without CEAL context overrides, then restart ${component}.`);
|
|
4369
5082
|
return { ok: true, value: probe.value };
|
|
4370
5083
|
}
|
|
@@ -4377,15 +5090,15 @@ function canonicalClientEnvironment(input) {
|
|
|
4377
5090
|
return environment;
|
|
4378
5091
|
}
|
|
4379
5092
|
async function resolveCealExecutable(command, environment) {
|
|
4380
|
-
const candidates =
|
|
5093
|
+
const candidates = isAbsolute3(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0).map((entry) => join7(entry, command));
|
|
4381
5094
|
for (const candidate of candidates) {
|
|
4382
5095
|
try {
|
|
4383
5096
|
await access(candidate);
|
|
4384
5097
|
const path = await realpath(candidate);
|
|
4385
|
-
const metadata = await
|
|
5098
|
+
const metadata = await stat2(path);
|
|
4386
5099
|
if (!metadata.isFile()) continue;
|
|
4387
5100
|
if (await isLauncher(path)) return resolveLauncherBinary(path, environment);
|
|
4388
|
-
return { path, sha256:
|
|
5101
|
+
return { path, sha256: createHash6("sha256").update(await readFile3(path)).digest("hex") };
|
|
4389
5102
|
} catch {
|
|
4390
5103
|
}
|
|
4391
5104
|
}
|
|
@@ -4393,7 +5106,7 @@ async function resolveCealExecutable(command, environment) {
|
|
|
4393
5106
|
}
|
|
4394
5107
|
async function isLauncher(path) {
|
|
4395
5108
|
if (/\.(?:bat|cjs|cmd|js|mjs|ps1)$/u.test(path)) return true;
|
|
4396
|
-
const handle = await
|
|
5109
|
+
const handle = await open3(path, "r");
|
|
4397
5110
|
try {
|
|
4398
5111
|
const buffer = Buffer.alloc(128);
|
|
4399
5112
|
const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
|
|
@@ -4404,25 +5117,26 @@ async function isLauncher(path) {
|
|
|
4404
5117
|
}
|
|
4405
5118
|
}
|
|
4406
5119
|
async function resolveLauncherBinary(launcherPath, environment) {
|
|
4407
|
-
const
|
|
5120
|
+
const invocation = hostScriptLauncherInvocation(launcherPath, CEAL_CLIENT_DOCTOR_COMMAND, process.platform, environment);
|
|
5121
|
+
const result = await execFileAsync(invocation.executable, [...invocation.args], { env: environment, timeout: 15e3, encoding: "utf8", windowsHide: false, ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments } });
|
|
4408
5122
|
let parsed;
|
|
4409
5123
|
try {
|
|
4410
5124
|
parsed = JSON.parse(result.stdout);
|
|
4411
5125
|
} catch {
|
|
4412
5126
|
throw new Error("launcher doctor returned unreadable output");
|
|
4413
5127
|
}
|
|
4414
|
-
if (!
|
|
5128
|
+
if (!isRecord6(parsed) || parsed.schema_version !== CEAL_CLIENT_DOCTOR_SCHEMA_VERSION || parsed.ok !== true || typeof parsed.binary_path !== "string" || parsed.binary_path.length === 0) throw new Error("launcher doctor did not report a binary path");
|
|
4415
5129
|
const binaryPath = await realpath(parsed.binary_path);
|
|
4416
|
-
const metadata = await
|
|
5130
|
+
const metadata = await stat2(binaryPath);
|
|
4417
5131
|
if (!metadata.isFile()) throw new Error("launcher doctor reported a non-file binary path");
|
|
4418
|
-
return { path: binaryPath, sha256:
|
|
5132
|
+
return { path: binaryPath, sha256: createHash6("sha256").update(await readFile3(binaryPath)).digest("hex") };
|
|
4419
5133
|
}
|
|
4420
5134
|
async function executeCealCommand(executable, args, environment) {
|
|
4421
5135
|
try {
|
|
4422
5136
|
const result = await execFileAsync(executable, [...args], { env: environment, timeout: 15e3, encoding: "utf8" });
|
|
4423
5137
|
return { exit_code: 0, stdout: result.stdout, stderr: result.stderr };
|
|
4424
5138
|
} catch (error) {
|
|
4425
|
-
if (
|
|
5139
|
+
if (isRecord6(error)) return { exit_code: typeof error.code === "number" ? error.code : 1, stdout: typeof error.stdout === "string" ? error.stdout : "", stderr: typeof error.stderr === "string" ? error.stderr : "" };
|
|
4426
5140
|
throw error;
|
|
4427
5141
|
}
|
|
4428
5142
|
}
|
|
@@ -4433,34 +5147,34 @@ function commandDocument(result) {
|
|
|
4433
5147
|
if (result === null) return null;
|
|
4434
5148
|
try {
|
|
4435
5149
|
const value = JSON.parse(result.stdout);
|
|
4436
|
-
return
|
|
5150
|
+
return isRecord6(value) ? value : null;
|
|
4437
5151
|
} catch {
|
|
4438
5152
|
return null;
|
|
4439
5153
|
}
|
|
4440
5154
|
}
|
|
4441
5155
|
function selectedContext(value) {
|
|
4442
5156
|
if (value?.schema_version !== "ceal.access-group.v1" || value.ok !== true || !Array.isArray(value.contexts)) return null;
|
|
4443
|
-
const selected = value.contexts.filter((item) =>
|
|
5157
|
+
const selected = value.contexts.filter((item) => isRecord6(item) && item.selected === true);
|
|
4444
5158
|
if (selected.length !== 1) return null;
|
|
4445
5159
|
return selectedContextRecord(selected[0]);
|
|
4446
5160
|
}
|
|
4447
5161
|
function selectedContextRecord(item) {
|
|
4448
|
-
if (!
|
|
4449
|
-
if (!
|
|
5162
|
+
if (!isRecord6(item) || typeof item.name !== "string" || typeof item.instance_ref !== "string") return null;
|
|
5163
|
+
if (!SAFE_REF4.test(item.instance_ref)) return null;
|
|
4450
5164
|
return { name: item.name, instance_ref: item.instance_ref };
|
|
4451
5165
|
}
|
|
4452
5166
|
function versionIdentity(value) {
|
|
4453
|
-
if (value?.schema_version !==
|
|
5167
|
+
if (value?.schema_version !== CEAL_CLIENT_VERSION_SCHEMA_VERSION || value.ok !== true || typeof value.version !== "string" || typeof value.source_commit !== "string" || typeof value.source_tree !== "string") return null;
|
|
4454
5168
|
if (value.version.length === 0 || !GIT_OID.test(value.source_commit) || !GIT_OID.test(value.source_tree)) return null;
|
|
4455
5169
|
return { version: value.version, source_commit: value.source_commit, source_tree: value.source_tree };
|
|
4456
5170
|
}
|
|
4457
5171
|
function validExpectations(options) {
|
|
4458
|
-
return (options.expected_sha256 === void 0 || SHA2562.test(options.expected_sha256)) && (options.expected_source_commit === void 0 || GIT_OID.test(options.expected_source_commit)) && (options.expected_instance_ref === void 0 ||
|
|
5172
|
+
return (options.expected_sha256 === void 0 || SHA2562.test(options.expected_sha256)) && (options.expected_source_commit === void 0 || GIT_OID.test(options.expected_source_commit)) && (options.expected_instance_ref === void 0 || SAFE_REF4.test(options.expected_instance_ref));
|
|
4459
5173
|
}
|
|
4460
5174
|
function failure(code2, message, next_action) {
|
|
4461
5175
|
return { ok: false, status: "failure", error: { code: code2, message, next_action } };
|
|
4462
5176
|
}
|
|
4463
|
-
function
|
|
5177
|
+
function isRecord6(value) {
|
|
4464
5178
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4465
5179
|
}
|
|
4466
5180
|
|