@phnx-labs/agents-cli 1.22.60 → 1.22.61
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 +49 -0
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/browser.js +9 -4
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/exec.js +35 -1
- package/dist/commands/harness-hooks.d.ts +55 -0
- package/dist/commands/harness-hooks.js +104 -0
- package/dist/commands/harness-wizard.d.ts +33 -14
- package/dist/commands/harness-wizard.js +53 -23
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +86 -5
- package/dist/commands/reminders.d.ts +9 -0
- package/dist/commands/reminders.js +49 -0
- package/dist/commands/run-account-picker.d.ts +14 -0
- package/dist/commands/run-account-picker.js +13 -0
- package/dist/commands/teams.d.ts +1 -1
- package/dist/commands/teams.js +9 -3
- package/dist/index.js +9 -0
- package/dist/lib/accounting/rotate.d.ts +63 -0
- package/dist/lib/accounting/rotate.js +229 -13
- package/dist/lib/browser/drivers/local.d.ts +11 -0
- package/dist/lib/browser/drivers/local.js +26 -0
- package/dist/lib/browser/profiles.js +8 -6
- package/dist/lib/browser/service.d.ts +12 -8
- package/dist/lib/browser/service.js +38 -10
- package/dist/lib/claude-statusline.d.ts +14 -1
- package/dist/lib/claude-statusline.js +27 -2
- package/dist/lib/daemon/runner.js +17 -2
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +22 -4
- package/dist/lib/doctor-diff.d.ts +21 -5
- package/dist/lib/doctor-diff.js +242 -76
- package/dist/lib/feed/events.d.ts +1 -1
- package/dist/lib/feed/events.js +25 -16
- package/dist/lib/github/gh-overload.d.ts +58 -0
- package/dist/lib/github/gh-overload.js +246 -0
- package/dist/lib/github/rest.d.ts +64 -0
- package/dist/lib/github/rest.js +111 -0
- package/dist/lib/harness-connection-test.d.ts +57 -0
- package/dist/lib/harness-connection-test.js +80 -0
- package/dist/lib/heal.js +8 -3
- package/dist/lib/installations/shims.d.ts +22 -0
- package/dist/lib/installations/shims.js +104 -0
- package/dist/lib/linear-project-counts.js +8 -0
- package/dist/lib/linear-rate-limit.d.ts +26 -0
- package/dist/lib/linear-rate-limit.js +163 -0
- package/dist/lib/mcp.d.ts +9 -0
- package/dist/lib/mcp.js +37 -1
- package/dist/lib/open-url.js +5 -3
- package/dist/lib/permissions.d.ts +28 -0
- package/dist/lib/permissions.js +156 -1
- package/dist/lib/refresh.js +9 -1
- package/dist/lib/reminders.d.ts +29 -0
- package/dist/lib/reminders.js +88 -0
- package/dist/lib/resource-content-diff.d.ts +33 -0
- package/dist/lib/resource-content-diff.js +103 -0
- package/dist/lib/rules/compile.d.ts +7 -0
- package/dist/lib/rules/compile.js +7 -1
- package/dist/lib/session/active.d.ts +41 -4
- package/dist/lib/session/active.js +58 -7
- package/dist/lib/session/host-link.d.ts +22 -0
- package/dist/lib/session/host-link.js +40 -4
- package/dist/lib/session/trajectory.d.ts +42 -0
- package/dist/lib/session/trajectory.js +46 -27
- package/dist/lib/ssh-exec.d.ts +30 -0
- package/dist/lib/ssh-exec.js +37 -5
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/subagents-registry.d.ts +18 -0
- package/dist/lib/subagents-registry.js +79 -0
- package/dist/lib/teams/agents.d.ts +12 -0
- package/dist/lib/teams/agents.js +51 -0
- package/dist/lib/traces/schema2-build.d.ts +85 -0
- package/dist/lib/traces/schema2-build.js +637 -0
- package/dist/lib/traces/schema2-danger.d.ts +36 -0
- package/dist/lib/traces/schema2-danger.js +185 -0
- package/dist/lib/traces/schema2.d.ts +149 -0
- package/dist/lib/traces/schema2.js +20 -0
- package/dist/lib/traces/sync.d.ts +93 -0
- package/dist/lib/traces/sync.js +75 -22
- package/dist/lib/traces/worker-template.js +5 -0
- package/dist/lib/uninstall.js +10 -1
- package/dist/lib/workflows.d.ts +11 -0
- package/dist/lib/workflows.js +67 -8
- package/package.json +1 -1
package/dist/commands/harness.js
CHANGED
|
@@ -17,7 +17,9 @@ import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists,
|
|
|
17
17
|
import { listPresets, getPreset } from '../lib/profiles-presets.js';
|
|
18
18
|
import { AGENTS, ALL_AGENT_IDS, resolveAgentName } from '../lib/agents.js';
|
|
19
19
|
import { findAccount } from '../lib/account-registry.js';
|
|
20
|
-
import { runWizardSteps, createSteps, editSteps, defaultWizardIO, } from './harness-wizard.js';
|
|
20
|
+
import { runWizardSteps, createSteps, editSteps, defaultWizardIO, runConnectionTest, } from './harness-wizard.js';
|
|
21
|
+
import { harnessHooks } from './harness-hooks.js';
|
|
22
|
+
import { CONNECTION_TEST_PROMPT } from '../lib/harness-connection-test.js';
|
|
21
23
|
/** Short capability summary for a native harness — its supported run modes. */
|
|
22
24
|
function nativeModes(id) {
|
|
23
25
|
const modes = AGENTS[id]?.capabilities?.modes ?? [];
|
|
@@ -196,6 +198,73 @@ export function addNeedsWizard(name, opts) {
|
|
|
196
198
|
return true;
|
|
197
199
|
return !getPreset(name);
|
|
198
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Resolve the tri-state connection-test gate (RUSH-2221), pure so the branching
|
|
203
|
+
* is unit-tested with no prompt or spawn. `--test` forces it on, `--no-test`
|
|
204
|
+
* forces it off, and with neither flag a TTY is asked (default yes) while a
|
|
205
|
+
* non-interactive caller (`--key-stdin`, piped, CI) skips it — so scripting stays
|
|
206
|
+
* non-interactive unless it opts in with `--test`.
|
|
207
|
+
*/
|
|
208
|
+
export function connectionTestGate(testFlag, interactive) {
|
|
209
|
+
if (testFlag === true)
|
|
210
|
+
return 'on';
|
|
211
|
+
if (testFlag === false)
|
|
212
|
+
return 'off';
|
|
213
|
+
return interactive ? 'ask' : 'off';
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Pre-save connection test (RUSH-2221). The harness is already on disk (the test
|
|
217
|
+
* drives the real `agents run <name>` path, so it must be), so this runs a
|
|
218
|
+
* classified smoke test and — on a TTY, when it fails — offers to keep it, edit
|
|
219
|
+
* it, or delete-and-cancel. A test is never blocking on its own: a save is only
|
|
220
|
+
* discarded when the user explicitly chooses to, so a `--test` failure in a
|
|
221
|
+
* non-interactive shell warns and keeps rather than exiting non-zero.
|
|
222
|
+
*/
|
|
223
|
+
async function preSaveConnectionTest(name, testFlag) {
|
|
224
|
+
const interactive = isInteractiveTerminal();
|
|
225
|
+
const gate = connectionTestGate(testFlag, interactive);
|
|
226
|
+
let shouldTest;
|
|
227
|
+
if (gate === 'on')
|
|
228
|
+
shouldTest = true;
|
|
229
|
+
else if (gate === 'off')
|
|
230
|
+
shouldTest = false;
|
|
231
|
+
else {
|
|
232
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
233
|
+
shouldTest = await confirm({ message: `Test the connection for '${name}' now?`, default: true });
|
|
234
|
+
}
|
|
235
|
+
if (!shouldTest)
|
|
236
|
+
return;
|
|
237
|
+
console.log(chalk.gray(`Testing '${name}' — sending "${CONNECTION_TEST_PROMPT}" through agents run…`));
|
|
238
|
+
const result = await runConnectionTest({ mode: 'create', name }, harnessHooks());
|
|
239
|
+
if (!result)
|
|
240
|
+
return;
|
|
241
|
+
if (result.ok) {
|
|
242
|
+
console.log(chalk.green(`✓ ${result.message}`));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
console.log(chalk.yellow(`✗ ${result.message}`));
|
|
246
|
+
if (!interactive) {
|
|
247
|
+
console.log(chalk.gray(`Kept anyway. Fix and retest with: agents harness edit ${name}`));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const { select } = await import('@inquirer/prompts');
|
|
251
|
+
const action = await select({
|
|
252
|
+
message: 'The connection test failed. What would you like to do?',
|
|
253
|
+
choices: [
|
|
254
|
+
{ name: 'Keep it (the endpoint may just be down right now)', value: 'keep' },
|
|
255
|
+
{ name: 'Edit it now', value: 'edit' },
|
|
256
|
+
{ name: 'Delete it and cancel', value: 'delete' },
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
if (action === 'edit') {
|
|
260
|
+
await runEditWizard(name, {});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (action === 'delete') {
|
|
264
|
+
deleteProfile(name);
|
|
265
|
+
throw new Error(`Harness '${name}' deleted after a failed connection test.`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
199
268
|
/**
|
|
200
269
|
* Shared build+persist flow for a fork — used by `agents harness fork`'s
|
|
201
270
|
* flag-driven path AND by the wizard (both for `fork` and, when it falls back
|
|
@@ -223,6 +292,7 @@ async function runForkFlow(source, name, opts) {
|
|
|
223
292
|
writeProfile(forked);
|
|
224
293
|
console.log(chalk.green(`Harness '${name}' forked from ${source}.`));
|
|
225
294
|
console.log(chalk.gray(`Try: agents run ${name} "hello"`));
|
|
295
|
+
await preSaveConnectionTest(name, opts.test);
|
|
226
296
|
}
|
|
227
297
|
/**
|
|
228
298
|
* Interactive `agents harness add`/`fork` wizard — runs when required info is
|
|
@@ -234,7 +304,7 @@ async function runForkFlow(source, name, opts) {
|
|
|
234
304
|
*/
|
|
235
305
|
async function runCreateWizard() {
|
|
236
306
|
const io = await defaultWizardIO();
|
|
237
|
-
const draft = await runWizardSteps(createSteps(), { mode: 'create' }, io);
|
|
307
|
+
const draft = await runWizardSteps(createSteps(), { mode: 'create' }, io, harnessHooks());
|
|
238
308
|
return {
|
|
239
309
|
source: draft.source,
|
|
240
310
|
name: draft.name,
|
|
@@ -292,7 +362,7 @@ async function runEditWizard(name, cliOpts) {
|
|
|
292
362
|
}
|
|
293
363
|
const original = readProfile(name);
|
|
294
364
|
const io = await defaultWizardIO();
|
|
295
|
-
const draft = await runWizardSteps(editSteps(original), { mode: 'edit', original, host: original.host.agent, name }, io);
|
|
365
|
+
const draft = await runWizardSteps(editSteps(original), { mode: 'edit', original, host: original.host.agent, name }, io, harnessHooks());
|
|
296
366
|
const opts = { ...draftToEditOptions(draft, original), keyStdin: cliOpts.keyStdin };
|
|
297
367
|
if (!hasEditFlags(opts)) {
|
|
298
368
|
console.log(chalk.gray(`No changes made to '${name}'.`));
|
|
@@ -308,6 +378,9 @@ async function runEditWizard(name, cliOpts) {
|
|
|
308
378
|
writeProfile(edited);
|
|
309
379
|
console.log(chalk.green(`Harness '${name}' updated.`));
|
|
310
380
|
console.log(chalk.gray(`Model: ${profileModelLabel(edited)}`));
|
|
381
|
+
// An edit that touched host/model/endpoint/auth is exactly what can break the
|
|
382
|
+
// harness, so re-test after saving (RUSH-2221). cliOpts carries the --test flag.
|
|
383
|
+
await preSaveConnectionTest(name, cliOpts.test);
|
|
311
384
|
}
|
|
312
385
|
export function registerHarnessCommands(program) {
|
|
313
386
|
const cmd = program
|
|
@@ -362,6 +435,8 @@ Examples:
|
|
|
362
435
|
.option('--from-secrets <bundle>[:<key>]', 'Removed: import the value with agents accounts add, then use --account')
|
|
363
436
|
.option('--key-stdin', 'Read API key from stdin instead of prompting (for scripts/CI)')
|
|
364
437
|
.option('--force', 'Overwrite an existing harness with the same name')
|
|
438
|
+
.option('--test', 'Run a connection test before saving (default: ask on a terminal)')
|
|
439
|
+
.option('--no-test', 'Skip the pre-save connection test')
|
|
365
440
|
.action(async (name, opts) => {
|
|
366
441
|
try {
|
|
367
442
|
if (opts.authProvider || opts.fromSecrets)
|
|
@@ -371,10 +446,11 @@ Examples:
|
|
|
371
446
|
throw new Error("'agents harness add' needs --preset or --host + --model (or a name and an interactive terminal for the wizard).");
|
|
372
447
|
}
|
|
373
448
|
const wiz = await runCreateWizard();
|
|
374
|
-
await runForkFlow(wiz.source, wiz.name, { ...wiz.opts, force: opts.force, keyStdin: opts.keyStdin });
|
|
449
|
+
await runForkFlow(wiz.source, wiz.name, { ...wiz.opts, force: opts.force, keyStdin: opts.keyStdin, test: opts.test });
|
|
375
450
|
return;
|
|
376
451
|
}
|
|
377
452
|
await addProfile(name, opts, 'Harness');
|
|
453
|
+
await preSaveConnectionTest(name, opts.test);
|
|
378
454
|
}
|
|
379
455
|
catch (err) {
|
|
380
456
|
console.error(chalk.red(err.message));
|
|
@@ -393,6 +469,8 @@ Examples:
|
|
|
393
469
|
.option('--from-secrets <bundle>[:<key>]', 'Removed: import the value with agents accounts add, then use --account')
|
|
394
470
|
.option('--key-stdin', 'Read the API key from stdin instead of prompting (for scripts/CI)')
|
|
395
471
|
.option('--force', 'Overwrite an existing harness with the same name')
|
|
472
|
+
.option('--test', 'Run a connection test before saving (default: ask on a terminal)')
|
|
473
|
+
.option('--no-test', 'Skip the pre-save connection test')
|
|
396
474
|
.addHelpText('after', `
|
|
397
475
|
Examples:
|
|
398
476
|
# Fork OpenCode into a harness pinned to a DeepSeek model on OpenRouter
|
|
@@ -417,7 +495,7 @@ Examples:
|
|
|
417
495
|
throw new Error("'agents harness fork' needs <source> and <name> (or an interactive terminal for the wizard).");
|
|
418
496
|
}
|
|
419
497
|
const wiz = await runCreateWizard();
|
|
420
|
-
await runForkFlow(wiz.source, wiz.name, { ...wiz.opts, force: opts.force, keyStdin: opts.keyStdin });
|
|
498
|
+
await runForkFlow(wiz.source, wiz.name, { ...wiz.opts, force: opts.force, keyStdin: opts.keyStdin, test: opts.test });
|
|
421
499
|
return;
|
|
422
500
|
}
|
|
423
501
|
await runForkFlow(source, name, opts);
|
|
@@ -439,6 +517,8 @@ Examples:
|
|
|
439
517
|
.option('--fallback-model <id>', 'Secondary model retried on the same host on a rate limit (pass an empty string to clear it)')
|
|
440
518
|
.option('--from-secrets <bundle>[:<key>]', 'Removed: import the value with agents accounts add, then use --account')
|
|
441
519
|
.option('--key-stdin', 'Read the API key from stdin instead of prompting (for scripts/CI)')
|
|
520
|
+
.option('--test', 'Run a connection test after saving (default: ask on a terminal)')
|
|
521
|
+
.option('--no-test', 'Skip the connection test')
|
|
442
522
|
.addHelpText('after', `
|
|
443
523
|
Examples:
|
|
444
524
|
# Swap the pinned model
|
|
@@ -479,6 +559,7 @@ Examples:
|
|
|
479
559
|
writeProfile(edited);
|
|
480
560
|
console.log(chalk.green(`Harness '${name}' updated.`));
|
|
481
561
|
console.log(chalk.gray(`Model: ${profileModelLabel(edited)}`));
|
|
562
|
+
await preSaveConnectionTest(name, opts.test);
|
|
482
563
|
}
|
|
483
564
|
catch (err) {
|
|
484
565
|
console.error(chalk.red(err.message));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents reminders` — list your personal operating reminders.
|
|
3
|
+
*
|
|
4
|
+
* The reminders live in `~/.agents/reminders/reminders.yaml` and are surfaced
|
|
5
|
+
* succinctly in the Claude statusline — one per session, chosen from the session
|
|
6
|
+
* id. This command shows the full set (and the file to edit).
|
|
7
|
+
*/
|
|
8
|
+
import type { Command } from 'commander';
|
|
9
|
+
export declare function registerRemindersCommand(program: Command): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadReminders, remindersFilePath } from '../lib/reminders.js';
|
|
3
|
+
import { setHelpSections } from '../lib/help.js';
|
|
4
|
+
const EXAMPLES = [
|
|
5
|
+
'agents reminders # list your operating reminders',
|
|
6
|
+
'agents reminders --json # machine-readable output',
|
|
7
|
+
].join('\n');
|
|
8
|
+
const NOTES = [
|
|
9
|
+
"Reminders live in ~/.agents/reminders/reminders.yaml and sync across the fleet via 'agents repo push'.",
|
|
10
|
+
'One shows succinctly in the Claude statusline per session, chosen deterministically from the session id,',
|
|
11
|
+
'so concurrent agents each show a different one and it stays stable within a session.',
|
|
12
|
+
].join('\n');
|
|
13
|
+
export function registerRemindersCommand(program) {
|
|
14
|
+
const cmd = program
|
|
15
|
+
.command('reminders')
|
|
16
|
+
.description('Personal operating reminders shown in the Claude statusline')
|
|
17
|
+
.option('--json', 'output as JSON')
|
|
18
|
+
.action((opts) => {
|
|
19
|
+
const file = remindersFilePath();
|
|
20
|
+
let reminders;
|
|
21
|
+
try {
|
|
22
|
+
reminders = loadReminders();
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error(chalk.red(`Could not read reminders: ${err instanceof Error ? err.message : String(err)}`));
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (opts.json) {
|
|
30
|
+
process.stdout.write(`${JSON.stringify({ file, reminders }, null, 2)}\n`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (reminders.length === 0) {
|
|
34
|
+
console.log(`No reminders yet. Add them to ${chalk.cyan(file)}`);
|
|
35
|
+
console.log(chalk.dim('Each shows succinctly in the Claude statusline — one per session.'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const count = `${reminders.length} reminder${reminders.length === 1 ? '' : 's'}`;
|
|
39
|
+
console.log(chalk.dim(`${count} · ${file}`));
|
|
40
|
+
console.log(chalk.dim('One shows in the Claude statusline per session, chosen from the session id.\n'));
|
|
41
|
+
for (const reminder of reminders) {
|
|
42
|
+
console.log(` ${chalk.cyan('◆')} ${chalk.bold(reminder.short)}`);
|
|
43
|
+
if (reminder.full && reminder.full !== reminder.short) {
|
|
44
|
+
console.log(` ${chalk.dim(reminder.full)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
setHelpSections(cmd, { examples: EXAMPLES, notes: NOTES });
|
|
49
|
+
}
|
|
@@ -52,6 +52,20 @@ export declare function signInLaunchDecision(input: {
|
|
|
52
52
|
tty: boolean;
|
|
53
53
|
json: boolean;
|
|
54
54
|
}): 'launch' | 'fail-loud';
|
|
55
|
+
/**
|
|
56
|
+
* How a `balanced`/`available` run reacts when every account's usage is stale and
|
|
57
|
+
* none is verified (PHNX-2526). A human present at a real terminal gets the
|
|
58
|
+
* account `picker` — they can choose knowing the numbers are stale — while every
|
|
59
|
+
* unattended shape (`--headless`, `--json`, or no TTY) `fail-loud`s with
|
|
60
|
+
* NO_VERIFIED_USAGE rather than silently guess on a stale snapshot. `headless`
|
|
61
|
+
* joins the gate because a routine/machine dispatch can carry a TTY yet have no
|
|
62
|
+
* human to answer a picker; the split mirrors `signInLaunchDecision`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function noVerifiedUsageDecision(input: {
|
|
65
|
+
tty: boolean;
|
|
66
|
+
json: boolean;
|
|
67
|
+
headless: boolean;
|
|
68
|
+
}): 'picker' | 'fail-loud';
|
|
55
69
|
/**
|
|
56
70
|
* Choose which installed version to launch so the user can authenticate, when a
|
|
57
71
|
* strategy found zero healthy accounts but at least one is merely signed out
|
|
@@ -221,6 +221,19 @@ export function signInLaunchDecision(input) {
|
|
|
221
221
|
const humanPresent = input.tty && !input.json;
|
|
222
222
|
return input.recoverable > 0 && humanPresent ? 'launch' : 'fail-loud';
|
|
223
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* How a `balanced`/`available` run reacts when every account's usage is stale and
|
|
226
|
+
* none is verified (PHNX-2526). A human present at a real terminal gets the
|
|
227
|
+
* account `picker` — they can choose knowing the numbers are stale — while every
|
|
228
|
+
* unattended shape (`--headless`, `--json`, or no TTY) `fail-loud`s with
|
|
229
|
+
* NO_VERIFIED_USAGE rather than silently guess on a stale snapshot. `headless`
|
|
230
|
+
* joins the gate because a routine/machine dispatch can carry a TTY yet have no
|
|
231
|
+
* human to answer a picker; the split mirrors `signInLaunchDecision`.
|
|
232
|
+
*/
|
|
233
|
+
export function noVerifiedUsageDecision(input) {
|
|
234
|
+
const humanPresent = input.tty && !input.json && !input.headless;
|
|
235
|
+
return humanPresent ? 'picker' : 'fail-loud';
|
|
236
|
+
}
|
|
224
237
|
/**
|
|
225
238
|
* Choose which installed version to launch so the user can authenticate, when a
|
|
226
239
|
* strategy found zero healthy accounts but at least one is merely signed out
|
package/dist/commands/teams.d.ts
CHANGED
|
@@ -108,7 +108,7 @@ export declare function staleRepoError(params: {
|
|
|
108
108
|
* the teammate itself so we don't need the original --cloud CLI args.
|
|
109
109
|
*/
|
|
110
110
|
export declare function wireCloudDispatcher(mgr: AgentManager): void;
|
|
111
|
-
export declare function cloudDispatchOptions(agent: Pick<AgentProcess, 'prompt' | 'agentType' | 'cloudRepo' | 'cloudBranch' | 'model'>): DispatchOptions;
|
|
111
|
+
export declare function cloudDispatchOptions(agent: Pick<AgentProcess, 'prompt' | 'agentType' | 'cloudRepo' | 'cloudBranch' | 'model' | 'mode'>): DispatchOptions;
|
|
112
112
|
/**
|
|
113
113
|
* Single-wave start used by `teams start` without --watch. startReady()
|
|
114
114
|
* terminalizes a placement/spawn/cloud/dependency failure to FAILED with
|
package/dist/commands/teams.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dieFriction, relTime, truncate, isJsonMode, padRight } from '../lib/for
|
|
|
3
3
|
import * as fs from 'fs/promises';
|
|
4
4
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
5
5
|
import * as path from 'path';
|
|
6
|
-
import { AgentManager, AgentStatus, checkCliSignedIn, collectTeamsDoctorData, getAgentsDir, VALID_TASK_TYPES, } from '../lib/teams/agents.js';
|
|
6
|
+
import { AgentManager, AgentStatus, checkCliSignedIn, collectTeamsDoctorData, getAgentsDir, VALID_TASK_TYPES, withTeammatePrPolicy, } from '../lib/teams/agents.js';
|
|
7
7
|
import { mailboxDir, enqueue } from '../lib/mailbox.js';
|
|
8
8
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
9
9
|
import { emit } from '../lib/feed/events.js';
|
|
@@ -373,7 +373,10 @@ export function wireCloudDispatcher(mgr) {
|
|
|
373
373
|
}
|
|
374
374
|
export function cloudDispatchOptions(agent) {
|
|
375
375
|
return {
|
|
376
|
-
|
|
376
|
+
// PHNX-3236: a cloud teammate runs in the provider sandbox and never inherits
|
|
377
|
+
// the local merge-guard.sh hook, so the prompt policy is its ONLY self-merge
|
|
378
|
+
// boundary — apply the same helper buildRunArgv uses for local/remote.
|
|
379
|
+
prompt: withTeammatePrPolicy(agent.prompt, agent.mode),
|
|
377
380
|
agent: agent.agentType,
|
|
378
381
|
repo: agent.cloudRepo ?? undefined,
|
|
379
382
|
branch: agent.cloudBranch ?? undefined,
|
|
@@ -1933,7 +1936,10 @@ export function registerTeamsCommands(program) {
|
|
|
1933
1936
|
mgr.setCloudDispatcher(async (a) => {
|
|
1934
1937
|
const prov = resolveProvider(providerId);
|
|
1935
1938
|
const dispatchOpts = {
|
|
1936
|
-
|
|
1939
|
+
// PHNX-3236: same self-merge boundary as the local/remote path — a
|
|
1940
|
+
// cloud teammate has no inherited merge-guard.sh, so the prompt is
|
|
1941
|
+
// its only layer.
|
|
1942
|
+
prompt: withTeammatePrPolicy(a.prompt, a.mode),
|
|
1937
1943
|
agent: a.agentType,
|
|
1938
1944
|
repo: opts.repo,
|
|
1939
1945
|
branch: opts.branch,
|
package/dist/index.js
CHANGED
|
@@ -72,6 +72,15 @@ if (process.argv[2] === '__shim') {
|
|
|
72
72
|
const code = await execShimPassthrough(agent, rawArgs, process.cwd(), pinned || undefined);
|
|
73
73
|
process.exit(code);
|
|
74
74
|
}
|
|
75
|
+
// gh overload delegate: the `gh` PATH shim routes `gh pr checks` here as
|
|
76
|
+
// `agents __gh --real-gh <path> -- pr checks …`, so the rate-limit-prone read
|
|
77
|
+
// runs over REST instead of GraphQL (PHNX-3501). Above bootstrap for the same
|
|
78
|
+
// reason as __shim: no update check, no command-tree load — this is on the hot
|
|
79
|
+
// path of an agent's CI watch, and the gh argv must pass through untouched.
|
|
80
|
+
if (process.argv[2] === '__gh') {
|
|
81
|
+
const { runGhOverload } = await import('./lib/github/gh-overload.js');
|
|
82
|
+
process.exit(await runGhOverload(process.argv.slice(3)));
|
|
83
|
+
}
|
|
75
84
|
if (process.argv[2] === '__claude-statusline') {
|
|
76
85
|
const { runClaudeStatusLine } = await import('./lib/claude-statusline.js');
|
|
77
86
|
process.exit(await runClaudeStatusLine());
|
|
@@ -8,6 +8,7 @@ import type { AgentId, RunStrategy } from '../types.js';
|
|
|
8
8
|
import type { FallbackEntry } from '../exec.js';
|
|
9
9
|
import { PROJECTION_HORIZON_MIN, capacityWeight } from './capacity.js';
|
|
10
10
|
import { type AccountInfo, type CredentialPresence } from '../agents.js';
|
|
11
|
+
import { type EventPayload } from '../feed/events.js';
|
|
11
12
|
import { type UsageSnapshot } from './usage.js';
|
|
12
13
|
import { type AuthVerdict } from '../auth-health.js';
|
|
13
14
|
export interface RotateCandidate {
|
|
@@ -75,6 +76,21 @@ export interface RotateResult {
|
|
|
75
76
|
* was silently reported as verified.)
|
|
76
77
|
*/
|
|
77
78
|
usageUnverified?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* True when NO candidate carries a fresh usage snapshot AND at least one
|
|
81
|
+
* carries a STALE-but-present one — the "entirely stale usage" case
|
|
82
|
+
* (PHNX-2526). The INITIAL route MUST NOT be decided on a stale number that
|
|
83
|
+
* looks plausible but is wrong (the yosemite-s1 incident: 26h–2.7d-old
|
|
84
|
+
* snapshots read 48% while the account was at its weekly cap). `picked` is
|
|
85
|
+
* still populated (a stale candidate) so `healthy` stays intact for BOUNDED
|
|
86
|
+
* post-rejection failover, but a caller doing the initial selection MUST NOT
|
|
87
|
+
* launch it — it diverts to the account picker (interactive) or fails loud
|
|
88
|
+
* with NO_VERIFIED_USAGE (unattended). Distinct from a BLIND pool with no
|
|
89
|
+
* snapshot at all (a worker box whose usage endpoint 403s, RUSH-2392): that
|
|
90
|
+
* carries no misleading number, so it still draws a pick and this stays
|
|
91
|
+
* false.
|
|
92
|
+
*/
|
|
93
|
+
noVerifiedUsage?: boolean;
|
|
78
94
|
}
|
|
79
95
|
export declare const RUN_STRATEGIES: RunStrategy[];
|
|
80
96
|
/**
|
|
@@ -139,6 +155,20 @@ export declare const USAGE_DECISION_MAX_AGE_MS: number;
|
|
|
139
155
|
* narrowing rule below exists to prevent.
|
|
140
156
|
*/
|
|
141
157
|
export declare function isUsageVerified(candidate: RotateCandidate, nowMs?: number): boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Whether this candidate carries a STALE-but-present usage number: a snapshot
|
|
160
|
+
* with windows whose capture time is older than {@link USAGE_DECISION_MAX_AGE_MS}.
|
|
161
|
+
*
|
|
162
|
+
* This is the misleading case the initial route must refuse — the number reads
|
|
163
|
+
* "48% used" with the same confidence whether captured a minute or three days
|
|
164
|
+
* ago, and a box whose refresh is failing stays wrong indefinitely. It is
|
|
165
|
+
* deliberately NARROWER than "not verified": a BLIND candidate with no snapshot
|
|
166
|
+
* (or a plan-only meterless one with no windows) carries no number to be misled
|
|
167
|
+
* by — a worker box whose usage endpoint 403s (RUSH-2392), or a meterless Grok
|
|
168
|
+
* login — so it is not "stale", and an entirely-blind pool still draws a pick
|
|
169
|
+
* (PHNX-3392) rather than fail loud with NO_VERIFIED_USAGE.
|
|
170
|
+
*/
|
|
171
|
+
export declare function hasStaleUsage(candidate: RotateCandidate, nowMs?: number): boolean;
|
|
142
172
|
/**
|
|
143
173
|
* Whether a specific account can serve a run right now, and — when it can't —
|
|
144
174
|
* why. `signed_out` covers a missing usable credential; `revoked` is a token the
|
|
@@ -291,6 +321,16 @@ export declare function earliestResetAcross(candidates: RotateCandidate[], nowMs
|
|
|
291
321
|
* and `resets <time>` (parsed for the rotate cooldown). Do not deviate.
|
|
292
322
|
*/
|
|
293
323
|
export declare function formatNoHealthyAccountError(agent: AgentId, strategy: RunStrategy, excluded: RotateCandidate[], nowMs?: number): string;
|
|
324
|
+
/**
|
|
325
|
+
* The all-stale-usage error (PHNX-2526) an UNATTENDED `balanced`/`available`
|
|
326
|
+
* run fails loud with when no account's usage is fresh enough to route on. EXACT
|
|
327
|
+
* contract — it MUST contain the literal `NO_VERIFIED_USAGE` so a machine caller
|
|
328
|
+
* (and the Factory watchdog) can tail-detect it distinctly from the
|
|
329
|
+
* `no healthy` throttle error, which is a different condition (throttled vs
|
|
330
|
+
* merely stale). Names each candidate with how stale its snapshot is, so the
|
|
331
|
+
* operator can see the failing-refresh box rather than guess.
|
|
332
|
+
*/
|
|
333
|
+
export declare function formatNoVerifiedUsageError(agent: AgentId, strategy: RunStrategy, candidates: RotateCandidate[], nowMs?: number): string;
|
|
294
334
|
/**
|
|
295
335
|
* The zero-healthy-harness error for `agents run auto` — names each harness's
|
|
296
336
|
* exclusion reason plus the earliest reset across all snapshots.
|
|
@@ -336,6 +376,16 @@ export declare function resolveAccountVersion(agent: AgentId, account: string):
|
|
|
336
376
|
export declare function selectBalancedVersion(agent: AgentId): Promise<RotateResult | null>;
|
|
337
377
|
/** Select the configured version if available, otherwise another available version. */
|
|
338
378
|
export declare function selectAvailableVersion(agent: AgentId, preferredVersion?: string | null): Promise<RotateResult | null>;
|
|
379
|
+
/**
|
|
380
|
+
* Build the enriched `rotation.resolved`/`rotation.unresolved` event payload:
|
|
381
|
+
* the full candidate pool as the router saw it, the pick and WHY, and a
|
|
382
|
+
* freshness tally. Replaces the old `{ version, healthy: <n>, excluded: <n> }`
|
|
383
|
+
* shape, which recorded only counts and a device-local version and so could not
|
|
384
|
+
* tell a blind-pool draw from a skewed-verified pick from a refused-stale route
|
|
385
|
+
* — the exact ambiguity that keeps a bad pick undebuggable from the log. All
|
|
386
|
+
* candidates share ONE `nowMs` so their `tier`/`ageMs` are mutually consistent.
|
|
387
|
+
*/
|
|
388
|
+
export declare function buildRotationDecisionEvent(rotation: RotateResult, agent: AgentId, strategy: RunStrategy): EventPayload;
|
|
339
389
|
/**
|
|
340
390
|
* Resolve the version `agents run` should use when the caller did not pin
|
|
341
391
|
* one with `@version`. The caller supplies the effective strategy.
|
|
@@ -362,6 +412,19 @@ export declare function resolveRunVersion(agent: AgentId, strategy: RunStrategy,
|
|
|
362
412
|
* path — there is no account to be "unhealthy").
|
|
363
413
|
*/
|
|
364
414
|
exhausted?: RotateCandidate[];
|
|
415
|
+
/**
|
|
416
|
+
* Set (with `version: null`) for a `balanced`/`available` route when EVERY
|
|
417
|
+
* eligible account's usage is stale and none is verified (PHNX-2526). The
|
|
418
|
+
* initial selection MUST NOT auto-launch on a stale number: an interactive
|
|
419
|
+
* caller diverts to the account picker, an unattended one fails loud with
|
|
420
|
+
* NO_VERIFIED_USAGE (`formatNoVerifiedUsageError`). `rotation` is still
|
|
421
|
+
* returned — its `healthy` set (the stale candidates) is preserved ONLY for
|
|
422
|
+
* bounded post-rejection failover, never the initial pick. Undefined when a
|
|
423
|
+
* verified account exists, when the pool is entirely blind (no snapshots —
|
|
424
|
+
* the worker-box case still draws a pick), for `pinned`, and for the
|
|
425
|
+
* zero-healthy `exhausted` case.
|
|
426
|
+
*/
|
|
427
|
+
noVerifiedUsage?: boolean;
|
|
365
428
|
}>;
|
|
366
429
|
/**
|
|
367
430
|
* Cap on the number of healthy accounts a single run will re-dispatch through
|