@phnx-labs/agents-cli 1.22.43 → 1.22.45
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 +85 -0
- package/README.md +14 -12
- package/dist/bootstrap.js +3 -6
- package/dist/cli/command-registry.d.ts +0 -7
- package/dist/cli/command-registry.js +1 -21
- package/dist/commands/accounts.d.ts +1 -9
- package/dist/commands/accounts.js +12 -90
- package/dist/commands/apply.d.ts +7 -10
- package/dist/commands/apply.js +15 -35
- package/dist/commands/artifacts.js +4 -5
- package/dist/commands/audit.d.ts +6 -6
- package/dist/commands/audit.js +12 -12
- package/dist/commands/beta.d.ts +7 -1
- package/dist/commands/beta.js +16 -9
- package/dist/commands/commands.js +1 -1
- package/dist/commands/doctor.js +1 -4
- package/dist/commands/events.d.ts +2 -2
- package/dist/commands/events.js +6 -5
- package/dist/commands/exec.d.ts +1 -1
- package/dist/commands/exec.js +8 -4
- package/dist/commands/factory.d.ts +1 -1
- package/dist/commands/fleet-capture.d.ts +1 -1
- package/dist/commands/fleet-capture.js +4 -5
- package/dist/commands/harness.js +1 -49
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +85 -156
- package/dist/commands/menubar.js +58 -1
- package/dist/commands/output.js +2 -2
- package/dist/commands/prune.js +5 -4
- package/dist/commands/routines.test-fixture.d.ts +86 -0
- package/dist/commands/routines.test-fixture.js +345 -0
- package/dist/commands/sessions.test-fixture.d.ts +24 -0
- package/dist/commands/sessions.test-fixture.js +224 -0
- package/dist/commands/setup.js +5 -1
- package/dist/commands/share.d.ts +9 -10
- package/dist/commands/share.js +68 -50
- package/dist/commands/skills.js +1 -1
- package/dist/commands/ssh.js +15 -62
- package/dist/commands/subagents.js +1 -1
- package/dist/commands/view.js +5 -2
- package/dist/lib/analytics/mix-commands.d.ts +6 -28
- package/dist/lib/analytics/mix-commands.js +14 -40
- package/dist/lib/audit/log.d.ts +2 -2
- package/dist/lib/audit/log.js +2 -2
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/domain-skills.d.ts +26 -6
- package/dist/lib/browser/domain-skills.js +81 -43
- package/dist/lib/daemon/daemon.js +11 -0
- package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
- package/dist/lib/daemon/daemon.test-fixture.js +71 -0
- package/dist/lib/daemon-ticks.d.ts +38 -1
- package/dist/lib/daemon-ticks.js +52 -5
- package/dist/lib/device-config.js +1 -2
- package/dist/lib/devices/discovery-policy.d.ts +3 -3
- package/dist/lib/devices/discovery-policy.js +3 -3
- package/dist/lib/devices/fleet.d.ts +4 -8
- package/dist/lib/devices/fleet.js +3 -15
- package/dist/lib/devices/health-report.js +3 -3
- package/dist/lib/devices/pool.d.ts +0 -6
- package/dist/lib/devices/pool.js +0 -6
- package/dist/lib/devices/registry.d.ts +0 -15
- package/dist/lib/devices/registry.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +12 -0
- package/dist/lib/devices/stats-cache.js +24 -5
- package/dist/lib/fleet/capture.d.ts +1 -1
- package/dist/lib/fleet/manifest.js +1 -1
- package/dist/lib/fleet/remote-login.d.ts +2 -2
- package/dist/lib/fleet/remote-login.js +3 -4
- package/dist/lib/hosts/dispatch.d.ts +2 -0
- package/dist/lib/hosts/dispatch.js +8 -1
- package/dist/lib/hosts/passthrough.js +0 -1
- package/dist/lib/hosts/providers/devices.js +1 -13
- package/dist/lib/hosts/ready.d.ts +12 -3
- package/dist/lib/hosts/ready.js +27 -12
- package/dist/lib/hosts/registry.d.ts +4 -5
- package/dist/lib/hosts/registry.js +3 -7
- package/dist/lib/installations/versions.js +21 -2
- package/dist/lib/menubar/install-menubar.d.ts +109 -0
- package/dist/lib/menubar/install-menubar.js +199 -4
- package/dist/lib/remote-agents-json.js +1 -7
- package/dist/lib/routines-placement.d.ts +1 -1
- package/dist/lib/routines-placement.js +1 -1
- 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/session/active.d.ts +4 -16
- package/dist/lib/session/active.js +2 -6
- package/dist/lib/session/db.d.ts +13 -98
- package/dist/lib/session/db.js +4 -11
- package/dist/lib/session/discover.d.ts +5 -81
- package/dist/lib/session/discover.js +5 -5
- package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote/remote-bundle.js +61 -0
- package/dist/lib/session/remote/remote-list.d.ts +148 -0
- package/dist/lib/session/remote/remote-list.js +607 -0
- package/dist/lib/session/remote/remote.d.ts +132 -0
- package/dist/lib/session/remote/remote.js +314 -0
- package/dist/lib/session/remote/watch.d.ts +101 -0
- package/dist/lib/session/remote/watch.js +241 -0
- package/dist/lib/session/remote-bundle.d.ts +1 -12
- package/dist/lib/session/remote-bundle.js +3 -61
- package/dist/lib/session/remote-list.d.ts +1 -148
- package/dist/lib/session/remote-list.js +3 -607
- package/dist/lib/session/remote.d.ts +1 -132
- package/dist/lib/session/remote.js +3 -314
- package/dist/lib/session/watch.d.ts +1 -101
- package/dist/lib/session/watch.js +3 -242
- package/dist/lib/share/config.d.ts +12 -2
- package/dist/lib/share/config.js +47 -11
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/publish.js +1 -1
- package/dist/lib/share/worker-template.js +2 -2
- package/dist/lib/smart-launch.d.ts +4 -4
- package/dist/lib/smart-launch.js +8 -18
- package/dist/lib/staleness/index.d.ts +3 -1
- package/dist/lib/staleness/index.js +21 -1
- package/dist/lib/staleness/types.d.ts +11 -0
- package/dist/lib/staleness/writers/commands.js +13 -4
- package/dist/lib/staleness/writers/hooks.js +3 -1
- package/dist/lib/staleness/writers/mcp.d.ts +0 -7
- package/dist/lib/staleness/writers/mcp.js +16 -1
- package/dist/lib/staleness/writers/rules.js +1 -1
- package/dist/lib/staleness/writers/skills.js +3 -1
- package/dist/lib/staleness/writers/subagents.d.ts +0 -9
- package/dist/lib/staleness/writers/subagents.js +18 -1
- package/dist/lib/staleness/writers/types.d.ts +10 -0
- package/dist/lib/startup/command-registry.d.ts +8 -1
- package/dist/lib/startup/command-registry.js +21 -6
- package/dist/lib/startup/root-command.d.ts +18 -1
- package/dist/lib/startup/root-command.js +18 -1
- package/dist/lib/state.js +7 -0
- package/dist/lib/teams/placement-probe.js +27 -12
- package/dist/lib/teams/scheduler.d.ts +3 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/view-types.d.ts +3 -0
- package/package.json +2 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/auth.d.ts +0 -9
- package/dist/commands/auth.js +0 -108
- package/dist/commands/org.d.ts +0 -6
- package/dist/commands/org.js +0 -175
- package/dist/commands/serve.d.ts +0 -10
- package/dist/commands/serve.js +0 -68
- package/dist/commands/trends.d.ts +0 -10
- package/dist/commands/trends.js +0 -12
- package/dist/lib/entitlement.d.ts +0 -31
- package/dist/lib/entitlement.js +0 -137
- package/dist/lib/prix-account.d.ts +0 -158
- package/dist/lib/prix-account.js +0 -214
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/serve/control.d.ts +0 -95
- package/dist/lib/serve/control.js +0 -260
- package/dist/lib/serve/data.d.ts +0 -81
- package/dist/lib/serve/data.js +0 -91
- package/dist/lib/serve/page.d.ts +0 -7
- package/dist/lib/serve/page.js +0 -140
- package/dist/lib/serve/server.d.ts +0 -80
- package/dist/lib/serve/server.js +0 -145
- package/dist/lib/serve/stream.d.ts +0 -43
- package/dist/lib/serve/stream.js +0 -116
- package/dist/lib/serve/token.d.ts +0 -35
- package/dist/lib/serve/token.js +0 -85
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* usage.db) still exists — it is now `agents insights mix` and the recipe
|
|
8
8
|
* subcommands below. Latency stays on `agents perf`; quota on `agents usage`.
|
|
9
9
|
*
|
|
10
|
-
* `agents trends`
|
|
11
|
-
* `
|
|
12
|
-
* the recipe tree.
|
|
10
|
+
* Former top-level `agents trends` is gone. The nested spelling
|
|
11
|
+
* `agents insights trends` is an alias of `agents insights mix`.
|
|
13
12
|
*/
|
|
14
13
|
import chalk from 'chalk';
|
|
15
14
|
import { setHelpSections } from '../help.js';
|
|
@@ -56,33 +55,24 @@ export function renderMixDashboard(days, asJson, bannerLabel = 'agents insights
|
|
|
56
55
|
for (const section of dash.sections)
|
|
57
56
|
printMixSection(section);
|
|
58
57
|
}
|
|
59
|
-
/** One-line deprecation for the retired top-level `agents trends` spelling. */
|
|
60
|
-
export function printTrendsDeprecation() {
|
|
61
|
-
console.error(chalk.yellow('agents trends is deprecated — use `agents insights mix` (or `agents insights <recipe>`).'));
|
|
62
|
-
}
|
|
63
58
|
/**
|
|
64
|
-
* Attach counter-mix subcommands to a parent (typically `insights`
|
|
65
|
-
* deprecated `trends` alias).
|
|
59
|
+
* Attach counter-mix subcommands to a parent (typically `insights`).
|
|
66
60
|
*
|
|
67
61
|
* Layout:
|
|
68
|
-
* <parent> mix multi-recipe board
|
|
62
|
+
* <parent> mix multi-recipe board
|
|
63
|
+
* <parent> trends alias of mix (former top-level `agents trends`)
|
|
69
64
|
* <parent> recipes list recipe ids
|
|
70
65
|
* <parent> query raw usage.db rows
|
|
71
66
|
* <parent> harness-mix|… one baked recipe
|
|
72
67
|
*/
|
|
73
|
-
export function registerMixCommands(parent
|
|
74
|
-
const
|
|
75
|
-
if (opts.deprecated)
|
|
76
|
-
printTrendsDeprecation();
|
|
77
|
-
};
|
|
78
|
-
const banner = opts.dashboardBanner ?? 'agents insights mix';
|
|
68
|
+
export function registerMixCommands(parent) {
|
|
69
|
+
const banner = 'agents insights mix';
|
|
79
70
|
const mix = parent
|
|
80
71
|
.command('mix')
|
|
81
72
|
.description('Counter recipes — harness/model mix, token ratios, resource frequency (sessions index + usage.db)')
|
|
82
73
|
.option('--days <n>', 'Days of history to include', '7')
|
|
83
74
|
.option('--json', 'Emit JSON instead of tables')
|
|
84
75
|
.action(function summary() {
|
|
85
|
-
before();
|
|
86
76
|
const o = this.opts();
|
|
87
77
|
renderMixDashboard(parseMixDays(o.days), Boolean(o.json), banner);
|
|
88
78
|
});
|
|
@@ -115,7 +105,6 @@ export function registerMixCommands(parent, opts = {}) {
|
|
|
115
105
|
.description('List baked mix-recipe ids')
|
|
116
106
|
.option('--json', 'Emit JSON')
|
|
117
107
|
.action((o) => {
|
|
118
|
-
before();
|
|
119
108
|
const list = listRecipes();
|
|
120
109
|
if (o.json) {
|
|
121
110
|
console.log(JSON.stringify(list, null, 2));
|
|
@@ -134,7 +123,6 @@ export function registerMixCommands(parent, opts = {}) {
|
|
|
134
123
|
.option('--limit <n>', 'Max rows', '40')
|
|
135
124
|
.option('--json', 'Emit JSON')
|
|
136
125
|
.action((o) => {
|
|
137
|
-
before();
|
|
138
126
|
const win = analyticsWindow(parseMixDays(o.days));
|
|
139
127
|
const kind = o.kind && USAGE_KINDS.includes(o.kind)
|
|
140
128
|
? o.kind
|
|
@@ -177,7 +165,6 @@ export function registerMixCommands(parent, opts = {}) {
|
|
|
177
165
|
.option('--days <n>', 'Days of history', '7')
|
|
178
166
|
.option('--json', 'Emit JSON')
|
|
179
167
|
.action(function recipeAction() {
|
|
180
|
-
before();
|
|
181
168
|
const parentOpts = this.parent?.opts?.();
|
|
182
169
|
const o = { ...parentOpts, ...this.opts() };
|
|
183
170
|
const win = analyticsWindow(parseMixDays(o.days));
|
|
@@ -193,37 +180,24 @@ export function registerMixCommands(parent, opts = {}) {
|
|
|
193
180
|
printMixSection(section);
|
|
194
181
|
});
|
|
195
182
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
* Thin top-level `agents trends` alias: same mix tree, deprecation on every path,
|
|
199
|
-
* default action = mix board (old `agents trends` with no subcommand).
|
|
200
|
-
*/
|
|
201
|
-
export function registerDeprecatedTrendsAlias(program) {
|
|
202
|
-
const trends = program
|
|
183
|
+
// Nested home for the former top-level `agents trends` spelling.
|
|
184
|
+
const trends = parent
|
|
203
185
|
.command('trends')
|
|
204
|
-
.description('
|
|
186
|
+
.description('Alias of `mix` — former top-level `agents trends`')
|
|
205
187
|
.option('--days <n>', 'Days of history to include', '7')
|
|
206
188
|
.option('--json', 'Emit JSON instead of tables')
|
|
207
189
|
.action(function summary() {
|
|
208
|
-
printTrendsDeprecation();
|
|
209
190
|
const o = this.opts();
|
|
210
|
-
renderMixDashboard(parseMixDays(o.days), Boolean(o.json),
|
|
191
|
+
renderMixDashboard(parseMixDays(o.days), Boolean(o.json), banner);
|
|
211
192
|
});
|
|
212
193
|
setHelpSections(trends, {
|
|
213
194
|
examples: `
|
|
214
|
-
|
|
195
|
+
agents insights trends
|
|
215
196
|
agents insights mix
|
|
216
|
-
|
|
217
|
-
# Still works (prints a deprecation line)
|
|
218
|
-
agents trends
|
|
219
|
-
agents trends harness-mix --json
|
|
220
197
|
`,
|
|
221
198
|
notes: `
|
|
222
|
-
\`agents trends\` is
|
|
223
|
-
\`agents
|
|
224
|
-
bare \`agents insights\`. Latency remains \`agents perf\`.
|
|
199
|
+
\`agents insights trends\` is the nested spelling of the former top-level
|
|
200
|
+
\`agents trends\`. Prefer \`agents insights mix\`.
|
|
225
201
|
`,
|
|
226
202
|
});
|
|
227
|
-
// Same subcommands as insights, with deprecation on every fire.
|
|
228
|
-
registerMixCommands(trends, { deprecated: true, dashboardBanner: 'agents insights mix' });
|
|
229
203
|
}
|
package/dist/lib/audit/log.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* NOT append there. Operators list run outcomes with:
|
|
7
7
|
*
|
|
8
8
|
* agents events --include runs
|
|
9
|
-
* agents audit
|
|
9
|
+
* agents events audit # nested alias of the above
|
|
10
10
|
*/
|
|
11
11
|
/** First record's `prevHash` — a fixed anchor so the chain has a root. */
|
|
12
12
|
export declare const GENESIS_HASH = "GENESIS";
|
|
@@ -59,7 +59,7 @@ export declare function verifyAuditChain(logPath?: string): {
|
|
|
59
59
|
ok: boolean;
|
|
60
60
|
brokenAt?: number;
|
|
61
61
|
};
|
|
62
|
-
/** Read the whole chain, oldest-first. Exposed for `agents audit
|
|
62
|
+
/** Read the whole chain, oldest-first. Exposed for `agents events audit verify`. */
|
|
63
63
|
export declare function readAuditLog(logPath?: string): AuditRecord[];
|
|
64
64
|
/**
|
|
65
65
|
* Record ONE dispatched run at the single exec chokepoint into the unified
|
package/dist/lib/audit/log.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* NOT append there. Operators list run outcomes with:
|
|
7
7
|
*
|
|
8
8
|
* agents events --include runs
|
|
9
|
-
* agents audit
|
|
9
|
+
* agents events audit # nested alias of the above
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import * as path from 'path';
|
|
@@ -116,7 +116,7 @@ export function verifyAuditChain(logPath = getAuditLogPath()) {
|
|
|
116
116
|
}
|
|
117
117
|
return { ok: true };
|
|
118
118
|
}
|
|
119
|
-
/** Read the whole chain, oldest-first. Exposed for `agents audit
|
|
119
|
+
/** Read the whole chain, oldest-first. Exposed for `agents events audit verify`. */
|
|
120
120
|
export function readAuditLog(logPath = getAuditLogPath()) {
|
|
121
121
|
return readRecords(logPath);
|
|
122
122
|
}
|
package/dist/lib/beta.js
CHANGED
|
@@ -86,5 +86,5 @@ export function setBetaEnabled(features, enabled) {
|
|
|
86
86
|
return { ...location, enabledFeatures };
|
|
87
87
|
}
|
|
88
88
|
export function betaEnableHint(feature) {
|
|
89
|
-
return `Enable it with: agents beta enable ${feature}`;
|
|
89
|
+
return `Enable it with: agents setup beta enable ${feature}`;
|
|
90
90
|
}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Domain-skill discovery for `agents browser start`.
|
|
3
3
|
*
|
|
4
4
|
* When a browser task opens a URL, look up a site-specific SKILL.md from
|
|
5
|
-
*
|
|
5
|
+
* `skills/browser/domain-skills/<dir>/SKILL.md` in the layered DotAgents
|
|
6
|
+
* repos — project > user > system, first layer with a match wins, the same
|
|
7
|
+
* precedence `resolveResource` in `../resources.ts` applies — and surface its
|
|
6
8
|
* contents so the calling agent gets per-site operating instructions
|
|
7
9
|
* (selectors, gotchas, sign-in quirks) before it starts driving the page.
|
|
8
10
|
*
|
|
@@ -24,8 +26,21 @@ export interface ResolvedDomainSkill {
|
|
|
24
26
|
/** Hostname the match was made against (post-www strip). */
|
|
25
27
|
hostname: string;
|
|
26
28
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Where domain-skills live, in precedence order: project > user > system >
|
|
31
|
+
* extra repos — the same layering `resolveResource` applies. A layer whose
|
|
32
|
+
* root does not exist is simply skipped at resolve time.
|
|
33
|
+
*
|
|
34
|
+
* The project layer resolves from `cwd` (default `process.cwd()`). The
|
|
35
|
+
* browser daemon is shared and long-lived, and its IPC request does not yet
|
|
36
|
+
* carry the calling CLI's cwd, so at that call site the project layer follows
|
|
37
|
+
* the daemon's own cwd until the caller's cwd is threaded through
|
|
38
|
+
* (RUSH-2996); user/system/extra-repo layers are cwd-independent.
|
|
39
|
+
*
|
|
40
|
+
* $AGENTS_BROWSER_DOMAIN_SKILLS_DIR overrides the whole list with a single
|
|
41
|
+
* root, for tests.
|
|
42
|
+
*/
|
|
43
|
+
export declare function domainSkillsRoots(cwd?: string): string[];
|
|
29
44
|
/**
|
|
30
45
|
* Derive match candidates from a hostname. Order matters — earlier candidates
|
|
31
46
|
* are tried first.
|
|
@@ -40,12 +55,17 @@ export declare function hostnameMatchCandidates(hostname: string): string[];
|
|
|
40
55
|
/**
|
|
41
56
|
* Resolve a URL to its matching domain-skill, or null if none.
|
|
42
57
|
*
|
|
43
|
-
*
|
|
58
|
+
* Roots are searched in `domainSkillsRoots` order (project > user > system);
|
|
59
|
+
* the first layer with a match wins outright — an earlier layer's
|
|
60
|
+
* directory-name match beats a later layer's `domains:` pin, mirroring
|
|
61
|
+
* `resolveResource`'s layer precedence. Within one layer, two passes:
|
|
44
62
|
* 1. Index every SKILL.md in the root and read its `domains:` frontmatter.
|
|
45
63
|
* If any pinned domain matches a candidate, return that skill.
|
|
46
64
|
* 2. Fall back to directory-name match against the candidate list.
|
|
47
65
|
*
|
|
48
66
|
* Errors (missing root, unreadable file, invalid URL) are swallowed and
|
|
49
|
-
* yield null — domain-skill discovery must never break browser start.
|
|
67
|
+
* yield null — domain-skill discovery must never break browser start. A miss
|
|
68
|
+
* logs the roots searched at debug level (AGENTS_DEBUG/DEBUG) so a skill
|
|
69
|
+
* sitting in an unsearched layer is diagnosable rather than silent.
|
|
50
70
|
*/
|
|
51
|
-
export declare function resolveDomainSkill(url: string): ResolvedDomainSkill | null;
|
|
71
|
+
export declare function resolveDomainSkill(url: string, cwd?: string): ResolvedDomainSkill | null;
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Domain-skill discovery for `agents browser start`.
|
|
3
3
|
*
|
|
4
4
|
* When a browser task opens a URL, look up a site-specific SKILL.md from
|
|
5
|
-
*
|
|
5
|
+
* `skills/browser/domain-skills/<dir>/SKILL.md` in the layered DotAgents
|
|
6
|
+
* repos — project > user > system, first layer with a match wins, the same
|
|
7
|
+
* precedence `resolveResource` in `../resources.ts` applies — and surface its
|
|
6
8
|
* contents so the calling agent gets per-site operating instructions
|
|
7
9
|
* (selectors, gotchas, sign-in quirks) before it starts driving the page.
|
|
8
10
|
*
|
|
@@ -14,14 +16,37 @@
|
|
|
14
16
|
* the directory-name match.
|
|
15
17
|
*/
|
|
16
18
|
import * as fs from 'fs';
|
|
17
|
-
import * as os from 'os';
|
|
18
19
|
import * as path from 'path';
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
import { getEnabledExtraRepos, getProjectAgentsDir, getSystemAgentsDir, getUserAgentsDir, } from '../state.js';
|
|
21
|
+
/**
|
|
22
|
+
* Where domain-skills live, in precedence order: project > user > system >
|
|
23
|
+
* extra repos — the same layering `resolveResource` applies. A layer whose
|
|
24
|
+
* root does not exist is simply skipped at resolve time.
|
|
25
|
+
*
|
|
26
|
+
* The project layer resolves from `cwd` (default `process.cwd()`). The
|
|
27
|
+
* browser daemon is shared and long-lived, and its IPC request does not yet
|
|
28
|
+
* carry the calling CLI's cwd, so at that call site the project layer follows
|
|
29
|
+
* the daemon's own cwd until the caller's cwd is threaded through
|
|
30
|
+
* (RUSH-2996); user/system/extra-repo layers are cwd-independent.
|
|
31
|
+
*
|
|
32
|
+
* $AGENTS_BROWSER_DOMAIN_SKILLS_DIR overrides the whole list with a single
|
|
33
|
+
* root, for tests.
|
|
34
|
+
*/
|
|
35
|
+
export function domainSkillsRoots(cwd) {
|
|
21
36
|
const override = process.env.AGENTS_BROWSER_DOMAIN_SKILLS_DIR;
|
|
22
37
|
if (override)
|
|
23
|
-
return override;
|
|
24
|
-
|
|
38
|
+
return [override];
|
|
39
|
+
const sub = ['skills', 'browser', 'domain-skills'];
|
|
40
|
+
const roots = [];
|
|
41
|
+
const projectDir = getProjectAgentsDir(cwd);
|
|
42
|
+
if (projectDir)
|
|
43
|
+
roots.push(path.join(projectDir, ...sub));
|
|
44
|
+
roots.push(path.join(getUserAgentsDir(), ...sub));
|
|
45
|
+
roots.push(path.join(getSystemAgentsDir(), ...sub));
|
|
46
|
+
for (const extra of getEnabledExtraRepos()) {
|
|
47
|
+
roots.push(path.join(extra.dir, ...sub));
|
|
48
|
+
}
|
|
49
|
+
return roots;
|
|
25
50
|
}
|
|
26
51
|
/**
|
|
27
52
|
* Derive match candidates from a hostname. Order matters — earlier candidates
|
|
@@ -85,18 +110,28 @@ function parseDomainsFrontmatter(content) {
|
|
|
85
110
|
}
|
|
86
111
|
return [];
|
|
87
112
|
}
|
|
113
|
+
/** Stderr debug line, gated on AGENTS_DEBUG/DEBUG — same convention as `../teams/debug.ts`. */
|
|
114
|
+
function debug(message) {
|
|
115
|
+
if (process.env.AGENTS_DEBUG || process.env.DEBUG)
|
|
116
|
+
console.error(message);
|
|
117
|
+
}
|
|
88
118
|
/**
|
|
89
119
|
* Resolve a URL to its matching domain-skill, or null if none.
|
|
90
120
|
*
|
|
91
|
-
*
|
|
121
|
+
* Roots are searched in `domainSkillsRoots` order (project > user > system);
|
|
122
|
+
* the first layer with a match wins outright — an earlier layer's
|
|
123
|
+
* directory-name match beats a later layer's `domains:` pin, mirroring
|
|
124
|
+
* `resolveResource`'s layer precedence. Within one layer, two passes:
|
|
92
125
|
* 1. Index every SKILL.md in the root and read its `domains:` frontmatter.
|
|
93
126
|
* If any pinned domain matches a candidate, return that skill.
|
|
94
127
|
* 2. Fall back to directory-name match against the candidate list.
|
|
95
128
|
*
|
|
96
129
|
* Errors (missing root, unreadable file, invalid URL) are swallowed and
|
|
97
|
-
* yield null — domain-skill discovery must never break browser start.
|
|
130
|
+
* yield null — domain-skill discovery must never break browser start. A miss
|
|
131
|
+
* logs the roots searched at debug level (AGENTS_DEBUG/DEBUG) so a skill
|
|
132
|
+
* sitting in an unsearched layer is diagnosable rather than silent.
|
|
98
133
|
*/
|
|
99
|
-
export function resolveDomainSkill(url) {
|
|
134
|
+
export function resolveDomainSkill(url, cwd) {
|
|
100
135
|
let hostname;
|
|
101
136
|
try {
|
|
102
137
|
hostname = new URL(url).hostname;
|
|
@@ -106,52 +141,55 @@ export function resolveDomainSkill(url) {
|
|
|
106
141
|
}
|
|
107
142
|
if (!hostname)
|
|
108
143
|
return null;
|
|
109
|
-
const root = domainSkillsRoot();
|
|
110
|
-
let entries;
|
|
111
|
-
try {
|
|
112
|
-
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
144
|
const candidates = hostnameMatchCandidates(hostname);
|
|
118
145
|
if (candidates.length === 0)
|
|
119
146
|
return null;
|
|
120
147
|
const candidateSet = new Set(candidates);
|
|
121
|
-
const
|
|
122
|
-
for (const
|
|
123
|
-
|
|
124
|
-
continue;
|
|
125
|
-
const skillPath = path.join(root, e.name, 'SKILL.md');
|
|
126
|
-
let content;
|
|
148
|
+
const roots = domainSkillsRoots(cwd);
|
|
149
|
+
for (const root of roots) {
|
|
150
|
+
let entries;
|
|
127
151
|
try {
|
|
128
|
-
|
|
152
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
129
153
|
}
|
|
130
154
|
catch {
|
|
131
155
|
continue;
|
|
132
156
|
}
|
|
133
|
-
indexed
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return { name: s.name, path: s.skillPath, content: s.content, hostname };
|
|
157
|
+
const indexed = [];
|
|
158
|
+
for (const e of entries) {
|
|
159
|
+
if (!e.isDirectory())
|
|
160
|
+
continue;
|
|
161
|
+
const skillPath = path.join(root, e.name, 'SKILL.md');
|
|
162
|
+
let content;
|
|
163
|
+
try {
|
|
164
|
+
content = fs.readFileSync(skillPath, 'utf-8');
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
continue;
|
|
145
168
|
}
|
|
169
|
+
indexed.push({
|
|
170
|
+
name: e.name,
|
|
171
|
+
skillPath,
|
|
172
|
+
content,
|
|
173
|
+
pinned: parseDomainsFrontmatter(content),
|
|
174
|
+
});
|
|
146
175
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
// Pass 1: explicit `domains:` overrides.
|
|
177
|
+
for (const s of indexed) {
|
|
178
|
+
for (const d of s.pinned) {
|
|
179
|
+
if (candidateSet.has(d)) {
|
|
180
|
+
return { name: s.name, path: s.skillPath, content: s.content, hostname };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Pass 2: directory-name match, walking candidates in priority order.
|
|
185
|
+
const byName = new Map(indexed.map((s) => [s.name.toLowerCase(), s]));
|
|
186
|
+
for (const c of candidates) {
|
|
187
|
+
const hit = byName.get(c);
|
|
188
|
+
if (hit) {
|
|
189
|
+
return { name: hit.name, path: hit.skillPath, content: hit.content, hostname };
|
|
190
|
+
}
|
|
154
191
|
}
|
|
155
192
|
}
|
|
193
|
+
debug(`[browser] no domain-skill for ${hostname}; searched: ${roots.join(', ')}`);
|
|
156
194
|
return null;
|
|
157
195
|
}
|
|
@@ -1868,6 +1868,17 @@ export function ensureDaemonStarted() {
|
|
|
1868
1868
|
// callers that branch on this return (e.g. secrets/agent.ts).
|
|
1869
1869
|
if (isDaemonRunning())
|
|
1870
1870
|
return startDaemon();
|
|
1871
|
+
// RUSH-3021: never LAUNCH a daemon from a redirected (sandbox/test) HOME.
|
|
1872
|
+
// #2860 gated service-manager registration on this signal but left the
|
|
1873
|
+
// detached spawn itself ungated, so a test-spawned CLI could fork a daemon
|
|
1874
|
+
// into the test's temp HOME; the child outlives the test and races its
|
|
1875
|
+
// recursive teardown rm (ENOTEMPTY). Placed after the already-running branch
|
|
1876
|
+
// — reporting a live daemon stays allowed, same as the circuit breaker.
|
|
1877
|
+
// AGENTS_SERVICE_MANAGER_ALLOW_REDIRECTED_HOME=1 is the test seam for suites
|
|
1878
|
+
// that exercise daemon startup deliberately; `agents daemon start` remains
|
|
1879
|
+
// the operator override.
|
|
1880
|
+
if (!serviceManagerRegistrationAllowed().allowed)
|
|
1881
|
+
return null;
|
|
1871
1882
|
// RUSH-2418: the auto-start circuit breaker. A daemon that dies during
|
|
1872
1883
|
// startup would otherwise be relaunched by EVERY foreground command that
|
|
1873
1884
|
// wants one (secrets unlock, browser start, watchdog, ...) — an
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type KeychainBackend } from '../secrets/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared fixture for the daemon.*.test.ts suite slices (RUSH-2819).
|
|
4
|
+
*
|
|
5
|
+
* daemon.test.ts was a single 2201-line file (88 tests, ~112s in CI) — the
|
|
6
|
+
* slowest file in the daemon test-ownership group, serializing an entire
|
|
7
|
+
* vitest fork while every other selected file finished. The suite is split
|
|
8
|
+
* into topical slices so per-file fork parallelism can spread the
|
|
9
|
+
* process-spawning / real-daemon integration tests across workers; the
|
|
10
|
+
* helpers shared by more than one slice live here.
|
|
11
|
+
*/
|
|
12
|
+
/** An in-memory KeychainBackend stand-in, so a test never touches the real OS keychain. */
|
|
13
|
+
export declare function makeMemoryBackend(): {
|
|
14
|
+
backend: KeychainBackend;
|
|
15
|
+
store: Map<string, string>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Registers the keychain-backend swap + AGENTS_SECRETS_NO_AGENT hermeticity
|
|
19
|
+
* beforeEach/afterEach every slice of the original daemon.test.ts ran under,
|
|
20
|
+
* verbatim. Call once at the top level of each slice file.
|
|
21
|
+
*/
|
|
22
|
+
export declare function installKeychainHermeticity(): void;
|
|
23
|
+
export declare const REPO_ROOT: string;
|
|
24
|
+
export declare const DIST_ENTRY: string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { setKeychainBackendForTest } from '../secrets/index.js';
|
|
5
|
+
import { deleteBundle } from '../secrets/bundles.js';
|
|
6
|
+
/**
|
|
7
|
+
* Shared fixture for the daemon.*.test.ts suite slices (RUSH-2819).
|
|
8
|
+
*
|
|
9
|
+
* daemon.test.ts was a single 2201-line file (88 tests, ~112s in CI) — the
|
|
10
|
+
* slowest file in the daemon test-ownership group, serializing an entire
|
|
11
|
+
* vitest fork while every other selected file finished. The suite is split
|
|
12
|
+
* into topical slices so per-file fork parallelism can spread the
|
|
13
|
+
* process-spawning / real-daemon integration tests across workers; the
|
|
14
|
+
* helpers shared by more than one slice live here.
|
|
15
|
+
*/
|
|
16
|
+
/** An in-memory KeychainBackend stand-in, so a test never touches the real OS keychain. */
|
|
17
|
+
export function makeMemoryBackend() {
|
|
18
|
+
const store = new Map();
|
|
19
|
+
const backend = {
|
|
20
|
+
has: (item) => store.has(item),
|
|
21
|
+
get: (item) => {
|
|
22
|
+
const v = store.get(item);
|
|
23
|
+
if (v === undefined)
|
|
24
|
+
throw new Error(`Keychain item '${item}' not found.`);
|
|
25
|
+
return v;
|
|
26
|
+
},
|
|
27
|
+
set: (item, value) => { store.set(item, value); },
|
|
28
|
+
delete: (item) => store.delete(item),
|
|
29
|
+
list: (prefix) => Array.from(store.keys()).filter((k) => k.startsWith(prefix)),
|
|
30
|
+
};
|
|
31
|
+
return { backend, store };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Registers the keychain-backend swap + AGENTS_SECRETS_NO_AGENT hermeticity
|
|
35
|
+
* beforeEach/afterEach every slice of the original daemon.test.ts ran under,
|
|
36
|
+
* verbatim. Call once at the top level of each slice file.
|
|
37
|
+
*/
|
|
38
|
+
export function installKeychainHermeticity() {
|
|
39
|
+
let restore = null;
|
|
40
|
+
let prevNoAgent;
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
const m = makeMemoryBackend();
|
|
43
|
+
restore = setKeychainBackendForTest(m.backend);
|
|
44
|
+
// Hermeticity: readAndResolveBundleEnv consults the running secrets-agent
|
|
45
|
+
// (bundles.ts agentGetSync fast-path) BEFORE the injected keychain backend.
|
|
46
|
+
// On a dev machine where the agent is live and the real `claude` bundle is
|
|
47
|
+
// unlocked, that returns the machine's real CLAUDE_CODE_OAUTH_TOKEN and this
|
|
48
|
+
// test reads a live credential instead of the seeded value (CI has no agent,
|
|
49
|
+
// so it only bites locally). Disable the agent so the read falls through to
|
|
50
|
+
// the in-memory backend above — hermetic regardless of host state.
|
|
51
|
+
prevNoAgent = process.env.AGENTS_SECRETS_NO_AGENT;
|
|
52
|
+
process.env.AGENTS_SECRETS_NO_AGENT = '1';
|
|
53
|
+
});
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
try {
|
|
56
|
+
deleteBundle('claude');
|
|
57
|
+
}
|
|
58
|
+
catch { /* not created */ }
|
|
59
|
+
setKeychainBackendForTest(restore);
|
|
60
|
+
if (prevNoAgent === undefined)
|
|
61
|
+
delete process.env.AGENTS_SECRETS_NO_AGENT;
|
|
62
|
+
else
|
|
63
|
+
process.env.AGENTS_SECRETS_NO_AGENT = prevNoAgent;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// The real compiled CLI entry, shared by every slice that drives an actual
|
|
67
|
+
// `__daemon-run` subprocess (lifecycle/registry/stop). Computed relative to
|
|
68
|
+
// this file's own location, which stays in src/lib/daemon/ alongside every
|
|
69
|
+
// slice, so the path math is unaffected by the split.
|
|
70
|
+
export const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
71
|
+
export const DIST_ENTRY = path.join(REPO_ROOT, 'dist', 'index.js');
|
|
@@ -18,11 +18,48 @@ export declare function isFreshFleetAuthSnapshot(value: {
|
|
|
18
18
|
row: FleetStatusRow;
|
|
19
19
|
authRows: AuthProbeRow[];
|
|
20
20
|
}, minimumCapturedAt: number): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* How stale a cached auth verdict may get before the periodic tick re-probes.
|
|
23
|
+
*
|
|
24
|
+
* The auth verdict rides the same rate-limited `/api/oauth/usage` endpoint as
|
|
25
|
+
* the usage probe. Firing it every 3-minute tick on every fleet device drove one
|
|
26
|
+
* per-account request quota to a permanent 429, and the shared backoff then
|
|
27
|
+
* parked usage fleet-wide and froze the usage cache (RUSH-2998). Re-probing at
|
|
28
|
+
* most every 20 minutes cuts that endpoint traffic ~5x while still catching a
|
|
29
|
+
* revocation within one window — and every device keeps a REAL verdict (not a
|
|
30
|
+
* degraded "unverified"), so `agents devices ping --strict` and the run
|
|
31
|
+
* auth-preflight keep working on every host, unlike a publisher/subscriber split
|
|
32
|
+
* that would blind every non-primary box to revocation. Fleet status still
|
|
33
|
+
* publishes every tick — it does not ride that endpoint.
|
|
34
|
+
*/
|
|
35
|
+
export declare const AUTH_PROBE_MAX_AGE_MS: number;
|
|
36
|
+
/**
|
|
37
|
+
* True when every cached auth row was probed within {@link AUTH_PROBE_MAX_AGE_MS}
|
|
38
|
+
* — i.e. reusing them would not let a verdict get staler than one probe window.
|
|
39
|
+
* Empty cache is never fresh (nothing to reuse). Pure — unit-tested.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isCachedFleetAuthProbeFresh(authRows: AuthProbeRow[], now: number, maxAgeMs?: number): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the tick may reuse the cached auth verdict instead of re-probing the
|
|
44
|
+
* rate-limited endpoint. `force` (an on-demand `agents devices ping`) ALWAYS
|
|
45
|
+
* re-probes — the whole point of the command is a genuinely live verdict, and
|
|
46
|
+
* missing this `!force` is exactly how a `--strict` check silently passed a
|
|
47
|
+
* revoked account (the second `runFleetPing` call site, RUSH-2998). Pure —
|
|
48
|
+
* unit-tested so that inversion cannot regress unnoticed.
|
|
49
|
+
*/
|
|
50
|
+
export declare function shouldReuseCachedAuthProbe(force: boolean, cached: AuthProbeRow[], now: number, maxAgeMs?: number): boolean;
|
|
21
51
|
/**
|
|
22
52
|
* Fleet cache warm: publish THIS host's row for the caches `agents fleet
|
|
23
53
|
* status` / `agents devices list` read (PUBLISH-OWN / READ-UNION, RUSH-2061).
|
|
54
|
+
*
|
|
55
|
+
* `force` is set by on-demand callers (`agents devices ping`) that must return a
|
|
56
|
+
* genuinely live verdict; the periodic daemon tick leaves it unset so it reuses a
|
|
57
|
+
* recent verdict per {@link AUTH_PROBE_MAX_AGE_MS} instead of hammering the
|
|
58
|
+
* rate-limited endpoint every 3 minutes (RUSH-2998).
|
|
24
59
|
*/
|
|
25
|
-
export declare function refreshLocalFleetAuthState(
|
|
60
|
+
export declare function refreshLocalFleetAuthState(opts?: {
|
|
61
|
+
force?: boolean;
|
|
62
|
+
}): Promise<{
|
|
26
63
|
row: FleetStatusRow;
|
|
27
64
|
authRows: import('./auth-health.js').AuthProbeRow[];
|
|
28
65
|
}>;
|
package/dist/lib/daemon-ticks.js
CHANGED
|
@@ -17,11 +17,51 @@ export function isFreshFleetAuthSnapshot(value, minimumCapturedAt) {
|
|
|
17
17
|
&& value.authRows.length > 0
|
|
18
18
|
&& value.authRows.every(authRow => authRow.health.checkedAt >= minimumCapturedAt);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* How stale a cached auth verdict may get before the periodic tick re-probes.
|
|
22
|
+
*
|
|
23
|
+
* The auth verdict rides the same rate-limited `/api/oauth/usage` endpoint as
|
|
24
|
+
* the usage probe. Firing it every 3-minute tick on every fleet device drove one
|
|
25
|
+
* per-account request quota to a permanent 429, and the shared backoff then
|
|
26
|
+
* parked usage fleet-wide and froze the usage cache (RUSH-2998). Re-probing at
|
|
27
|
+
* most every 20 minutes cuts that endpoint traffic ~5x while still catching a
|
|
28
|
+
* revocation within one window — and every device keeps a REAL verdict (not a
|
|
29
|
+
* degraded "unverified"), so `agents devices ping --strict` and the run
|
|
30
|
+
* auth-preflight keep working on every host, unlike a publisher/subscriber split
|
|
31
|
+
* that would blind every non-primary box to revocation. Fleet status still
|
|
32
|
+
* publishes every tick — it does not ride that endpoint.
|
|
33
|
+
*/
|
|
34
|
+
export const AUTH_PROBE_MAX_AGE_MS = 20 * 60_000;
|
|
35
|
+
/**
|
|
36
|
+
* True when every cached auth row was probed within {@link AUTH_PROBE_MAX_AGE_MS}
|
|
37
|
+
* — i.e. reusing them would not let a verdict get staler than one probe window.
|
|
38
|
+
* Empty cache is never fresh (nothing to reuse). Pure — unit-tested.
|
|
39
|
+
*/
|
|
40
|
+
export function isCachedFleetAuthProbeFresh(authRows, now, maxAgeMs = AUTH_PROBE_MAX_AGE_MS) {
|
|
41
|
+
return authRows.length > 0 && authRows.every((r) => now - r.health.checkedAt < maxAgeMs);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether the tick may reuse the cached auth verdict instead of re-probing the
|
|
45
|
+
* rate-limited endpoint. `force` (an on-demand `agents devices ping`) ALWAYS
|
|
46
|
+
* re-probes — the whole point of the command is a genuinely live verdict, and
|
|
47
|
+
* missing this `!force` is exactly how a `--strict` check silently passed a
|
|
48
|
+
* revoked account (the second `runFleetPing` call site, RUSH-2998). Pure —
|
|
49
|
+
* unit-tested so that inversion cannot regress unnoticed.
|
|
50
|
+
*/
|
|
51
|
+
export function shouldReuseCachedAuthProbe(force, cached, now, maxAgeMs = AUTH_PROBE_MAX_AGE_MS) {
|
|
52
|
+
return !force && isCachedFleetAuthProbeFresh(cached, now, maxAgeMs);
|
|
53
|
+
}
|
|
20
54
|
/**
|
|
21
55
|
* Fleet cache warm: publish THIS host's row for the caches `agents fleet
|
|
22
56
|
* status` / `agents devices list` read (PUBLISH-OWN / READ-UNION, RUSH-2061).
|
|
57
|
+
*
|
|
58
|
+
* `force` is set by on-demand callers (`agents devices ping`) that must return a
|
|
59
|
+
* genuinely live verdict; the periodic daemon tick leaves it unset so it reuses a
|
|
60
|
+
* recent verdict per {@link AUTH_PROBE_MAX_AGE_MS} instead of hammering the
|
|
61
|
+
* rate-limited endpoint every 3 minutes (RUSH-2998).
|
|
23
62
|
*/
|
|
24
|
-
export async function refreshLocalFleetAuthState() {
|
|
63
|
+
export async function refreshLocalFleetAuthState(opts) {
|
|
64
|
+
const force = opts?.force === true;
|
|
25
65
|
const { machineId } = await import('./machine-id.js');
|
|
26
66
|
const { probeLocalFleetAuth, readFleetAuthRows, writeFleetAuthRows } = await import('./auth-health.js');
|
|
27
67
|
const { getCliVersion } = await import('./version.js');
|
|
@@ -40,11 +80,18 @@ export async function refreshLocalFleetAuthState() {
|
|
|
40
80
|
return { row, authRows: readFleetAuthRows(self) };
|
|
41
81
|
},
|
|
42
82
|
// A recent daemon publication is the completed result, not a reason to probe
|
|
43
|
-
// every provider a second time.
|
|
44
|
-
|
|
83
|
+
// every provider a second time. An on-demand caller (force) never accepts a
|
|
84
|
+
// cached snapshot — it must return a genuinely live verdict.
|
|
85
|
+
isCompleted: (value) => !force && isFreshFleetAuthSnapshot(value, minimumCapturedAt),
|
|
45
86
|
refresh: async () => {
|
|
46
|
-
|
|
47
|
-
|
|
87
|
+
// Re-probe the rate-limited /oauth/usage endpoint at most every
|
|
88
|
+
// AUTH_PROBE_MAX_AGE_MS; reuse the last real verdict in between (RUSH-2998).
|
|
89
|
+
// Fleet status publishes every tick regardless — it does not ride that endpoint.
|
|
90
|
+
const cached = readFleetAuthRows(self);
|
|
91
|
+
const reuse = shouldReuseCachedAuthProbe(force, cached, requestedAt);
|
|
92
|
+
const authRows = reuse ? cached : await probeLocalFleetAuth({ cliVersion: getCliVersion() });
|
|
93
|
+
if (!reuse)
|
|
94
|
+
writeFleetAuthRows(self, authRows);
|
|
48
95
|
const row = await publishLocalFleetStatus(self);
|
|
49
96
|
return { row, authRows };
|
|
50
97
|
},
|
|
@@ -243,8 +243,7 @@ export const CONFIG_KEYS = [
|
|
|
243
243
|
type: 'string',
|
|
244
244
|
description: "What this device is for, fleet-wide: 'worker' (a box agents run on) or 'personal' (a machine you sit at — never " +
|
|
245
245
|
'picked automatically). Marking ANY device worker turns automatic placement into an allowlist: `--device auto` then ' +
|
|
246
|
-
'picks only from the marked workers.
|
|
247
|
-
'and is excluded from placement by that role, not this key.)',
|
|
246
|
+
'picks only from the marked workers.',
|
|
248
247
|
validate: (v) => DEVICE_ROLES.includes(v)
|
|
249
248
|
? null
|
|
250
249
|
: `role must be one of ${DEVICE_ROLES.join(' | ')}.`,
|