@phnx-labs/agents-cli 1.22.52 → 1.22.54
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 +336 -0
- package/README.md +42 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +220 -174
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +26 -17
- package/dist/commands/fleet-capture.js +7 -0
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +4 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +8 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions-inject.js +8 -3
- package/dist/commands/sessions-picker.js +2 -1
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +94 -40
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +52 -3
- package/dist/commands/share.js +262 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +53 -14
- package/dist/commands/status.js +14 -0
- package/dist/commands/sync.js +44 -0
- package/dist/commands/view.d.ts +3 -1
- package/dist/commands/view.js +5 -4
- package/dist/lib/account-registry.d.ts +15 -5
- package/dist/lib/account-registry.js +165 -53
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +37 -1
- package/dist/lib/accounting/usage.js +71 -6
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/answer-router.js +2 -1
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/profiles.d.ts +18 -0
- package/dist/lib/browser/profiles.js +26 -1
- package/dist/lib/browser/registry.d.ts +44 -14
- package/dist/lib/browser/registry.js +141 -45
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/daemon.js +30 -4
- package/dist/lib/daemon/runner.js +10 -2
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +8 -7
- package/dist/lib/devices/config-migration.js +147 -1
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/device-docs.d.ts +35 -0
- package/dist/lib/devices/device-docs.js +163 -0
- package/dist/lib/devices/discovery-policy.d.ts +14 -2
- package/dist/lib/devices/discovery-policy.js +31 -21
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/registry.d.ts +11 -5
- package/dist/lib/devices/registry.js +46 -18
- package/dist/lib/exec.d.ts +88 -30
- package/dist/lib/exec.js +138 -34
- package/dist/lib/feed/feed.d.ts +10 -2
- package/dist/lib/feed/feed.js +35 -2
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/dispatch.d.ts +4 -3
- package/dist/lib/hosts/dispatch.js +12 -8
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/hosts/providers/local.d.ts +9 -3
- package/dist/lib/hosts/providers/local.js +23 -12
- package/dist/lib/hosts/reconnect.d.ts +7 -4
- package/dist/lib/hosts/reconnect.js +29 -25
- package/dist/lib/hosts/registry.js +4 -1
- package/dist/lib/hosts/remote-os.js +3 -1
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.js +8 -1
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.d.ts +10 -1
- package/dist/lib/session/active.js +8 -5
- package/dist/lib/session/actor-sidecar.d.ts +7 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +39 -4
- package/dist/lib/session/db.js +168 -31
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +126 -37
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/pid-registry.d.ts +7 -0
- package/dist/lib/session/prompt.d.ts +15 -0
- package/dist/lib/session/prompt.js +21 -0
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +25 -0
- package/dist/lib/session/types.js +10 -0
- package/dist/lib/share/publish.d.ts +53 -5
- package/dist/lib/share/publish.js +99 -17
- package/dist/lib/share/worker-template.js +594 -64
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +24 -0
- package/dist/lib/state.js +318 -54
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/terminal/resolve.d.ts +7 -0
- package/dist/lib/terminal/resolve.js +41 -2
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/traces/insights.d.ts +67 -0
- package/dist/lib/traces/insights.js +178 -0
- package/dist/lib/traces/phenotype.d.ts +67 -0
- package/dist/lib/traces/phenotype.js +437 -0
- package/dist/lib/traces/segments.d.ts +133 -0
- package/dist/lib/traces/segments.js +301 -0
- package/dist/lib/traces/sync.d.ts +33 -0
- package/dist/lib/traces/sync.js +11 -2
- package/dist/lib/types.d.ts +47 -1
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +2 -0
- package/dist/lib/watchdog/runner.js +18 -4
- package/package.json +2 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group-by dimensions + time-to-first-tool latency for the traces insight
|
|
3
|
+
* engine (the console's Issue bar).
|
|
4
|
+
*
|
|
5
|
+
* Pure functions, no I/O. Classifiers are DATA-DRIVEN TABLES — a new task type
|
|
6
|
+
* or timing bucket is a row, not a new if/else-by-name arm. The integrator
|
|
7
|
+
* (`insights.ts` / `sync.ts`) tags each session; this file does not write the
|
|
8
|
+
* shard.
|
|
9
|
+
*
|
|
10
|
+
* Input is the redacted `SessionDetail` from `agents traces sync` plus the
|
|
11
|
+
* SyncRow fields the detail strips (prompt, gitBranch, files). Structural —
|
|
12
|
+
* `SessionDetail` is a valid `SegmentSession`.
|
|
13
|
+
*/
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Public types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export const TASK_TYPES = ['bugfix', 'feature', 'refactor', 'test', 'chore', 'other'];
|
|
18
|
+
export const FAILURE_TIMINGS = ['early', 'mid', 'late'];
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Tables — the classifiers. Add a row; do not add an if/else-by-name arm.
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Tool name → file action. Lowercased. Write-family is a new file; Edit-family
|
|
24
|
+
* is a patch. Anything else is ignored for diff-shape.
|
|
25
|
+
*/
|
|
26
|
+
export const FILE_ACTION_TOOLS = [
|
|
27
|
+
{ action: 'new', tools: ['write', 'write_file', 'create_file', 'create'] },
|
|
28
|
+
{ action: 'edit', tools: ['edit', 'strreplace', 'apply_patch', 'applypatch', 'notebookedit'] },
|
|
29
|
+
];
|
|
30
|
+
/** Path heuristics for a test-only diff. */
|
|
31
|
+
const TEST_PATH_PATTERN = /(?:^|\/)(?:__tests__|testdata|test)\/|\.(?:test|spec)\.[cm]?[jt]sx?$|_test\.(?:go|py|rs)$/i;
|
|
32
|
+
/** Path heuristics for a chore-only diff (lockfiles, CI, docs). */
|
|
33
|
+
const CHORE_PATH_PATTERN = /(?:^|\/)(?:package(?:-lock)?\.json|bun\.lockb?|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|go\.sum|Dockerfile|Makefile|\.github\/|CHANGELOG[^/]*|README[^/]*|\.gitignore)$/i;
|
|
34
|
+
const DIFF_SHAPE_MATCHERS = [
|
|
35
|
+
{
|
|
36
|
+
key: 'testOnly',
|
|
37
|
+
match: (files) => files.length > 0 && files.every((f) => TEST_PATH_PATTERN.test(f.path)),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: 'newOnly',
|
|
41
|
+
match: (files) => files.length > 0 && files.every((f) => f.action === 'new'),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: 'choreOnly',
|
|
45
|
+
match: (files) => files.length > 0 && files.every((f) => CHORE_PATH_PATTERN.test(f.path)),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Task-type rules, first match wins. Prompt/branch keywords outrank diff shape
|
|
50
|
+
* except where `shape` is set (test-only files, chore-only files, all-new files).
|
|
51
|
+
*
|
|
52
|
+
* Order is the product priority: test-only work is not a "feature"; a `fix/`
|
|
53
|
+
* branch is a bugfix even if files are new; chore lockfile edits are not features.
|
|
54
|
+
*/
|
|
55
|
+
export const TASK_TYPE_RULES = [
|
|
56
|
+
{
|
|
57
|
+
type: 'test',
|
|
58
|
+
patterns: [
|
|
59
|
+
/\bunit tests?\b/,
|
|
60
|
+
/\bintegration tests?\b/,
|
|
61
|
+
/\btestdata\b/,
|
|
62
|
+
/\bcoverage\b/,
|
|
63
|
+
/\b(?:add|write|update|fix)\s+tests?\b/,
|
|
64
|
+
/\btest[- ]only\b/,
|
|
65
|
+
/(?:^|\/)tests?\//,
|
|
66
|
+
/^test\//,
|
|
67
|
+
/\.(?:test|spec)\./,
|
|
68
|
+
],
|
|
69
|
+
shape: 'testOnly',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'bugfix',
|
|
73
|
+
patterns: [
|
|
74
|
+
/\bbugfix(?:es)?\b/,
|
|
75
|
+
/\bhot[- ]?fix(?:es)?\b/,
|
|
76
|
+
/\bfixes\b/,
|
|
77
|
+
/\bfixing\b/,
|
|
78
|
+
/\bfix\b/,
|
|
79
|
+
/\bbugs?\b/,
|
|
80
|
+
/\bregression\b/,
|
|
81
|
+
/\bcrash(?:es|ed|ing)?\b/,
|
|
82
|
+
/\bbroken\b/,
|
|
83
|
+
/(?:^|\/)fix(?:es)?\//,
|
|
84
|
+
/^bugfix\//,
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: 'refactor',
|
|
89
|
+
patterns: [
|
|
90
|
+
/\brefactor(?:s|ing|ed)?\b/,
|
|
91
|
+
/\bclean[- ]?ups?\b/,
|
|
92
|
+
/\bextract(?:s|ing|ed)?\b/,
|
|
93
|
+
/\brename(?:s|d|ing)?\b/,
|
|
94
|
+
/(?:^|\/)refactor\//,
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: 'chore',
|
|
99
|
+
patterns: [
|
|
100
|
+
/\bchore\b/,
|
|
101
|
+
/\bdependenc(?:y|ies)\b/,
|
|
102
|
+
/\bbump\b/,
|
|
103
|
+
/\blint(?:ing|er)?\b/,
|
|
104
|
+
/\bformat(?:ting)?\b/,
|
|
105
|
+
/(?:^|\/)chore\//,
|
|
106
|
+
/\b(?:ci|cd)[-_/]/,
|
|
107
|
+
],
|
|
108
|
+
shape: 'choreOnly',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'feature',
|
|
112
|
+
patterns: [
|
|
113
|
+
/\bfeat(?:ure)?s?\b/,
|
|
114
|
+
/\bimplement(?:s|ing|ed)?\b/,
|
|
115
|
+
/\bnew feature\b/,
|
|
116
|
+
/(?:^|\/)feat(?:ure)?\//,
|
|
117
|
+
],
|
|
118
|
+
shape: 'newOnly',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
/**
|
|
122
|
+
* Normalized position of the first failing step inside the session span.
|
|
123
|
+
* Early failures cascade — highest-signal bucket. Exclusive upper bound.
|
|
124
|
+
*/
|
|
125
|
+
export const FAILURE_TIMING_BUCKETS = [
|
|
126
|
+
{ timing: 'early', maxExclusive: 1 / 3 },
|
|
127
|
+
{ timing: 'mid', maxExclusive: 2 / 3 },
|
|
128
|
+
{ timing: 'late', maxExclusive: Number.POSITIVE_INFINITY },
|
|
129
|
+
];
|
|
130
|
+
const AGENT_FIELDS = {
|
|
131
|
+
model: [
|
|
132
|
+
(s) => s.meta?.model,
|
|
133
|
+
(s) => s.model,
|
|
134
|
+
],
|
|
135
|
+
harness: [
|
|
136
|
+
(s) => s.meta?.agent,
|
|
137
|
+
(s) => s.harness,
|
|
138
|
+
(s) => s.agent,
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Public functions
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
/** Model × harness unit — the #1 group-by axis. */
|
|
145
|
+
export function deriveAgent(session) {
|
|
146
|
+
return {
|
|
147
|
+
model: firstPresent(AGENT_FIELDS.model.map((read) => read(session))),
|
|
148
|
+
harness: firstPresent(AGENT_FIELDS.harness.map((read) => read(session))),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Classify the session's work from the opening prompt + diff shape (files
|
|
153
|
+
* touched, new vs edit, test-only). First matching table row wins.
|
|
154
|
+
*/
|
|
155
|
+
export function classifyTaskType(session) {
|
|
156
|
+
const haystack = taskHaystack(session);
|
|
157
|
+
const shape = inferDiffShape(session);
|
|
158
|
+
for (const rule of TASK_TYPE_RULES) {
|
|
159
|
+
if (rule.patterns.some((pattern) => pattern.test(haystack)))
|
|
160
|
+
return rule.type;
|
|
161
|
+
if (rule.shape && shape[rule.shape])
|
|
162
|
+
return rule.type;
|
|
163
|
+
}
|
|
164
|
+
return 'other';
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Bucket the FIRST failing step by its normalized position in the session
|
|
168
|
+
* span (`firstFailure.startMs / spanMs`). Sessions with no failing step
|
|
169
|
+
* return null — they do not belong on the failure-timing axis.
|
|
170
|
+
*/
|
|
171
|
+
export function failureTiming(session) {
|
|
172
|
+
const firstFailure = (session.steps ?? []).find((step) => step.outcome === 'error');
|
|
173
|
+
if (!firstFailure)
|
|
174
|
+
return null;
|
|
175
|
+
const span = sessionSpanMs(session);
|
|
176
|
+
const position = span > 0 ? firstFailure.startMs / span : 0;
|
|
177
|
+
return FAILURE_TIMING_BUCKETS.find((bucket) => position < bucket.maxExclusive)?.timing ?? null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Time-to-first-tool latency from `steps[0].startMs` across sessions that
|
|
181
|
+
* recorded at least one step. Nearest-rank percentiles (same formula as the
|
|
182
|
+
* traces index stats) so p99 on `/tmp/traces-real` lands at ~128s.
|
|
183
|
+
*/
|
|
184
|
+
export function computeLatency(sessions) {
|
|
185
|
+
const samples = [];
|
|
186
|
+
for (const session of sessions) {
|
|
187
|
+
const steps = session.steps ?? [];
|
|
188
|
+
if (steps.length === 0)
|
|
189
|
+
continue;
|
|
190
|
+
const startMs = steps[0]?.startMs;
|
|
191
|
+
if (typeof startMs !== 'number' || !Number.isFinite(startMs))
|
|
192
|
+
continue;
|
|
193
|
+
samples.push(startMs);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
firstToolMs: {
|
|
197
|
+
p50: percentileNearest(samples, 0.5),
|
|
198
|
+
p90: percentileNearest(samples, 0.9),
|
|
199
|
+
p99: percentileNearest(samples, 0.99),
|
|
200
|
+
max: samples.length === 0 ? 0 : Math.max(...samples),
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** All three group-by dimensions for one session. */
|
|
205
|
+
export function deriveDimensions(session) {
|
|
206
|
+
return {
|
|
207
|
+
agent: deriveAgent(session),
|
|
208
|
+
taskType: classifyTaskType(session),
|
|
209
|
+
failureTiming: failureTiming(session),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Internals
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
function firstPresent(values) {
|
|
216
|
+
for (const value of values) {
|
|
217
|
+
if (typeof value === 'string' && value.length > 0)
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
return 'unknown';
|
|
221
|
+
}
|
|
222
|
+
function taskHaystack(session) {
|
|
223
|
+
const filePaths = inferFiles(session).map((file) => file.path);
|
|
224
|
+
const writeLabels = (session.steps ?? [])
|
|
225
|
+
.filter((step) => fileActionForTool(step.tool) !== undefined)
|
|
226
|
+
.map((step) => step.label ?? '');
|
|
227
|
+
return [
|
|
228
|
+
session.prompt,
|
|
229
|
+
session.gitBranch,
|
|
230
|
+
session.topic,
|
|
231
|
+
session.label,
|
|
232
|
+
session.meta?.repo,
|
|
233
|
+
...filePaths,
|
|
234
|
+
...writeLabels,
|
|
235
|
+
]
|
|
236
|
+
.filter((value) => typeof value === 'string' && value.length > 0)
|
|
237
|
+
.join('\n')
|
|
238
|
+
.toLowerCase();
|
|
239
|
+
}
|
|
240
|
+
function inferFiles(session) {
|
|
241
|
+
if (session.files && session.files.length > 0)
|
|
242
|
+
return session.files;
|
|
243
|
+
const files = [];
|
|
244
|
+
for (const step of session.steps ?? []) {
|
|
245
|
+
const action = fileActionForTool(step.tool);
|
|
246
|
+
if (!action)
|
|
247
|
+
continue;
|
|
248
|
+
const path = filePathFromLabel(step.label);
|
|
249
|
+
if (!path)
|
|
250
|
+
continue;
|
|
251
|
+
files.push({ path, action });
|
|
252
|
+
}
|
|
253
|
+
return files;
|
|
254
|
+
}
|
|
255
|
+
function fileActionForTool(tool) {
|
|
256
|
+
if (!tool)
|
|
257
|
+
return undefined;
|
|
258
|
+
const name = tool.toLowerCase();
|
|
259
|
+
for (const row of FILE_ACTION_TOOLS) {
|
|
260
|
+
if (row.tools.includes(name))
|
|
261
|
+
return row.action;
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
/** Best-effort path from a redacted Write/Edit label (`path/to/file.ts`). */
|
|
266
|
+
function filePathFromLabel(label) {
|
|
267
|
+
if (!label)
|
|
268
|
+
return undefined;
|
|
269
|
+
const match = label.match(/(?:^|[\s`'"])(\/?[\w.@-]+(?:\/[\w.@-]+)+\.[A-Za-z][\w.]*)/);
|
|
270
|
+
return match?.[1];
|
|
271
|
+
}
|
|
272
|
+
function inferDiffShape(session) {
|
|
273
|
+
const files = inferFiles(session);
|
|
274
|
+
const shape = { files, testOnly: false, newOnly: false, choreOnly: false };
|
|
275
|
+
for (const matcher of DIFF_SHAPE_MATCHERS) {
|
|
276
|
+
shape[matcher.key] = matcher.match(files);
|
|
277
|
+
}
|
|
278
|
+
return shape;
|
|
279
|
+
}
|
|
280
|
+
function sessionSpanMs(session) {
|
|
281
|
+
const declared = session.meta?.spanMs ?? session.spanMs ?? 0;
|
|
282
|
+
if (declared > 0)
|
|
283
|
+
return declared;
|
|
284
|
+
let max = 0;
|
|
285
|
+
for (const step of session.steps ?? []) {
|
|
286
|
+
max = Math.max(max, step.startMs + (step.durationMs ?? 0));
|
|
287
|
+
}
|
|
288
|
+
return max;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Nearest-rank percentile, ratio in (0, 1]. Same index rule as
|
|
292
|
+
* `sync.ts`'s private `percentile` — linear interpolation (`lib/percentile.ts`)
|
|
293
|
+
* would report ~71s for p99 on the 738-session corpus; the published figure is
|
|
294
|
+
* 128s / 2m8s.
|
|
295
|
+
*/
|
|
296
|
+
function percentileNearest(values, ratio) {
|
|
297
|
+
if (values.length === 0)
|
|
298
|
+
return 0;
|
|
299
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
300
|
+
return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * ratio) - 1)];
|
|
301
|
+
}
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { type SessionTrajectory } from '../session/trajectory.js';
|
|
23
23
|
import { type BucketStats, type DriftSignal, type TraceFailureCause, type TraceTopicGroup } from './classify.js';
|
|
24
|
+
import { type FailurePattern } from './insights.js';
|
|
25
|
+
import type { LatencyInsight } from './segments.js';
|
|
24
26
|
export interface SyncOpts {
|
|
25
27
|
/** Limit to N sessions (for testing / --dry-run); no limit when undefined. */
|
|
26
28
|
limit?: number;
|
|
@@ -82,6 +84,12 @@ export interface TracesIndexShard {
|
|
|
82
84
|
bucketHistory: BucketStats[][];
|
|
83
85
|
/** Movement signals for buckets with ≥3 days of history. */
|
|
84
86
|
driftSignals: DriftSignal[];
|
|
87
|
+
/** Cross-session failure clusters ranked by wasted time, from computeInsights(). Top-K, bounded. */
|
|
88
|
+
failurePatterns: FailurePattern[];
|
|
89
|
+
/** Sum of wastedMs across every cluster (not just the top-K rows above) — the headline number. */
|
|
90
|
+
wastedMsTotal: number;
|
|
91
|
+
/** Time-to-first-tool percentiles across this device's sessions. */
|
|
92
|
+
latency: LatencyInsight;
|
|
85
93
|
}
|
|
86
94
|
export interface IndexedSession {
|
|
87
95
|
id: string;
|
|
@@ -93,6 +101,21 @@ export interface IndexedSession {
|
|
|
93
101
|
severity: number;
|
|
94
102
|
flags: string[];
|
|
95
103
|
}
|
|
104
|
+
/** A row from `tool_calls`. `ordinal`/`timestamp` order calls within a session for computeInsights(). */
|
|
105
|
+
export interface ToolCallRow {
|
|
106
|
+
session_id: string;
|
|
107
|
+
ordinal: number;
|
|
108
|
+
timestamp: string;
|
|
109
|
+
tool: string;
|
|
110
|
+
outcome: string;
|
|
111
|
+
exit_code: number | null;
|
|
112
|
+
status_code: number | null;
|
|
113
|
+
error_code: string | null;
|
|
114
|
+
error: string | null;
|
|
115
|
+
parse_error: string | null;
|
|
116
|
+
}
|
|
117
|
+
/** Human description of a failed call, keyed by (tool, desc, cause) for grouping. Exported for computeInsights(). */
|
|
118
|
+
export declare function failureDescription(call: ToolCallRow, cause: TraceFailureCause): string;
|
|
96
119
|
/** Build the redacted rich console shard from indexed metadata and derived caches. */
|
|
97
120
|
export declare function buildIndexShard(rows: SyncRow[], device: string, owner: string, prevShard?: TracesIndexShard | null): TracesIndexShard;
|
|
98
121
|
/** Per-session drill-down detail the console consumes (sessions/<id>.json). */
|
|
@@ -116,6 +139,16 @@ export interface SessionDetail {
|
|
|
116
139
|
/** Steps dropped from `steps` when a run was too long — surfaced so truncation is never silent. */
|
|
117
140
|
truncatedSteps: number;
|
|
118
141
|
whereItWentWrong: string | null;
|
|
142
|
+
/**
|
|
143
|
+
* Failed tool steps, surfaced even when `meta.outcome === 'completed'` — a run
|
|
144
|
+
* can finish without throwing while a tool call inside it failed (the
|
|
145
|
+
* LangSmith trap: a green run-level status hiding a real failure).
|
|
146
|
+
*/
|
|
147
|
+
surfacedToolFailures: Array<{
|
|
148
|
+
tool?: string;
|
|
149
|
+
label: string;
|
|
150
|
+
detail?: string;
|
|
151
|
+
}>;
|
|
119
152
|
}
|
|
120
153
|
/**
|
|
121
154
|
* Map the derived trajectory to the console's SessionDetail shape, stripping
|
package/dist/lib/traces/sync.js
CHANGED
|
@@ -30,6 +30,7 @@ import { knownSecretValuesFromEnv, redactSecrets } from '../redact.js';
|
|
|
30
30
|
import { getRuntimeStateDir } from '../state.js';
|
|
31
31
|
import { resolveTracesBackend } from './backend.js';
|
|
32
32
|
import { classifyCause, classifyTopic, computeDriftSignal, } from './classify.js';
|
|
33
|
+
import { computeInsights } from './insights.js';
|
|
33
34
|
/** Push derived, redacted trajectories for this device to the traces store. */
|
|
34
35
|
export async function syncTraces(opts = {}) {
|
|
35
36
|
const dryRun = opts.dryRun === true;
|
|
@@ -210,7 +211,8 @@ function percentile(values, ratio) {
|
|
|
210
211
|
const sorted = [...values].sort((a, b) => a - b);
|
|
211
212
|
return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * ratio) - 1)];
|
|
212
213
|
}
|
|
213
|
-
|
|
214
|
+
/** Human description of a failed call, keyed by (tool, desc, cause) for grouping. Exported for computeInsights(). */
|
|
215
|
+
export function failureDescription(call, cause) {
|
|
214
216
|
if (cause === 'guard')
|
|
215
217
|
return /main-branch-guard/i.test(`${call.error_code ?? ''} ${call.error ?? ''}`)
|
|
216
218
|
? 'main branch guard' : 'git guard';
|
|
@@ -254,7 +256,7 @@ export function buildIndexShard(rows, device, owner, prevShard) {
|
|
|
254
256
|
for (let i = 0; i < ids.length; i += 400) {
|
|
255
257
|
const chunk = ids.slice(i, i + 400);
|
|
256
258
|
calls.push(...db.prepare(`
|
|
257
|
-
SELECT session_id, tool, outcome, exit_code, status_code, error_code, error, parse_error
|
|
259
|
+
SELECT session_id, ordinal, timestamp, tool, outcome, exit_code, status_code, error_code, error, parse_error
|
|
258
260
|
FROM tool_calls
|
|
259
261
|
WHERE session_id IN (${chunk.map(() => '?').join(',')})
|
|
260
262
|
`).all(...chunk));
|
|
@@ -359,6 +361,7 @@ export function buildIndexShard(rows, device, owner, prevShard) {
|
|
|
359
361
|
const prevHistory = prevShard?.bucketHistory ?? [];
|
|
360
362
|
const bucketHistory = [...prevHistory, todayStats].slice(-14);
|
|
361
363
|
const driftSignals = computeDriftSignal(prevHistory, todayStats);
|
|
364
|
+
const patternInsights = computeInsights(rows, calls, prevShard);
|
|
362
365
|
return {
|
|
363
366
|
schema: 1,
|
|
364
367
|
device,
|
|
@@ -379,6 +382,9 @@ export function buildIndexShard(rows, device, owner, prevShard) {
|
|
|
379
382
|
},
|
|
380
383
|
bucketHistory,
|
|
381
384
|
driftSignals,
|
|
385
|
+
failurePatterns: patternInsights.failurePatterns,
|
|
386
|
+
wastedMsTotal: patternInsights.wastedMsTotal,
|
|
387
|
+
latency: patternInsights.latency,
|
|
382
388
|
};
|
|
383
389
|
}
|
|
384
390
|
/** Plain-language summary of the friction in a run, or null when it ran clean. */
|
|
@@ -426,6 +432,9 @@ export function buildSessionDetail(traj) {
|
|
|
426
432
|
gaps: traj.gaps,
|
|
427
433
|
truncatedSteps: traj.truncatedSteps,
|
|
428
434
|
whereItWentWrong: buildWhereItWentWrong(traj),
|
|
435
|
+
surfacedToolFailures: traj.steps
|
|
436
|
+
.filter((step) => step.outcome === 'error')
|
|
437
|
+
.map((step) => ({ tool: step.tool, label: step.label, detail: step.detail })),
|
|
429
438
|
};
|
|
430
439
|
}
|
|
431
440
|
async function getIndexShard(backend, device) {
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -820,7 +820,11 @@ export interface Meta {
|
|
|
820
820
|
/** Preferred provider account per harness. Explicit --account wins. */
|
|
821
821
|
accounts?: {
|
|
822
822
|
defaults?: Partial<Record<AgentId, string>>;
|
|
823
|
-
/**
|
|
823
|
+
/**
|
|
824
|
+
* Named harness-owned identities. Metadata only; OAuth credentials stay in
|
|
825
|
+
* the harness home. Central-synced via agents.yaml; labels bind to
|
|
826
|
+
* `(agent, identityKey)`.
|
|
827
|
+
*/
|
|
824
828
|
native?: Record<string, {
|
|
825
829
|
id: string;
|
|
826
830
|
name: string;
|
|
@@ -832,6 +836,26 @@ export interface Meta {
|
|
|
832
836
|
/** Exact installation/custom-harness target -> stable account id. */
|
|
833
837
|
bindings?: Record<string, string>;
|
|
834
838
|
};
|
|
839
|
+
/**
|
|
840
|
+
* Device-scoped slice of {@link Meta.accounts}, written as `accounts:` in
|
|
841
|
+
* `~/.agents/devices/<machine>/agents.yaml` (PHNX-3315). Holds this box's own
|
|
842
|
+
* `scope: 'device'` native identities and the bindings that target them, so a
|
|
843
|
+
* per-box login no longer rewrites the fleet-shared central `agents.yaml` (and
|
|
844
|
+
* its identity PII no longer lands on that one file). The effective account
|
|
845
|
+
* view is the union of central (fleet-shared `defaults` + `scope:'version'`
|
|
846
|
+
* natives) and every device doc's block; only this machine writes this key.
|
|
847
|
+
*/
|
|
848
|
+
deviceAccounts?: {
|
|
849
|
+
native?: Record<string, {
|
|
850
|
+
id: string;
|
|
851
|
+
name: string;
|
|
852
|
+
agent: AgentId;
|
|
853
|
+
identityKey: string;
|
|
854
|
+
identityLabel?: string;
|
|
855
|
+
scope: 'version' | 'device';
|
|
856
|
+
}>;
|
|
857
|
+
bindings?: Record<string, string>;
|
|
858
|
+
};
|
|
835
859
|
agents?: Partial<Record<AgentId, string>>;
|
|
836
860
|
/**
|
|
837
861
|
* Per-agent preferred ISOLATED version — which copy a bare `agents run <agent>`
|
|
@@ -1012,6 +1036,15 @@ export interface Meta {
|
|
|
1012
1036
|
* are never copied. `inline` hosts carry their own address/user.
|
|
1013
1037
|
*/
|
|
1014
1038
|
hosts?: Record<string, HostEntry>;
|
|
1039
|
+
/**
|
|
1040
|
+
* Device-scoped agent-host registry, written as `hosts:` in
|
|
1041
|
+
* `~/.agents/devices/<machine>/agents.yaml` (PHNX-3315). Locally-discovered
|
|
1042
|
+
* SSH hosts and inline registrations land here so one box's enrollment no
|
|
1043
|
+
* longer rewrites the fleet-shared `hosts:` map (the source of pull conflicts).
|
|
1044
|
+
* The effective host view is the union across every device doc (plus any
|
|
1045
|
+
* lingering central legacy entries); only this machine writes this key.
|
|
1046
|
+
*/
|
|
1047
|
+
deviceHosts?: Record<string, HostEntry>;
|
|
1015
1048
|
/**
|
|
1016
1049
|
* Declarative fleet profile (`agents apply` / `ag apply`). Additive to the
|
|
1017
1050
|
* schema — project `agents:` version-pins are untouched. Declares which agents
|
|
@@ -1022,6 +1055,19 @@ export interface Meta {
|
|
|
1022
1055
|
* Full shape in `lib/fleet/types.ts` (FleetManifest).
|
|
1023
1056
|
*/
|
|
1024
1057
|
fleet?: import('./fleet/types.js').FleetManifest;
|
|
1058
|
+
/**
|
|
1059
|
+
* Device-scoped slice of {@link Meta.fleet}: THIS box's own discovery
|
|
1060
|
+
* decisions and dismissals, written as `fleet:` in
|
|
1061
|
+
* `~/.agents/devices/<machine>/agents.yaml` (PHNX-3315). Each box records only
|
|
1062
|
+
* its own choices here, so N boxes no longer rewrite one shared
|
|
1063
|
+
* `fleet.discovery`/`fleet.ignored` map (the guaranteed pull conflict). The
|
|
1064
|
+
* effective fleet view is computed as a UNION across every device doc (plus
|
|
1065
|
+
* lingering central legacy) at read time; only this machine writes this key.
|
|
1066
|
+
*/
|
|
1067
|
+
deviceFleet?: {
|
|
1068
|
+
discovery?: Record<string, 'approved' | 'ignored'>;
|
|
1069
|
+
ignored?: import('./fleet/types.js').IgnoredDeviceEntry[];
|
|
1070
|
+
};
|
|
1025
1071
|
/** Artifact share endpoint (Cloudflare R2 + Worker). Set by `agents artifacts
|
|
1026
1072
|
* setup`/`join`; syncs fleet-wide via `agents repo push/pull`. The write token
|
|
1027
1073
|
* lives in the `share` secrets bundle, not here. */
|
|
@@ -245,7 +245,8 @@ export async function buildLocalUsageAccounts() {
|
|
|
245
245
|
// fileOnly: never open the ACL-bound keychain item from the daemon —
|
|
246
246
|
// that path is the Touch ID storm. Usage reads the file-based setup-token
|
|
247
247
|
// only, never the interactive login (see loadClaudeOauth); no setup-token
|
|
248
|
-
// reads as "usage pending"
|
|
248
|
+
// reads as "usage unavailable (no usage credential)" — "usage pending"
|
|
249
|
+
// is now the cold-cache state only (#2987).
|
|
249
250
|
fetch: async () => {
|
|
250
251
|
const { getUsageInfoForIdentity } = await import('./accounting/usage.js');
|
|
251
252
|
return getUsageInfoForIdentity({
|
package/dist/lib/view-types.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface ViewJsonVersion {
|
|
|
21
21
|
amount: number;
|
|
22
22
|
currency: string;
|
|
23
23
|
} | null;
|
|
24
|
+
/** Human-readable usage refresh error, when the live refresh could not populate a snapshot. */
|
|
25
|
+
usageError?: string | null;
|
|
24
26
|
windows: Array<{
|
|
25
27
|
key: 'session' | 'week' | 'sonnet_week' | 'month';
|
|
26
28
|
label?: string;
|
|
@@ -146,7 +146,13 @@ function planDelivery(session, chosenText, block, allowGhosttyFocus) {
|
|
|
146
146
|
// silently dropping a nudge into a spool it will never read.
|
|
147
147
|
if (route.kind === 'mailbox' && isOpenQuestionBlock(block))
|
|
148
148
|
return { via: 'mailbox', mailboxId };
|
|
149
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
via: 'refuse',
|
|
151
|
+
// answer-router's refuse reason already bakes in the recovery hint; the
|
|
152
|
+
// resolver's reason does not, so carry hint separately for the caller.
|
|
153
|
+
reason: route.kind === 'refuse' ? route.reason : resolution.reason,
|
|
154
|
+
hint: route.kind === 'refuse' ? undefined : resolution.hint,
|
|
155
|
+
};
|
|
150
156
|
}
|
|
151
157
|
/** Default open-block reader — the same lookup `agents message` uses. */
|
|
152
158
|
function defaultOpenBlockFor(session) {
|
|
@@ -596,7 +602,9 @@ export async function runWatchdogTick(opts = {}) {
|
|
|
596
602
|
flags[sid] = { reason: `rotate: ${resolution.reason}`, host: session.host, atMs: nowMs };
|
|
597
603
|
outcomes.push({
|
|
598
604
|
...base, decision: 'skip', addressable: false,
|
|
599
|
-
reason:
|
|
605
|
+
reason: resolution.hint
|
|
606
|
+
? `rate-limited but un-addressable — ${resolution.reason} — ${resolution.hint}`
|
|
607
|
+
: `rate-limited but un-addressable — ${resolution.reason}`,
|
|
600
608
|
});
|
|
601
609
|
continue;
|
|
602
610
|
}
|
|
@@ -737,7 +745,11 @@ export async function runWatchdogTick(opts = {}) {
|
|
|
737
745
|
const machineHost = session.provenance?.host ?? 'unknown';
|
|
738
746
|
const runtime = session.kind;
|
|
739
747
|
try {
|
|
740
|
-
const declaredBlock = buildDeclaredBlock({ sessionId: session.sessionId, mailboxId, host: machineHost, runtime, cwd: session.cwd }, {
|
|
748
|
+
const declaredBlock = buildDeclaredBlock({ sessionId: session.sessionId, mailboxId, host: machineHost, runtime, cwd: session.cwd }, {
|
|
749
|
+
text: resolution.hint
|
|
750
|
+
? `Session genuinely needs Muqsit and is un-addressable — ${decision.reason}. Needs attention. ${resolution.hint}`
|
|
751
|
+
: `Session genuinely needs Muqsit and is un-addressable — ${decision.reason}. Needs attention.`,
|
|
752
|
+
});
|
|
741
753
|
publishBlockFn(declaredBlock);
|
|
742
754
|
ledgerUpdates[session.sessionId] = nowMs;
|
|
743
755
|
}
|
|
@@ -769,7 +781,9 @@ export async function runWatchdogTick(opts = {}) {
|
|
|
769
781
|
flags[session.sessionId] = { reason: plan.reason, host: session.host, atMs: nowMs };
|
|
770
782
|
outcomes.push({
|
|
771
783
|
...base, decision: 'skip', addressable: false,
|
|
772
|
-
reason:
|
|
784
|
+
reason: plan.hint
|
|
785
|
+
? `nudge-worthy but un-addressable — ${plan.reason} — ${plan.hint}`
|
|
786
|
+
: `nudge-worthy but un-addressable — ${plan.reason}`,
|
|
773
787
|
nudgeText: chosenText,
|
|
774
788
|
});
|
|
775
789
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.54",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
"@types/node": "26.1.0",
|
|
117
117
|
"@types/ssh2": "1.15.5",
|
|
118
118
|
"@types/ws": "^8.18.1",
|
|
119
|
+
"miniflare": "4.20260730.0",
|
|
119
120
|
"ssh2": "1.17.0",
|
|
120
121
|
"tsx": "4.23.0",
|
|
121
122
|
"typescript": "6.0.3",
|