melchizedek-agents 1.0.2 → 1.1.0
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/.env.example +19 -2
- package/CHANGELOG.md +333 -0
- package/DOCUMENTATION.md +99 -39
- package/README.md +1 -0
- package/config/agents/README.md +2 -2
- package/config/agents/examples/ares.yaml +6 -10
- package/config/agents/examples/assistant.yaml +6 -6
- package/config/agents/examples/augustin.yaml +9 -15
- package/config/agents/examples/cartographers.yaml +6 -6
- package/config/agents/examples/council.yaml +6 -6
- package/config/agents/examples/critic.yaml +16 -16
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +33 -32
- package/config/agents/examples/librarian.yaml +4 -4
- package/config/agents/examples/patient_advocate.yaml +6 -10
- package/config/agents/examples/pipeline.yaml +20 -19
- package/config/agents/examples/research.yaml +36 -43
- package/config/agents/examples/scribe.yaml +31 -32
- package/config/agents/examples/scriptorium.yaml +6 -6
- package/config/agents/examples/structured_critic.yaml +82 -0
- package/config/agents/examples/style_council.yaml +9 -11
- package/config/agents/examples/syndicate.yaml +3 -5
- package/config/agents/examples/tutor.yaml +2 -2
- package/config/agents/examples/weather.yaml +2 -2
- package/config/agents/syndicate.schema.json +1979 -0
- package/config/agents/syndicateSchema.yaml +109 -72
- package/config/agents/templates/README.md +1 -1
- package/config/agents/templates/account_memory.yaml +4 -4
- package/config/agents/templates/case_desk.yaml +6 -6
- package/config/agents/templates/conversational.yaml +2 -2
- package/config/agents/templates/draft_review.yaml +6 -6
- package/config/agents/templates/intake_extractor.yaml +61 -60
- package/config/agents/templates/research_brief.yaml +6 -6
- package/config/agents/templates/research_desk.yaml +6 -6
- package/config/agents/templates/review_panel.yaml +8 -8
- package/config/agents/templates/support_triage.yaml +30 -29
- package/config/agents/templates/systems_operator.yaml +21 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +15 -0
- package/dist/lib/a2a/app.js +53 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +34 -4
- package/dist/lib/a2a/oauthSetup.d.ts +90 -0
- package/dist/lib/a2a/oauthSetup.js +205 -0
- package/dist/lib/agentDialect.d.ts +66 -0
- package/dist/lib/agentDialect.js +134 -0
- package/dist/lib/compile.d.ts +10 -5
- package/dist/lib/compile.js +69 -37
- package/dist/lib/compileNative.js +2 -0
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +34 -1
- package/dist/lib/doctor.js +47 -3
- package/dist/lib/loadSyndicate.d.ts +45 -2
- package/dist/lib/loadSyndicate.js +22 -2
- package/dist/lib/memory/namespace.d.ts +6 -5
- package/dist/lib/memory/namespace.js +6 -5
- package/dist/lib/memory/providers.js +3 -4
- package/dist/lib/models/capabilities.d.ts +16 -1
- package/dist/lib/models/capabilities.js +55 -1
- package/dist/lib/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/runtime/approvals.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +18 -1
- package/dist/lib/runtime/native/agentLoop.js +105 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +164 -0
- package/dist/lib/runtime/native/checkpoint.js +365 -0
- package/dist/lib/runtime/native/delegate.d.ts +141 -18
- package/dist/lib/runtime/native/delegate.js +235 -39
- package/dist/lib/runtime/native/interrupts.d.ts +101 -2
- package/dist/lib/runtime/native/interrupts.js +201 -1
- package/dist/lib/runtime/native/request.d.ts +13 -3
- package/dist/lib/runtime/native/request.js +28 -4
- package/dist/lib/runtime/native/telemetry.js +10 -15
- package/dist/lib/runtime/questions.d.ts +7 -4
- package/dist/lib/runtime/sessions.d.ts +1 -1
- package/dist/lib/runtime/syndicateTurn.d.ts +7 -1
- package/dist/lib/runtime/syndicateTurn.js +74 -22
- package/dist/lib/runtime/turnControl.d.ts +15 -0
- package/dist/lib/runtime/turnControl.js +10 -0
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +7 -2
- package/dist/lib/storage/postgres/taskQueue.d.ts +14 -2
- package/dist/lib/storage/postgres/taskQueue.js +20 -3
- package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
- package/dist/lib/storage/postgres/taskStore.js +74 -8
- package/dist/lib/syndicateSchema.d.ts +278 -0
- package/dist/lib/syndicateSchema.js +182 -15
- package/dist/lib/tools/auth.d.ts +7 -5
- package/dist/lib/tools/auth.js +12 -4
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthHosts.d.ts +71 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +233 -0
- package/dist/lib/tools/oauthTools.js +421 -0
- package/dist/lib/tools/openapiTools.d.ts +4 -6
- package/dist/lib/tools/openapiTools.js +40 -36
- package/dist/lib/tools/taskTools.d.ts +30 -5
- package/dist/lib/tools/taskTools.js +74 -10
- package/dist/lib/workflow.d.ts +8 -4
- package/dist/lib/workflow.js +8 -4
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +122 -1
- package/dist/scripts/assistant_worker.d.ts +10 -0
- package/dist/scripts/assistant_worker.js +69 -19
- package/dist/scripts/doctor.d.ts +4 -1
- package/dist/scripts/doctor.js +5 -2
- package/dist/scripts/yaml_codemod.d.ts +52 -0
- package/dist/scripts/yaml_codemod.js +570 -0
- package/package.json +11 -7
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +4 -3
- package/skills/briefs/melchizedek.md +1 -0
- package/skills/melchizedek/SKILL.md +1 -0
- package/skills/melchizedek-author/SKILL.md +7 -4
- package/skills/melchizedek-author/assets/minimal.yaml +13 -13
- package/skills/melchizedek-models/SKILL.md +6 -3
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/yaml_codemod.ts — the `melchizedek-codemod` bin and `npm run yaml:codemod`.
|
|
4
|
+
*
|
|
5
|
+
* Rewrites an agent's v1 spellings into the v2 keys (ADR 0115):
|
|
6
|
+
*
|
|
7
|
+
* npm run yaml:codemod -- config/agents/my_syndicate.yaml # rewrite in place
|
|
8
|
+
* npm run yaml:codemod -- config/agents # every *.yaml below it
|
|
9
|
+
* npm run yaml:codemod -- --check config/agents # exit 1 if any file would change
|
|
10
|
+
*
|
|
11
|
+
* Per agent (the orchestrator and every inline subagent):
|
|
12
|
+
*
|
|
13
|
+
* outputSchema → output.schema
|
|
14
|
+
* generateContentConfig.responseMimeType → output.mime (application/json | text/plain)
|
|
15
|
+
* generateContentConfig.temperature → sampling.temperature
|
|
16
|
+
* generateContentConfig.topP → sampling.top_p
|
|
17
|
+
* generateContentConfig.maxOutputTokens → sampling.max_output_tokens
|
|
18
|
+
* generateContentConfig.stopSequences → sampling.stop
|
|
19
|
+
* generateContentConfig.thinkingConfig / reasoningEffort → reasoning:, ONLY
|
|
20
|
+
* when the engine reads the same ReasoningSetting before and after for
|
|
21
|
+
* the agent's model (reasoningOf over the old config equals reasoningOf
|
|
22
|
+
* over what reasoningConfig makes of the new key).
|
|
23
|
+
* generateContentConfig.thinkingConfig.includeThoughts: false → dropped (the default; read by nothing)
|
|
24
|
+
*
|
|
25
|
+
* Anything else stays under generateContentConfig with a note naming its key
|
|
26
|
+
* path (never its value); an emptied generateContentConfig is removed. An
|
|
27
|
+
* agent that already has the v2 key for a concept is left alone for that
|
|
28
|
+
* concept, with a note: the loader refuses an overlap.
|
|
29
|
+
*
|
|
30
|
+
* HOW: the file is parsed with the `yaml` package's Document API, which
|
|
31
|
+
* locates every key and value in the source. The edit splices whole lines:
|
|
32
|
+
* a moved key travels with its own lines (its leading and inline comments,
|
|
33
|
+
* its indentation and quoting); the comments of a key that is converted or
|
|
34
|
+
* dropped are kept as comment lines where the new key goes. Every line the
|
|
35
|
+
* codemod does not move is left byte for byte. The result is parsed again and
|
|
36
|
+
* must read back as the planned data, or the file is refused. Running it
|
|
37
|
+
* twice changes nothing the second time.
|
|
38
|
+
*/
|
|
39
|
+
import { readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
40
|
+
import { join } from 'node:path';
|
|
41
|
+
import { pathToFileURL } from 'node:url';
|
|
42
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
43
|
+
import { isMap, isPair, isScalar, isSeq, parseDocument } from 'yaml';
|
|
44
|
+
import { reasoningOf } from '../lib/models/genaiMapping.js';
|
|
45
|
+
import { reasoningConfig } from '../lib/models/reasoning.js';
|
|
46
|
+
/** generateContentConfig keys with a `sampling:` form, and the check its value must pass. */
|
|
47
|
+
const SAMPLING = new Map([
|
|
48
|
+
['temperature', { key: 'temperature', ok: (v) => typeof v === 'number' && Number.isFinite(v) }],
|
|
49
|
+
['topP', { key: 'top_p', ok: (v) => typeof v === 'number' && Number.isFinite(v) }],
|
|
50
|
+
['maxOutputTokens', { key: 'max_output_tokens', ok: (v) => typeof v === 'number' && Number.isInteger(v) && v > 0 }],
|
|
51
|
+
['stopSequences', { key: 'stop', ok: (v) => Array.isArray(v) && v.length > 0 && v.every((s) => typeof s === 'string') }],
|
|
52
|
+
]);
|
|
53
|
+
const OUTPUT_MIMES = new Set(['application/json', 'text/plain']);
|
|
54
|
+
const THINKING_LEVEL = new Map([['MINIMAL', 'none'], ['LOW', 'low'], ['MEDIUM', 'medium'], ['HIGH', 'high']]);
|
|
55
|
+
const EFFORT_WORD = new Map([['none', 'none'], ['minimal', 'none'], ['low', 'low'], ['medium', 'medium'], ['high', 'high']]);
|
|
56
|
+
const THINKING_KEYS = new Set(['thinkingLevel', 'thinkingBudget', 'includeThoughts']);
|
|
57
|
+
// ── The source, by line ──────────────────────────────────────────────────────
|
|
58
|
+
class Source {
|
|
59
|
+
text;
|
|
60
|
+
eol;
|
|
61
|
+
lines;
|
|
62
|
+
starts;
|
|
63
|
+
constructor(text) {
|
|
64
|
+
this.text = text;
|
|
65
|
+
this.eol = text.includes('\r\n') ? '\r\n' : '\n';
|
|
66
|
+
const raw = text.split('\n');
|
|
67
|
+
this.starts = [];
|
|
68
|
+
let at = 0;
|
|
69
|
+
for (const line of raw) {
|
|
70
|
+
this.starts.push(at);
|
|
71
|
+
at += line.length + 1;
|
|
72
|
+
}
|
|
73
|
+
this.lines = raw.map((l) => (l.endsWith('\r') ? l.slice(0, -1) : l));
|
|
74
|
+
}
|
|
75
|
+
lineOf(offset) {
|
|
76
|
+
let lo = 0;
|
|
77
|
+
let hi = this.starts.length - 1;
|
|
78
|
+
while (lo < hi) {
|
|
79
|
+
const mid = (lo + hi + 1) >> 1;
|
|
80
|
+
if (this.starts[mid] <= offset)
|
|
81
|
+
lo = mid;
|
|
82
|
+
else
|
|
83
|
+
hi = mid - 1;
|
|
84
|
+
}
|
|
85
|
+
return lo;
|
|
86
|
+
}
|
|
87
|
+
col(offset) {
|
|
88
|
+
return offset - this.starts[this.lineOf(offset)];
|
|
89
|
+
}
|
|
90
|
+
/** The line of a node's last non-blank character (a trailing comment or blank line is not the node's). */
|
|
91
|
+
endLineOf(range, fallback) {
|
|
92
|
+
if (!range || range[1] <= range[0])
|
|
93
|
+
return fallback;
|
|
94
|
+
let i = range[1] - 1;
|
|
95
|
+
while (i > range[0] && isSpace(this.text.charCodeAt(i)))
|
|
96
|
+
i--;
|
|
97
|
+
return Math.max(fallback, this.lineOf(i));
|
|
98
|
+
}
|
|
99
|
+
/** The `# ...` comment that follows `offset` on its line (after an optional `:`), if any. */
|
|
100
|
+
commentAt(offset) {
|
|
101
|
+
const line = this.lineOf(offset);
|
|
102
|
+
const text = this.lines[line];
|
|
103
|
+
let i = offset - this.starts[line];
|
|
104
|
+
const skip = () => {
|
|
105
|
+
while (i < text.length && (text[i] === ' ' || text[i] === '\t'))
|
|
106
|
+
i++;
|
|
107
|
+
};
|
|
108
|
+
skip();
|
|
109
|
+
if (text[i] === ':') {
|
|
110
|
+
i++;
|
|
111
|
+
skip();
|
|
112
|
+
}
|
|
113
|
+
return text[i] === '#' ? text.slice(i).trimEnd() : undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function isSpace(code) {
|
|
117
|
+
return code === 32 || code === 9 || code === 10 || code === 13;
|
|
118
|
+
}
|
|
119
|
+
const keyName = (pair) => (isScalar(pair.key) && typeof pair.key.value === 'string' ? pair.key.value : undefined);
|
|
120
|
+
const findPair = (map, key) => map.items.find((p) => keyName(p) === key);
|
|
121
|
+
const keyRange = (pair) => pair.key.range;
|
|
122
|
+
const valueRange = (pair) => pair.value?.range;
|
|
123
|
+
/** The pair's own lines: its key line through its value's last line. */
|
|
124
|
+
function pairSpan(src, pair) {
|
|
125
|
+
const keyLine = src.lineOf(keyRange(pair)[0]);
|
|
126
|
+
return [keyLine, src.endLineOf(valueRange(pair), keyLine)];
|
|
127
|
+
}
|
|
128
|
+
/** Every comment in lines [from, to] of a subtree: full-line comments, and inline ones after keys and values. */
|
|
129
|
+
function commentsIn(src, from, to, root) {
|
|
130
|
+
const byLine = new Map();
|
|
131
|
+
for (let l = from; l <= to; l++) {
|
|
132
|
+
const t = src.lines[l].trim();
|
|
133
|
+
if (t.startsWith('#'))
|
|
134
|
+
byLine.set(l, t);
|
|
135
|
+
}
|
|
136
|
+
const inline = (offset) => {
|
|
137
|
+
if (offset === undefined)
|
|
138
|
+
return;
|
|
139
|
+
const line = src.lineOf(offset);
|
|
140
|
+
if (byLine.has(line))
|
|
141
|
+
return;
|
|
142
|
+
const c = src.commentAt(offset);
|
|
143
|
+
if (c)
|
|
144
|
+
byLine.set(line, c);
|
|
145
|
+
};
|
|
146
|
+
const walk = (node) => {
|
|
147
|
+
if (isPair(node)) {
|
|
148
|
+
inline(node.key?.range?.[1]);
|
|
149
|
+
walk(node.value);
|
|
150
|
+
}
|
|
151
|
+
else if (isMap(node) || isSeq(node)) {
|
|
152
|
+
if (node.flow)
|
|
153
|
+
inline(node.range?.[1]);
|
|
154
|
+
for (const item of node.items)
|
|
155
|
+
walk(item);
|
|
156
|
+
}
|
|
157
|
+
else if (isScalar(node)) {
|
|
158
|
+
inline(node.range?.[1]);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
walk(root);
|
|
162
|
+
return [...byLine.entries()].sort((a, b) => a[0] - b[0]).map(([, c]) => c);
|
|
163
|
+
}
|
|
164
|
+
function reindent(lines, delta) {
|
|
165
|
+
if (delta === 0)
|
|
166
|
+
return lines;
|
|
167
|
+
return lines.map((l) => {
|
|
168
|
+
if (l.length === 0)
|
|
169
|
+
return l;
|
|
170
|
+
if (delta > 0)
|
|
171
|
+
return ' '.repeat(delta) + l;
|
|
172
|
+
let n = 0;
|
|
173
|
+
while (n < -delta && l[n] === ' ')
|
|
174
|
+
n++;
|
|
175
|
+
return l.slice(n);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function scalarString(map, key) {
|
|
179
|
+
const p = findPair(map, key);
|
|
180
|
+
return p && isScalar(p.value) && typeof p.value.value === 'string' ? p.value.value : undefined;
|
|
181
|
+
}
|
|
182
|
+
function planAgent(doc, src, map, path, notes) {
|
|
183
|
+
const gccPair = findPair(map, 'generateContentConfig');
|
|
184
|
+
const osPair = findPair(map, 'outputSchema');
|
|
185
|
+
if (!gccPair && !osPair)
|
|
186
|
+
return undefined;
|
|
187
|
+
const has = (k) => findPair(map, k) !== undefined;
|
|
188
|
+
const plan = { path, map, sampling: [] };
|
|
189
|
+
const gccPath = `${path}.generateContentConfig`;
|
|
190
|
+
if (has('yaml_reference') || has('a2a_agent_url')) {
|
|
191
|
+
if (gccPair)
|
|
192
|
+
notes.push(`${gccPath}: a yaml_reference or a2a_agent_url entry brings its own config; left in place`);
|
|
193
|
+
if (osPair)
|
|
194
|
+
notes.push(`${path}.outputSchema: a yaml_reference or a2a_agent_url entry brings its own config; left in place`);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
// A key at the head of a list item shares its line with `- `: rewriting
|
|
198
|
+
// that line is not worth the risk, and no shipped file does it.
|
|
199
|
+
for (const pair of [gccPair, osPair]) {
|
|
200
|
+
if (!pair)
|
|
201
|
+
continue;
|
|
202
|
+
const start = keyRange(pair)[0];
|
|
203
|
+
if (src.lines[src.lineOf(start)].slice(0, src.col(start)).trim() !== '') {
|
|
204
|
+
notes.push(`${path}.${keyName(pair)}: the first key of its list item; move it by hand`);
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (osPair) {
|
|
209
|
+
if (has('output'))
|
|
210
|
+
notes.push(`${path}.outputSchema: ${path}.output is already set; left in place (the loader refuses both)`);
|
|
211
|
+
else if (!isMap(osPair.value))
|
|
212
|
+
notes.push(`${path}.outputSchema: not a mapping, so no output.schema form; left in place`);
|
|
213
|
+
else
|
|
214
|
+
plan.schema = osPair;
|
|
215
|
+
}
|
|
216
|
+
if (gccPair) {
|
|
217
|
+
const gcc = gccPair.value;
|
|
218
|
+
if (!isMap(gcc) || gcc.flow || gcc.items.length === 0) {
|
|
219
|
+
notes.push(`${gccPath}: not a block mapping; left in place, move it by hand`);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const keyLine = src.lineOf(keyRange(gccPair)[0]);
|
|
223
|
+
const chunks = [];
|
|
224
|
+
let prev = keyLine;
|
|
225
|
+
for (const item of gcc.items) {
|
|
226
|
+
const [, to] = pairSpan(src, item);
|
|
227
|
+
chunks.push({ pair: item, name: keyName(item) ?? '', from: prev + 1, to });
|
|
228
|
+
prev = to;
|
|
229
|
+
}
|
|
230
|
+
plan.gcc = { pair: gccPair, chunks, keyLine, indent: src.col(keyRange(gccPair)[0]), childIndent: src.col(keyRange(chunks[0].pair)[0]) };
|
|
231
|
+
const valueOf = (c) => c.pair.value?.toJS?.(doc) ?? c.pair.value;
|
|
232
|
+
// Sampling.
|
|
233
|
+
for (const c of chunks) {
|
|
234
|
+
const spec = SAMPLING.get(c.name);
|
|
235
|
+
if (!spec)
|
|
236
|
+
continue;
|
|
237
|
+
if (!spec.ok(valueOf(c)))
|
|
238
|
+
notes.push(`${gccPath}.${c.name}: this value has no sampling.${spec.key} form; left in place`);
|
|
239
|
+
else if (has('sampling'))
|
|
240
|
+
notes.push(`${gccPath}.${c.name}: ${path}.sampling is already set; left in place (move it by hand)`);
|
|
241
|
+
else
|
|
242
|
+
plan.sampling.push(c);
|
|
243
|
+
}
|
|
244
|
+
// Output mime.
|
|
245
|
+
const mime = chunks.find((c) => c.name === 'responseMimeType');
|
|
246
|
+
if (mime) {
|
|
247
|
+
if (!OUTPUT_MIMES.has(valueOf(mime)))
|
|
248
|
+
notes.push(`${gccPath}.responseMimeType: no output.mime form for this value; left in place`);
|
|
249
|
+
else if (has('output'))
|
|
250
|
+
notes.push(`${gccPath}.responseMimeType: ${path}.output is already set; left in place (move it by hand)`);
|
|
251
|
+
else
|
|
252
|
+
plan.mime = mime;
|
|
253
|
+
}
|
|
254
|
+
// Thinking.
|
|
255
|
+
const tc = chunks.find((c) => c.name === 'thinkingConfig');
|
|
256
|
+
const re = chunks.find((c) => c.name === 'reasoningEffort');
|
|
257
|
+
if (tc || re)
|
|
258
|
+
planThinking(plan, map, tc, re, valueOf, notes);
|
|
259
|
+
// Leftovers: everything not moved, converted or dropped.
|
|
260
|
+
const handled = new Set([...plan.sampling, ...(plan.mime ? [plan.mime] : []), ...(plan.reasoning?.remove ?? [])]);
|
|
261
|
+
if (plan.dropInclude?.empties)
|
|
262
|
+
handled.add(plan.dropInclude.tc);
|
|
263
|
+
const noted = new Set(['thinkingConfig', 'reasoningEffort', 'responseMimeType', ...SAMPLING.keys()]);
|
|
264
|
+
for (const c of chunks) {
|
|
265
|
+
if (handled.has(c) || noted.has(c.name))
|
|
266
|
+
continue;
|
|
267
|
+
notes.push(`${gccPath}.${c.name || '(non-string key)'}: no v2 form; left under generateContentConfig`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const changes = plan.sampling.length > 0 || plan.mime || plan.schema || plan.reasoning || plan.dropInclude;
|
|
272
|
+
return changes ? plan : undefined;
|
|
273
|
+
}
|
|
274
|
+
function planThinking(plan, map, tc, re, valueOf, notes) {
|
|
275
|
+
const gccPath = `${plan.path}.generateContentConfig`;
|
|
276
|
+
const where = [tc, re].filter((c) => !!c).map((c) => `${gccPath}.${c.name}`).join(', ');
|
|
277
|
+
const tcValue = tc ? valueOf(tc) : undefined;
|
|
278
|
+
const tcObject = tcValue && typeof tcValue === 'object' && !Array.isArray(tcValue) ? tcValue : undefined;
|
|
279
|
+
const model = scalarString(map, 'model');
|
|
280
|
+
// includeThoughts: false is the default and read by nothing: drop it from a
|
|
281
|
+
// block thinkingConfig whatever else happens to the thinking keys.
|
|
282
|
+
const incPair = tc && isMap(tc.pair.value) && !tc.pair.value.flow ? findPair(tc.pair.value, 'includeThoughts') : undefined;
|
|
283
|
+
const incFalse = incPair && isScalar(incPair.value) && incPair.value.value === false;
|
|
284
|
+
const leave = (why) => {
|
|
285
|
+
notes.push(`${where}: ${why}; left in place`);
|
|
286
|
+
if (tc && incFalse) {
|
|
287
|
+
const tcMap = tc.pair.value;
|
|
288
|
+
plan.dropInclude = { tc, inc: incPair, empties: tcMap.items.length === 1 };
|
|
289
|
+
}
|
|
290
|
+
if (tcObject?.includeThoughts === true)
|
|
291
|
+
notes.push(`${gccPath}.thinkingConfig.includeThoughts: no v2 form; left in place`);
|
|
292
|
+
};
|
|
293
|
+
if (findPair(map, 'reasoning'))
|
|
294
|
+
return leave(`${plan.path}.reasoning is already set (the loader refuses both)`);
|
|
295
|
+
if (!model || model.includes('{{'))
|
|
296
|
+
return leave('the agent names no model id, so no reasoning: form can be checked');
|
|
297
|
+
if (tc && !tcObject)
|
|
298
|
+
return leave('thinkingConfig is not a mapping');
|
|
299
|
+
if (tcObject && Object.keys(tcObject).some((k) => !THINKING_KEYS.has(k)))
|
|
300
|
+
return leave('thinkingConfig holds a key with no reasoning: form');
|
|
301
|
+
if (tcObject && tcObject.includeThoughts !== undefined && tcObject.includeThoughts !== false)
|
|
302
|
+
return leave('includeThoughts has no reasoning: form');
|
|
303
|
+
const v1 = {};
|
|
304
|
+
if (tc)
|
|
305
|
+
v1.thinkingConfig = tcValue;
|
|
306
|
+
if (re)
|
|
307
|
+
v1.reasoningEffort = valueOf(re);
|
|
308
|
+
let candidate;
|
|
309
|
+
const level = THINKING_LEVEL.get(tcObject?.thinkingLevel);
|
|
310
|
+
const budget = tcObject?.thinkingBudget;
|
|
311
|
+
if (level)
|
|
312
|
+
candidate = level;
|
|
313
|
+
else if (typeof budget === 'number' && Number.isInteger(budget) && budget >= 0)
|
|
314
|
+
candidate = { budget_tokens: budget };
|
|
315
|
+
else if (EFFORT_WORD.has(v1.reasoningEffort))
|
|
316
|
+
candidate = EFFORT_WORD.get(v1.reasoningEffort);
|
|
317
|
+
if (candidate === undefined)
|
|
318
|
+
return leave('no reasoning: setting reads the same');
|
|
319
|
+
const before = reasoningOf(v1);
|
|
320
|
+
const after = reasoningOf(reasoningConfig(model, candidate));
|
|
321
|
+
if (!isDeepStrictEqual(before, after))
|
|
322
|
+
return leave(`reasoning: would change what the engine reads for ${providerWord(model)}`);
|
|
323
|
+
plan.reasoning = { setting: candidate, remove: [tc, re].filter((c) => !!c) };
|
|
324
|
+
}
|
|
325
|
+
/** A model family word for a note: never the id's value beyond its prefix. */
|
|
326
|
+
function providerWord(model) {
|
|
327
|
+
return model.startsWith('gemini-') ? 'this Gemini model' : 'this model';
|
|
328
|
+
}
|
|
329
|
+
const reasoningText = (s) => (typeof s === 'string' ? s : `{ budget_tokens: ${s.budget_tokens} }`);
|
|
330
|
+
function chunkLines(src, c) {
|
|
331
|
+
return src.lines.slice(c.from, c.to + 1);
|
|
332
|
+
}
|
|
333
|
+
/** A chunk with its key token renamed (same line, same column). */
|
|
334
|
+
function renamed(src, c, key) {
|
|
335
|
+
const lines = chunkLines(src, c);
|
|
336
|
+
const [start, end] = keyRange(c.pair);
|
|
337
|
+
const at = src.lineOf(start) - c.from;
|
|
338
|
+
const line = lines[at];
|
|
339
|
+
lines[at] = line.slice(0, src.col(start)) + key + line.slice(src.col(end));
|
|
340
|
+
return lines;
|
|
341
|
+
}
|
|
342
|
+
function renderGcc(src, plan) {
|
|
343
|
+
const g = plan.gcc;
|
|
344
|
+
if (!g)
|
|
345
|
+
return undefined;
|
|
346
|
+
if (!(plan.sampling.length || plan.mime || plan.reasoning || plan.dropInclude))
|
|
347
|
+
return undefined;
|
|
348
|
+
const pad = ' '.repeat(g.indent);
|
|
349
|
+
const removed = new Set([...plan.sampling, ...(plan.mime ? [plan.mime] : []), ...(plan.reasoning?.remove ?? [])]);
|
|
350
|
+
if (plan.dropInclude?.empties)
|
|
351
|
+
removed.add(plan.dropInclude.tc);
|
|
352
|
+
const leftovers = g.chunks.filter((c) => !removed.has(c));
|
|
353
|
+
const out = [];
|
|
354
|
+
if (leftovers.length === 0) {
|
|
355
|
+
const c = src.commentAt(keyRange(g.pair)[1]);
|
|
356
|
+
if (c)
|
|
357
|
+
out.push(pad + c);
|
|
358
|
+
}
|
|
359
|
+
if (plan.sampling.length) {
|
|
360
|
+
out.push(`${pad}sampling:`);
|
|
361
|
+
for (const c of plan.sampling)
|
|
362
|
+
out.push(...renamed(src, c, SAMPLING.get(c.name).key));
|
|
363
|
+
}
|
|
364
|
+
if (plan.reasoning) {
|
|
365
|
+
for (const c of plan.reasoning.remove)
|
|
366
|
+
for (const comment of commentsIn(src, c.from, c.to, c.pair))
|
|
367
|
+
out.push(pad + comment);
|
|
368
|
+
out.push(`${pad}reasoning: ${reasoningText(plan.reasoning.setting)}`);
|
|
369
|
+
}
|
|
370
|
+
if (plan.mime && !plan.schema) {
|
|
371
|
+
out.push(`${pad}output:`);
|
|
372
|
+
out.push(...renamed(src, plan.mime, 'mime'));
|
|
373
|
+
}
|
|
374
|
+
const orphans = plan.dropInclude?.empties ? commentsIn(src, plan.dropInclude.tc.from, plan.dropInclude.tc.to, plan.dropInclude.tc.pair) : [];
|
|
375
|
+
if (leftovers.length) {
|
|
376
|
+
out.push(src.lines[g.keyLine]);
|
|
377
|
+
for (const c of leftovers) {
|
|
378
|
+
if (plan.dropInclude && c === plan.dropInclude.tc)
|
|
379
|
+
out.push(...withoutPair(src, c, plan.dropInclude.inc));
|
|
380
|
+
else
|
|
381
|
+
out.push(...chunkLines(src, c));
|
|
382
|
+
}
|
|
383
|
+
for (const comment of orphans)
|
|
384
|
+
out.push(' '.repeat(g.childIndent) + comment);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
for (const comment of orphans)
|
|
388
|
+
out.push(pad + comment);
|
|
389
|
+
}
|
|
390
|
+
return { from: g.keyLine, to: g.chunks[g.chunks.length - 1].to, lines: out };
|
|
391
|
+
}
|
|
392
|
+
/** A chunk with one nested pair's own lines removed, its inline comments kept as comment lines. */
|
|
393
|
+
function withoutPair(src, c, inner) {
|
|
394
|
+
const [from, to] = pairSpan(src, inner);
|
|
395
|
+
const indent = ' '.repeat(src.col(keyRange(inner)[0]));
|
|
396
|
+
const comments = commentsIn(src, from, to, inner).map((t) => indent + t);
|
|
397
|
+
return [...src.lines.slice(c.from, from), ...comments, ...src.lines.slice(to + 1, c.to + 1)];
|
|
398
|
+
}
|
|
399
|
+
function renderSchema(src, plan) {
|
|
400
|
+
const os = plan.schema;
|
|
401
|
+
if (!os)
|
|
402
|
+
return undefined;
|
|
403
|
+
const [keyStart, keyEnd] = keyRange(os);
|
|
404
|
+
const indent = src.col(keyStart);
|
|
405
|
+
const [from, to] = pairSpan(src, os);
|
|
406
|
+
const value = os.value;
|
|
407
|
+
const first = value.items[0];
|
|
408
|
+
const step = !value.flow && first && src.lineOf(keyRange(first)[0]) > from
|
|
409
|
+
? src.col(keyRange(first)[0]) - indent
|
|
410
|
+
: plan.gcc
|
|
411
|
+
? plan.gcc.childIndent - plan.gcc.indent
|
|
412
|
+
: 2;
|
|
413
|
+
const out = [`${' '.repeat(indent)}output:`, ' '.repeat(indent + step) + 'schema' + src.lines[from].slice(src.col(keyEnd))];
|
|
414
|
+
out.push(...reindent(src.lines.slice(from + 1, to + 1), step));
|
|
415
|
+
if (plan.mime) {
|
|
416
|
+
const g = plan.gcc;
|
|
417
|
+
out.push(...reindent(renamed(src, plan.mime, 'mime'), indent + step - g.childIndent));
|
|
418
|
+
}
|
|
419
|
+
return { from, to, lines: out };
|
|
420
|
+
}
|
|
421
|
+
/** What the plan should read back as, applied to the agent's data. */
|
|
422
|
+
function expectAgent(agent, plan) {
|
|
423
|
+
const gcc = agent.generateContentConfig;
|
|
424
|
+
if (plan.schema) {
|
|
425
|
+
agent.output = { schema: agent.outputSchema };
|
|
426
|
+
delete agent.outputSchema;
|
|
427
|
+
}
|
|
428
|
+
if (!gcc)
|
|
429
|
+
return;
|
|
430
|
+
if (plan.sampling.length) {
|
|
431
|
+
const sampling = {};
|
|
432
|
+
for (const c of plan.sampling) {
|
|
433
|
+
sampling[SAMPLING.get(c.name).key] = gcc[c.name];
|
|
434
|
+
delete gcc[c.name];
|
|
435
|
+
}
|
|
436
|
+
agent.sampling = sampling;
|
|
437
|
+
}
|
|
438
|
+
if (plan.mime) {
|
|
439
|
+
agent.output = { ...(agent.output ?? {}), mime: gcc.responseMimeType };
|
|
440
|
+
delete gcc.responseMimeType;
|
|
441
|
+
}
|
|
442
|
+
if (plan.reasoning) {
|
|
443
|
+
for (const c of plan.reasoning.remove)
|
|
444
|
+
delete gcc[c.name];
|
|
445
|
+
agent.reasoning = plan.reasoning.setting;
|
|
446
|
+
}
|
|
447
|
+
if (plan.dropInclude) {
|
|
448
|
+
const tc = gcc.thinkingConfig;
|
|
449
|
+
delete tc.includeThoughts;
|
|
450
|
+
if (Object.keys(tc).length === 0)
|
|
451
|
+
delete gcc.thinkingConfig;
|
|
452
|
+
}
|
|
453
|
+
if (Object.keys(gcc).length === 0)
|
|
454
|
+
delete agent.generateContentConfig;
|
|
455
|
+
}
|
|
456
|
+
/** The agents of a syndicate document: the orchestrator and each subagent, with their key paths. */
|
|
457
|
+
function agentsOf(root) {
|
|
458
|
+
const out = [];
|
|
459
|
+
const orchestrator = findPair(root, 'orchestrator')?.value;
|
|
460
|
+
if (isMap(orchestrator))
|
|
461
|
+
out.push({ map: orchestrator, path: 'orchestrator', locate: (js) => js.orchestrator });
|
|
462
|
+
const subagents = findPair(root, 'subagents')?.value;
|
|
463
|
+
if (isSeq(subagents)) {
|
|
464
|
+
subagents.items.forEach((item, i) => {
|
|
465
|
+
if (isMap(item))
|
|
466
|
+
out.push({ map: item, path: `subagents[${i}]`, locate: (js) => js.subagents[i] });
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Rewrites one YAML text's v1 agent keys into v2 (see the header). Throws on
|
|
473
|
+
* text that is not one YAML document, and on an edit that would not read
|
|
474
|
+
* back as planned (a bug, never a silent change).
|
|
475
|
+
*/
|
|
476
|
+
export function migrateYaml(text) {
|
|
477
|
+
const doc = parseDocument(text);
|
|
478
|
+
if (doc.errors.length)
|
|
479
|
+
throw new Error(`not valid YAML (${doc.errors[0].code} at line ${doc.errors[0].linePos?.[0]?.line ?? '?'})`);
|
|
480
|
+
const notes = [];
|
|
481
|
+
if (!isMap(doc.contents))
|
|
482
|
+
return { text, changed: false, notes };
|
|
483
|
+
const src = new Source(text);
|
|
484
|
+
const plans = [];
|
|
485
|
+
for (const agent of agentsOf(doc.contents)) {
|
|
486
|
+
const plan = planAgent(doc, src, agent.map, agent.path, notes);
|
|
487
|
+
if (plan)
|
|
488
|
+
plans.push({ plan, locate: agent.locate });
|
|
489
|
+
}
|
|
490
|
+
if (plans.length === 0)
|
|
491
|
+
return { text, changed: false, notes };
|
|
492
|
+
const edits = plans.flatMap(({ plan }) => [renderGcc(src, plan), renderSchema(src, plan)].filter((e) => !!e));
|
|
493
|
+
edits.sort((a, b) => b.from - a.from);
|
|
494
|
+
const lines = [...src.lines];
|
|
495
|
+
for (const e of edits)
|
|
496
|
+
lines.splice(e.from, e.to - e.from + 1, ...e.lines);
|
|
497
|
+
const out = lines.join(src.eol);
|
|
498
|
+
const expected = doc.toJS();
|
|
499
|
+
for (const { plan, locate } of plans)
|
|
500
|
+
expectAgent(locate(expected), plan);
|
|
501
|
+
const back = parseDocument(out);
|
|
502
|
+
if (back.errors.length || !isDeepStrictEqual(back.toJS(), expected)) {
|
|
503
|
+
throw new Error('the rewrite does not read back as planned; file left unchanged (please report this)');
|
|
504
|
+
}
|
|
505
|
+
return { text: out, changed: out !== text, notes };
|
|
506
|
+
}
|
|
507
|
+
// ── CLI ──────────────────────────────────────────────────────────────────────
|
|
508
|
+
function yamlFiles(path) {
|
|
509
|
+
if (!statSync(path).isDirectory())
|
|
510
|
+
return [path];
|
|
511
|
+
const out = [];
|
|
512
|
+
for (const entry of readdirSync(path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
513
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
514
|
+
continue;
|
|
515
|
+
const p = join(path, entry.name);
|
|
516
|
+
if (entry.isDirectory())
|
|
517
|
+
out.push(...yamlFiles(p));
|
|
518
|
+
else if (entry.isFile() && entry.name.endsWith('.yaml'))
|
|
519
|
+
out.push(p);
|
|
520
|
+
}
|
|
521
|
+
return out;
|
|
522
|
+
}
|
|
523
|
+
const USAGE = `usage: yaml_codemod [--check] <file|dir>...
|
|
524
|
+
Rewrites agent v1 spellings (generateContentConfig, outputSchema) into the
|
|
525
|
+
v2 keys (sampling, output, reasoning), in place. A directory means every
|
|
526
|
+
*.yaml below it. --check writes nothing and exits 1 if any file would change.`;
|
|
527
|
+
export function main(argv) {
|
|
528
|
+
const check = argv.includes('--check');
|
|
529
|
+
const targets = argv.filter((a) => a !== '--check');
|
|
530
|
+
if (targets.length === 0 || targets.some((a) => a === '--help' || a === '-h')) {
|
|
531
|
+
console.log(USAGE);
|
|
532
|
+
return targets.length === 0 ? 2 : 0;
|
|
533
|
+
}
|
|
534
|
+
let changed = 0;
|
|
535
|
+
let failed = 0;
|
|
536
|
+
for (const file of targets.flatMap(yamlFiles)) {
|
|
537
|
+
let result;
|
|
538
|
+
try {
|
|
539
|
+
result = migrateYaml(readFileSync(file, 'utf8'));
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
console.error(`✗ ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
543
|
+
failed++;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (result.changed) {
|
|
547
|
+
changed++;
|
|
548
|
+
if (!check)
|
|
549
|
+
writeFileSync(file, result.text);
|
|
550
|
+
console.log(`${check ? 'would change' : 'migrated'}: ${file}`);
|
|
551
|
+
}
|
|
552
|
+
for (const note of result.notes)
|
|
553
|
+
console.log(` note: ${file}: ${note}`);
|
|
554
|
+
}
|
|
555
|
+
if (check && changed)
|
|
556
|
+
console.log(`${changed} file(s) still use v1 spellings; run without --check to rewrite them.`);
|
|
557
|
+
return failed ? 1 : check && changed ? 1 : 0;
|
|
558
|
+
}
|
|
559
|
+
const invokedAsMain = (() => {
|
|
560
|
+
if (!process.argv[1])
|
|
561
|
+
return false;
|
|
562
|
+
try {
|
|
563
|
+
return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
})();
|
|
569
|
+
if (invokedAsMain)
|
|
570
|
+
process.exitCode = main(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "melchizedek-agents",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A multi-model, multi-agent orchestration framework — YAML-defined syndicates on the engine's own agent loop, Supabase or Postgres session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,12 +54,14 @@
|
|
|
54
54
|
"melchizedek-worker": "./dist/scripts/assistant_worker.js",
|
|
55
55
|
"melchizedek-db": "./dist/scripts/db.js",
|
|
56
56
|
"melchizedek-init": "./dist/scripts/init.js",
|
|
57
|
-
"melchizedek-registry": "./dist/scripts/registry.js"
|
|
57
|
+
"melchizedek-registry": "./dist/scripts/registry.js",
|
|
58
|
+
"melchizedek-codemod": "./dist/scripts/yaml_codemod.js"
|
|
58
59
|
},
|
|
59
60
|
"files": [
|
|
60
61
|
"dist",
|
|
61
62
|
"config/agents/README.md",
|
|
62
63
|
"config/agents/syndicateSchema.yaml",
|
|
64
|
+
"config/agents/syndicate.schema.json",
|
|
63
65
|
"config/agents/examples",
|
|
64
66
|
"config/agents/templates",
|
|
65
67
|
"db",
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
"syndicate:synthesis": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate syndicate",
|
|
83
85
|
"syndicate:delegation": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate delegation",
|
|
84
86
|
"syndicate:critic": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate critic",
|
|
87
|
+
"syndicate:structured-critic": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate structured_critic",
|
|
85
88
|
"syndicate:hierarchical": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate hierarchical",
|
|
86
89
|
"syndicate:style": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate style_council",
|
|
87
90
|
"syndicate:image": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate image_production",
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
"wiki:build": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/build.ts",
|
|
112
115
|
"wiki:check": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/check.ts",
|
|
113
116
|
"schema:gen": "node --disable-warning=DEP0040 --experimental-strip-types scripts/gen_syndicate_schema.ts",
|
|
117
|
+
"yaml:codemod": "node --disable-warning=DEP0040 --experimental-strip-types scripts/yaml_codemod.ts",
|
|
114
118
|
"start:a2a": "node --disable-warning=DEP0040 --experimental-strip-types scripts/a2a_server.ts",
|
|
115
119
|
"test": "node --disable-warning=DEP0040 --experimental-strip-types --test tests/**/*.test.ts",
|
|
116
120
|
"init": "node --disable-warning=DEP0040 --experimental-strip-types scripts/init.ts",
|
|
@@ -132,9 +136,9 @@
|
|
|
132
136
|
},
|
|
133
137
|
"dependencies": {
|
|
134
138
|
"@a2a-js/sdk": "^1.3.0",
|
|
135
|
-
"@anthropic-ai/sdk": "^0.
|
|
136
|
-
"@google/genai": "2.
|
|
137
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
139
|
+
"@anthropic-ai/sdk": "^0.131.0",
|
|
140
|
+
"@google/genai": "2.27.0",
|
|
141
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
138
142
|
"@opentelemetry/api": "^1.9.1",
|
|
139
143
|
"@opentelemetry/core": "^2.8.0",
|
|
140
144
|
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
@@ -146,14 +150,14 @@
|
|
|
146
150
|
"express": "^5.2.1",
|
|
147
151
|
"express-rate-limit": "^8.5.2",
|
|
148
152
|
"pg": "^8.23.1",
|
|
149
|
-
"openai": "^7.
|
|
153
|
+
"openai": "^7.28.0",
|
|
150
154
|
"yaml": "^2.9.0",
|
|
151
155
|
"zod": "4.6.5"
|
|
152
156
|
},
|
|
153
157
|
"devDependencies": {
|
|
154
158
|
"@types/pg": "^8.23.1",
|
|
155
159
|
"@types/express": "^5.0.6",
|
|
156
|
-
"@types/node": "^
|
|
160
|
+
"@types/node": "^25",
|
|
157
161
|
"typescript": "^7.0.2"
|
|
158
162
|
},
|
|
159
163
|
"overrides": {
|