maka-agent 0.2.0-dev.32.20260914 → 0.2.0-dev.35.20260915

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.
Files changed (33) hide show
  1. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  2. package/node_modules/@maka/eval/dist/metering-checkpoint.js +4 -4
  3. package/node_modules/@maka/eval/harbor/relay_agent.py +1 -2
  4. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +2 -18
  5. package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +1 -2
  6. package/node_modules/@maka/runtime/dist/provider-error-classification.js +29 -38
  7. package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +4 -1
  8. package/node_modules/@maka/runtime-host/dist/client/wait-for-ready.js +1 -1
  9. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +7 -6
  10. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +2 -2
  11. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +1 -0
  12. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +942 -0
  13. package/node_modules/@maka/storage/dist/agent-run-store.js +3 -923
  14. package/node_modules/@maka/storage/dist/artifact-store.js +63 -7
  15. package/node_modules/@maka/storage/dist/execution-persistence-provider.js +54 -0
  16. package/node_modules/@maka/storage/dist/execution-stores.js +181 -58
  17. package/node_modules/@maka/storage/dist/goal-authority.js +39 -8
  18. package/node_modules/@maka/storage/dist/graph-control-values.js +126 -0
  19. package/node_modules/@maka/storage/dist/interaction-store-contract.js +231 -0
  20. package/node_modules/@maka/storage/dist/interaction-store.js +62 -217
  21. package/node_modules/@maka/storage/dist/local-execution-persistence.js +80 -0
  22. package/node_modules/@maka/storage/dist/root-authority.js +4 -0
  23. package/node_modules/@maka/storage/dist/runtime-event-store-contract.js +19 -0
  24. package/node_modules/@maka/storage/dist/runtime-partial-values.js +114 -0
  25. package/node_modules/@maka/storage/dist/session-store-contract.js +61 -0
  26. package/node_modules/@maka/storage/dist/session-store-values.js +342 -0
  27. package/node_modules/@maka/storage/dist/session-store.js +7 -351
  28. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +4 -185
  29. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +9 -129
  30. package/node_modules/@maka/storage/dist/storage-writer-composition.js +2 -3
  31. package/node_modules/@maka/storage/dist/tool-commit-validation.js +111 -0
  32. package/node_modules/@maka/storage/package.json +1 -0
  33. package/package.json +1 -1
