@phnx-labs/agents-cli 1.22.21 → 1.22.23
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/CHANGELOG.md +256 -0
- package/README.md +8 -2
- package/dist/commands/doctor.js +15 -7
- package/dist/commands/exec.js +20 -6
- package/dist/commands/focus.d.ts +76 -4
- package/dist/commands/focus.js +219 -40
- package/dist/commands/fork.d.ts +20 -2
- package/dist/commands/fork.js +91 -64
- package/dist/commands/go.d.ts +25 -0
- package/dist/commands/go.js +63 -2
- package/dist/commands/harness-wizard.d.ts +206 -0
- package/dist/commands/harness-wizard.js +403 -0
- package/dist/commands/harness.d.ts +12 -0
- package/dist/commands/harness.js +97 -101
- package/dist/commands/insights.d.ts +32 -0
- package/dist/commands/insights.js +478 -0
- package/dist/commands/resume.js +10 -3
- package/dist/commands/secrets.js +25 -30
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +1 -0
- package/dist/commands/sessions-resume.d.ts +21 -3
- package/dist/commands/sessions-resume.js +50 -11
- package/dist/commands/sessions.d.ts +81 -5
- package/dist/commands/sessions.js +331 -66
- package/dist/commands/watchdog.js +13 -2
- package/dist/index.js +3 -1
- package/dist/lib/agents.d.ts +1 -1
- package/dist/lib/agents.js +132 -0
- package/dist/lib/codex-policy.d.ts +17 -0
- package/dist/lib/codex-policy.js +48 -0
- package/dist/lib/crabbox/lease.d.ts +25 -0
- package/dist/lib/crabbox/lease.js +62 -0
- package/dist/lib/daemon.js +70 -0
- package/dist/lib/exec.d.ts +4 -0
- package/dist/lib/exec.js +88 -54
- package/dist/lib/feed-broadcast.d.ts +1 -20
- package/dist/lib/feed-broadcast.js +31 -1
- package/dist/lib/hooks.js +12 -2
- package/dist/lib/hosts/passthrough.js +1 -0
- package/dist/lib/mcp.js +44 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
- package/dist/lib/models.d.ts +0 -5
- package/dist/lib/models.js +48 -0
- package/dist/lib/picker.d.ts +45 -0
- package/dist/lib/picker.js +75 -6
- package/dist/lib/plugin-marketplace.js +9 -0
- package/dist/lib/pricing/prices.json +119 -92
- package/dist/lib/pricing/table.js +13 -0
- package/dist/lib/remote-agents-json.d.ts +29 -1
- package/dist/lib/remote-agents-json.js +47 -10
- package/dist/lib/resources/mcp.js +2 -0
- package/dist/lib/resources/permissions.js +3 -0
- package/dist/lib/resources/types.d.ts +2 -1
- package/dist/lib/runner.js +28 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +118 -49
- package/dist/lib/secrets/reaper.d.ts +87 -0
- package/dist/lib/secrets/reaper.js +184 -0
- package/dist/lib/secrets/remote.d.ts +29 -0
- package/dist/lib/secrets/remote.js +37 -1
- package/dist/lib/session/active.d.ts +36 -1
- package/dist/lib/session/active.js +60 -19
- package/dist/lib/session/actor-sidecar.d.ts +14 -0
- package/dist/lib/session/actor-sidecar.js +67 -4
- package/dist/lib/session/db.d.ts +35 -1
- package/dist/lib/session/db.js +132 -1
- package/dist/lib/session/digest.d.ts +3 -0
- package/dist/lib/session/digest.js +3 -3
- package/dist/lib/session/discover.js +168 -0
- package/dist/lib/session/insights.d.ts +126 -0
- package/dist/lib/session/insights.js +330 -0
- package/dist/lib/session/parse.d.ts +29 -2
- package/dist/lib/session/parse.js +111 -5
- package/dist/lib/session/remote-list.d.ts +10 -1
- package/dist/lib/session/remote-list.js +2 -8
- package/dist/lib/session/remote.d.ts +57 -3
- package/dist/lib/session/remote.js +90 -26
- package/dist/lib/session/resume-command.d.ts +6 -0
- package/dist/lib/session/resume-command.js +8 -0
- package/dist/lib/session/session-cache.d.ts +173 -0
- package/dist/lib/session/session-cache.js +399 -0
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/session/width.d.ts +1 -1
- package/dist/lib/session/width.js +12 -2
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +40 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/usage.d.ts +13 -0
- package/dist/lib/usage.js +254 -3
- package/dist/lib/versions.js +13 -2
- package/package.json +1 -1
- package/dist/bin/agents +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
package/dist/lib/picker.js
CHANGED
|
@@ -17,12 +17,47 @@ import chalk from 'chalk';
|
|
|
17
17
|
import { stripVTControlCharacters } from 'node:util';
|
|
18
18
|
const DEFAULT_TERMINAL_ROWS = 24;
|
|
19
19
|
const DEFAULT_TERMINAL_WIDTH = 80;
|
|
20
|
+
/**
|
|
21
|
+
* Rows the detail preview is guaranteed when it is open and a row is selected.
|
|
22
|
+
* The list page is capped so this floor always fits the viewport — without it a
|
|
23
|
+
* long list (PICKER_RECENT_COUNT = 15) consumes the whole default 24-row
|
|
24
|
+
* terminal, `availablePreviewRows` goes <= 0, and the preview silently collapses
|
|
25
|
+
* to empty (RUSH-2198).
|
|
26
|
+
*/
|
|
27
|
+
export const PREVIEW_MIN_ROWS = 6;
|
|
28
|
+
/** Floor for the visible list page, so a short terminal still shows a few rows. */
|
|
29
|
+
export const PICKER_MIN_LIST_ROWS = 3;
|
|
20
30
|
function terminalWidth() {
|
|
21
31
|
return Math.max(1, process.stdout.columns || DEFAULT_TERMINAL_WIDTH);
|
|
22
32
|
}
|
|
23
33
|
function terminalRows() {
|
|
24
34
|
return Math.max(1, process.stdout.rows || DEFAULT_TERMINAL_ROWS);
|
|
25
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Cap the visible list page so an open preview keeps a guaranteed floor of rows.
|
|
38
|
+
*
|
|
39
|
+
* The picker renders header + list page + separator + preview + help. When the
|
|
40
|
+
* requested page size (e.g. 15) is large enough to fill the terminal on its own,
|
|
41
|
+
* the preview has no room left and collapses. This reserves
|
|
42
|
+
* {@link PREVIEW_MIN_ROWS} (plus its separator) for the preview and hands the
|
|
43
|
+
* list whatever remains, never below {@link PICKER_MIN_LIST_ROWS}.
|
|
44
|
+
*
|
|
45
|
+
* `chromeRows` counts the fixed non-list, non-preview lines (header, subtitle,
|
|
46
|
+
* help, any flash). `linesAbovePrompt` counts lines the caller printed above the
|
|
47
|
+
* Inquirer prompt that have scrolled the viewport but the picker cannot measure —
|
|
48
|
+
* today the session picker passes the hidden-session footer; subtracting it keeps
|
|
49
|
+
* that notice on screen alongside the preview. (The fleet browser folds its
|
|
50
|
+
* unreachable-peer warning into the header instead, so it needs no reserve here.)
|
|
51
|
+
*/
|
|
52
|
+
export function pickerPageSize(opts) {
|
|
53
|
+
const previewMinRows = opts.previewMinRows ?? PREVIEW_MIN_ROWS;
|
|
54
|
+
const minListRows = opts.minListRows ?? PICKER_MIN_LIST_ROWS;
|
|
55
|
+
const linesAbove = Math.max(0, opts.linesAbovePrompt ?? 0);
|
|
56
|
+
// The separator line rides with the preview only when it is open.
|
|
57
|
+
const previewReserve = opts.previewOpen ? previewMinRows + 1 : 0;
|
|
58
|
+
const budget = opts.terminalRows - linesAbove - opts.chromeRows - previewReserve;
|
|
59
|
+
return Math.max(minListRows, Math.min(opts.requestedPageSize, budget));
|
|
60
|
+
}
|
|
26
61
|
function renderedRows(text, width) {
|
|
27
62
|
const normalizedWidth = Math.max(1, width);
|
|
28
63
|
return text.split('\n').reduce((rows, line) => {
|
|
@@ -157,6 +192,17 @@ export function itemPicker(config) {
|
|
|
157
192
|
: '(type to filter)';
|
|
158
193
|
const searchStr = searchTerm ? chalk.cyan(searchTerm) : chalk.gray(placeholder);
|
|
159
194
|
const header = [prefix, message, searchStr].filter(Boolean).join(' ');
|
|
195
|
+
// Cap the list page so an open preview keeps a guaranteed floor of rows.
|
|
196
|
+
// chrome = header + optional subtitle + help; the preview separator is
|
|
197
|
+
// reserved inside pickerPageSize.
|
|
198
|
+
const chromeRows = 1 + (cfg.subtitle ? 1 : 0) + 1;
|
|
199
|
+
const effectivePageSize = pickerPageSize({
|
|
200
|
+
requestedPageSize: cfg.pageSize ?? 10,
|
|
201
|
+
terminalRows: terminalRows(),
|
|
202
|
+
chromeRows,
|
|
203
|
+
previewOpen: previewOpen && Boolean(cfg.buildPreview),
|
|
204
|
+
linesAbovePrompt: cfg.linesAbovePrompt,
|
|
205
|
+
});
|
|
160
206
|
const page = usePagination({
|
|
161
207
|
items: results,
|
|
162
208
|
active,
|
|
@@ -167,7 +213,7 @@ export function itemPicker(config) {
|
|
|
167
213
|
const row = isActive ? chalk.bold(item.label) : item.label;
|
|
168
214
|
return `${cursor} ${row}`;
|
|
169
215
|
},
|
|
170
|
-
pageSize:
|
|
216
|
+
pageSize: effectivePageSize,
|
|
171
217
|
loop: false,
|
|
172
218
|
});
|
|
173
219
|
const enter = cfg.enterHint ?? 'select';
|
|
@@ -188,7 +234,7 @@ export function itemPicker(config) {
|
|
|
188
234
|
renderedRows(parts.slice(1).join('\n'), width) +
|
|
189
235
|
renderedRows(separator, width) +
|
|
190
236
|
renderedRows(help, width);
|
|
191
|
-
const availablePreviewRows = terminalRows() - fixedRows;
|
|
237
|
+
const availablePreviewRows = terminalRows() - Math.max(0, cfg.linesAbovePrompt ?? 0) - fixedRows;
|
|
192
238
|
const preview = limitPreviewHeight(cfg.buildPreview(selected.value), availablePreviewRows, width);
|
|
193
239
|
if (preview) {
|
|
194
240
|
parts.push(separator);
|
|
@@ -283,6 +329,17 @@ export function multiItemPicker(config) {
|
|
|
283
329
|
const placeholder = '(type to filter · space to toggle · enter to resume)';
|
|
284
330
|
const searchStr = searchTerm ? chalk.cyan(searchTerm) : chalk.gray(placeholder);
|
|
285
331
|
const header = [prefix, message, searchStr].filter(Boolean).join(' ');
|
|
332
|
+
// Cap the list page so an open preview keeps a guaranteed floor of rows.
|
|
333
|
+
// chrome = header + help; the preview separator is reserved inside
|
|
334
|
+
// pickerPageSize.
|
|
335
|
+
const chromeRows = 2;
|
|
336
|
+
const effectivePageSize = pickerPageSize({
|
|
337
|
+
requestedPageSize: cfg.pageSize ?? 10,
|
|
338
|
+
terminalRows: terminalRows(),
|
|
339
|
+
chromeRows,
|
|
340
|
+
previewOpen: previewOpen && Boolean(cfg.buildPreview),
|
|
341
|
+
linesAbovePrompt: cfg.linesAbovePrompt,
|
|
342
|
+
});
|
|
286
343
|
const page = usePagination({
|
|
287
344
|
items: results,
|
|
288
345
|
active,
|
|
@@ -295,7 +352,7 @@ export function multiItemPicker(config) {
|
|
|
295
352
|
const row = isActive ? chalk.bold(item.label) : item.label;
|
|
296
353
|
return `${cursor} ${box} ${row}`;
|
|
297
354
|
},
|
|
298
|
-
pageSize:
|
|
355
|
+
pageSize: effectivePageSize,
|
|
299
356
|
loop: false,
|
|
300
357
|
});
|
|
301
358
|
const enter = cfg.enterHint ?? 'resume';
|
|
@@ -312,7 +369,7 @@ export function multiItemPicker(config) {
|
|
|
312
369
|
renderedRows(parts.slice(1).join('\n'), width) +
|
|
313
370
|
renderedRows(separator, width) +
|
|
314
371
|
renderedRows(help, width);
|
|
315
|
-
const availablePreviewRows = terminalRows() - fixedRows;
|
|
372
|
+
const availablePreviewRows = terminalRows() - Math.max(0, cfg.linesAbovePrompt ?? 0) - fixedRows;
|
|
316
373
|
const preview = limitPreviewHeight(cfg.buildPreview(selected.value), availablePreviewRows, width);
|
|
317
374
|
if (preview) {
|
|
318
375
|
parts.push(separator);
|
|
@@ -553,6 +610,18 @@ export function dynamicPicker(config) {
|
|
|
553
610
|
headerBits.push(chalk.cyan('/' + query));
|
|
554
611
|
}
|
|
555
612
|
const header = headerBits.filter(Boolean).join(' ');
|
|
613
|
+
// Cap the list page so an open preview keeps a guaranteed floor of rows.
|
|
614
|
+
// chrome = header + help + optional flash line; the preview separator is
|
|
615
|
+
// reserved inside pickerPageSize. Only the loaded list steals viewport, so
|
|
616
|
+
// skip the cap while the loading placeholder is showing.
|
|
617
|
+
const chromeRows = 2 + (flash ? renderedRows(flash, terminalWidth()) : 0);
|
|
618
|
+
const effectivePageSize = pickerPageSize({
|
|
619
|
+
requestedPageSize: cfg.pageSize ?? 12,
|
|
620
|
+
terminalRows: terminalRows(),
|
|
621
|
+
chromeRows,
|
|
622
|
+
previewOpen: previewOpen && Boolean(cfg.buildPreview) && !loading,
|
|
623
|
+
linesAbovePrompt: cfg.linesAbovePrompt,
|
|
624
|
+
});
|
|
556
625
|
const page = usePagination({
|
|
557
626
|
items: results,
|
|
558
627
|
active,
|
|
@@ -563,7 +632,7 @@ export function dynamicPicker(config) {
|
|
|
563
632
|
const row = isActive ? chalk.bold(item.label) : item.label;
|
|
564
633
|
return `${cursor} ${row}`;
|
|
565
634
|
},
|
|
566
|
-
pageSize:
|
|
635
|
+
pageSize: effectivePageSize,
|
|
567
636
|
loop: false,
|
|
568
637
|
});
|
|
569
638
|
const help = chalk.gray(cfg.helpFor
|
|
@@ -590,7 +659,7 @@ export function dynamicPicker(config) {
|
|
|
590
659
|
renderedRows(separator, width) +
|
|
591
660
|
renderedRows(help, width) +
|
|
592
661
|
flashRows;
|
|
593
|
-
const availablePreviewRows = terminalRows() - fixedRows;
|
|
662
|
+
const availablePreviewRows = terminalRows() - Math.max(0, cfg.linesAbovePrompt ?? 0) - fixedRows;
|
|
594
663
|
const preview = limitPreviewHeight(cfg.buildPreview(selected.value), availablePreviewRows, width);
|
|
595
664
|
if (preview) {
|
|
596
665
|
parts.push(separator);
|
|
@@ -121,6 +121,11 @@ function dirExists(p) {
|
|
|
121
121
|
}
|
|
122
122
|
// ─── Per-version paths ────────────────────────────────────────────────────────
|
|
123
123
|
function pluginsRootForVersion(agent, versionHome) {
|
|
124
|
+
// Muse Code keeps its plugin store under XDG data (`~/.local/share/muse/plugins`),
|
|
125
|
+
// not under the config dir. Under HOME isolation the version home is $HOME.
|
|
126
|
+
if (agent === 'muse') {
|
|
127
|
+
return path.join(versionHome, '.local', 'share', 'muse', 'plugins');
|
|
128
|
+
}
|
|
124
129
|
return path.join(versionHome, agentConfigDirName(agent), 'plugins');
|
|
125
130
|
}
|
|
126
131
|
export function marketplaceRoot(specOrName, agent, versionHome) {
|
|
@@ -678,6 +683,10 @@ export function addPluginToSettings(pluginName, marketplaceName, agent, versionH
|
|
|
678
683
|
settings = {};
|
|
679
684
|
}
|
|
680
685
|
}
|
|
686
|
+
// Muse refuses to start without schema_version: 1.
|
|
687
|
+
if (agent === 'muse' && settings.schema_version === undefined) {
|
|
688
|
+
settings.schema_version = 1;
|
|
689
|
+
}
|
|
681
690
|
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== 'object') {
|
|
682
691
|
settings.enabledPlugins = {};
|
|
683
692
|
}
|
|
@@ -1,153 +1,180 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2026-08-
|
|
2
|
+
"version": "2026-08-05",
|
|
3
3
|
"models": {
|
|
4
4
|
"claude-opus-4": {
|
|
5
|
+
"inputPerToken": 5e-06,
|
|
6
|
+
"outputPerToken": 2.5e-05,
|
|
7
|
+
"cacheReadPerToken": 5e-07,
|
|
8
|
+
"cacheWritePerToken": 6.25e-06
|
|
9
|
+
},
|
|
10
|
+
"claude-sonnet-4": {
|
|
11
|
+
"inputPerToken": 3e-06,
|
|
12
|
+
"outputPerToken": 1.5e-05,
|
|
13
|
+
"cacheReadPerToken": 3e-07,
|
|
14
|
+
"cacheWritePerToken": 3.75e-06
|
|
15
|
+
},
|
|
16
|
+
"claude-haiku-4": {
|
|
17
|
+
"inputPerToken": 1e-06,
|
|
18
|
+
"outputPerToken": 5e-06,
|
|
19
|
+
"cacheReadPerToken": 1e-07,
|
|
20
|
+
"cacheWritePerToken": 1.25e-06
|
|
21
|
+
},
|
|
22
|
+
"claude-opus-5": {
|
|
5
23
|
"inputPerToken": 0.000005,
|
|
6
24
|
"outputPerToken": 0.000025,
|
|
7
25
|
"cacheReadPerToken": 0.0000005,
|
|
8
26
|
"cacheWritePerToken": 0.00000625
|
|
9
27
|
},
|
|
10
|
-
"claude-sonnet-
|
|
11
|
-
"inputPerToken": 0.
|
|
12
|
-
"outputPerToken": 0.
|
|
13
|
-
"cacheReadPerToken": 0.
|
|
14
|
-
"cacheWritePerToken": 0.
|
|
15
|
-
},
|
|
16
|
-
"claude-haiku-4": {
|
|
17
|
-
"inputPerToken": 0.000001,
|
|
18
|
-
"outputPerToken": 0.000005,
|
|
19
|
-
"cacheReadPerToken": 0.0000001,
|
|
20
|
-
"cacheWritePerToken": 0.00000125
|
|
28
|
+
"claude-sonnet-5": {
|
|
29
|
+
"inputPerToken": 0.000002,
|
|
30
|
+
"outputPerToken": 0.00001,
|
|
31
|
+
"cacheReadPerToken": 0.0000002,
|
|
32
|
+
"cacheWritePerToken": 0.0000025
|
|
21
33
|
},
|
|
22
34
|
"claude-fable-5": {
|
|
23
|
-
"inputPerToken":
|
|
24
|
-
"outputPerToken":
|
|
25
|
-
"cacheReadPerToken":
|
|
26
|
-
"cacheWritePerToken":
|
|
35
|
+
"inputPerToken": 1e-05,
|
|
36
|
+
"outputPerToken": 5e-05,
|
|
37
|
+
"cacheReadPerToken": 1e-06,
|
|
38
|
+
"cacheWritePerToken": 1.25e-05
|
|
27
39
|
},
|
|
28
40
|
"claude-mythos-5": {
|
|
29
|
-
"inputPerToken":
|
|
30
|
-
"outputPerToken":
|
|
31
|
-
"cacheReadPerToken":
|
|
32
|
-
"cacheWritePerToken":
|
|
41
|
+
"inputPerToken": 1e-05,
|
|
42
|
+
"outputPerToken": 5e-05,
|
|
43
|
+
"cacheReadPerToken": 1e-06,
|
|
44
|
+
"cacheWritePerToken": 1.25e-05
|
|
33
45
|
},
|
|
34
46
|
"claude-3-5-sonnet": {
|
|
35
|
-
"inputPerToken":
|
|
36
|
-
"outputPerToken":
|
|
37
|
-
"cacheReadPerToken":
|
|
38
|
-
"cacheWritePerToken":
|
|
47
|
+
"inputPerToken": 3e-06,
|
|
48
|
+
"outputPerToken": 1.5e-05,
|
|
49
|
+
"cacheReadPerToken": 3e-07,
|
|
50
|
+
"cacheWritePerToken": 3.75e-06
|
|
39
51
|
},
|
|
40
52
|
"claude-3-5-haiku": {
|
|
41
|
-
"inputPerToken":
|
|
42
|
-
"outputPerToken":
|
|
43
|
-
"cacheReadPerToken":
|
|
44
|
-
"cacheWritePerToken":
|
|
53
|
+
"inputPerToken": 8e-07,
|
|
54
|
+
"outputPerToken": 4e-06,
|
|
55
|
+
"cacheReadPerToken": 8e-08,
|
|
56
|
+
"cacheWritePerToken": 1e-06
|
|
45
57
|
},
|
|
46
58
|
"claude-3-opus": {
|
|
47
|
-
"inputPerToken":
|
|
48
|
-
"outputPerToken":
|
|
49
|
-
"cacheReadPerToken":
|
|
50
|
-
"cacheWritePerToken":
|
|
59
|
+
"inputPerToken": 1.5e-05,
|
|
60
|
+
"outputPerToken": 7.5e-05,
|
|
61
|
+
"cacheReadPerToken": 1.5e-06,
|
|
62
|
+
"cacheWritePerToken": 1.875e-05
|
|
51
63
|
},
|
|
52
64
|
"gpt-5.6-sol": {
|
|
53
|
-
"inputPerToken":
|
|
54
|
-
"outputPerToken":
|
|
55
|
-
"cacheReadPerToken":
|
|
65
|
+
"inputPerToken": 5e-06,
|
|
66
|
+
"outputPerToken": 3e-05,
|
|
67
|
+
"cacheReadPerToken": 5e-07
|
|
56
68
|
},
|
|
57
69
|
"gpt-5.6-terra": {
|
|
58
|
-
"inputPerToken":
|
|
59
|
-
"outputPerToken":
|
|
60
|
-
"cacheReadPerToken":
|
|
70
|
+
"inputPerToken": 2.5e-06,
|
|
71
|
+
"outputPerToken": 1.5e-05,
|
|
72
|
+
"cacheReadPerToken": 2.5e-07
|
|
61
73
|
},
|
|
62
74
|
"gpt-5.6-luna": {
|
|
63
|
-
"inputPerToken":
|
|
64
|
-
"outputPerToken":
|
|
65
|
-
"cacheReadPerToken":
|
|
75
|
+
"inputPerToken": 1e-06,
|
|
76
|
+
"outputPerToken": 6e-06,
|
|
77
|
+
"cacheReadPerToken": 1e-07
|
|
66
78
|
},
|
|
67
79
|
"gpt-5.5": {
|
|
68
|
-
"inputPerToken":
|
|
69
|
-
"outputPerToken":
|
|
70
|
-
"cacheReadPerToken":
|
|
80
|
+
"inputPerToken": 5e-06,
|
|
81
|
+
"outputPerToken": 3e-05,
|
|
82
|
+
"cacheReadPerToken": 5e-07
|
|
71
83
|
},
|
|
72
84
|
"gpt-5.4": {
|
|
73
|
-
"inputPerToken":
|
|
74
|
-
"outputPerToken":
|
|
75
|
-
"cacheReadPerToken":
|
|
85
|
+
"inputPerToken": 2.5e-06,
|
|
86
|
+
"outputPerToken": 1.5e-05,
|
|
87
|
+
"cacheReadPerToken": 2.5e-07
|
|
76
88
|
},
|
|
77
89
|
"gpt-5.4-mini": {
|
|
78
|
-
"inputPerToken":
|
|
79
|
-
"outputPerToken":
|
|
80
|
-
"cacheReadPerToken":
|
|
90
|
+
"inputPerToken": 7.5e-07,
|
|
91
|
+
"outputPerToken": 4.5e-06,
|
|
92
|
+
"cacheReadPerToken": 7.5e-08
|
|
81
93
|
},
|
|
82
94
|
"gpt-5.4-nano": {
|
|
83
|
-
"inputPerToken":
|
|
84
|
-
"outputPerToken":
|
|
85
|
-
"cacheReadPerToken":
|
|
95
|
+
"inputPerToken": 2e-07,
|
|
96
|
+
"outputPerToken": 1.25e-06,
|
|
97
|
+
"cacheReadPerToken": 2e-08
|
|
86
98
|
},
|
|
87
99
|
"gpt-5": {
|
|
88
|
-
"inputPerToken":
|
|
89
|
-
"outputPerToken":
|
|
90
|
-
"cacheReadPerToken":
|
|
100
|
+
"inputPerToken": 1.25e-06,
|
|
101
|
+
"outputPerToken": 1e-05,
|
|
102
|
+
"cacheReadPerToken": 1.25e-07
|
|
91
103
|
},
|
|
92
104
|
"gpt-4.1": {
|
|
93
|
-
"inputPerToken":
|
|
94
|
-
"outputPerToken":
|
|
95
|
-
"cacheReadPerToken":
|
|
105
|
+
"inputPerToken": 2e-06,
|
|
106
|
+
"outputPerToken": 8e-06,
|
|
107
|
+
"cacheReadPerToken": 5e-07
|
|
96
108
|
},
|
|
97
109
|
"gpt-4.1-mini": {
|
|
98
|
-
"inputPerToken":
|
|
99
|
-
"outputPerToken":
|
|
100
|
-
"cacheReadPerToken":
|
|
110
|
+
"inputPerToken": 4e-07,
|
|
111
|
+
"outputPerToken": 1.6e-06,
|
|
112
|
+
"cacheReadPerToken": 1e-07
|
|
101
113
|
},
|
|
102
114
|
"gpt-4.1-nano": {
|
|
103
|
-
"inputPerToken":
|
|
104
|
-
"outputPerToken":
|
|
105
|
-
"cacheReadPerToken":
|
|
115
|
+
"inputPerToken": 1e-07,
|
|
116
|
+
"outputPerToken": 4e-07,
|
|
117
|
+
"cacheReadPerToken": 2.5e-08
|
|
106
118
|
},
|
|
107
119
|
"gpt-4o": {
|
|
108
|
-
"inputPerToken":
|
|
109
|
-
"outputPerToken":
|
|
110
|
-
"cacheReadPerToken":
|
|
120
|
+
"inputPerToken": 2.5e-06,
|
|
121
|
+
"outputPerToken": 1e-05,
|
|
122
|
+
"cacheReadPerToken": 1.25e-06
|
|
111
123
|
},
|
|
112
124
|
"gpt-4o-mini": {
|
|
113
|
-
"inputPerToken":
|
|
114
|
-
"outputPerToken":
|
|
115
|
-
"cacheReadPerToken":
|
|
125
|
+
"inputPerToken": 1.5e-07,
|
|
126
|
+
"outputPerToken": 6e-07,
|
|
127
|
+
"cacheReadPerToken": 7.5e-08
|
|
116
128
|
},
|
|
117
129
|
"o3": {
|
|
118
|
-
"inputPerToken":
|
|
119
|
-
"outputPerToken":
|
|
120
|
-
"cacheReadPerToken":
|
|
130
|
+
"inputPerToken": 2e-06,
|
|
131
|
+
"outputPerToken": 8e-06,
|
|
132
|
+
"cacheReadPerToken": 5e-07
|
|
121
133
|
},
|
|
122
134
|
"o4-mini": {
|
|
123
|
-
"inputPerToken":
|
|
124
|
-
"outputPerToken":
|
|
125
|
-
"cacheReadPerToken":
|
|
135
|
+
"inputPerToken": 1.1e-06,
|
|
136
|
+
"outputPerToken": 4.4e-06,
|
|
137
|
+
"cacheReadPerToken": 2.75e-07
|
|
126
138
|
},
|
|
127
139
|
"gemini-2.5-pro": {
|
|
128
|
-
"inputPerToken":
|
|
129
|
-
"outputPerToken":
|
|
130
|
-
"cacheReadPerToken":
|
|
140
|
+
"inputPerToken": 1.25e-06,
|
|
141
|
+
"outputPerToken": 1e-05,
|
|
142
|
+
"cacheReadPerToken": 3.125e-07
|
|
131
143
|
},
|
|
132
144
|
"gemini-2.5-flash": {
|
|
133
|
-
"inputPerToken":
|
|
134
|
-
"outputPerToken":
|
|
135
|
-
"cacheReadPerToken":
|
|
145
|
+
"inputPerToken": 3e-07,
|
|
146
|
+
"outputPerToken": 2.5e-06,
|
|
147
|
+
"cacheReadPerToken": 7.5e-08
|
|
136
148
|
},
|
|
137
149
|
"gemini-2.5-flash-lite": {
|
|
138
|
-
"inputPerToken":
|
|
139
|
-
"outputPerToken":
|
|
140
|
-
"cacheReadPerToken":
|
|
150
|
+
"inputPerToken": 1e-07,
|
|
151
|
+
"outputPerToken": 4e-07,
|
|
152
|
+
"cacheReadPerToken": 2.5e-08
|
|
141
153
|
},
|
|
142
154
|
"gemini-1.5-pro": {
|
|
143
|
-
"inputPerToken":
|
|
144
|
-
"outputPerToken":
|
|
145
|
-
"cacheReadPerToken":
|
|
155
|
+
"inputPerToken": 1.25e-06,
|
|
156
|
+
"outputPerToken": 5e-06,
|
|
157
|
+
"cacheReadPerToken": 3.125e-07
|
|
146
158
|
},
|
|
147
159
|
"gemini-1.5-flash": {
|
|
148
|
-
"inputPerToken":
|
|
149
|
-
"outputPerToken":
|
|
150
|
-
"cacheReadPerToken":
|
|
160
|
+
"inputPerToken": 7.5e-08,
|
|
161
|
+
"outputPerToken": 3e-07,
|
|
162
|
+
"cacheReadPerToken": 1.875e-08
|
|
163
|
+
},
|
|
164
|
+
"muse-spark-1.2": {
|
|
165
|
+
"inputPerToken": 1.25e-06,
|
|
166
|
+
"outputPerToken": 4.25e-06,
|
|
167
|
+
"cacheReadPerToken": 1.5e-07
|
|
168
|
+
},
|
|
169
|
+
"muse-spark-1.1": {
|
|
170
|
+
"inputPerToken": 1.25e-06,
|
|
171
|
+
"outputPerToken": 4.25e-06,
|
|
172
|
+
"cacheReadPerToken": 1.5e-07
|
|
173
|
+
},
|
|
174
|
+
"muse-spark-1.2-contributor": {
|
|
175
|
+
"inputPerToken": 1e-07,
|
|
176
|
+
"outputPerToken": 2e-07,
|
|
177
|
+
"cacheReadPerToken": 2e-09
|
|
151
178
|
}
|
|
152
179
|
}
|
|
153
180
|
}
|
|
@@ -5,6 +5,19 @@
|
|
|
5
5
|
* and is imported with a `type: json` attribute so it survives `tsc` emit AND
|
|
6
6
|
* Node ESM's import-attribute requirement at runtime (the package is ESM).
|
|
7
7
|
*
|
|
8
|
+
* ## Keeping it current
|
|
9
|
+
*
|
|
10
|
+
* A model missing from `prices.json` prices to NOTHING — `getModelPricing` returns
|
|
11
|
+
* null and the session contributes $0, silently. That is how 478 `claude-opus-5`
|
|
12
|
+
* sessions read as free: matching is dash-bounded, so `claude-opus-5` cannot fall back
|
|
13
|
+
* to the `claude-opus-4` entry. When a new model ships, add it here in the same change.
|
|
14
|
+
*
|
|
15
|
+
* **Dated rate: Claude Sonnet 5 is priced at its introductory $2/$10 per MTok, which
|
|
16
|
+
* ends 2026-08-31.** From 2026-09-01 the standard rate is $3/$15 (cache write $3.75,
|
|
17
|
+
* cache read $0.30). This table has no notion of an effective date — every row is a
|
|
18
|
+
* single current rate — so that entry MUST be updated then, or Sonnet 5 spend will
|
|
19
|
+
* read ~33% low. Source: https://platform.claude.com/docs/en/about-claude/pricing
|
|
20
|
+
*
|
|
8
21
|
* `getModelPricing` is prefix/suffix-tolerant: real model identifiers carry
|
|
9
22
|
* vendor prefixes (`us.anthropic.`), version dashes (`claude-opus-4-8`), and
|
|
10
23
|
* date suffixes (`-20250514`), none of which appear in the canonical keys. We
|
|
@@ -12,6 +12,34 @@ export interface RemoteAgentsJsonOptions<T> {
|
|
|
12
12
|
quiet?: boolean;
|
|
13
13
|
/** Per-peer deadline. Long-running maintenance commands override 12 seconds. */
|
|
14
14
|
timeoutMs?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Opt-in early-exit for a globally-unique lookup (a full session UUID). When a
|
|
17
|
+
* peer returns an item that satisfies {@link isDefinitive}, the fan-out
|
|
18
|
+
* resolves immediately and SIGTERMs every still-outstanding peer instead of
|
|
19
|
+
* waiting for the slowest one to hit {@link REMOTE_TIMEOUT_MS}.
|
|
20
|
+
*
|
|
21
|
+
* OMITTED BY DEFAULT — tool-search, program-count, and the default session
|
|
22
|
+
* listing keep the all-settle behavior (ambiguity, or a label/prefix conflict,
|
|
23
|
+
* is only known once every peer has answered). Only the full-UUID resolve path
|
|
24
|
+
* opts in, since only a UUID guarantees the first hit is the only hit.
|
|
25
|
+
*/
|
|
26
|
+
earlyExit?: {
|
|
27
|
+
isDefinitive: (item: T, machine: string) => boolean;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The SSH boundary as a swappable dependency so the fan-out logic is testable
|
|
32
|
+
* without a live tailnet. Production wires this to the real {@link sshCapture}.
|
|
33
|
+
*/
|
|
34
|
+
export type SshCaptureFn = (target: string, remoteCmd: string, opts: {
|
|
35
|
+
timeoutMs: number;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}) => Promise<{
|
|
38
|
+
code: number | null;
|
|
39
|
+
stdout: string;
|
|
40
|
+
}>;
|
|
41
|
+
export interface GatherRemoteAgentsJsonDeps {
|
|
42
|
+
capture?: SshCaptureFn;
|
|
15
43
|
}
|
|
16
44
|
export interface RemoteAgentsJsonParseResult<T> {
|
|
17
45
|
items: T[];
|
|
@@ -35,4 +63,4 @@ export declare function parseRemoteAgentsJsonPayload<T>(stdout: string, machine:
|
|
|
35
63
|
/** Build the command one peer runs, with a guard that prevents recursive fan-out. */
|
|
36
64
|
export declare function remoteAgentsJsonCommand(args: string[], noFanoutEnv: string, os?: string): string;
|
|
37
65
|
/** Query explicit hosts, or every registered online peer when hosts is omitted. */
|
|
38
|
-
export declare function gatherRemoteAgentsJson<T>(options: RemoteAgentsJsonOptions<T
|
|
66
|
+
export declare function gatherRemoteAgentsJson<T>(options: RemoteAgentsJsonOptions<T>, deps?: GatherRemoteAgentsJsonDeps): Promise<RemoteAgentsJsonResult<T>>;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* healthy results from the rest of the fleet.
|
|
8
8
|
*/
|
|
9
9
|
import { spawn } from 'child_process';
|
|
10
|
+
import { setMaxListeners } from 'node:events';
|
|
10
11
|
import chalk from 'chalk';
|
|
11
12
|
import { SSH_OPTS, controlOpts, assertValidSshTarget, shellQuote } from './ssh-exec.js';
|
|
12
13
|
import { sshTargetFor } from './devices/connect.js';
|
|
@@ -32,9 +33,13 @@ export function remoteAgentsJsonCommand(args, noFanoutEnv, os) {
|
|
|
32
33
|
const inner = `${noFanoutEnv}=1 agents ${args.map(shellQuote).join(' ')}`;
|
|
33
34
|
return `bash -lc ${shellQuote(inner)}`;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
const sshCapture = (target, remoteCmd, { timeoutMs, signal }) => {
|
|
36
37
|
assertValidSshTarget(target);
|
|
37
38
|
return new Promise((resolve) => {
|
|
39
|
+
if (signal?.aborted) {
|
|
40
|
+
resolve({ code: null, stdout: '' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
38
43
|
const args = [...SSH_OPTS, ...controlOpts(), target, remoteCmd];
|
|
39
44
|
const child = spawn('ssh', args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
40
45
|
let stdout = '';
|
|
@@ -44,19 +49,25 @@ function sshCapture(target, remoteCmd, timeoutMs) {
|
|
|
44
49
|
return;
|
|
45
50
|
settled = true;
|
|
46
51
|
clearTimeout(timer);
|
|
52
|
+
signal?.removeEventListener('abort', onAbort);
|
|
47
53
|
resolve({ code, stdout });
|
|
48
54
|
};
|
|
49
55
|
const timer = setTimeout(() => {
|
|
50
56
|
child.kill('SIGKILL');
|
|
51
57
|
done(null);
|
|
52
58
|
}, timeoutMs);
|
|
59
|
+
// A definitive hit on a fast peer cancels the rest: SIGTERM the child so an
|
|
60
|
+
// unreachable peer is not left running to its full timeout.
|
|
61
|
+
const onAbort = () => { child.kill('SIGTERM'); done(null); };
|
|
62
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
53
63
|
child.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
54
64
|
child.on('error', () => done(null));
|
|
55
65
|
child.on('close', (code) => done(code));
|
|
56
66
|
});
|
|
57
|
-
}
|
|
67
|
+
};
|
|
58
68
|
/** Query explicit hosts, or every registered online peer when hosts is omitted. */
|
|
59
|
-
export async function gatherRemoteAgentsJson(options) {
|
|
69
|
+
export async function gatherRemoteAgentsJson(options, deps = {}) {
|
|
70
|
+
const capture = deps.capture ?? sshCapture;
|
|
60
71
|
const self = machineId();
|
|
61
72
|
const targets = [];
|
|
62
73
|
if (options.hosts && options.hosts.length > 0) {
|
|
@@ -100,24 +111,50 @@ export async function gatherRemoteAgentsJson(options) {
|
|
|
100
111
|
}
|
|
101
112
|
const skipped = [];
|
|
102
113
|
const parseFailed = [];
|
|
114
|
+
// The controller exists ONLY for early-exit: the first definitive hit aborts
|
|
115
|
+
// every still-outstanding peer. When earlyExit is not requested there is no
|
|
116
|
+
// controller and no per-peer abort listener, so the default path is a plain
|
|
117
|
+
// all-settle Promise.all, byte-identical to before (tool-search, program-count).
|
|
118
|
+
const controller = options.earlyExit ? new AbortController() : undefined;
|
|
119
|
+
// A fleet can hold more peers than Node's default 10-listener cap; each peer's
|
|
120
|
+
// capture adds one abort listener, so lift the cap to avoid a spurious warning.
|
|
121
|
+
if (controller)
|
|
122
|
+
setMaxListeners(targets.length + 1, controller.signal);
|
|
123
|
+
let earlyResolved = false;
|
|
103
124
|
const results = await Promise.all(targets.map(async (target) => {
|
|
104
125
|
const command = remoteAgentsJsonCommand(options.args, options.noFanoutEnv, target.os);
|
|
105
|
-
const result = await
|
|
126
|
+
const result = await capture(target.target, command, {
|
|
127
|
+
timeoutMs: options.timeoutMs ?? REMOTE_TIMEOUT_MS,
|
|
128
|
+
signal: controller?.signal,
|
|
129
|
+
});
|
|
130
|
+
// A peer we deliberately cancelled is neither a hit nor a failure — it never
|
|
131
|
+
// got to answer, so it must not pollute skipped/parseFailed (which drive the
|
|
132
|
+
// "unreachable" listing and the fail-closed partial-resolution gate).
|
|
133
|
+
const cancelled = controller?.signal.aborted ?? false;
|
|
106
134
|
if (result.code !== 0) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
if (!cancelled) {
|
|
136
|
+
skipped.push(target.name);
|
|
137
|
+
if (!options.quiet) {
|
|
138
|
+
process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
|
|
139
|
+
}
|
|
110
140
|
}
|
|
111
141
|
return [];
|
|
112
142
|
}
|
|
113
143
|
const parsed = parseRemoteAgentsJsonPayload(result.stdout, target.machine, options.parse);
|
|
114
144
|
if (parsed.parseFailed) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
145
|
+
if (!cancelled) {
|
|
146
|
+
parseFailed.push(target.name);
|
|
147
|
+
if (!options.quiet) {
|
|
148
|
+
process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
|
|
149
|
+
}
|
|
118
150
|
}
|
|
119
151
|
return [];
|
|
120
152
|
}
|
|
153
|
+
if (controller && options.earlyExit && !earlyResolved
|
|
154
|
+
&& parsed.items.some((item) => options.earlyExit.isDefinitive(item, target.machine))) {
|
|
155
|
+
earlyResolved = true;
|
|
156
|
+
controller.abort(); // SIGTERM the remaining peers; they settle fast below.
|
|
157
|
+
}
|
|
121
158
|
return parsed.items;
|
|
122
159
|
}));
|
|
123
160
|
return { items: results.flat(), deviceCount: targets.length, skipped, parseFailed, discoveryFailed: false };
|
|
@@ -127,6 +127,8 @@ export function getMcpConfigPath(agent, versionHome) {
|
|
|
127
127
|
case 'pi':
|
|
128
128
|
// omp reads user-scope MCP from ~/.omp/agent/.mcp.json (Claude schema).
|
|
129
129
|
return path.join(versionHome, '.omp', 'agent', '.mcp.json');
|
|
130
|
+
case 'muse':
|
|
131
|
+
return path.join(versionHome, '.config', 'muse', 'settings.json');
|
|
130
132
|
default:
|
|
131
133
|
return null;
|
|
132
134
|
}
|
|
@@ -81,6 +81,9 @@ function getAgentConfigPath(agent, versionHome) {
|
|
|
81
81
|
return path.join(versionHome, '.hermes', 'config.yaml');
|
|
82
82
|
case 'goose':
|
|
83
83
|
return path.join(versionHome, '.config', 'goose', 'permission.yaml');
|
|
84
|
+
case 'muse':
|
|
85
|
+
// Muse approval defaults live in settings.json; no separate permissions file.
|
|
86
|
+
return path.join(versionHome, '.config', 'muse', 'settings.json');
|
|
84
87
|
default:
|
|
85
88
|
return null;
|
|
86
89
|
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* - Union: All resources from all layers are combined
|
|
6
6
|
* - Override on name conflict: Higher layer wins (project > user > system)
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
import type { AgentId } from '../types.js';
|
|
9
|
+
export type { AgentId };
|
|
9
10
|
/** Resource origin. Precedence (highest first): project > user > plugin > system. */
|
|
10
11
|
export type Layer = 'system' | 'user' | 'project' | 'plugin';
|
|
11
12
|
export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow' | 'memory';
|