@zhihaohong52/sonata 0.8.2 → 0.9.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/README.md +4 -0
- package/dist/adapters/claude.js +21 -3
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.js +11 -1
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/opencode.js +19 -1
- package/dist/adapters/opencode.js.map +1 -1
- package/dist/adapters/pi.js +14 -1
- package/dist/adapters/pi.js.map +1 -1
- package/dist/adapters/reasonix.js +10 -1
- package/dist/adapters/reasonix.js.map +1 -1
- package/dist/catalog.js +252 -33
- package/dist/catalog.js.map +1 -1
- package/dist/cli.js +127 -9
- package/dist/cli.js.map +1 -1
- package/dist/commands/agents.js +258 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/catalog.js +74 -11
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/dispatch.js +27 -10
- package/dist/commands/dispatch.js.map +1 -1
- package/dist/commands/doctor.js +82 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/reset.js +340 -0
- package/dist/commands/reset.js.map +1 -0
- package/dist/commands/route.js +139 -16
- package/dist/commands/route.js.map +1 -1
- package/dist/commands/run.js +6 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/tail.js +30 -2
- package/dist/commands/tail.js.map +1 -1
- package/dist/commands/usage.js +19 -0
- package/dist/commands/usage.js.map +1 -1
- package/dist/commands/verify.js +16 -3
- package/dist/commands/verify.js.map +1 -1
- package/dist/config.js +25 -4
- package/dist/config.js.map +1 -1
- package/dist/detect.js +14 -4
- package/dist/detect.js.map +1 -1
- package/dist/effort.js +68 -0
- package/dist/effort.js.map +1 -0
- package/dist/extended-context.js +3 -2
- package/dist/extended-context.js.map +1 -1
- package/dist/init/guidance.js +117 -36
- package/dist/init/guidance.js.map +1 -1
- package/dist/init/helpers.js +4 -1
- package/dist/init/helpers.js.map +1 -1
- package/dist/init/interactive-state.js +25 -0
- package/dist/init/interactive-state.js.map +1 -1
- package/dist/init/plan.js +48 -7
- package/dist/init/plan.js.map +1 -1
- package/dist/init/toml.js +154 -2
- package/dist/init/toml.js.map +1 -1
- package/dist/ledger.js.map +1 -1
- package/dist/modelsdev.js +113 -0
- package/dist/modelsdev.js.map +1 -1
- package/dist/native/router.js +285 -6
- package/dist/native/router.js.map +1 -1
- package/dist/native/tenants.js +12 -1
- package/dist/native/tenants.js.map +1 -1
- package/dist/pricing.js +129 -1
- package/dist/pricing.js.map +1 -1
- package/dist/settings.js +28 -0
- package/dist/settings.js.map +1 -1
- package/dist/tui-ink/agents-app.js +67 -0
- package/dist/tui-ink/agents-app.js.map +1 -0
- package/dist/tui-ink/agents-run.js +23 -0
- package/dist/tui-ink/agents-run.js.map +1 -0
- package/dist/tui-ink/app-state.js +106 -2
- package/dist/tui-ink/app-state.js.map +1 -1
- package/dist/tui-ink/app.js +107 -16
- package/dist/tui-ink/app.js.map +1 -1
- package/dist/tui-ink/components/models-step.js +17 -4
- package/dist/tui-ink/components/models-step.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sonata reset` — undo what `sonata init` wrote, and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* Init scatters its output across five places: the config, the generated tier
|
|
5
|
+
* agents, the loop skill, a block inside a `CLAUDE.md` sonata does not own,
|
|
6
|
+
* and two settings files holding the routing env, four lifecycle hooks, the
|
|
7
|
+
* permission hook and the tool allow-list. Undoing that by hand means knowing
|
|
8
|
+
* all five, which is why the only reliable answer used to be "delete the repo
|
|
9
|
+
* and start again".
|
|
10
|
+
*
|
|
11
|
+
* Three rules shape the whole command:
|
|
12
|
+
*
|
|
13
|
+
* - **It removes only what sonata wrote.** An agent file without sonata's
|
|
14
|
+
* marker is left alone; `permissions.allow` keeps every entry that is not
|
|
15
|
+
* sonata's; `CLAUDE.md` loses what is between the markers and not one byte
|
|
16
|
+
* more. A settings file is rewritten, never deleted — sonata is one writer
|
|
17
|
+
* of it among several.
|
|
18
|
+
* - **It keeps everything that is expensive to recreate**: API keys, the usage
|
|
19
|
+
* ledger, the ranking catalogs, and the `.sonata/` run store. Those are not
|
|
20
|
+
* configuration, and a user resetting a bad setup should not lose their
|
|
21
|
+
* spend history or have to re-enter every key. The kept list is printed, so
|
|
22
|
+
* "reset" cannot be read as "removed everything".
|
|
23
|
+
* - **It plans first, then applies.** The plan is a list of typed actions with
|
|
24
|
+
* a label each, so the confirmation names every path before anything is
|
|
25
|
+
* touched, and the set shown is exactly the set removed.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
28
|
+
import { join } from 'node:path';
|
|
29
|
+
import { GLOBAL_CONFIG_RELATIVE } from '../config.js';
|
|
30
|
+
import { isSonataAgentText } from '../detect.js';
|
|
31
|
+
import { agentsDirFor, configPathFor } from '../init/helpers.js';
|
|
32
|
+
import { removeGuidance } from '../init/guidance.js';
|
|
33
|
+
import { readSettings, revokeSonataTools, settingsPath, uninstallHook, writeSettings, } from '../settings.js';
|
|
34
|
+
import { planRouteManual, planRouteOff, routeSessionsFile, routeSettingsFile, routeSubagentsFile } from './route.js';
|
|
35
|
+
/**
|
|
36
|
+
* The agent files in this directory that sonata generated.
|
|
37
|
+
*
|
|
38
|
+
* Only a missing directory is an empty answer. Every other failure — a
|
|
39
|
+
* permission error, a bad mount, an unreadable file — used to be flattened to
|
|
40
|
+
* `[]` or to `false`, which produced a plan with no agents in it and a command
|
|
41
|
+
* that reported success having left every agent installed. That is the worst
|
|
42
|
+
* shape a cleanup can fail in: silent, and indistinguishable from having had
|
|
43
|
+
* nothing to do. Unreadable files are named as warnings instead, and the
|
|
44
|
+
* ownership test is the same one `sync` and `prune` use.
|
|
45
|
+
*/
|
|
46
|
+
function sonataAgents(dir) {
|
|
47
|
+
const warnings = [];
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = readdirSync(dir);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err.code === 'ENOENT')
|
|
54
|
+
return { files: [], warnings };
|
|
55
|
+
return { files: [], warnings: [`could not read ${dir}: ${err.message} — agent files left in place`] };
|
|
56
|
+
}
|
|
57
|
+
const files = [];
|
|
58
|
+
for (const f of entries.filter((name) => name.endsWith('.md')).sort()) {
|
|
59
|
+
const path = join(dir, f);
|
|
60
|
+
try {
|
|
61
|
+
if (isSonataAgentText(readFileSync(path, 'utf8')))
|
|
62
|
+
files.push(f);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
warnings.push(`could not read ${path}: ${err.message} — left in place`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { files, warnings };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Strip sonata out of one settings file.
|
|
72
|
+
*
|
|
73
|
+
* Every transform is by command *shape* rather than by this install's own
|
|
74
|
+
* path: a user who ran `sonata init` from a different checkout — an npm
|
|
75
|
+
* install now replaced by a clone, say — would otherwise have their hooks left
|
|
76
|
+
* behind by the very command whose job is removing them. `planRouteOff` can
|
|
77
|
+
* refuse (a non-localhost base URL it did not write), and that refusal must
|
|
78
|
+
* not take the rest of the file down with it, so it is caught and reported.
|
|
79
|
+
*/
|
|
80
|
+
export function planSettingsReset(settings, packageRoot, scope) {
|
|
81
|
+
let next = settings;
|
|
82
|
+
let changed = false;
|
|
83
|
+
const warnings = [];
|
|
84
|
+
try {
|
|
85
|
+
const off = planRouteOff(next, packageRoot);
|
|
86
|
+
next = off.settings;
|
|
87
|
+
changed = changed || off.changed;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
warnings.push(`left the routing env in place: ${err.message}`);
|
|
91
|
+
}
|
|
92
|
+
const manual = planRouteManual(next, packageRoot, scope);
|
|
93
|
+
next = manual.settings;
|
|
94
|
+
changed = changed || manual.changed;
|
|
95
|
+
// The permission hook is found by the file it runs, not by this install's
|
|
96
|
+
// absolute path, for the same reason `planRouteOff` finds its own that way.
|
|
97
|
+
for (const [event, needle] of [
|
|
98
|
+
['PreToolUse', 'capture-mode.mjs'],
|
|
99
|
+
['SessionStart', 'ensure-serve.mjs'],
|
|
100
|
+
['SessionStart', 'route-session.mjs'],
|
|
101
|
+
['SessionEnd', 'route-session.mjs'],
|
|
102
|
+
['SubagentStart', 'route-subagent.mjs'],
|
|
103
|
+
['SubagentStop', 'route-subagent.mjs'],
|
|
104
|
+
]) {
|
|
105
|
+
for (;;) {
|
|
106
|
+
const command = (next.hooks?.[event] ?? [])
|
|
107
|
+
.flatMap((entry) => entry.hooks)
|
|
108
|
+
.find((h) => h.command.includes(needle))?.command;
|
|
109
|
+
if (command === undefined)
|
|
110
|
+
break;
|
|
111
|
+
const removed = uninstallHook(next, command, event);
|
|
112
|
+
next = removed.settings;
|
|
113
|
+
changed = changed || removed.changed;
|
|
114
|
+
// `uninstallHook` matches by exact command, so a second install's copy
|
|
115
|
+
// of the same hook survives the first pass. Without this guard, a
|
|
116
|
+
// no-op removal would also spin here forever.
|
|
117
|
+
if (!removed.changed)
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const revoked = revokeSonataTools(next);
|
|
122
|
+
next = revoked.settings;
|
|
123
|
+
changed = changed || revoked.changed;
|
|
124
|
+
return { settings: next, changed, warnings };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* What `sonata reset` would do, without doing any of it.
|
|
128
|
+
*
|
|
129
|
+
* Reads the filesystem — the plan has to name real paths — but writes nothing,
|
|
130
|
+
* so it doubles as the dry run behind the confirmation prompt.
|
|
131
|
+
*/
|
|
132
|
+
export function planReset(opts) {
|
|
133
|
+
const scope = opts.scope ?? 'project';
|
|
134
|
+
const { cwd, home, packageRoot } = opts;
|
|
135
|
+
const base = scope === 'global' ? home : cwd;
|
|
136
|
+
const actions = [];
|
|
137
|
+
const warnings = [];
|
|
138
|
+
const configPath = configPathFor(scope, cwd, home);
|
|
139
|
+
if (existsSync(configPath)) {
|
|
140
|
+
actions.push({ kind: 'delete-file', label: 'config', path: configPath });
|
|
141
|
+
}
|
|
142
|
+
const agentsDir = agentsDirFor(scope, cwd, home);
|
|
143
|
+
const agents = sonataAgents(agentsDir);
|
|
144
|
+
warnings.push(...agents.warnings);
|
|
145
|
+
if (agents.files.length > 0) {
|
|
146
|
+
actions.push({
|
|
147
|
+
kind: 'delete-agents',
|
|
148
|
+
label: `${agents.files.length} generated agent${agents.files.length === 1 ? '' : 's'}`,
|
|
149
|
+
dir: agentsDir,
|
|
150
|
+
files: agents.files,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const skillDir = join(base, '.claude', 'skills', 'sonata-loop');
|
|
154
|
+
if (existsSync(skillDir)) {
|
|
155
|
+
actions.push({ kind: 'delete-dir', label: 'sonata-loop skill', path: skillDir });
|
|
156
|
+
}
|
|
157
|
+
// The CLAUDE.md block is the one artifact in a file sonata does not own, so
|
|
158
|
+
// it is spliced out rather than the file being removed — and a file whose
|
|
159
|
+
// markers do not pair up is left entirely alone, with the reason reported.
|
|
160
|
+
const guidancePath = scope === 'global'
|
|
161
|
+
? join(home, '.claude', 'CLAUDE.md')
|
|
162
|
+
: join(cwd, 'CLAUDE.md');
|
|
163
|
+
if (existsSync(guidancePath)) {
|
|
164
|
+
try {
|
|
165
|
+
const existing = readFileSync(guidancePath, 'utf8');
|
|
166
|
+
const without = removeGuidance(existing);
|
|
167
|
+
if (without !== undefined) {
|
|
168
|
+
actions.push({
|
|
169
|
+
kind: 'write-file', label: 'the sonata block in CLAUDE.md', path: guidancePath, content: without,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
warnings.push(`left ${guidancePath} unchanged: ${err.message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Project scope writes routing into `settings.local.json` and the permission
|
|
178
|
+
// hook into `settings.json`; global scope puts both in one file. Deduplicated
|
|
179
|
+
// so the same file is never planned twice.
|
|
180
|
+
const settingsFiles = [...new Set([
|
|
181
|
+
routeSettingsFile(cwd, scope, home),
|
|
182
|
+
settingsPath(scope, cwd, home),
|
|
183
|
+
])];
|
|
184
|
+
for (const path of settingsFiles) {
|
|
185
|
+
if (!existsSync(path))
|
|
186
|
+
continue;
|
|
187
|
+
const current = readSettings(path);
|
|
188
|
+
const stripped = planSettingsReset(current, packageRoot, scope);
|
|
189
|
+
warnings.push(...stripped.warnings);
|
|
190
|
+
if (stripped.changed) {
|
|
191
|
+
actions.push({
|
|
192
|
+
kind: 'write-settings', label: 'routing, hooks and the tool allow-list', path, settings: stripped.settings,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (const path of [routeSessionsFile(cwd, scope, home), routeSubagentsFile(cwd, scope, home)]) {
|
|
197
|
+
if (existsSync(path)) {
|
|
198
|
+
actions.push({ kind: 'delete-file', label: 'routed-session registry', path });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
scope,
|
|
203
|
+
actions,
|
|
204
|
+
kept: [
|
|
205
|
+
`gateway keys (${join(home, '.config', 'sonata', 'credentials')}) — \`sonata auth list\``,
|
|
206
|
+
`the usage ledger (${join(home, '.config', 'sonata', 'usage')}) — \`sonata usage\``,
|
|
207
|
+
'the ranking and pricing caches',
|
|
208
|
+
scope === 'global'
|
|
209
|
+
? `any project's own sonata.toml`
|
|
210
|
+
: `the machine config (${join(home, GLOBAL_CONFIG_RELATIVE)})`,
|
|
211
|
+
],
|
|
212
|
+
warnings,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/** The plan as the lines a user is asked to approve. */
|
|
216
|
+
export function describeReset(plan) {
|
|
217
|
+
const lines = [];
|
|
218
|
+
if (plan.actions.length === 0) {
|
|
219
|
+
lines.push(` nothing to remove — no sonata ${plan.scope} setup found`);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
lines.push(` sonata reset (${plan.scope}) will remove:`);
|
|
223
|
+
for (const action of plan.actions) {
|
|
224
|
+
lines.push(` ${action.kind === 'write-settings' || action.kind === 'write-file' ? '~' : '-'} ${action.label}`);
|
|
225
|
+
if (action.kind === 'delete-agents') {
|
|
226
|
+
// Every path, never a truncated sample. Elsewhere a "… and N more" is
|
|
227
|
+
// a courtesy; here the list *is* the thing being agreed to, and a
|
|
228
|
+
// confirmation that hides paths it then deletes is not a confirmation.
|
|
229
|
+
for (const f of action.files)
|
|
230
|
+
lines.push(` ${join(action.dir, f)}`);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
lines.push(` ${action.path}`);
|
|
234
|
+
// `writeSettings` copies the file aside before writing, so the reset
|
|
235
|
+
// touches a second path — and would overwrite a backup already there.
|
|
236
|
+
// Naming it keeps "the set shown is the set touched" literally true,
|
|
237
|
+
// and the copy is the undo for this command, so it is kept.
|
|
238
|
+
if (action.kind === 'write-settings') {
|
|
239
|
+
lines.push(` ${action.path}.bak (previous contents, overwritten if one exists)`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
lines.push('');
|
|
244
|
+
lines.push(' it keeps:');
|
|
245
|
+
for (const k of plan.kept)
|
|
246
|
+
lines.push(` · ${k}`);
|
|
247
|
+
}
|
|
248
|
+
for (const w of plan.warnings)
|
|
249
|
+
lines.push(` ! ${w}`);
|
|
250
|
+
return lines;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Delete one path, reporting anything that is not "it was already gone".
|
|
254
|
+
*
|
|
255
|
+
* `ENOENT` is the only benign failure: a concurrent `sonata sync` or a
|
|
256
|
+
* half-finished earlier reset is a race, not a fault. Every other errno —
|
|
257
|
+
* `EACCES`, `EPERM`, `EBUSY`, `EISDIR` — means the artifact is still there,
|
|
258
|
+
* and swallowing it let the command report success over a setup it had not
|
|
259
|
+
* removed.
|
|
260
|
+
*/
|
|
261
|
+
function removePath(path, touched, failures) {
|
|
262
|
+
try {
|
|
263
|
+
unlinkSync(path);
|
|
264
|
+
touched.push(path);
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
if (err.code === 'ENOENT')
|
|
268
|
+
return;
|
|
269
|
+
failures.push(`${path}: ${err.message}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** Carry out a plan. */
|
|
273
|
+
export function applyReset(plan) {
|
|
274
|
+
const touched = [];
|
|
275
|
+
const failures = [];
|
|
276
|
+
for (const action of plan.actions) {
|
|
277
|
+
switch (action.kind) {
|
|
278
|
+
case 'delete-file':
|
|
279
|
+
removePath(action.path, touched, failures);
|
|
280
|
+
break;
|
|
281
|
+
case 'delete-dir':
|
|
282
|
+
try {
|
|
283
|
+
rmSync(action.path, { recursive: true, force: true });
|
|
284
|
+
touched.push(action.path);
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
failures.push(`${action.path}: ${err.message}`);
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
case 'delete-agents':
|
|
291
|
+
for (const f of action.files)
|
|
292
|
+
removePath(join(action.dir, f), touched, failures);
|
|
293
|
+
break;
|
|
294
|
+
case 'write-file':
|
|
295
|
+
try {
|
|
296
|
+
writeFileSync(action.path, action.content);
|
|
297
|
+
touched.push(action.path);
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
failures.push(`${action.path}: ${err.message}`);
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
case 'write-settings':
|
|
304
|
+
try {
|
|
305
|
+
writeSettings(action.path, action.settings);
|
|
306
|
+
touched.push(action.path);
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
failures.push(`${action.path}: ${err.message}`);
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return { touched, failures };
|
|
315
|
+
}
|
|
316
|
+
export async function cmdReset(opts, io) {
|
|
317
|
+
const plan = planReset(opts);
|
|
318
|
+
for (const line of describeReset(plan))
|
|
319
|
+
io.out(line);
|
|
320
|
+
if (plan.actions.length === 0)
|
|
321
|
+
return 0;
|
|
322
|
+
io.out('');
|
|
323
|
+
if (!opts.yes && !await io.confirm('Remove these?')) {
|
|
324
|
+
io.out(' · nothing removed');
|
|
325
|
+
return 1;
|
|
326
|
+
}
|
|
327
|
+
const { touched, failures } = applyReset(plan);
|
|
328
|
+
io.out(` ✓ removed ${touched.length} path${touched.length === 1 ? '' : 's'}`);
|
|
329
|
+
if (failures.length > 0) {
|
|
330
|
+
// A partial reset that exits 0 is the failure this command exists to
|
|
331
|
+
// avoid: the user believes the setup is gone and it is not.
|
|
332
|
+
io.out(` ! ${failures.length} path(s) could not be removed:`);
|
|
333
|
+
for (const f of failures)
|
|
334
|
+
io.out(` ${f}`);
|
|
335
|
+
return 1;
|
|
336
|
+
}
|
|
337
|
+
io.out(' ❯ run `sonata init` to set up again');
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=reset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/commands/reset.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/route.js
CHANGED
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
18
18
|
import { dirname, join } from 'node:path';
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
|
+
import { spawn } from 'node:child_process';
|
|
20
21
|
import { readSettings, writeSettings, installHook, uninstallHook, hookInstalled } from '../settings.js';
|
|
21
22
|
import { loadConfig, GLOBAL_CONFIG_RELATIVE, NoConfigError, parseConfig } from '../config.js';
|
|
23
|
+
import { assertEffortsPinned, loadAaCatalog } from '../catalog.js';
|
|
24
|
+
import { loadModelsDev } from '../modelsdev.js';
|
|
25
|
+
import { configUpstreamFor } from '../pricing.js';
|
|
22
26
|
import { SONATA_PROJECT_HEADER } from '../native/tenants.js';
|
|
23
27
|
import { SONATA_TOKEN_HEADER, ensureRouterToken } from '../native/router-token.js';
|
|
24
28
|
import { nativeSessionEnv } from './code.js';
|
|
@@ -290,14 +294,22 @@ export function sessionHookCommand(packageRoot, phase, scope = 'project') {
|
|
|
290
294
|
/**
|
|
291
295
|
* Which subagents are worth turning routing on for.
|
|
292
296
|
*
|
|
293
|
-
* Every agent `sonata sync` generates is `<role
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
297
|
+
* Every agent `sonata sync` generates is `<role>-<tier>`, `native-<role>-…`,
|
|
298
|
+
* or — when a role's two tier lists are element-wise identical and
|
|
299
|
+
* `tiersCollapse` folds them — the bare `<role>`. The four roles are fixed.
|
|
300
|
+
*
|
|
301
|
+
* The role may therefore be the WHOLE name, which is why the boundary is
|
|
302
|
+
* `(-|$)` and not a bare hyphen. Requiring the hyphen silently excluded every
|
|
303
|
+
* collapsed agent: an `explore` dispatch fired no hook, wrote no routing env,
|
|
304
|
+
* and died with `model_not_found` at api.anthropic.com, indistinguishable from
|
|
305
|
+
* a broken agent (measured 2026-09-14). A matcher and a filename rule that
|
|
306
|
+
* disagree about what sonata generates is the failure this shape prevents.
|
|
307
|
+
*
|
|
308
|
+
* Claude Code's own built-ins still do not match: the regex is case-sensitive,
|
|
309
|
+
* so `Explore` and `Plan` are excluded by their capital, and the boundary
|
|
310
|
+
* keeps a longer name like `planner` out rather than matching on its prefix.
|
|
299
311
|
*/
|
|
300
|
-
export const SONATA_AGENT_MATCHER = '^(native-)?(code|review|explore|plan)
|
|
312
|
+
export const SONATA_AGENT_MATCHER = '^(native-)?(code|review|explore|plan)(-|$)';
|
|
301
313
|
/**
|
|
302
314
|
* The SubagentStart/SubagentStop command auto mode installs, alongside the
|
|
303
315
|
* session pair.
|
|
@@ -482,7 +494,15 @@ export async function cmdRoute(action, opts) {
|
|
|
482
494
|
if (!existsSync(globalPath)) {
|
|
483
495
|
throw new NoConfigError(`No sonata.toml found at ${globalPath}. Run \`sonata init\` or create one.`);
|
|
484
496
|
}
|
|
485
|
-
|
|
497
|
+
const config = parseConfig(readFileSync(globalPath, 'utf8'));
|
|
498
|
+
// The project branch above goes through `loadConfig`, which refuses an
|
|
499
|
+
// unpinned effort candidate; this one parsed the text directly, so
|
|
500
|
+
// `route on --global` installed env and hooks for a router that then
|
|
501
|
+
// refuses every request — and reported success. The comment above about
|
|
502
|
+
// `on`/`auto` installing something that depends on the config loading
|
|
503
|
+
// applies to the machine config exactly as it does to the project's.
|
|
504
|
+
assertEffortsPinned(config, loadAaCatalog(opts.home), configUpstreamFor(config, loadModelsDev(opts.home)));
|
|
505
|
+
return { config };
|
|
486
506
|
}
|
|
487
507
|
catch (err) {
|
|
488
508
|
return { config: { native: undefined }, error: err };
|
|
@@ -541,6 +561,67 @@ export async function cmdRoute(action, opts) {
|
|
|
541
561
|
}
|
|
542
562
|
return status(settings);
|
|
543
563
|
}
|
|
564
|
+
/**
|
|
565
|
+
* How long the routing env stays in the settings file before the settle takes
|
|
566
|
+
* it out again.
|
|
567
|
+
*
|
|
568
|
+
* It only has to outlast this session's own read of the file, which was
|
|
569
|
+
* measured as effectively immediate — a session's first routed request landed
|
|
570
|
+
* in the same second as the hook's write. The margin is for a loaded machine,
|
|
571
|
+
* not for a slow mechanism.
|
|
572
|
+
*/
|
|
573
|
+
export const ROUTE_SETTLE_MS = 5_000;
|
|
574
|
+
/**
|
|
575
|
+
* Takes the routing env back out of the settings file, leaving the session
|
|
576
|
+
* registered and still routing.
|
|
577
|
+
*
|
|
578
|
+
* This is the half of the design that buys Remote Control back. A session
|
|
579
|
+
* keeps routing on the value it has already read — measured 2026-09-14 at 54
|
|
580
|
+
* minutes and still routing, confirmed per session id in the ledger rather
|
|
581
|
+
* than by log line — so what the removal changes is not this session but the
|
|
582
|
+
* NEXT one, which then launches into a clean file and keeps Remote Control.
|
|
583
|
+
*
|
|
584
|
+
* The registries are deliberately left alone: clearing this session's
|
|
585
|
+
* registration would make the next SessionEnd believe it was the last one out
|
|
586
|
+
* and tear routing down under a session that is still working.
|
|
587
|
+
*
|
|
588
|
+
* **Only the newest registered session clears.** Two sessions starting inside
|
|
589
|
+
* one settle window otherwise race: s1's settle fires while s2 has written the
|
|
590
|
+
* env but may not have read it yet, and s2 then runs unrouted — silently, and
|
|
591
|
+
* indistinguishably from a broken agent, which is the single failure this
|
|
592
|
+
* design exists to remove. Deferring to the newest registration means the env
|
|
593
|
+
* survives until a full window after the LAST start, and every session in the
|
|
594
|
+
* burst has had its own window.
|
|
595
|
+
*
|
|
596
|
+
* The check and the removal are taken under the session lock, the same lock
|
|
597
|
+
* `cmdRouteSession('start')` holds while it registers and writes the env, so
|
|
598
|
+
* "is anyone newer?" cannot be answered against a list that changes underneath.
|
|
599
|
+
*
|
|
600
|
+
* A session that is no longer registered does nothing. That direction is
|
|
601
|
+
* deliberate: an env left in place costs the NEXT session's Remote Control,
|
|
602
|
+
* which is visible at launch and recoverable, while clearing one that a live
|
|
603
|
+
* session has not read yet costs that session's routing, which is not.
|
|
604
|
+
*
|
|
605
|
+
* The honest limit: the earlier `bdf8e27` experiment saw a session keep
|
|
606
|
+
* routing for tens of minutes after a removal and then stop, so the value a
|
|
607
|
+
* session holds is known to be long-lived and NOT known to be permanent.
|
|
608
|
+
*/
|
|
609
|
+
export async function cmdRouteSettle(sessionId, opts, deps = {}) {
|
|
610
|
+
const scope = opts.scope ?? 'project';
|
|
611
|
+
const wait = deps.delay ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
612
|
+
await wait(ROUTE_SETTLE_MS);
|
|
613
|
+
const registry = routeSessionsFile(opts.cwd, scope, opts.home);
|
|
614
|
+
await withSessionLock(registry, () => {
|
|
615
|
+
const registered = readSessions(registry);
|
|
616
|
+
// The newest registration, not merely membership. Registration order is
|
|
617
|
+
// the file's order — `cmdRouteSession('start')` appends — so the last
|
|
618
|
+
// entry is the most recent session to have written the env, and only its
|
|
619
|
+
// own settle may take that env away again.
|
|
620
|
+
if (registered[registered.length - 1] !== sessionId)
|
|
621
|
+
return;
|
|
622
|
+
routeOffKeepingRegistries(opts, scope);
|
|
623
|
+
});
|
|
624
|
+
}
|
|
544
625
|
export async function cmdRouteSession(phase, sessionId, opts, deps = {}) {
|
|
545
626
|
// ONE scope resolution for this entry point; everything below takes the
|
|
546
627
|
// resolved value. The writer and the cleaner of `route-subagents.json` used
|
|
@@ -645,9 +726,15 @@ export async function cmdRouteSession(phase, sessionId, opts, deps = {}) {
|
|
|
645
726
|
}
|
|
646
727
|
}
|
|
647
728
|
}
|
|
648
|
-
|
|
729
|
+
// Registering and writing the routing env are ONE critical section, because
|
|
730
|
+
// `cmdRouteSettle` decides whether to clear that env by asking who is
|
|
731
|
+
// newest. Split apart, a settle could read the registry between another
|
|
732
|
+
// session's append and its write and answer that question against a list
|
|
733
|
+
// that was about to change.
|
|
734
|
+
await withSessionLock(registry, async () => {
|
|
649
735
|
const current = readSessions(registry);
|
|
650
736
|
writeSessions(registry, current.includes(sessionId) ? current : [...current, sessionId]);
|
|
737
|
+
await cmdRoute('on', opts);
|
|
651
738
|
});
|
|
652
739
|
// Records which project this session belongs to, so `sonata usage --by
|
|
653
740
|
// project` can attribute the router's rows. Guarded: a session must start
|
|
@@ -656,14 +743,50 @@ export async function cmdRouteSession(phase, sessionId, opts, deps = {}) {
|
|
|
656
743
|
await recordSession(opts.home, { session: sessionId, cwd: opts.cwd, started: new Date().toISOString() });
|
|
657
744
|
}
|
|
658
745
|
catch { /* attribution is a nicety; starting the session is not */ }
|
|
659
|
-
//
|
|
660
|
-
//
|
|
661
|
-
//
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
//
|
|
746
|
+
// Routes, and then schedules the settle that takes the env back out.
|
|
747
|
+
//
|
|
748
|
+
// Routing here once made `route auto` degrade into `route on` — the env
|
|
749
|
+
// stayed for as long as any session lived, so every later session read it at
|
|
750
|
+
// startup and lost Remote Control. The answer then was to route at
|
|
751
|
+
// SubagentStart instead, which routes nothing: the subagent that FIRES that
|
|
752
|
+
// hook has already resolved its endpoint, so it reaches api.anthropic.com
|
|
753
|
+
// and dies with `model_not_found`, which reads as a broken agent.
|
|
754
|
+
//
|
|
755
|
+
// Writing it HERE happens before any subagent exists, so nothing races it;
|
|
756
|
+
// taking it out again a moment later is what keeps the next session's launch
|
|
757
|
+
// clean. Both halves were measured on 2026-09-14 (Claude Code 2.1.270) — see
|
|
758
|
+
// `cmdRouteSettle` and the tests.
|
|
759
|
+
(deps.settle ?? ((id) => spawnSettle(id, opts, scope)))(sessionId);
|
|
665
760
|
const after = readSessions(registry);
|
|
666
|
-
return { sessions: after.length, routing: '
|
|
761
|
+
return { sessions: after.length, routing: 'on' };
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Runs the settle in a detached child, so the hook that started this session
|
|
765
|
+
* returns immediately.
|
|
766
|
+
*
|
|
767
|
+
* The delay must not be waited out in-process: Claude Code blocks on a
|
|
768
|
+
* SessionStart hook, so every session would pay it before showing a prompt.
|
|
769
|
+
*/
|
|
770
|
+
function spawnSettle(sessionId, opts, scope) {
|
|
771
|
+
try {
|
|
772
|
+
// The installed CLI, resolved the same way the hook commands are, rather
|
|
773
|
+
// than `process.argv[1]`: that is whatever binary happens to be running
|
|
774
|
+
// this code — a test runner, an editor integration — and re-invoking it
|
|
775
|
+
// with sonata's arguments is not a thing to do blind. A package root with
|
|
776
|
+
// no built CLI simply schedules nothing.
|
|
777
|
+
const cli = join(opts.packageRoot, 'dist', 'cli.js');
|
|
778
|
+
if (!existsSync(cli))
|
|
779
|
+
return;
|
|
780
|
+
const args = [cli, 'route', 'session-settle', '--id', sessionId];
|
|
781
|
+
if (scope === 'global')
|
|
782
|
+
args.push('--global');
|
|
783
|
+
spawn(process.execPath, args, { cwd: opts.cwd, detached: true, stdio: 'ignore' }).unref();
|
|
784
|
+
}
|
|
785
|
+
catch {
|
|
786
|
+
// A settle that cannot be scheduled costs the NEXT session's Remote
|
|
787
|
+
// Control, never this session's routing. Failing the hook over it would
|
|
788
|
+
// trade a small loss for the whole feature.
|
|
789
|
+
}
|
|
667
790
|
}
|
|
668
791
|
/**
|
|
669
792
|
* Removes the routing env, leaving both registries alone.
|