@@ -0,0 +1,942 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { isDeepStrictEqual } from 'node:util';
20
+ import { normalizeSubmittedTurnIntent, submittedTurnIntentsEqual, } from './submitted-turn-intent.js';
21
+ import { assertNoReservedWorkspaceAuthorityAppend } from './runtime-event-authority.js';
22
+ import { decodeSkillInvocationResult, } from '@maka/core/skill-invocation';
23
+ import { aggregateMessageContents, decodeMessageContent, hasMeaningfulMessageContent, isCanonicalAttachmentRef, messageContentsEqual, } from '@maka/core/events';
24
+ import { decodeAgentGraphIntentClaim } from '@maka/core/agent-graph-control';
25
+ import { MAX_ATTACHMENT_BYTES, MAX_ATTACHMENT_COUNT } from '@maka/core/attachments';
26
+ import { LATEST_CONTEXT_PROJECTION_TYPE, supersedesLatestContext, } from '@maka/core/agent-run';
27
+ import { encodeCanonicalRuntimeEvent } from '@maka/core/canonical-runtime-event';
28
+ import { isOrchestrationMode, isTurnOrchestrationSource, } from '@maka/core/orchestration';
29
+ import { validateGenericToolLedgerAppend } from '@maka/core/tool-ledger-scanner';
30
+ export const SAFE_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
31
+ export const ROOT_TURN_ADMISSION_SCHEMA_VERSION = 1;
32
+ export const ROOT_TURN_ADMISSION_MAX_SOURCE_MESSAGES = 64;
33
+ export const ROOT_TURN_ADMISSION_MAX_CONTENT_BYTES = 64 * 1024;
34
+ export const ROOT_TURN_ADMISSION_MAX_RECORD_BYTES = 1024 * 1024;
35
+ export const ROOT_TURN_ADMISSION_MAX_AGGREGATED_ATTACHMENTS = ROOT_TURN_ADMISSION_MAX_SOURCE_MESSAGES * MAX_ATTACHMENT_COUNT;
36
+ export function normalizeRootTurnStartRejection(input) {
37
+ return normalizeStoredRootTurnStartRejection({
38
+ schemaVersion: 1,
39
+ sessionId: input.sessionId,
40
+ turnId: input.turnId,
41
+ execution: input.execution,
42
+ skillInvocation: input.skillInvocation,
43
+ rejectedAt: input.rejectedAt,
44
+ }, input.sessionId, input.turnId);
45
+ }
46
+ export function normalizeStoredRootTurnStartRejection(value, sessionId, turnId) {
47
+ assertSafeId(sessionId, 'Invalid session id');
48
+ assertSafeId(turnId, 'Invalid turn id');
49
+ if (!isPlainRecord(value) ||
50
+ !hasExactKeys(value, [
51
+ 'schemaVersion',
52
+ 'sessionId',
53
+ 'turnId',
54
+ 'execution',
55
+ 'skillInvocation',
56
+ 'rejectedAt',
57
+ ]) ||
58
+ value.schemaVersion !== 1 ||
59
+ value.sessionId !== sessionId ||
60
+ value.turnId !== turnId ||
61
+ !Number.isSafeInteger(value.rejectedAt) ||
62
+ value.rejectedAt < 0) {
63
+ throw new Error(`Invalid root Turn start rejection for turn ${turnId}`);
64
+ }
65
+ const execution = normalizeRootExecutionDescriptor(value.execution);
66
+ if (execution.kind !== 'external_message') {
67
+ throw new Error('Root Turn start rejection requires external message execution');
68
+ }
69
+ const skillInvocation = decodeSkillInvocationResult(value.skillInvocation);
70
+ if (skillInvocation.loaded.length !== 0 || skillInvocation.failed.length === 0) {
71
+ throw new Error('Root Turn start rejection requires only failed Skill invocations');
72
+ }
73
+ const rejection = {
74
+ schemaVersion: 1,
75
+ sessionId,
76
+ turnId,
77
+ execution,
78
+ skillInvocation,
79
+ rejectedAt: value.rejectedAt,
80
+ };
81
+ assertRootTurnAdmissionSerializedSize(`${JSON.stringify(rejection)}\n`);
82
+ Object.freeze(rejection.execution);
83
+ return Object.freeze(rejection);
84
+ }
85
+ export function normalizeAdmitRootTurnInput(input) {
86
+ assertSafeId(input.sessionId, 'Invalid session id');
87
+ assertSafeId(input.turnId, 'Invalid turn id');
88
+ assertSafeId(input.proposedRunId, 'Invalid run id');
89
+ if (input.proposedUserMessageId !== null) {
90
+ assertSafeId(input.proposedUserMessageId, 'Invalid user message id');
91
+ }
92
+ if (input.previousRootTurnId !== null) {
93
+ assertSafeId(input.previousRootTurnId, 'Invalid previous root turn id');
94
+ if (input.previousRootTurnId === input.turnId) {
95
+ throw new Error('Root turn admission cannot reference itself');
96
+ }
97
+ }
98
+ if (!Number.isSafeInteger(input.admittedAt) || input.admittedAt < 0) {
99
+ throw new Error('Invalid root turn admission timestamp');
100
+ }
101
+ const { normalizedInput, sourceMessages } = normalizeRootTurnAdmissionPayload(input.normalizedInput, input.sourceMessages);
102
+ const turnOrchestration = normalizeTurnOrchestration(input.turnOrchestration);
103
+ const skillInvocation = input.skillInvocation === undefined
104
+ ? undefined
105
+ : decodeSkillInvocationResult(input.skillInvocation);
106
+ const authorization = normalizeRootTurnAdmissionAuthorization(input.authorization);
107
+ const execution = normalizeRootExecutionDescriptor(input.execution);
108
+ if (execution.kind === 'legacy_automation') {
109
+ throw new Error('New root admission cannot use removed Automation authority');
110
+ }
111
+ const admission = {
112
+ schemaVersion: ROOT_TURN_ADMISSION_SCHEMA_VERSION,
113
+ sessionId: input.sessionId,
114
+ turnId: input.turnId,
115
+ runId: input.proposedRunId,
116
+ userMessageId: input.proposedUserMessageId,
117
+ execution,
118
+ previousRootTurnId: input.previousRootTurnId,
119
+ normalizedInput,
120
+ ...(turnOrchestration ? { turnOrchestration } : {}),
121
+ ...(skillInvocation ? { skillInvocation } : {}),
122
+ ...(authorization ? { authorization } : {}),
123
+ sourceMessages,
124
+ admittedAt: input.admittedAt,
125
+ };
126
+ assertRootTurnAdmissionContract(admission);
127
+ assertRootTurnAdmissionRecordSize(admission);
128
+ return deepFreezeRootTurnAdmission(admission);
129
+ }
130
+ /** Whether a proposed admission satisfies the complete durable record contract and size bound. */
131
+ export function rootTurnAdmissionRecordFits(input) {
132
+ try {
133
+ normalizeAdmitRootTurnInput(input);
134
+ return true;
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
140
+ export function shouldPreserveCheckpointProjectionDuringAppend(current, candidate) {
141
+ if (!current)
142
+ return false;
143
+ const currentSourceBound = historyCompactProjectionIsSourceBound(current);
144
+ const candidateSourceBound = historyCompactProjectionIsSourceBound(candidate);
145
+ if (currentSourceBound !== candidateSourceBound)
146
+ return currentSourceBound;
147
+ const currentCoverage = historyCompactProjectionCoverage(current);
148
+ const candidateCoverage = historyCompactProjectionCoverage(candidate);
149
+ return (currentCoverage !== undefined &&
150
+ (candidateCoverage === undefined || currentCoverage > candidateCoverage));
151
+ }
152
+ export function shouldPreserveProjectionDuringRepair(current, candidate, type) {
153
+ if (!current)
154
+ return false;
155
+ if (type === LATEST_CONTEXT_PROJECTION_TYPE) {
156
+ // Same ordering rule as the append-time guard, so repair and write cannot
157
+ // disagree about which request is the latest one. An incumbent whose order
158
+ // cannot be read is NOT preserved: the reader already treats an
159
+ // undecodable row as unanswered and rebuilds from the ledger, so keeping
160
+ // it would make that rebuild unwritable and leave every later refresh
161
+ // rescanning the whole session (#2323).
162
+ const incumbent = latestContextOrder(current);
163
+ if (!incumbent)
164
+ return false;
165
+ const arriving = candidate && latestContextOrder(candidate);
166
+ if (!arriving)
167
+ return true;
168
+ return !supersedesLatestContext(arriving, incumbent);
169
+ }
170
+ if (type !== 'history_compact_checkpoint_recorded')
171
+ return true;
172
+ const currentSourceBound = historyCompactProjectionIsSourceBound(current);
173
+ const candidateSourceBound = candidate ? historyCompactProjectionIsSourceBound(candidate) : false;
174
+ if (currentSourceBound !== candidateSourceBound)
175
+ return currentSourceBound;
176
+ const currentCoverage = historyCompactProjectionCoverage(current);
177
+ const candidateCoverage = candidate && historyCompactProjectionCoverage(candidate);
178
+ return (currentCoverage !== undefined &&
179
+ (candidateCoverage === null ||
180
+ candidateCoverage === undefined ||
181
+ currentCoverage >= candidateCoverage));
182
+ }
183
+ /**
184
+ * The ordering facts a stored latest-context row carries, or `undefined` when
185
+ * the row cannot state them — a damaged snapshot, or one written by a shape
186
+ * this build does not understand.
187
+ */
188
+ export function latestContextOrder(event) {
189
+ const data = event.data;
190
+ if (!data || typeof data.completedAt !== 'number' || typeof data.attemptId !== 'string') {
191
+ return undefined;
192
+ }
193
+ return { completedAt: data.completedAt, attemptId: data.attemptId };
194
+ }
195
+ export function historyCompactProjectionIsSourceBound(event) {
196
+ const checkpoint = event.data?.checkpoint;
197
+ if (!checkpoint || typeof checkpoint !== 'object')
198
+ return false;
199
+ const source = checkpoint.source;
200
+ if (!source || typeof source !== 'object')
201
+ return false;
202
+ return source.kind === 'runtime_event_projection';
203
+ }
204
+ export function assertNoReservedToolLedgerFact(event) {
205
+ assertNoReservedWorkspaceAuthorityAppend(event);
206
+ if (event.actions?.continuationStart !== undefined) {
207
+ throw new Error('Continuation start facts require SQLite continuation authority');
208
+ }
209
+ const validation = validateGenericToolLedgerAppend(event);
210
+ if (validation.ok)
211
+ return;
212
+ if (validation.code === 'reserved_recovery_fact') {
213
+ throw new Error('Tool recovery facts require the atomic recovery bundle writer');
214
+ }
215
+ if (validation.code === 'reserved_tool_boundary_fact') {
216
+ throw new Error('Durable tool facts require the atomic tool boundary writer');
217
+ }
218
+ throw new Error(`RuntimeEvent ${event.id} violates its semantic lane`);
219
+ }
220
+ export function canonicalizeRuntimeEventForStorage(event) {
221
+ return encodeCanonicalRuntimeEvent(event).event;
222
+ }
223
+ export function isToolLedgerBearingEvent(event) {
224
+ return (event.content?.kind === 'function_call' ||
225
+ event.content?.kind === 'function_response' ||
226
+ event.actions?.toolDispatch !== undefined ||
227
+ event.actions?.toolRecovery !== undefined);
228
+ }
229
+ export function historyCompactProjectionCoverage(event) {
230
+ const checkpoint = event.data?.checkpoint;
231
+ if (!checkpoint || typeof checkpoint !== 'object')
232
+ return undefined;
233
+ const coverage = checkpoint.coverage;
234
+ if (!coverage || typeof coverage !== 'object')
235
+ return undefined;
236
+ const eventCount = coverage.eventCount;
237
+ return typeof eventCount === 'number' && Number.isSafeInteger(eventCount) && eventCount >= 0
238
+ ? eventCount
239
+ : undefined;
240
+ }
241
+ export function isProjectedAgentRunEvent(value, sessionId, type) {
242
+ if (!value || typeof value !== 'object')
243
+ return false;
244
+ const event = value;
245
+ return (event.type === type &&
246
+ event.sessionId === sessionId &&
247
+ typeof event.id === 'string' &&
248
+ typeof event.runId === 'string' &&
249
+ typeof event.turnId === 'string' &&
250
+ Number.isFinite(event.ts));
251
+ }
252
+ export function assertSafeId(value, message) {
253
+ if (!isSafeId(value))
254
+ throw new Error(message);
255
+ }
256
+ export function assertIdentitySearchLimit(limit) {
257
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 256) {
258
+ throw new RangeError('AgentRun identity search limit must be an integer between 1 and 256');
259
+ }
260
+ }
261
+ export function isSafeId(value) {
262
+ return SAFE_ID_PATTERN.test(value);
263
+ }
264
+ export function isGraphControlIdentity(value) {
265
+ return (value.length > 0 &&
266
+ value.length <= 256 &&
267
+ value.trim() === value &&
268
+ /^[A-Za-z0-9._:-]+$/.test(value));
269
+ }
270
+ export function normalizeRootTurnAdmission(value, sessionId, turnId) {
271
+ if (!isPlainRecord(value)) {
272
+ throw new Error(`Invalid root turn admission for turn ${turnId}: expected an object`);
273
+ }
274
+ const record = value;
275
+ const valid = record.schemaVersion === ROOT_TURN_ADMISSION_SCHEMA_VERSION &&
276
+ record.sessionId === sessionId &&
277
+ record.turnId === turnId &&
278
+ typeof record.runId === 'string' &&
279
+ isSafeId(record.runId) &&
280
+ (record.userMessageId === null ||
281
+ (typeof record.userMessageId === 'string' && isSafeId(record.userMessageId))) &&
282
+ (record.previousRootTurnId === null ||
283
+ (typeof record.previousRootTurnId === 'string' &&
284
+ isSafeId(record.previousRootTurnId) &&
285
+ record.previousRootTurnId !== turnId)) &&
286
+ Number.isSafeInteger(record.admittedAt) &&
287
+ record.admittedAt >= 0 &&
288
+ hasRootTurnAdmissionKeys(record);
289
+ if (!valid) {
290
+ throw new Error(`Invalid root turn admission for turn ${turnId}: malformed fields`);
291
+ }
292
+ const { normalizedInput, sourceMessages } = normalizeRootTurnAdmissionPayload(record.normalizedInput, record.sourceMessages);
293
+ const turnOrchestration = normalizeTurnOrchestration(record.turnOrchestration);
294
+ const skillInvocation = record.skillInvocation === undefined
295
+ ? undefined
296
+ : decodeSkillInvocationResult(record.skillInvocation);
297
+ const authorization = normalizeRootTurnAdmissionAuthorization(record.authorization);
298
+ const admission = {
299
+ schemaVersion: ROOT_TURN_ADMISSION_SCHEMA_VERSION,
300
+ sessionId,
301
+ turnId,
302
+ runId: record.runId,
303
+ userMessageId: record.userMessageId,
304
+ execution: normalizeRootExecutionDescriptor(record.execution),
305
+ previousRootTurnId: record.previousRootTurnId,
306
+ normalizedInput,
307
+ ...(turnOrchestration ? { turnOrchestration } : {}),
308
+ ...(skillInvocation ? { skillInvocation } : {}),
309
+ ...(authorization ? { authorization } : {}),
310
+ sourceMessages,
311
+ admittedAt: record.admittedAt,
312
+ };
313
+ assertRootTurnAdmissionContract(admission);
314
+ assertRootTurnAdmissionRecordSize(admission);
315
+ return deepFreezeRootTurnAdmission(admission);
316
+ }
317
+ export function decodeRootSourceMessageProofPointer(value, sessionId, messageId) {
318
+ if (!isPlainRecord(value) ||
319
+ !hasExactKeys(value, ['schemaVersion', 'sessionId', 'messageId', 'turnId']) ||
320
+ value.schemaVersion !== 1 ||
321
+ value.sessionId !== sessionId ||
322
+ value.messageId !== messageId ||
323
+ typeof value.turnId !== 'string' ||
324
+ !isSafeId(value.turnId)) {
325
+ throw new Error(`Invalid root source message proof: ${messageId}`);
326
+ }
327
+ return Object.freeze({ turnId: value.turnId });
328
+ }
329
+ export function orderRootTurnAdmissionChain(sessionId, admissions) {
330
+ if (admissions.length === 0)
331
+ return [];
332
+ const byTurnId = new Map(admissions.map((admission) => [admission.turnId, admission]));
333
+ if (byTurnId.size !== admissions.length) {
334
+ throw new Error(`Session ${sessionId} has duplicate root turn admissions`);
335
+ }
336
+ for (const admission of admissions) {
337
+ const predecessor = admission.previousRootTurnId;
338
+ if (predecessor !== null && !byTurnId.has(predecessor)) {
339
+ throw new Error(`Root turn admission ${admission.turnId} has missing predecessor ${predecessor}`);
340
+ }
341
+ }
342
+ const roots = admissions.filter((admission) => admission.previousRootTurnId === null);
343
+ if (roots.length !== 1) {
344
+ throw new Error(`Session ${sessionId} must have exactly one root turn admission root`);
345
+ }
346
+ const childByTurnId = new Map();
347
+ for (const admission of admissions) {
348
+ const predecessor = admission.previousRootTurnId;
349
+ if (predecessor === null)
350
+ continue;
351
+ const existing = childByTurnId.get(predecessor);
352
+ if (existing) {
353
+ throw new Error(`Root turn admission ${predecessor} branches to ${existing.turnId} and ${admission.turnId}`);
354
+ }
355
+ childByTurnId.set(predecessor, admission);
356
+ }
357
+ const ordered = [];
358
+ let current = roots[0];
359
+ while (current) {
360
+ ordered.push(current);
361
+ current = childByTurnId.get(current.turnId);
362
+ }
363
+ if (ordered.length !== admissions.length) {
364
+ throw new Error(`Session ${sessionId} root turn admissions do not form one linear chain`);
365
+ }
366
+ return ordered;
367
+ }
368
+ export function normalizeRootTurnMessageContent(value, description, maxAttachments) {
369
+ let normalized;
370
+ try {
371
+ normalized = decodeMessageContent(value);
372
+ }
373
+ catch {
374
+ if (isPlainRecord(value) && Array.isArray(value.attachments)) {
375
+ const invalidAttachmentIndex = value.attachments.findIndex((attachment) => !isCanonicalAttachmentRef(attachment));
376
+ if (invalidAttachmentIndex >= 0) {
377
+ throw new Error(`Invalid ${description} attachment at index ${invalidAttachmentIndex}`);
378
+ }
379
+ }
380
+ throw new Error(`Invalid ${description}`);
381
+ }
382
+ // Quote- or attachment-only input is meaningful (#4804): the text carrier
383
+ // alone no longer decides durability admission.
384
+ if (!hasMeaningfulMessageContent(normalized) ||
385
+ (normalized.attachments?.length ?? 0) > maxAttachments) {
386
+ throw new Error(`Invalid ${description}`);
387
+ }
388
+ for (const [index, attachment] of (normalized.attachments ?? []).entries()) {
389
+ if (!isValidRootTurnAttachment(attachment)) {
390
+ throw new Error(`Invalid ${description} attachment at index ${index}`);
391
+ }
392
+ }
393
+ if (Buffer.byteLength(JSON.stringify(normalized), 'utf8') > ROOT_TURN_ADMISSION_MAX_CONTENT_BYTES) {
394
+ throw new Error(`Invalid ${description}: content exceeds size limit`);
395
+ }
396
+ deepFreezeRootTurnMessageContent(normalized);
397
+ return normalized;
398
+ }
399
+ export function isValidRootTurnAttachment(attachment) {
400
+ return isCanonicalAttachmentRef(attachment) && attachment.bytes <= MAX_ATTACHMENT_BYTES;
401
+ }
402
+ export function normalizeRootTurnAdmissionPayload(normalizedInputValue, sourceMessagesValue) {
403
+ const sourceMessages = normalizeRootTurnSourceMessages(sourceMessagesValue);
404
+ if (normalizedInputValue === null) {
405
+ if (sourceMessages.length > 0) {
406
+ throw new Error('Root turn admission without input cannot have source messages');
407
+ }
408
+ return { normalizedInput: null, sourceMessages };
409
+ }
410
+ const normalizedInputMaxAttachments = sourceMessages.length > 1
411
+ ? ROOT_TURN_ADMISSION_MAX_AGGREGATED_ATTACHMENTS
412
+ : MAX_ATTACHMENT_COUNT;
413
+ const normalizedInput = normalizeRootTurnMessageContent(normalizedInputValue, 'root turn normalized input', normalizedInputMaxAttachments);
414
+ if (sourceMessages.length > 0) {
415
+ const expectedInput = normalizeRootTurnMessageContent(aggregateMessageContents(sourceMessages.map((source) => source.content)), 'root turn aggregated source content', normalizedInputMaxAttachments);
416
+ if (!messageContentsEqual(normalizedInput, expectedInput)) {
417
+ throw new Error('Root turn admission input content does not match source messages');
418
+ }
419
+ }
420
+ const turnStartedCount = sourceMessages.filter((source) => source.disposition === 'turn_started').length;
421
+ if (turnStartedCount > 0 && (turnStartedCount !== 1 || sourceMessages.length !== 1)) {
422
+ throw new Error('Root turn admission turn_started source must be the only source message');
423
+ }
424
+ return { normalizedInput, sourceMessages };
425
+ }
426
+ export function normalizeRootTurnSourceMessages(value) {
427
+ if (!Array.isArray(value) || value.length > ROOT_TURN_ADMISSION_MAX_SOURCE_MESSAGES) {
428
+ throw new Error('Invalid root turn source messages: expected a bounded array');
429
+ }
430
+ const messageIds = new Set();
431
+ const normalized = value.map((item, index) => {
432
+ if (!isPlainRecord(item) ||
433
+ !hasExactKeys(item, [
434
+ 'messageId',
435
+ 'content',
436
+ 'placement',
437
+ 'disposition',
438
+ ...(Object.hasOwn(item, 'submittedContentDigest') ? ['submittedContentDigest'] : []),
439
+ ...(Object.hasOwn(item, 'submittedPlacement') ? ['submittedPlacement'] : []),
440
+ ...(Object.hasOwn(item, 'submittedIntent') ? ['submittedIntent'] : []),
441
+ ...(Object.hasOwn(item, 'skillInvocation') ? ['skillInvocation'] : []),
442
+ ])) {
443
+ throw new Error(`Invalid root turn source message at index ${index}`);
444
+ }
445
+ const { messageId, content, submittedContentDigest, submittedPlacement, submittedIntent, skillInvocation, placement, disposition, } = item;
446
+ if (typeof messageId !== 'string' ||
447
+ !isSafeId(messageId) ||
448
+ (placement !== 'current_turn' && placement !== 'next_turn') ||
449
+ (disposition !== 'steering' &&
450
+ disposition !== 'followup' &&
451
+ disposition !== 'turn_started') ||
452
+ (disposition === 'steering' && placement !== 'current_turn') ||
453
+ (disposition === 'followup' && placement !== 'next_turn') ||
454
+ (submittedPlacement !== undefined &&
455
+ submittedPlacement !== 'current_turn' &&
456
+ submittedPlacement !== 'next_turn') ||
457
+ (submittedContentDigest !== undefined && !isSha256Digest(submittedContentDigest))) {
458
+ throw new Error(`Invalid root turn source message at index ${index}`);
459
+ }
460
+ if (messageIds.has(messageId)) {
461
+ throw new Error(`Duplicate root turn source message id: ${messageId}`);
462
+ }
463
+ messageIds.add(messageId);
464
+ return Object.freeze({
465
+ messageId,
466
+ content: normalizeRootTurnMessageContent(content, `root turn source message content at index ${index}`, MAX_ATTACHMENT_COUNT),
467
+ ...(submittedContentDigest !== undefined ? { submittedContentDigest } : {}),
468
+ ...(submittedPlacement !== undefined ? { submittedPlacement } : {}),
469
+ ...(submittedIntent !== undefined
470
+ ? { submittedIntent: normalizeSubmittedTurnIntent(submittedIntent) }
471
+ : {}),
472
+ ...(skillInvocation !== undefined
473
+ ? { skillInvocation: decodeSkillInvocationResult(skillInvocation) }
474
+ : {}),
475
+ placement,
476
+ disposition,
477
+ });
478
+ });
479
+ return Object.freeze(normalized);
480
+ }
481
+ export function rootTurnAdmissionPayloadsEqual(left, right) {
482
+ return (isDeepStrictEqual(left.execution, right.execution) &&
483
+ isDeepStrictEqual(left.turnOrchestration, right.turnOrchestration) &&
484
+ isDeepStrictEqual(left.skillInvocation, right.skillInvocation) &&
485
+ isDeepStrictEqual(left.authorization, right.authorization) &&
486
+ (left.normalizedInput === null || right.normalizedInput === null
487
+ ? left.normalizedInput === right.normalizedInput
488
+ : messageContentsEqual(left.normalizedInput, right.normalizedInput)) &&
489
+ left.sourceMessages.length === right.sourceMessages.length &&
490
+ left.sourceMessages.every((source, index) => {
491
+ const other = right.sourceMessages[index];
492
+ return (other !== undefined &&
493
+ source.messageId === other.messageId &&
494
+ source.placement === other.placement &&
495
+ source.disposition === other.disposition &&
496
+ source.submittedContentDigest === other.submittedContentDigest &&
497
+ (source.submittedPlacement ?? source.placement) ===
498
+ (other.submittedPlacement ?? other.placement) &&
499
+ submittedTurnIntentsEqual(source.submittedIntent, other.submittedIntent) &&
500
+ isDeepStrictEqual(source.skillInvocation, other.skillInvocation) &&
501
+ messageContentsEqual(source.content, other.content));
502
+ }));
503
+ }
504
+ export function assertRootTurnAdmissionRecordSize(admission) {
505
+ assertRootTurnAdmissionSerializedSize(`${JSON.stringify(admission)}\n`);
506
+ }
507
+ export function assertRootTurnAdmissionSerializedSize(serialized) {
508
+ if (Buffer.byteLength(serialized, 'utf8') > ROOT_TURN_ADMISSION_MAX_RECORD_BYTES) {
509
+ throw new Error('Invalid root turn admission: record exceeds size limit');
510
+ }
511
+ }
512
+ export function assertRootTurnAdmissionContract(admission) {
513
+ const execution = admission.execution;
514
+ const providerRetry = execution.kind === 'linked_child_provider_retry';
515
+ const inputlessExecution = execution.kind === 'safe_boundary_continuation' || execution.kind === 'context_compact';
516
+ const allowsQueueSources = execution.kind === 'external_message' ||
517
+ (execution.kind === 'workhub_coordination' && execution.operation !== 'action');
518
+ const sourceBatch = allowsQueueSources && admission.sourceMessages.length > 1;
519
+ const messageLessExecution = inputlessExecution || providerRetry || sourceBatch;
520
+ if (execution.kind === 'agent_graph_supervisor_wake') {
521
+ if (admission.turnOrchestration?.mode !== 'graph' ||
522
+ admission.turnOrchestration.source !== 'host_api') {
523
+ throw new Error('Invalid root turn admission contract: Agent Graph supervisor wake requires Host Graph orchestration');
524
+ }
525
+ }
526
+ else if (admission.turnOrchestration && execution.kind !== 'external_message') {
527
+ throw new Error('Invalid root turn admission contract: orchestration override is not authorized for this execution');
528
+ }
529
+ if ((admission.userMessageId === null) !== messageLessExecution) {
530
+ throw new Error('Invalid root turn admission contract: execution has an invalid UserMessage requirement');
531
+ }
532
+ if ((admission.normalizedInput === null) !== inputlessExecution) {
533
+ throw new Error('Invalid root turn admission contract: execution has an invalid input requirement');
534
+ }
535
+ if (!allowsQueueSources && admission.sourceMessages.length !== 0) {
536
+ throw new Error('Invalid root turn admission contract: host-authored execution cannot have source messages');
537
+ }
538
+ if (admission.skillInvocation && execution.kind !== 'external_message') {
539
+ throw new Error('Invalid root turn admission contract: Skill invocation requires external message execution');
540
+ }
541
+ if (admission.authorization &&
542
+ execution.kind !== 'external_message' &&
543
+ execution.kind !== 'regenerate') {
544
+ throw new Error('Invalid root turn admission contract: authorization proof requires external message or regenerate execution');
545
+ }
546
+ if (execution.kind === 'claimed_agent_graph_intent') {
547
+ if (execution.claim.targetSessionId !== admission.sessionId ||
548
+ execution.claim.targetTurnId !== admission.turnId ||
549
+ execution.claim.targetRunId !== admission.runId) {
550
+ throw new Error('Invalid root turn admission contract: agent graph claim target does not match admission identity');
551
+ }
552
+ if (admission.userMessageId === null) {
553
+ throw new Error('Invalid root turn admission contract: agent graph execution requires a UserMessage');
554
+ }
555
+ }
556
+ if ((execution.kind === 'linked_child_resume' ||
557
+ execution.kind === 'linked_child_provider_retry') &&
558
+ execution.sourceRunId === admission.runId) {
559
+ throw new Error('Invalid root turn admission contract: linked child source Run cannot be the admitted Run');
560
+ }
561
+ if (execution.kind === 'safe_boundary_continuation' &&
562
+ (execution.sourceRunId === admission.runId ||
563
+ execution.sourceTurnId === admission.turnId ||
564
+ execution.sourceInvocationId === execution.targetInvocationId ||
565
+ admission.normalizedInput !== null)) {
566
+ throw new Error('Invalid root turn admission contract: safe-boundary continuation identity is invalid');
567
+ }
568
+ if (execution.kind === 'regenerate' && execution.sourceTurnId === admission.turnId) {
569
+ throw new Error('Invalid root turn admission contract: regenerate source Turn cannot be the admitted Turn');
570
+ }
571
+ if (execution.kind === 'external_message' &&
572
+ admission.sourceMessages.some((source) => source.disposition === 'turn_started' && source.messageId !== admission.userMessageId)) {
573
+ throw new Error('Invalid root turn admission contract: turn-started source must own the UserMessage');
574
+ }
575
+ }
576
+ export function deepFreezeRootTurnAdmission(admission) {
577
+ if (admission.execution.kind === 'claimed_agent_graph_intent') {
578
+ Object.freeze(admission.execution.claim);
579
+ }
580
+ Object.freeze(admission.execution);
581
+ if (admission.turnOrchestration)
582
+ Object.freeze(admission.turnOrchestration);
583
+ if (admission.skillInvocation)
584
+ Object.freeze(admission.skillInvocation);
585
+ if (admission.authorization)
586
+ Object.freeze(admission.authorization);
587
+ if (admission.normalizedInput)
588
+ deepFreezeRootTurnMessageContent(admission.normalizedInput);
589
+ for (const sourceMessage of admission.sourceMessages) {
590
+ deepFreezeRootTurnMessageContent(sourceMessage.content);
591
+ Object.freeze(sourceMessage);
592
+ }
593
+ Object.freeze(admission.sourceMessages);
594
+ return Object.freeze(admission);
595
+ }
596
+ export function normalizeTurnOrchestration(value) {
597
+ if (value === undefined)
598
+ return undefined;
599
+ if (!isPlainRecord(value) ||
600
+ !hasExactKeys(value, ['mode', 'source']) ||
601
+ !isOrchestrationMode(value.mode) ||
602
+ !isTurnOrchestrationSource(value.source)) {
603
+ throw new Error('Invalid root turn orchestration');
604
+ }
605
+ return Object.freeze({ mode: value.mode, source: value.source });
606
+ }
607
+ export function normalizeRootTurnAdmissionAuthorization(value) {
608
+ if (value === undefined)
609
+ return undefined;
610
+ if (!isPlainRecord(value) ||
611
+ !hasExactKeys(value, [
612
+ 'kind',
613
+ 'requestId',
614
+ 'principalId',
615
+ 'grantId',
616
+ 'approvedAt',
617
+ 'approvedBy',
618
+ ]) ||
619
+ value.kind !== 'session_turn_access_request' ||
620
+ typeof value.requestId !== 'string' ||
621
+ !isSafeId(value.requestId) ||
622
+ typeof value.principalId !== 'string' ||
623
+ !isGraphControlIdentity(value.principalId) ||
624
+ typeof value.grantId !== 'string' ||
625
+ !isSafeId(value.grantId) ||
626
+ !Number.isSafeInteger(value.approvedAt) ||
627
+ value.approvedAt < 0 ||
628
+ typeof value.approvedBy !== 'string' ||
629
+ !isGraphControlIdentity(value.approvedBy)) {
630
+ throw new Error('Invalid root turn admission authorization');
631
+ }
632
+ return Object.freeze({
633
+ kind: value.kind,
634
+ requestId: value.requestId,
635
+ principalId: value.principalId,
636
+ grantId: value.grantId,
637
+ approvedAt: value.approvedAt,
638
+ approvedBy: value.approvedBy,
639
+ });
640
+ }
641
+ export function hasRootTurnAdmissionKeys(record) {
642
+ const keys = [
643
+ 'schemaVersion',
644
+ 'sessionId',
645
+ 'turnId',
646
+ 'runId',
647
+ 'userMessageId',
648
+ 'execution',
649
+ 'previousRootTurnId',
650
+ 'normalizedInput',
651
+ 'sourceMessages',
652
+ 'admittedAt',
653
+ ];
654
+ const optionalKeys = ['turnOrchestration', 'skillInvocation', 'authorization'].filter((key) => Object.hasOwn(record, key));
655
+ return hasExactKeys(record, [...keys, ...optionalKeys]);
656
+ }
657
+ export function normalizeRootExecutionDescriptor(value) {
658
+ if (!isPlainRecord(value) || typeof value.kind !== 'string') {
659
+ throw new Error('Invalid root execution descriptor');
660
+ }
661
+ if (value.kind === 'external_message') {
662
+ const allowedKeys = ['kind', 'inputDigest', 'maxSteps'];
663
+ if (!Object.keys(value).every((key) => allowedKeys.includes(key))) {
664
+ throw new Error('Invalid root execution descriptor');
665
+ }
666
+ if (value.inputDigest !== undefined && !isSha256Digest(value.inputDigest)) {
667
+ throw new Error('Invalid root execution descriptor');
668
+ }
669
+ if (value.maxSteps !== undefined &&
670
+ (typeof value.maxSteps !== 'number' ||
671
+ !Number.isSafeInteger(value.maxSteps) ||
672
+ value.maxSteps <= 0)) {
673
+ throw new Error('Invalid root execution descriptor');
674
+ }
675
+ return Object.freeze({
676
+ kind: 'external_message',
677
+ ...(value.inputDigest !== undefined ? { inputDigest: value.inputDigest } : {}),
678
+ ...(value.maxSteps !== undefined ? { maxSteps: value.maxSteps } : {}),
679
+ });
680
+ }
681
+ if (value.kind === 'workhub_coordination') {
682
+ const routingDecision = normalizeWorkHubRoutingDecision(value.routingDecision);
683
+ if (!hasExactKeys(value, [
684
+ 'kind',
685
+ 'inputDigest',
686
+ ...(value.capabilityBinding === undefined ? [] : ['capabilityBinding']),
687
+ ...(value.routingDecision === undefined ? [] : ['routingDecision']),
688
+ ...(value.operation === undefined ? [] : ['operation']),
689
+ ...(value.actionId === undefined ? [] : ['actionId']),
690
+ ]) ||
691
+ (value.capabilityBinding !== undefined && !isSha256Digest(value.capabilityBinding)) ||
692
+ (value.actionId !== undefined && value.operation !== 'action') ||
693
+ (value.operation !== undefined && routingDecision !== undefined) ||
694
+ (value.operation !== undefined && value.operation !== 'action') ||
695
+ (value.actionId !== undefined &&
696
+ (typeof value.actionId !== 'string' || !isSafeId(value.actionId))) ||
697
+ !isSha256Digest(value.inputDigest)) {
698
+ throw new Error('Invalid root execution descriptor');
699
+ }
700
+ return Object.freeze({
701
+ kind: 'workhub_coordination',
702
+ ...(value.operation === 'action' ? { operation: 'action' } : {}),
703
+ inputDigest: value.inputDigest,
704
+ ...(isSha256Digest(value.capabilityBinding)
705
+ ? { capabilityBinding: value.capabilityBinding }
706
+ : {}),
707
+ ...(typeof value.actionId === 'string' ? { actionId: value.actionId } : {}),
708
+ ...(routingDecision ? { routingDecision } : {}),
709
+ });
710
+ }
711
+ if (value.kind === 'regenerate') {
712
+ if (!hasExactKeys(value, ['kind', 'sourceTurnId']) ||
713
+ typeof value.sourceTurnId !== 'string' ||
714
+ !isSafeId(value.sourceTurnId)) {
715
+ throw new Error('Invalid root execution descriptor');
716
+ }
717
+ return Object.freeze({ kind: 'regenerate', sourceTurnId: value.sourceTurnId });
718
+ }
719
+ if (value.kind === 'context_compact') {
720
+ if (!hasExactKeys(value, ['kind']))
721
+ throw new Error('Invalid root execution descriptor');
722
+ return Object.freeze({ kind: 'context_compact' });
723
+ }
724
+ if (value.kind === 'scheduled_task') {
725
+ if (!hasExactKeys(value, [
726
+ 'kind',
727
+ 'scheduledTaskId',
728
+ ...(Object.hasOwn(value, 'executionFingerprint') ? ['executionFingerprint'] : []),
729
+ ]) ||
730
+ typeof value.scheduledTaskId !== 'string' ||
731
+ !isSafeId(value.scheduledTaskId) ||
732
+ (value.executionFingerprint !== undefined && !isSha256Digest(value.executionFingerprint))) {
733
+ throw new Error('Invalid root execution descriptor');
734
+ }
735
+ return Object.freeze({
736
+ kind: 'scheduled_task',
737
+ scheduledTaskId: value.scheduledTaskId,
738
+ ...(value.executionFingerprint !== undefined
739
+ ? { executionFingerprint: value.executionFingerprint }
740
+ : {}),
741
+ });
742
+ }
743
+ if (value.kind === 'automation' || value.kind === 'legacy_automation') {
744
+ if (!hasExactKeys(value, ['kind', 'automationId']) ||
745
+ typeof value.automationId !== 'string' ||
746
+ !isSafeId(value.automationId)) {
747
+ throw new Error('Invalid root execution descriptor');
748
+ }
749
+ return Object.freeze({ kind: 'legacy_automation', automationId: value.automationId });
750
+ }
751
+ if (value.kind === 'goal') {
752
+ if (!hasExactKeys(value, ['kind', 'goalId']) ||
753
+ typeof value.goalId !== 'string' ||
754
+ !isSafeId(value.goalId)) {
755
+ throw new Error('Invalid root execution descriptor');
756
+ }
757
+ return Object.freeze({ kind: 'goal', goalId: value.goalId });
758
+ }
759
+ if (value.kind === 'agent_graph_supervisor_wake') {
760
+ if (!hasExactKeys(value, ['kind', 'graphId', 'wakeId', 'attemptId']) ||
761
+ typeof value.graphId !== 'string' ||
762
+ !isGraphControlIdentity(value.graphId) ||
763
+ typeof value.wakeId !== 'string' ||
764
+ !isGraphControlIdentity(value.wakeId) ||
765
+ !value.wakeId.startsWith(`${value.graphId}:`) ||
766
+ typeof value.attemptId !== 'string' ||
767
+ !isGraphControlIdentity(value.attemptId)) {
768
+ throw new Error('Invalid root execution descriptor');
769
+ }
770
+ return Object.freeze({
771
+ kind: value.kind,
772
+ graphId: value.graphId,
773
+ wakeId: value.wakeId,
774
+ attemptId: value.attemptId,
775
+ });
776
+ }
777
+ if (value.kind === 'safe_boundary_continuation') {
778
+ const keys = [
779
+ 'kind',
780
+ 'sourceInvocationId',
781
+ 'sourceRunId',
782
+ 'sourceTurnId',
783
+ 'sourceRuntimeEventHighWater',
784
+ 'claimId',
785
+ 'boundaryDigest',
786
+ 'providerReplayDigest',
787
+ 'safetyDigest',
788
+ 'targetInvocationId',
789
+ ];
790
+ if (!hasExactKeys(value, keys) ||
791
+ typeof value.sourceInvocationId !== 'string' ||
792
+ !isSafeId(value.sourceInvocationId) ||
793
+ typeof value.sourceRunId !== 'string' ||
794
+ !isSafeId(value.sourceRunId) ||
795
+ typeof value.sourceTurnId !== 'string' ||
796
+ !isSafeId(value.sourceTurnId) ||
797
+ !Number.isSafeInteger(value.sourceRuntimeEventHighWater) ||
798
+ value.sourceRuntimeEventHighWater < 1 ||
799
+ typeof value.claimId !== 'string' ||
800
+ !isSafeId(value.claimId) ||
801
+ !isSha256Digest(value.boundaryDigest) ||
802
+ !isSha256Digest(value.providerReplayDigest) ||
803
+ !isSha256Digest(value.safetyDigest) ||
804
+ typeof value.targetInvocationId !== 'string' ||
805
+ !isSafeId(value.targetInvocationId)) {
806
+ throw new Error('Invalid root execution descriptor');
807
+ }
808
+ return Object.freeze({
809
+ kind: value.kind,
810
+ sourceInvocationId: value.sourceInvocationId,
811
+ sourceRunId: value.sourceRunId,
812
+ sourceTurnId: value.sourceTurnId,
813
+ sourceRuntimeEventHighWater: value.sourceRuntimeEventHighWater,
814
+ claimId: value.claimId,
815
+ boundaryDigest: value.boundaryDigest,
816
+ providerReplayDigest: value.providerReplayDigest,
817
+ safetyDigest: value.safetyDigest,
818
+ targetInvocationId: value.targetInvocationId,
819
+ });
820
+ }
821
+ if (value.kind === 'claimed_agent_graph_intent') {
822
+ if (!hasExactKeys(value, ['kind', 'claim', 'agentId', 'agentName']) ||
823
+ typeof value.agentId !== 'string' ||
824
+ !isSafeId(value.agentId) ||
825
+ typeof value.agentName !== 'string' ||
826
+ value.agentName.length === 0 ||
827
+ Buffer.byteLength(value.agentName, 'utf8') > 256) {
828
+ throw new Error('Invalid root execution descriptor');
829
+ }
830
+ let claim;
831
+ try {
832
+ claim = decodeAgentGraphIntentClaim(value.claim);
833
+ }
834
+ catch {
835
+ throw new Error('Invalid root execution descriptor');
836
+ }
837
+ Object.freeze(claim);
838
+ return Object.freeze({
839
+ kind: value.kind,
840
+ claim,
841
+ agentId: value.agentId,
842
+ agentName: value.agentName,
843
+ });
844
+ }
845
+ if (value.kind !== 'linked_child_initial' &&
846
+ value.kind !== 'linked_child_resume' &&
847
+ value.kind !== 'linked_child_provider_retry') {
848
+ throw new Error('Invalid root execution descriptor');
849
+ }
850
+ const hasSource = value.kind !== 'linked_child_initial';
851
+ if (!hasExactKeys(value, hasSource
852
+ ? ['kind', 'agentId', 'agentName', 'sourceRunId']
853
+ : ['kind', 'agentId', 'agentName']) ||
854
+ typeof value.agentId !== 'string' ||
855
+ !isSafeId(value.agentId) ||
856
+ typeof value.agentName !== 'string' ||
857
+ value.agentName.length === 0 ||
858
+ Buffer.byteLength(value.agentName, 'utf8') > 256 ||
859
+ (hasSource && (typeof value.sourceRunId !== 'string' || !isSafeId(value.sourceRunId)))) {
860
+ throw new Error('Invalid root execution descriptor');
861
+ }
862
+ if (value.kind === 'linked_child_initial') {
863
+ return Object.freeze({
864
+ kind: value.kind,
865
+ agentId: value.agentId,
866
+ agentName: value.agentName,
867
+ });
868
+ }
869
+ return Object.freeze({
870
+ kind: value.kind,
871
+ agentId: value.agentId,
872
+ agentName: value.agentName,
873
+ sourceRunId: value.sourceRunId,
874
+ });
875
+ }
876
+ function normalizeWorkHubRoutingDecision(value) {
877
+ if (value === undefined)
878
+ return undefined;
879
+ if (!isPlainRecord(value) || typeof value.kind !== 'string') {
880
+ throw new Error('Invalid WorkHub routing decision');
881
+ }
882
+ if (value.kind === 'routing' &&
883
+ (value.disposition === 'answer_here' ||
884
+ value.disposition === 'create_new' ||
885
+ value.disposition === 'clarify') &&
886
+ hasExactKeys(value, ['kind', 'disposition'])) {
887
+ return Object.freeze({ kind: 'routing', disposition: value.disposition });
888
+ }
889
+ if (value.kind === 'routing' &&
890
+ value.disposition === 'delegate_existing' &&
891
+ typeof value.candidateSetId === 'string' &&
892
+ /^sha256:[a-f0-9]{64}$/.test(value.candidateSetId) &&
893
+ typeof value.candidateRef === 'string' &&
894
+ isSafeId(value.candidateRef) &&
895
+ hasExactKeys(value, ['kind', 'disposition', 'candidateSetId', 'candidateRef'])) {
896
+ return Object.freeze({
897
+ kind: 'routing',
898
+ disposition: 'delegate_existing',
899
+ candidateSetId: value.candidateSetId,
900
+ candidateRef: value.candidateRef,
901
+ });
902
+ }
903
+ if (value.kind === 'linked' &&
904
+ (value.operation === 'correct' || value.operation === 'stop' || value.operation === 'resume') &&
905
+ hasExactKeys(value, ['kind', 'operation'])) {
906
+ return Object.freeze({ kind: 'linked', operation: value.operation });
907
+ }
908
+ throw new Error('Invalid WorkHub routing decision');
909
+ }
910
+ export function deepFreezeRootTurnMessageContent(content) {
911
+ for (const attachment of content.attachments ?? []) {
912
+ Object.freeze(attachment.ref);
913
+ Object.freeze(attachment);
914
+ }
915
+ if (content.attachments)
916
+ Object.freeze(content.attachments);
917
+ for (const reference of content.directoryReferences ?? [])
918
+ Object.freeze(reference);
919
+ if (content.directoryReferences)
920
+ Object.freeze(content.directoryReferences);
921
+ for (const quote of content.quotes ?? [])
922
+ Object.freeze(quote);
923
+ if (content.quotes)
924
+ Object.freeze(content.quotes);
925
+ Object.freeze(content);
926
+ }
927
+ export function isPlainRecord(value) {
928
+ if (!value || typeof value !== 'object' || Array.isArray(value))
929
+ return false;
930
+ const prototype = Object.getPrototypeOf(value);
931
+ return prototype === Object.prototype || prototype === null;
932
+ }
933
+ export function isSha256Digest(value) {
934
+ return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);
935
+ }
936
+ export function hasExactKeys(record, expected) {
937
+ const keys = Object.keys(record);
938
+ return keys.length === expected.length && expected.every((key) => Object.hasOwn(record, key));
939
+ }
940
+ export function sanitizeJson(_key, value) {
941
+ return value === undefined ? undefined : value;
942
+ }