@adhdev/daemon-core 0.9.82-rc.14 → 0.9.82-rc.141
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/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +5 -1
- package/dist/cli-adapters/cli-script-runner.d.ts +45 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +87 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +21 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +5 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +18 -6
- package/dist/index.js +10431 -2827
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10376 -2811
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-registry.d.ts +25 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +77 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- package/dist/providers/contracts.d.ts +19 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +67 -0
- package/dist/shared-types.d.ts +12 -0
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +3 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +3 -1
- package/src/cli-adapters/cli-script-runner.ts +145 -0
- package/src/cli-adapters/cli-state-engine.ts +1083 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +630 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +51 -11
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1428 -50
- package/src/commands/cli-manager.ts +145 -3
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3271 -427
- package/src/config/chat-history.ts +79 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +249 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +45 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +40 -22
- package/src/mesh/coordinator-registry.ts +75 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +799 -56
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.ts +39 -5
- package/src/providers/chat-message-normalization.ts +7 -12
- package/src/providers/cli-provider-instance.ts +362 -41
- package/src/providers/contracts.ts +19 -0
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +31 -11
- package/src/providers/provider-schema.ts +12 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +77 -0
- package/src/shared-types.ts +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -43,6 +43,20 @@ type ReadChatPayload = {
|
|
|
43
43
|
[key: string]: unknown;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
|
47
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
48
|
+
try {
|
|
49
|
+
return await Promise.race([
|
|
50
|
+
promise,
|
|
51
|
+
new Promise<never>((_, reject) => {
|
|
52
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
55
|
+
} finally {
|
|
56
|
+
if (timer) clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
export class IdeProviderInstance implements ProviderInstance {
|
|
47
61
|
readonly type: string;
|
|
48
62
|
readonly category = 'ide' as const;
|
|
@@ -320,7 +334,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
320
334
|
if (webviewScript) {
|
|
321
335
|
const matchText = this.provider.webviewMatchText;
|
|
322
336
|
const matchFn = matchText ? (body: string) => body.includes(matchText) : undefined;
|
|
323
|
-
const webviewRaw = await cdp.evaluateInWebviewFrame(webviewScript, matchFn);
|
|
337
|
+
const webviewRaw = await withTimeout(cdp.evaluateInWebviewFrame(webviewScript, matchFn), 30000, 'evaluateInWebviewFrame');
|
|
324
338
|
if (webviewRaw) {
|
|
325
339
|
raw = typeof webviewRaw === 'string' ? (() => { try { return JSON.parse(webviewRaw); } catch { return null; } })() : webviewRaw;
|
|
326
340
|
}
|
|
@@ -331,7 +345,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
331
345
|
if (!raw) {
|
|
332
346
|
const readChatScript = this.getReadChatScript();
|
|
333
347
|
if (!readChatScript) return;
|
|
334
|
-
raw = await cdp.evaluate(readChatScript, 30000);
|
|
348
|
+
raw = await withTimeout(cdp.evaluate(readChatScript, 30000), 30000, 'evaluate.readChatScript');
|
|
335
349
|
if (typeof raw === 'string') {
|
|
336
350
|
try { raw = JSON.parse(raw); } catch { return; }
|
|
337
351
|
}
|
|
@@ -706,7 +720,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
706
720
|
);
|
|
707
721
|
|
|
708
722
|
LOG.info('IdeInstance', `[IdeInstance:${this.type}] autoApprove: executing resolveAction for "${targetButton}"`);
|
|
709
|
-
let rawResult = await cdp.evaluate(script, 10000);
|
|
723
|
+
let rawResult = await withTimeout(cdp.evaluate(script, 10000), 10000, 'evaluate.autoApprove');
|
|
710
724
|
if (typeof rawResult === 'string') {
|
|
711
725
|
try { rawResult = JSON.parse(rawResult); } catch { }
|
|
712
726
|
}
|
|
@@ -1067,13 +1067,17 @@ export class ProviderLoader {
|
|
|
1067
1067
|
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
|
|
1068
1068
|
});
|
|
1069
1069
|
|
|
1070
|
+
let reloadTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1070
1071
|
const handleChange = (filePath: string) => {
|
|
1071
1072
|
if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
|
|
1072
1073
|
return;
|
|
1073
1074
|
}
|
|
1074
1075
|
if (filePath.endsWith('.js') || filePath.endsWith('.json')) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1076
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
1077
|
+
reloadTimer = setTimeout(() => {
|
|
1078
|
+
this.log(`File changed: ${path.basename(filePath)}, reloading...`);
|
|
1079
|
+
this.reload();
|
|
1080
|
+
}, 300);
|
|
1077
1081
|
}
|
|
1078
1082
|
};
|
|
1079
1083
|
|
|
@@ -1130,7 +1134,9 @@ export class ProviderLoader {
|
|
|
1130
1134
|
return { updated: false };
|
|
1131
1135
|
}
|
|
1132
1136
|
const https = require('https') as typeof import('https');
|
|
1133
|
-
const {
|
|
1137
|
+
const { exec } = require('child_process') as typeof import('child_process');
|
|
1138
|
+
const { promisify } = require('util');
|
|
1139
|
+
const execAsync = promisify(exec);
|
|
1134
1140
|
|
|
1135
1141
|
const metaPath = path.join(this.upstreamDir, ProviderLoader.META_FILE);
|
|
1136
1142
|
let prevEtag = '';
|
|
@@ -1207,7 +1213,7 @@ export class ProviderLoader {
|
|
|
1207
1213
|
|
|
1208
1214
|
// Extract
|
|
1209
1215
|
fs.mkdirSync(tmpExtract, { recursive: true });
|
|
1210
|
-
|
|
1216
|
+
await execAsync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
|
|
1211
1217
|
|
|
1212
1218
|
// Tarball internal structure: adhdev-providers-main/ide/... → strip 1 level
|
|
1213
1219
|
const extracted = fs.readdirSync(tmpExtract);
|
|
@@ -1372,11 +1378,15 @@ export class ProviderLoader {
|
|
|
1372
1378
|
return args ? args.map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(' ') : '';
|
|
1373
1379
|
}
|
|
1374
1380
|
const schemaDef = this.getSettingsSchema(providerType)[key];
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1381
|
+
// (fix) Previously this hard-coded `autoApprove` boolean default to `true`,
|
|
1382
|
+
// overriding whatever schemaDef.default the provider.json declared. That
|
|
1383
|
+
// surfaced as soon as a provider added an `autoApprove` schema entry with
|
|
1384
|
+
// default=false: the user had never opted in but the daemon treated the
|
|
1385
|
+
// session as auto-approve, which then triggered recordAutoApproval every
|
|
1386
|
+
// time the CLI showed an approval modal — producing a flood of system
|
|
1387
|
+
// "Auto-approved: ..." messages and keeping the session pinned to
|
|
1388
|
+
// generating while modals cycled in and out. Trust the schemaDef.default.
|
|
1389
|
+
const defaultVal = schemaDef ? schemaDef.default : undefined;
|
|
1380
1390
|
|
|
1381
1391
|
const config = this.readConfig();
|
|
1382
1392
|
const userVal = config?.providerSettings?.[providerType]?.[key];
|
|
@@ -1490,10 +1500,17 @@ export class ProviderLoader {
|
|
|
1490
1500
|
...this.getSyntheticSettings(type, provider),
|
|
1491
1501
|
...(provider.settings || {}),
|
|
1492
1502
|
};
|
|
1503
|
+
// (fix) Previously this clause forced `autoApprove.default = true` for any
|
|
1504
|
+
// boolean autoApprove schema, even when the provider.json explicitly set
|
|
1505
|
+
// `default: false`. Combined with the synthetic-settings fallback at
|
|
1506
|
+
// getSyntheticSettings (which also defaults autoApprove to true when the
|
|
1507
|
+
// provider doesn't supply one), that meant CLI providers silently turned on
|
|
1508
|
+
// auto-approval, producing a flood of "Auto-approved: ..." system messages
|
|
1509
|
+
// every time an approval modal appeared and pinning the session to
|
|
1510
|
+
// generating while modals cycled. Trust the provider's declared default.
|
|
1493
1511
|
if (result.autoApprove?.type === 'boolean') {
|
|
1494
1512
|
result.autoApprove = {
|
|
1495
1513
|
...result.autoApprove,
|
|
1496
|
-
default: true,
|
|
1497
1514
|
public: true,
|
|
1498
1515
|
label: result.autoApprove.label || 'Auto Approve',
|
|
1499
1516
|
description: result.autoApprove.description || 'Automatically approve actionable prompts without sending approval alerts.',
|
|
@@ -1518,7 +1535,10 @@ export class ProviderLoader {
|
|
|
1518
1535
|
if (!provider.settings?.autoApprove) {
|
|
1519
1536
|
result.autoApprove = {
|
|
1520
1537
|
type: 'boolean',
|
|
1521
|
-
default
|
|
1538
|
+
// (fix) Safe default is *off*. Auto-approving every modal without the
|
|
1539
|
+
// user opting in produced silent-bash-execution surprises and the
|
|
1540
|
+
// "Auto-approved: ..." system-message flood seen on AGY/Codex.
|
|
1541
|
+
default: false,
|
|
1522
1542
|
public: true,
|
|
1523
1543
|
label: 'Auto Approve',
|
|
1524
1544
|
description: 'Automatically approve actionable prompts without sending approval alerts.',
|
|
@@ -64,6 +64,8 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
|
|
|
64
64
|
'sendKey',
|
|
65
65
|
'submitStrategy',
|
|
66
66
|
'requirePromptEchoBeforeSubmit',
|
|
67
|
+
'requiresFinalAssistantBeforeIdle',
|
|
68
|
+
'augmentStaleSnapshot',
|
|
67
69
|
'timeouts',
|
|
68
70
|
'disableUpstream',
|
|
69
71
|
])
|
|
@@ -251,6 +253,16 @@ function validateCanonicalHistory(raw: unknown, errors: string[]): void {
|
|
|
251
253
|
errors.push('canonicalHistory.mode must be one of: native-source, materialized-mirror, disabled')
|
|
252
254
|
}
|
|
253
255
|
|
|
256
|
+
// Chat transcript contract version (transcript-v2.ts). Absent → treated as
|
|
257
|
+
// v1 by readDeclaredChatContractVersion(). A2 will reject unrecognised
|
|
258
|
+
// values at load time; A1 only validates the field shape.
|
|
259
|
+
const chatContractVersion = canonicalHistory.contractVersion
|
|
260
|
+
if (chatContractVersion !== undefined
|
|
261
|
+
&& chatContractVersion !== '1.0'
|
|
262
|
+
&& chatContractVersion !== '2.0') {
|
|
263
|
+
errors.push(`canonicalHistory.contractVersion must be '1.0' or '2.0' when provided (got ${JSON.stringify(chatContractVersion)})`)
|
|
264
|
+
}
|
|
265
|
+
|
|
254
266
|
const scripts = canonicalHistory.scripts
|
|
255
267
|
if (scripts === undefined) return
|
|
256
268
|
if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts)) {
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { MessagePart, ModalInfo, ReadChatResult } from './contracts.js'
|
|
2
2
|
import { normalizeMessageParts } from './contracts.js'
|
|
3
3
|
import type { ChatBubbleState, ChatMessage } from '../types.js'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
CHAT_CONTRACT_VERSION_V1,
|
|
6
|
+
CHAT_CONTRACT_VERSION_V2,
|
|
7
|
+
assertReadChatResultV2Payload,
|
|
8
|
+
isSupportedChatContractVersion,
|
|
9
|
+
type ChatContractVersion,
|
|
10
|
+
type ReadChatResultV2,
|
|
11
|
+
} from './transcript-v2.js'
|
|
12
|
+
|
|
13
|
+
const VALID_STATUSES = ['idle', 'generating', 'waiting_approval', 'error', 'panel_hidden', 'starting', 'streaming', 'long_generating'] as const
|
|
6
14
|
const VALID_ROLES = ['user', 'assistant', 'system', 'human'] as const
|
|
7
15
|
const VALID_BUBBLE_STATES = ['draft', 'streaming', 'final', 'removed'] as const
|
|
8
16
|
const VALID_TURN_STATUSES = ['open', 'waiting_approval', 'complete', 'error'] as const
|
|
@@ -63,7 +71,7 @@ function validateMessage(message: unknown, source: string, index: number): ChatM
|
|
|
63
71
|
content: validateMessageContent(message.content, source, index),
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
if (typeof message.kind === 'string') normalized.kind = message.kind as
|
|
74
|
+
if (typeof message.kind === 'string') normalized.kind = message.kind as ChatMessage['kind']
|
|
67
75
|
if (typeof message.id === 'string') normalized.id = message.id
|
|
68
76
|
if (typeof message.bubbleId === 'string') normalized.bubbleId = message.bubbleId
|
|
69
77
|
if (typeof message.providerUnitKey === 'string') normalized.providerUnitKey = message.providerUnitKey
|
|
@@ -71,20 +79,23 @@ function validateMessage(message: unknown, source: string, index: number): ChatM
|
|
|
71
79
|
if (isFiniteNumber(message.index)) normalized.index = message.index
|
|
72
80
|
if (isFiniteNumber(message.timestamp)) normalized.timestamp = message.timestamp
|
|
73
81
|
if (isFiniteNumber(message.receivedAt)) normalized.receivedAt = message.receivedAt
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
82
|
+
// (A2.3) sequence is the monotonic ordering key consumed by ChatSourceMachine.
|
|
83
|
+
// v1 producers omit it; the daemon derives it in normalizeNativeHistoryMessages.
|
|
84
|
+
if (isFiniteNumber(message.sequence)) normalized.sequence = message.sequence
|
|
85
|
+
if (typeof message._turnKey === 'string') normalized._turnKey = message._turnKey
|
|
86
|
+
if (Array.isArray(message.toolCalls)) normalized.toolCalls = message.toolCalls as ChatMessage['toolCalls']
|
|
87
|
+
if (isPlainObject(message.meta)) normalized.meta = message.meta as ChatMessage['meta']
|
|
77
88
|
if (typeof message.senderName === 'string') normalized.senderName = message.senderName
|
|
78
|
-
if (typeof
|
|
79
|
-
if (typeof
|
|
80
|
-
if (typeof
|
|
81
|
-
if (typeof
|
|
82
|
-
if (typeof
|
|
83
|
-
if (typeof
|
|
84
|
-
if (typeof
|
|
85
|
-
if (typeof
|
|
86
|
-
if (typeof
|
|
87
|
-
if (typeof
|
|
89
|
+
if (typeof message._type === 'string') normalized._type = message._type
|
|
90
|
+
if (typeof message._sub === 'string') normalized._sub = message._sub
|
|
91
|
+
if (typeof message.visibility === 'string') normalized.visibility = message.visibility
|
|
92
|
+
if (typeof message.transcriptVisibility === 'string') normalized.transcriptVisibility = message.transcriptVisibility
|
|
93
|
+
if (typeof message.audience === 'string') normalized.audience = message.audience
|
|
94
|
+
if (typeof message.source === 'string') normalized.source = message.source
|
|
95
|
+
if (typeof message.userFacing === 'boolean') normalized.userFacing = message.userFacing
|
|
96
|
+
if (typeof message.internal === 'boolean') normalized.internal = message.internal
|
|
97
|
+
if (typeof message.isInternal === 'boolean') normalized.isInternal = message.isInternal
|
|
98
|
+
if (typeof message.debug === 'boolean') normalized.debug = message.debug
|
|
88
99
|
|
|
89
100
|
return normalized
|
|
90
101
|
}
|
|
@@ -129,6 +140,55 @@ function validateControlValues(controlValues: unknown, source: string): Record<s
|
|
|
129
140
|
return normalized
|
|
130
141
|
}
|
|
131
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Read the producer-declared contract version from a raw read_chat payload.
|
|
145
|
+
* Returns v1 when absent or unrecognised, so legacy producers keep working
|
|
146
|
+
* through A1. A2 will tighten this to throw when an unsupported version is
|
|
147
|
+
* declared.
|
|
148
|
+
*/
|
|
149
|
+
export function readPayloadContractVersion(raw: unknown): ChatContractVersion {
|
|
150
|
+
if (!isPlainObject(raw)) return CHAT_CONTRACT_VERSION_V1
|
|
151
|
+
const declared = (raw as Record<string, unknown>).contractVersion
|
|
152
|
+
if (isSupportedChatContractVersion(declared)) return declared
|
|
153
|
+
return CHAT_CONTRACT_VERSION_V1
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validate a v2 payload. Thin wrapper around assertReadChatResultV2Payload
|
|
158
|
+
* that prefixes the contract violation with the caller's source label.
|
|
159
|
+
*/
|
|
160
|
+
export function validateReadChatResultV2Payload(raw: unknown, source = 'read_chat'): ReadChatResultV2 {
|
|
161
|
+
try {
|
|
162
|
+
return assertReadChatResultV2Payload(raw)
|
|
163
|
+
} catch (err) {
|
|
164
|
+
if (err instanceof Error) {
|
|
165
|
+
err.message = `${source}: ${err.message}`
|
|
166
|
+
}
|
|
167
|
+
throw err
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Versioned entry point. Routes on the producer-declared contractVersion:
|
|
173
|
+
* - v2 → strict v2 validation (transcript-v2.ts invariants)
|
|
174
|
+
* - v1 (or absent) → legacy permissive validation
|
|
175
|
+
*
|
|
176
|
+
* Callers that have not yet been audited to handle v2 outputs should use
|
|
177
|
+
* validateReadChatResultPayload directly; that path stays bound to v1 shape
|
|
178
|
+
* during the A1 transition.
|
|
179
|
+
*/
|
|
180
|
+
export function validateReadChatResultPayloadVersioned(
|
|
181
|
+
raw: unknown,
|
|
182
|
+
source = 'read_chat',
|
|
183
|
+
): { version: typeof CHAT_CONTRACT_VERSION_V1; payload: ReadChatResult & Record<string, unknown> }
|
|
184
|
+
| { version: typeof CHAT_CONTRACT_VERSION_V2; payload: ReadChatResultV2 } {
|
|
185
|
+
const version = readPayloadContractVersion(raw)
|
|
186
|
+
if (version === CHAT_CONTRACT_VERSION_V2) {
|
|
187
|
+
return { version, payload: validateReadChatResultV2Payload(raw, source) }
|
|
188
|
+
}
|
|
189
|
+
return { version: CHAT_CONTRACT_VERSION_V1, payload: validateReadChatResultPayload(raw, source) }
|
|
190
|
+
}
|
|
191
|
+
|
|
132
192
|
export function validateReadChatResultPayload(raw: unknown, source = 'read_chat'): ReadChatResult & Record<string, unknown> {
|
|
133
193
|
if (!isPlainObject(raw)) {
|
|
134
194
|
throw new Error(`${source}: payload must be an object`)
|