@phnx-labs/agents-cli 1.22.59 → 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 +71 -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/perf.js +10 -0
- 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/sessions-picker.d.ts +13 -0
- package/dist/commands/sessions-picker.js +17 -8
- package/dist/commands/sessions.js +13 -11
- package/dist/commands/teams-picker.js +20 -6
- package/dist/commands/teams.d.ts +3 -3
- package/dist/commands/teams.js +86 -24
- package/dist/index.js +9 -0
- package/dist/lib/accounting/rotate.d.ts +63 -0
- package/dist/lib/accounting/rotate.js +240 -16
- package/dist/lib/accounting/usage-sync.d.ts +12 -2
- package/dist/lib/accounting/usage-sync.js +34 -6
- 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 +28 -15
- 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/perf/db.d.ts +1 -1
- package/dist/lib/perf/db.js +53 -2
- package/dist/lib/perf/types.d.ts +14 -0
- 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/live-metadata.js +3 -3
- 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/teams/api.d.ts +8 -0
- package/dist/lib/teams/api.js +50 -6
- package/dist/lib/teams/delivery.d.ts +14 -4
- package/dist/lib/teams/delivery.js +15 -5
- 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/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';
|
|
@@ -62,6 +62,7 @@ function statusColor(status) {
|
|
|
62
62
|
case 'running': return chalk.yellow;
|
|
63
63
|
case 'completed': return chalk.green;
|
|
64
64
|
case 'pr_open': return chalk.magenta; // RUSH-2380: process done, PR not merged
|
|
65
|
+
case 'stranded': return chalk.yellow; // PHNX-2951: done but work not committed
|
|
65
66
|
case 'failed': return chalk.red;
|
|
66
67
|
case 'stopped': return chalk.gray;
|
|
67
68
|
default: return chalk.white;
|
|
@@ -150,6 +151,7 @@ function snapshotToStatusDetail(agent) {
|
|
|
150
151
|
after: agent.after,
|
|
151
152
|
task_type: agent.task_type,
|
|
152
153
|
host: agent.host,
|
|
154
|
+
workspace_dir: agent.workspace_dir,
|
|
153
155
|
};
|
|
154
156
|
}
|
|
155
157
|
function fullName(type, version) {
|
|
@@ -371,7 +373,10 @@ export function wireCloudDispatcher(mgr) {
|
|
|
371
373
|
}
|
|
372
374
|
export function cloudDispatchOptions(agent) {
|
|
373
375
|
return {
|
|
374
|
-
|
|
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),
|
|
375
380
|
agent: agent.agentType,
|
|
376
381
|
repo: agent.cloudRepo ?? undefined,
|
|
377
382
|
branch: agent.cloudBranch ?? undefined,
|
|
@@ -784,6 +789,9 @@ function printAgentDetail(a, session) {
|
|
|
784
789
|
}
|
|
785
790
|
if (a.has_errors)
|
|
786
791
|
console.log(` ${chalk.red('! reported an error')}`);
|
|
792
|
+
if (delivery === 'stranded' && a.workspace_dir) {
|
|
793
|
+
console.log(` ${chalk.yellow('! stranded')} uncommitted work at ${a.workspace_dir}`);
|
|
794
|
+
}
|
|
787
795
|
if (a.pr_url)
|
|
788
796
|
console.log(` ${chalk.gray('PR ')}${a.pr_url}`);
|
|
789
797
|
}
|
|
@@ -864,17 +872,34 @@ function printAgentSummary(s) {
|
|
|
864
872
|
console.log(` ${chalk.gray('>')} ${truncate(firstLine, 96)}`);
|
|
865
873
|
}
|
|
866
874
|
}
|
|
875
|
+
if (delivery === 'stranded' && s.workspace_dir) {
|
|
876
|
+
console.log(` ${chalk.yellow('! stranded')} uncommitted work at ${s.workspace_dir}`);
|
|
877
|
+
}
|
|
867
878
|
if (s.pr_url)
|
|
868
879
|
console.log(` ${chalk.gray('PR ')} ${chalk.cyan(s.pr_url)}`);
|
|
869
880
|
}
|
|
881
|
+
function formatTeamStatusSummary(summary) {
|
|
882
|
+
const done = Math.max(0, summary.completed - summary.stranded);
|
|
883
|
+
const parts = [];
|
|
884
|
+
if (summary.pending > 0)
|
|
885
|
+
parts.push(`${summary.pending} pending`);
|
|
886
|
+
if (summary.running > 0 || parts.length === 0)
|
|
887
|
+
parts.push(`${summary.running} working`);
|
|
888
|
+
if (done > 0 || summary.stranded === 0)
|
|
889
|
+
parts.push(`${done} done`);
|
|
890
|
+
if (summary.stranded > 0)
|
|
891
|
+
parts.push(`${summary.stranded} stranded`);
|
|
892
|
+
if (summary.failed > 0 || parts.length === 0)
|
|
893
|
+
parts.push(`${summary.failed} failed`);
|
|
894
|
+
if (summary.stopped > 0 || parts.length === 0)
|
|
895
|
+
parts.push(`${summary.stopped} stopped`);
|
|
896
|
+
return `(${parts.join(', ')})`;
|
|
897
|
+
}
|
|
870
898
|
// Render a team's status in the same format the `status` subcommand uses, so
|
|
871
899
|
// the interactive picker's Enter action drops the user into a familiar view.
|
|
872
900
|
async function printTeamStatus(team, result) {
|
|
873
901
|
const { summary, agents } = result;
|
|
874
|
-
console.log(chalk.bold(`Team ${chalk.cyan(team)} `) +
|
|
875
|
-
chalk.gray(summary.pending > 0
|
|
876
|
-
? `(${summary.pending} pending, ${summary.running} working, ${summary.completed} done, ${summary.failed} failed, ${summary.stopped} stopped)`
|
|
877
|
-
: `(${summary.running} working, ${summary.completed} done, ${summary.failed} failed, ${summary.stopped} stopped)`));
|
|
902
|
+
console.log(chalk.bold(`Team ${chalk.cyan(team)} `) + chalk.gray(formatTeamStatusSummary(summary)));
|
|
878
903
|
if (agents.length === 0) {
|
|
879
904
|
console.log(chalk.gray(' (no teammates yet — add one with `agents teams add`)'));
|
|
880
905
|
}
|
|
@@ -900,10 +925,7 @@ async function printTeamStatus(team, result) {
|
|
|
900
925
|
// verbose/legacy layout.
|
|
901
926
|
function printTeamSummary(team, result) {
|
|
902
927
|
const { summary, agents } = result;
|
|
903
|
-
console.log(chalk.bold(`Team ${chalk.cyan(team)} `) +
|
|
904
|
-
chalk.gray(summary.pending > 0
|
|
905
|
-
? `(${summary.pending} pending, ${summary.running} working, ${summary.completed} done, ${summary.failed} failed, ${summary.stopped} stopped)`
|
|
906
|
-
: `(${summary.running} working, ${summary.completed} done, ${summary.failed} failed, ${summary.stopped} stopped)`));
|
|
928
|
+
console.log(chalk.bold(`Team ${chalk.cyan(team)} `) + chalk.gray(formatTeamStatusSummary(summary)));
|
|
907
929
|
if (agents.length === 0) {
|
|
908
930
|
console.log(chalk.gray(' (no teammates yet — add one with `agents teams add`)'));
|
|
909
931
|
}
|
|
@@ -925,12 +947,13 @@ function printTeamSummary(team, result) {
|
|
|
925
947
|
console.log(chalk.gray('Raw log: agents teams logs --team ' + team + ' --teammate <name>'));
|
|
926
948
|
}
|
|
927
949
|
// Classify a team into a single bucket for --status filtering.
|
|
928
|
-
// - empty:
|
|
929
|
-
// - waiting:
|
|
930
|
-
// - working:
|
|
931
|
-
// - failed:
|
|
932
|
-
//
|
|
933
|
-
// -
|
|
950
|
+
// - empty: no teammates (created but nobody added yet)
|
|
951
|
+
// - waiting: only staged teammates — call `teams start` to kick them off
|
|
952
|
+
// - working: at least one teammate still running
|
|
953
|
+
// - failed: at least one teammate failed or was stopped (any failure wins —
|
|
954
|
+
// even if others finished, you want to know about the failure)
|
|
955
|
+
// - stranded: everyone finished, but at least one has uncommitted work and no PR
|
|
956
|
+
// - done: everyone finished successfully, no failures, no stranded work
|
|
934
957
|
function classifyTeamStatus(t) {
|
|
935
958
|
if (t.agent_count === 0)
|
|
936
959
|
return 'empty';
|
|
@@ -943,6 +966,10 @@ function classifyTeamStatus(t) {
|
|
|
943
966
|
const accounted = t.running + t.completed + t.failed + t.stopped;
|
|
944
967
|
if (accounted < t.agent_count)
|
|
945
968
|
return 'waiting';
|
|
969
|
+
// Completed-without-delivery is not "done": the work is still in the worktree
|
|
970
|
+
// and will be lost on cleanup (PHNX-2951).
|
|
971
|
+
if ((t.stranded ?? 0) > 0)
|
|
972
|
+
return 'stranded';
|
|
946
973
|
return 'done';
|
|
947
974
|
}
|
|
948
975
|
// Merge persistent team registry with tasks derived from live agents so empty
|
|
@@ -959,6 +986,7 @@ function mergeTeams(registry, tasks) {
|
|
|
959
986
|
pending: 0,
|
|
960
987
|
running: 0,
|
|
961
988
|
completed: 0,
|
|
989
|
+
stranded: 0,
|
|
962
990
|
failed: 0,
|
|
963
991
|
stopped: 0,
|
|
964
992
|
workspace_dir: null,
|
|
@@ -969,7 +997,7 @@ function mergeTeams(registry, tasks) {
|
|
|
969
997
|
}
|
|
970
998
|
return Array.from(byName.values()).sort((a, b) => new Date(b.modified_at).getTime() - new Date(a.modified_at).getTime());
|
|
971
999
|
}
|
|
972
|
-
function buildTasksFromSnapshots(agents) {
|
|
1000
|
+
async function buildTasksFromSnapshots(agents) {
|
|
973
1001
|
const byTeam = new Map();
|
|
974
1002
|
for (const agent of agents) {
|
|
975
1003
|
const teamAgents = byTeam.get(agent.task_name) || [];
|
|
@@ -981,6 +1009,7 @@ function buildTasksFromSnapshots(agents) {
|
|
|
981
1009
|
let pending = 0;
|
|
982
1010
|
let running = 0;
|
|
983
1011
|
let completed = 0;
|
|
1012
|
+
let stranded = 0;
|
|
984
1013
|
let failed = 0;
|
|
985
1014
|
let stopped = 0;
|
|
986
1015
|
let earliestStart = null;
|
|
@@ -998,6 +1027,16 @@ function buildTasksFromSnapshots(agents) {
|
|
|
998
1027
|
failed++;
|
|
999
1028
|
else if (status === AgentStatus.STOPPED)
|
|
1000
1029
|
stopped++;
|
|
1030
|
+
// Stranded = completed with no PR and a dirty local worktree (PHNX-2951).
|
|
1031
|
+
if (status === AgentStatus.COMPLETED &&
|
|
1032
|
+
!agent.pr_url?.trim() &&
|
|
1033
|
+
!agent.host &&
|
|
1034
|
+
agent.workspace_dir) {
|
|
1035
|
+
const dirty = await hasUncommittedChanges(agent.workspace_dir);
|
|
1036
|
+
if (dirty) {
|
|
1037
|
+
stranded++;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1001
1040
|
const startedAt = parseTimestamp(agent.started_at);
|
|
1002
1041
|
if (startedAt && (!earliestStart || startedAt < earliestStart)) {
|
|
1003
1042
|
earliestStart = startedAt;
|
|
@@ -1017,6 +1056,7 @@ function buildTasksFromSnapshots(agents) {
|
|
|
1017
1056
|
pending,
|
|
1018
1057
|
running,
|
|
1019
1058
|
completed,
|
|
1059
|
+
stranded,
|
|
1020
1060
|
failed,
|
|
1021
1061
|
stopped,
|
|
1022
1062
|
workspace_dir: workspaceDir,
|
|
@@ -1026,7 +1066,7 @@ function buildTasksFromSnapshots(agents) {
|
|
|
1026
1066
|
}
|
|
1027
1067
|
return tasks.sort((a, b) => new Date(b.modified_at).getTime() - new Date(a.modified_at).getTime());
|
|
1028
1068
|
}
|
|
1029
|
-
export function buildTeamRowsFromSnapshots(registry, agents,
|
|
1069
|
+
export async function buildTeamRowsFromSnapshots(registry, agents,
|
|
1030
1070
|
/** team name -> the session that spawned it (see teamSpawners). */
|
|
1031
1071
|
spawners) {
|
|
1032
1072
|
const byTeam = new Map();
|
|
@@ -1035,7 +1075,26 @@ spawners) {
|
|
|
1035
1075
|
details.push(snapshotToStatusDetail(agent));
|
|
1036
1076
|
byTeam.set(agent.task_name, details);
|
|
1037
1077
|
}
|
|
1038
|
-
const teams = mergeTeams(registry, buildTasksFromSnapshots(agents));
|
|
1078
|
+
const teams = mergeTeams(registry, await buildTasksFromSnapshots(agents));
|
|
1079
|
+
// Recompute delivery for cached snapshots so `teams list` reflects stranded
|
|
1080
|
+
// work discovered by probing the real worktree (PHNX-2951).
|
|
1081
|
+
for (const details of byTeam.values()) {
|
|
1082
|
+
for (const a of details) {
|
|
1083
|
+
const snapshot = agents.find((s) => s.agent_id === a.agent_id);
|
|
1084
|
+
if (snapshot &&
|
|
1085
|
+
normalizeTeamListStatus(a.status) === AgentStatus.COMPLETED &&
|
|
1086
|
+
!snapshot.pr_url?.trim() &&
|
|
1087
|
+
!snapshot.host &&
|
|
1088
|
+
snapshot.workspace_dir) {
|
|
1089
|
+
const dirty = await hasUncommittedChanges(snapshot.workspace_dir);
|
|
1090
|
+
a.delivery = resolveTeammateDelivery({
|
|
1091
|
+
status: a.status,
|
|
1092
|
+
prUrl: snapshot.pr_url,
|
|
1093
|
+
hasUncommittedChanges: dirty,
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1039
1098
|
return {
|
|
1040
1099
|
teams,
|
|
1041
1100
|
rows: teams.map((team) => ({
|
|
@@ -1107,7 +1166,7 @@ async function loadTeamRows(_mgr) {
|
|
|
1107
1166
|
catch {
|
|
1108
1167
|
// The index is an enrichment here — a missing/locked DB just drops the column.
|
|
1109
1168
|
}
|
|
1110
|
-
return buildTeamRowsFromSnapshots(registry, agents, spawners);
|
|
1169
|
+
return await buildTeamRowsFromSnapshots(registry, agents, spawners);
|
|
1111
1170
|
}
|
|
1112
1171
|
// Picker fallback for `teams logs` when the teammate ref is omitted. Shows a
|
|
1113
1172
|
// flat list of every teammate with their team context; Enter picks one.
|
|
@@ -1271,7 +1330,7 @@ export function registerTeamsCommands(program) {
|
|
|
1271
1330
|
.alias('ls')
|
|
1272
1331
|
.description('List your teams, most recent activity first')
|
|
1273
1332
|
.option('-a, --agent <agent>', 'Filter: only teams with this agent (e.g. claude or claude@2.1.112)')
|
|
1274
|
-
.option('--status <status>', 'Filter: only teams with this status (working, done, failed, or empty)')
|
|
1333
|
+
.option('--status <status>', 'Filter: only teams with this status (working, done, stranded, failed, or empty)')
|
|
1275
1334
|
.option('--since <time>', 'Filter: teams active after this time (e.g. "2h", "7d", or ISO date)')
|
|
1276
1335
|
.option('--until <time>', 'Filter: teams active before this time (e.g. "30d", or ISO date)')
|
|
1277
1336
|
.option('-n, --limit <n>', 'Show at most this many teams (default: 20)', '20')
|
|
@@ -1298,7 +1357,7 @@ export function registerTeamsCommands(program) {
|
|
|
1298
1357
|
catch {
|
|
1299
1358
|
// The session index is an enrichment here; a missing one drops the column.
|
|
1300
1359
|
}
|
|
1301
|
-
let rows = buildTeamRowsFromSnapshots(registry, everyAgent, spawners).rows;
|
|
1360
|
+
let rows = (await buildTeamRowsFromSnapshots(registry, everyAgent, spawners)).rows;
|
|
1302
1361
|
// --- query: substring match on team name ---
|
|
1303
1362
|
if (query) {
|
|
1304
1363
|
const q = query.toLowerCase();
|
|
@@ -1318,7 +1377,7 @@ export function registerTeamsCommands(program) {
|
|
|
1318
1377
|
// --- --status: classify each team, filter ---
|
|
1319
1378
|
if (opts.status) {
|
|
1320
1379
|
const want = opts.status.toLowerCase();
|
|
1321
|
-
const validStatuses = ['working', 'done', 'failed', 'empty'];
|
|
1380
|
+
const validStatuses = ['working', 'done', 'stranded', 'failed', 'empty'];
|
|
1322
1381
|
if (!validStatuses.includes(want)) {
|
|
1323
1382
|
dieFriction('teams', 'invalid-status-filter', `Invalid --status '${opts.status}'. Use one of: ${validStatuses.join(', ')}`);
|
|
1324
1383
|
}
|
|
@@ -1877,7 +1936,10 @@ export function registerTeamsCommands(program) {
|
|
|
1877
1936
|
mgr.setCloudDispatcher(async (a) => {
|
|
1878
1937
|
const prov = resolveProvider(providerId);
|
|
1879
1938
|
const dispatchOpts = {
|
|
1880
|
-
|
|
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),
|
|
1881
1943
|
agent: a.agentType,
|
|
1882
1944
|
repo: opts.repo,
|
|
1883
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
|