@phnx-labs/agents-cli 1.22.46 → 1.22.47
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 +279 -0
- package/LICENSE +102 -182
- package/README.md +31 -18
- package/dist/cli/command-registry.d.ts +1 -1
- package/dist/cli/command-registry.js +4 -2
- package/dist/commands/accounts.js +56 -12
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +266 -15
- package/dist/commands/config.js +26 -4
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +55 -38
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/lease.js +3 -3
- package/dist/commands/routines.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +4 -0
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +194 -16
- package/dist/commands/teams.js +34 -2
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +18 -31
- package/dist/commands/watchdog.js +9 -14
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +70 -3
- package/dist/lib/accounting/usage.js +235 -63
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +14 -6
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +33 -7
- package/dist/lib/browser/profiles.d.ts +106 -1
- package/dist/lib/browser/profiles.js +300 -17
- package/dist/lib/browser/remote-control.d.ts +26 -2
- package/dist/lib/browser/remote-control.js +33 -3
- package/dist/lib/browser/runtime-state.d.ts +9 -0
- package/dist/lib/browser/runtime-state.js +24 -8
- package/dist/lib/browser/service.d.ts +37 -0
- package/dist/lib/browser/service.js +78 -20
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/config-keys.d.ts +4 -3
- package/dist/lib/config-keys.js +17 -7
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon-ticks.js +2 -2
- package/dist/lib/device-config.d.ts +25 -8
- package/dist/lib/device-config.js +90 -17
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +6 -0
- package/dist/lib/devices/stats-cache.js +10 -2
- package/dist/lib/exec.d.ts +5 -3
- package/dist/lib/exec.js +32 -16
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/hooks/cache.js +8 -0
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/passthrough.js +16 -0
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +13 -0
- package/dist/lib/menubar/install-menubar.js +26 -2
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +69 -1
- package/dist/lib/session/db.js +4 -2
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +12 -6
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +6 -1
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.js +5 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +15 -0
- package/dist/lib/session/trajectory-html.js +750 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +277 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +335 -0
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +5 -2
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +4 -2
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +173 -27
- package/dist/lib/types.d.ts +16 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +17 -3
- package/dist/lib/usage-refresh.js +57 -11
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/package.json +2 -2
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents sessions trace <selectors...>` (alias: `agents trace <selectors...>`) —
|
|
3
|
+
* render one session as a derived trajectory, or two as a compare.
|
|
4
|
+
*
|
|
5
|
+
* One model (`buildTrajectory`), three renderings, audience auto-selected: a
|
|
6
|
+
* person at a TTY gets the self-contained HTML opened in a browser; a piped or
|
|
7
|
+
* headless caller (an agent) gets the compact text trajectory; `--json` emits the
|
|
8
|
+
* versioned envelope the AGI EXT Fleet panel and triaging agents consume. Explicit
|
|
9
|
+
* `--html/--text/--json` always win.
|
|
10
|
+
*
|
|
11
|
+
* Exactly two resolved selectors turn the single trajectory into a **compare**
|
|
12
|
+
* (`diffTrajectories`, PR2/3) — the same three renderings, laid on a shared axis
|
|
13
|
+
* with a divergence marker. `--tree` turns one selector into a **lineage**
|
|
14
|
+
* (`buildLineage`, PR3/3): the orchestrator and every session it spawned, edges
|
|
15
|
+
* read from the team records. Three or more selectors still fail loud.
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import chalk from 'chalk';
|
|
20
|
+
import { setHelpSections } from '../lib/help.js';
|
|
21
|
+
import { showFile } from '../lib/open-url.js';
|
|
22
|
+
import { knownSecretValuesFromEnv } from '../lib/redact.js';
|
|
23
|
+
import { getCacheDir } from '../lib/state.js';
|
|
24
|
+
import { discoverSessions } from '../lib/session/discover.js';
|
|
25
|
+
import { parseSession } from '../lib/session/parse.js';
|
|
26
|
+
import { buildTrajectory } from '../lib/session/trajectory.js';
|
|
27
|
+
import { diffTrajectories } from '../lib/session/trajectory-compare.js';
|
|
28
|
+
import { buildLineage } from '../lib/session/trajectory-lineage.js';
|
|
29
|
+
import { renderTrajectoryHtml, renderTrajectoryCompareHtml, renderLineageHtml } from '../lib/session/trajectory-html.js';
|
|
30
|
+
import { renderTrajectoryText, renderTrajectoryCompareText, renderLineageText } from '../lib/session/trajectory-text.js';
|
|
31
|
+
import { parseAgentFilter } from './sessions.js';
|
|
32
|
+
import { selectSessions } from './sessions-export.js';
|
|
33
|
+
/** Versioned envelope emitted by `--json` — the stable contract for consumers. */
|
|
34
|
+
export const SESSIONS_TRACE_SCHEMA_VERSION = 1;
|
|
35
|
+
/** Build the versioned `--json` envelope for a single-session trajectory. */
|
|
36
|
+
export function buildTraceEnvelope(models) {
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: SESSIONS_TRACE_SCHEMA_VERSION,
|
|
39
|
+
kind: 'sessions-trace',
|
|
40
|
+
layout: 'single',
|
|
41
|
+
sessions: models,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Build the versioned `--json` envelope for a two-session compare. */
|
|
45
|
+
export function buildCompareTraceEnvelope(cmp) {
|
|
46
|
+
return {
|
|
47
|
+
schemaVersion: SESSIONS_TRACE_SCHEMA_VERSION,
|
|
48
|
+
kind: 'sessions-trace',
|
|
49
|
+
layout: 'compare',
|
|
50
|
+
sessions: [cmp.a, cmp.b],
|
|
51
|
+
diff: {
|
|
52
|
+
divergence: cmp.divergence,
|
|
53
|
+
added: cmp.added,
|
|
54
|
+
removed: cmp.removed,
|
|
55
|
+
summaryA: cmp.summaryA,
|
|
56
|
+
summaryB: cmp.summaryB,
|
|
57
|
+
truncatedA: cmp.truncatedA,
|
|
58
|
+
truncatedB: cmp.truncatedB,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the versioned `--json` envelope for a lineage. `sessions` carries the
|
|
64
|
+
* ROOT's trajectory only: the graph's own numbers come from the indexed session
|
|
65
|
+
* rows, so a consumer pays one transcript parse instead of one per teammate —
|
|
66
|
+
* `agents sessions trace <child>` is how you get a child's full trajectory.
|
|
67
|
+
*/
|
|
68
|
+
export function buildLineageTraceEnvelope(lineage, rootTrajectory) {
|
|
69
|
+
return {
|
|
70
|
+
schemaVersion: SESSIONS_TRACE_SCHEMA_VERSION,
|
|
71
|
+
kind: 'sessions-trace',
|
|
72
|
+
layout: 'lineage',
|
|
73
|
+
sessions: [rootTrajectory],
|
|
74
|
+
lineage: {
|
|
75
|
+
rootId: lineage.rootId,
|
|
76
|
+
nodes: lineage.nodes,
|
|
77
|
+
edges: lineage.edges,
|
|
78
|
+
teams: lineage.teams,
|
|
79
|
+
unresolvedParentIds: lineage.unresolvedParentIds,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Pick the rendering by audience: explicit `--html/--text/--json` win; otherwise
|
|
85
|
+
* a person at a TTY gets the visual HTML and a piped/headless caller (an agent)
|
|
86
|
+
* gets the compact text trajectory.
|
|
87
|
+
*/
|
|
88
|
+
export function chooseFormat(options, isTTY) {
|
|
89
|
+
if (options.json)
|
|
90
|
+
return 'json';
|
|
91
|
+
if (options.html)
|
|
92
|
+
return 'html';
|
|
93
|
+
if (options.text)
|
|
94
|
+
return 'text';
|
|
95
|
+
return isTTY ? 'html' : 'text';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Decide the trace layout from what the user TYPED (`selectorCount`) vs what
|
|
99
|
+
* those selectors RESOLVED to (`resolvedCount`). Keyed on selector count so a
|
|
100
|
+
* single content-search selector that happens to match two sessions never
|
|
101
|
+
* silently becomes a compare — every unsupported combination fails loud with a
|
|
102
|
+
* clear message. Pure, so the boundaries are unit-tested without the command.
|
|
103
|
+
*/
|
|
104
|
+
export function decideTraceLayout(options, selectorCount, resolvedCount) {
|
|
105
|
+
if (options.tree) {
|
|
106
|
+
if (options.compare) {
|
|
107
|
+
throw new Error('`--tree` (lineage) and `--compare` are different layouts — pass one or the other.');
|
|
108
|
+
}
|
|
109
|
+
if (selectorCount !== 1) {
|
|
110
|
+
throw new Error(`Lineage roots at ONE session; you passed ${selectorCount} selectors. ` +
|
|
111
|
+
'Pass the orchestrator session whose team you want to see.');
|
|
112
|
+
}
|
|
113
|
+
if (resolvedCount !== 1) {
|
|
114
|
+
throw new Error(`The selector must match exactly one session to root a lineage; it matched ${resolvedCount}. ` +
|
|
115
|
+
'Use a more specific selector (e.g. the full session id).');
|
|
116
|
+
}
|
|
117
|
+
return 'lineage';
|
|
118
|
+
}
|
|
119
|
+
const wantCompare = options.compare === true || selectorCount >= 2;
|
|
120
|
+
if (wantCompare) {
|
|
121
|
+
if (selectorCount !== 2) {
|
|
122
|
+
throw new Error(`Comparing needs exactly two selectors; you passed ${selectorCount}. ` +
|
|
123
|
+
'Pass two session selectors (ids or queries), each matching one session.');
|
|
124
|
+
}
|
|
125
|
+
if (resolvedCount !== 2) {
|
|
126
|
+
throw new Error(`Each selector must match exactly one session to compare; the two selectors matched ${resolvedCount}. ` +
|
|
127
|
+
'Use more specific selectors (e.g. full session ids).');
|
|
128
|
+
}
|
|
129
|
+
return 'compare';
|
|
130
|
+
}
|
|
131
|
+
if (resolvedCount > 1) {
|
|
132
|
+
throw new Error(`${resolvedCount} sessions matched — pass a more specific selector for one trajectory, ` +
|
|
133
|
+
'or two selectors to compare them.');
|
|
134
|
+
}
|
|
135
|
+
return 'single';
|
|
136
|
+
}
|
|
137
|
+
/** Attach the trace behaviour to a command node (canonical or top-level alias). */
|
|
138
|
+
export function configureTraceCommand(cmd) {
|
|
139
|
+
cmd
|
|
140
|
+
.description('Visualize a session as a trajectory — a tool-call timeline you can read at a glance. Opens a visual for a person; prints a compact trajectory for an agent.')
|
|
141
|
+
.option('--html', 'Force the HTML rendering (opens in a browser)')
|
|
142
|
+
.option('--text', 'Force the compact text trajectory')
|
|
143
|
+
.option('--json', 'Emit the versioned sessions-trace JSON envelope')
|
|
144
|
+
.option('-o, --output <path>', 'Write the rendering to a path instead of opening/printing')
|
|
145
|
+
.option('--no-open', 'Do not open the HTML; print its path instead')
|
|
146
|
+
.option('--errors-only', 'Collapse the text trajectory to error steps and their neighbours')
|
|
147
|
+
.option('--compare', 'Force the compare layout for exactly two selectors (this is also the default for two)')
|
|
148
|
+
.option('--tree', 'Render the lineage of one session — it and every session it spawned')
|
|
149
|
+
.option('--no-redact', 'Local-only: skip secret redaction of derived labels (never for a shared file)')
|
|
150
|
+
.option('--all', 'Search every directory and all time, not just this project')
|
|
151
|
+
.option('--since <time>', 'Only sessions newer than this (7d, 4w, an ISO date)')
|
|
152
|
+
.option('--limit <n>', 'Max sessions to scan when resolving the selector (default 100)')
|
|
153
|
+
.option('-a, --agent <agent>', 'Filter by agent type (e.g. claude, codex)');
|
|
154
|
+
setHelpSections(cmd, {
|
|
155
|
+
examples: `# Open one session's trajectory in a browser (a person at a terminal)
|
|
156
|
+
agents sessions trace a1b2c3d4
|
|
157
|
+
|
|
158
|
+
# The compact text trajectory an agent reads in-context
|
|
159
|
+
agents sessions trace a1b2c3d4 --text
|
|
160
|
+
|
|
161
|
+
# Just the failures and their neighbours — for a triaging agent
|
|
162
|
+
agents sessions trace a1b2c3d4 --text --errors-only
|
|
163
|
+
|
|
164
|
+
# The stable JSON envelope for the AGI EXT Fleet panel or a tool
|
|
165
|
+
agents sessions trace a1b2c3d4 --json
|
|
166
|
+
|
|
167
|
+
# Write the self-contained HTML to a file without opening it
|
|
168
|
+
agents sessions trace a1b2c3d4 --html -o trace.html --no-open
|
|
169
|
+
|
|
170
|
+
# Two selectors — compare where a passing run and a failing one diverge
|
|
171
|
+
agents sessions trace a1b2c3d4 e5f6a7b8
|
|
172
|
+
|
|
173
|
+
# The compare, as text — for a triaging agent comparing harnesses
|
|
174
|
+
agents sessions trace a1b2c3d4 e5f6a7b8 --text
|
|
175
|
+
|
|
176
|
+
# The team an orchestrator spawned, as a graph (one selector + --tree)
|
|
177
|
+
agents sessions trace a1b2c3d4 --tree
|
|
178
|
+
|
|
179
|
+
# The same lineage as an indented tree an agent can read
|
|
180
|
+
agents sessions trace a1b2c3d4 --tree --text`,
|
|
181
|
+
notes: `Audience auto-select: with no --html/--text/--json, HTML opens on a TTY (a person) and
|
|
182
|
+
the compact text trajectory prints when piped or headless (an agent). The HTML is self-contained
|
|
183
|
+
(no CDN, no external asset) and redacted by default, as safe to share as an 'agents sessions share' page.
|
|
184
|
+
|
|
185
|
+
One selector renders the single trajectory; exactly two selectors compare them (a shared time axis,
|
|
186
|
+
the first divergence point, and the step-level diff); one selector with --tree renders its lineage —
|
|
187
|
+
the session and every session it spawned, with the edges read from the teams records
|
|
188
|
+
(teamOrigin.parentSessionId), never from an inline Task sub-agent, which is a step inside one transcript
|
|
189
|
+
and has no session of its own. --json emits { schemaVersion, kind: 'sessions-trace',
|
|
190
|
+
layout: 'single' | 'compare' | 'lineage', sessions: [SessionTrajectory], diff?, lineage? } — the contract
|
|
191
|
+
consumers read so nothing re-parses a transcript.
|
|
192
|
+
|
|
193
|
+
Lineage resolves children from the scanned pool, so a very old team may need a wider --limit or --since.
|
|
194
|
+
Three or more selectors, and --tree with more than one selector, fail loud.`,
|
|
195
|
+
});
|
|
196
|
+
cmd.action(async (selectors, _options, command) => {
|
|
197
|
+
// Merge parent globals: under `agents sessions trace`, the `sessions` command
|
|
198
|
+
// owns `--json`/`--no-redact`/`--all`/`--agent`/`--since` and captures them, so
|
|
199
|
+
// read the merged view (as `render`/`insights` do) — the top-level `agents
|
|
200
|
+
// trace` alias has no such parent and its own options fill the same fields.
|
|
201
|
+
const options = command.optsWithGlobals();
|
|
202
|
+
// Lineage resolves its children FROM the scanned pool, and a teammate can sit
|
|
203
|
+
// well below the orchestrator in recency order, so --tree scans wider by
|
|
204
|
+
// default than the single/compare layouts. An explicit --limit still wins.
|
|
205
|
+
const defaultLimit = options.tree ? 500 : 100;
|
|
206
|
+
const limit = Math.max(1, Number.parseInt(options.limit || String(defaultLimit), 10) || defaultLimit);
|
|
207
|
+
const agent = parseAgentFilter(options.agent).agent;
|
|
208
|
+
const pool = await discoverSessions({
|
|
209
|
+
// Default to every directory so an id from any project resolves, matching
|
|
210
|
+
// `agents sessions render` (sessions-render.ts:126).
|
|
211
|
+
all: options.all !== false,
|
|
212
|
+
since: options.since,
|
|
213
|
+
limit,
|
|
214
|
+
agent: agent ?? undefined,
|
|
215
|
+
});
|
|
216
|
+
const sessions = selectSessions(pool, selectors);
|
|
217
|
+
if (sessions.length === 0) {
|
|
218
|
+
process.stderr.write(chalk.yellow('No sessions matched the selection.\n'));
|
|
219
|
+
process.exitCode = 1;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Layout is keyed on what the USER TYPED (selector count), not the resolved
|
|
223
|
+
// count — a single content-search selector matching two sessions must NOT
|
|
224
|
+
// silently become a compare. All the fail-loud boundaries live in the pure
|
|
225
|
+
// decideTraceLayout so they are unit-tested (see sessions-trace.test.ts).
|
|
226
|
+
const layout = decideTraceLayout(options, selectors.length, sessions.length);
|
|
227
|
+
const redact = options.redact !== false;
|
|
228
|
+
const knownSecrets = redact ? knownSecretValuesFromEnv() : undefined;
|
|
229
|
+
const format = chooseFormat(options, Boolean(process.stdout.isTTY));
|
|
230
|
+
const buildOne = (session) => {
|
|
231
|
+
const events = parseSession(session.filePath, session.agent);
|
|
232
|
+
return buildTrajectory(events, session, { redact, knownSecrets });
|
|
233
|
+
};
|
|
234
|
+
if (layout === 'lineage') {
|
|
235
|
+
// The pool must keep team-origin rows: they are hidden from the ordinary
|
|
236
|
+
// listing by default (AGENTS.md invariant 7), and they ARE the children.
|
|
237
|
+
// discoverSessions does not apply that presentation filter, so `pool` is
|
|
238
|
+
// already the teams-included set the graph needs.
|
|
239
|
+
const root = sessions[0];
|
|
240
|
+
// An id-shaped selector resolves through the session INDEX, which reaches
|
|
241
|
+
// rows the scanned pool does not hold (selectSessions -> findSessionsById,
|
|
242
|
+
// sessions-export.ts:386). Seed the pool with the resolved root so the
|
|
243
|
+
// graph always roots where the user pointed.
|
|
244
|
+
const pooled = pool.some((s) => s.id === root.id) ? pool : [root, ...pool];
|
|
245
|
+
const lineage = buildLineage(pooled, { rootId: root.id });
|
|
246
|
+
const idPart = root.shortId || root.id;
|
|
247
|
+
if (format === 'json') {
|
|
248
|
+
const out = JSON.stringify(buildLineageTraceEnvelope(lineage, buildOne(root)), null, 2) + '\n';
|
|
249
|
+
if (options.output) {
|
|
250
|
+
fs.writeFileSync(options.output, out, { mode: 0o600 });
|
|
251
|
+
process.stderr.write(chalk.green(`Wrote lineage JSON to ${options.output}\n`));
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
process.stdout.write(out);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (format === 'text') {
|
|
259
|
+
const out = renderLineageText(lineage);
|
|
260
|
+
if (options.output) {
|
|
261
|
+
fs.writeFileSync(options.output, out, { mode: 0o600 });
|
|
262
|
+
process.stderr.write(chalk.green(`Wrote lineage text to ${options.output}\n`));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
process.stdout.write(out);
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// HTML.
|
|
270
|
+
const html = renderLineageHtml(lineage);
|
|
271
|
+
if (options.output) {
|
|
272
|
+
fs.writeFileSync(options.output, html, { mode: 0o600 });
|
|
273
|
+
process.stdout.write(`${path.resolve(options.output)}\n`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const dir = path.join(getCacheDir(), 'traces');
|
|
277
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
278
|
+
const file = path.join(dir, `${idPart}-lineage-${Date.now()}.html`);
|
|
279
|
+
fs.writeFileSync(file, html, { mode: 0o600 });
|
|
280
|
+
if (options.open === false) {
|
|
281
|
+
process.stdout.write(`${file}\n`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
await showFile(file);
|
|
285
|
+
process.stderr.write(chalk.green(`Opened lineage for ${idPart}: ${file}\n`));
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (layout === 'compare') {
|
|
290
|
+
const cmp = diffTrajectories(buildOne(sessions[0]), buildOne(sessions[1]));
|
|
291
|
+
const idPart = `${sessions[0].shortId || sessions[0].id}-vs-${sessions[1].shortId || sessions[1].id}`;
|
|
292
|
+
if (format === 'json') {
|
|
293
|
+
const out = JSON.stringify(buildCompareTraceEnvelope(cmp), null, 2) + '\n';
|
|
294
|
+
if (options.output) {
|
|
295
|
+
fs.writeFileSync(options.output, out, { mode: 0o600 });
|
|
296
|
+
process.stderr.write(chalk.green(`Wrote compare JSON to ${options.output}\n`));
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
process.stdout.write(out);
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (format === 'text') {
|
|
304
|
+
const out = renderTrajectoryCompareText(cmp);
|
|
305
|
+
if (options.output) {
|
|
306
|
+
fs.writeFileSync(options.output, out, { mode: 0o600 });
|
|
307
|
+
process.stderr.write(chalk.green(`Wrote compare text to ${options.output}\n`));
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
process.stdout.write(out);
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
// HTML.
|
|
315
|
+
const html = renderTrajectoryCompareHtml(cmp);
|
|
316
|
+
if (options.output) {
|
|
317
|
+
fs.writeFileSync(options.output, html, { mode: 0o600 });
|
|
318
|
+
process.stdout.write(`${path.resolve(options.output)}\n`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const dir = path.join(getCacheDir(), 'traces');
|
|
322
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
323
|
+
const file = path.join(dir, `${idPart}-${Date.now()}.html`);
|
|
324
|
+
fs.writeFileSync(file, html, { mode: 0o600 });
|
|
325
|
+
if (options.open === false) {
|
|
326
|
+
process.stdout.write(`${file}\n`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
await showFile(file);
|
|
330
|
+
process.stderr.write(chalk.green(`Opened compare for ${idPart}: ${file}\n`));
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// One selector — the single-session trajectory.
|
|
335
|
+
const session = sessions[0];
|
|
336
|
+
const model = buildOne(session);
|
|
337
|
+
if (format === 'json') {
|
|
338
|
+
const out = JSON.stringify(buildTraceEnvelope([model]), null, 2) + '\n';
|
|
339
|
+
if (options.output) {
|
|
340
|
+
fs.writeFileSync(options.output, out, { mode: 0o600 });
|
|
341
|
+
process.stderr.write(chalk.green(`Wrote trajectory JSON to ${options.output}\n`));
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
process.stdout.write(out);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (format === 'text') {
|
|
349
|
+
const out = renderTrajectoryText(model, { errorsOnly: options.errorsOnly === true });
|
|
350
|
+
if (options.output) {
|
|
351
|
+
fs.writeFileSync(options.output, out, { mode: 0o600 });
|
|
352
|
+
process.stderr.write(chalk.green(`Wrote text trajectory to ${options.output}\n`));
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
process.stdout.write(out);
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// HTML.
|
|
360
|
+
const html = renderTrajectoryHtml(model);
|
|
361
|
+
if (options.output) {
|
|
362
|
+
fs.writeFileSync(options.output, html, { mode: 0o600 });
|
|
363
|
+
process.stdout.write(`${path.resolve(options.output)}\n`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const dir = path.join(getCacheDir(), 'traces');
|
|
367
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
368
|
+
const file = path.join(dir, `${session.shortId || session.id}-${Date.now()}.html`);
|
|
369
|
+
fs.writeFileSync(file, html, { mode: 0o600 });
|
|
370
|
+
if (options.open === false) {
|
|
371
|
+
process.stdout.write(`${file}\n`);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
await showFile(file);
|
|
375
|
+
process.stderr.write(chalk.green(`Opened trajectory for ${session.shortId || session.id}: ${file}\n`));
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
return cmd;
|
|
379
|
+
}
|
|
380
|
+
/** Canonical `agents sessions trace <selectors...>`. */
|
|
381
|
+
export function registerSessionsTraceCommand(sessionsCmd) {
|
|
382
|
+
configureTraceCommand(sessionsCmd.command('trace <selectors...>'));
|
|
383
|
+
}
|
|
384
|
+
/** Top-level alias `agents trace <selectors...>` (mirrors `agents insights`). */
|
|
385
|
+
export function registerTraceCommand(program) {
|
|
386
|
+
configureTraceCommand(program.command('trace <selectors...>'));
|
|
387
|
+
}
|
|
@@ -66,10 +66,12 @@ import { registerGoCommand } from './go.js';
|
|
|
66
66
|
import { registerFocusCommand } from './focus.js';
|
|
67
67
|
import { registerReconnectCommand } from './reconnect.js';
|
|
68
68
|
import { registerDetachCommand } from './detach.js';
|
|
69
|
+
import { registerSessionsStopCommand } from './sessions-stop.js';
|
|
69
70
|
import { registerAttachCommand } from './attach.js';
|
|
70
71
|
import { registerSessionsInjectCommand } from './sessions-inject.js';
|
|
71
72
|
import { registerSessionsExportCommand } from './sessions-export.js';
|
|
72
73
|
import { registerSessionsRenderCommand } from './sessions-render.js';
|
|
74
|
+
import { registerSessionsTraceCommand } from './sessions-trace.js';
|
|
73
75
|
import { registerSessionsShareCommand } from './sessions-share.js';
|
|
74
76
|
import { registerSessionsImportCommand } from './sessions-import.js';
|
|
75
77
|
import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
|
|
@@ -5170,10 +5172,12 @@ export function registerSessionsCommands(program) {
|
|
|
5170
5172
|
registerFocusCommand(sessionsCmd);
|
|
5171
5173
|
registerReconnectCommand(sessionsCmd);
|
|
5172
5174
|
registerDetachCommand(sessionsCmd);
|
|
5175
|
+
registerSessionsStopCommand(sessionsCmd);
|
|
5173
5176
|
registerAttachCommand(sessionsCmd);
|
|
5174
5177
|
registerSessionsInjectCommand(sessionsCmd);
|
|
5175
5178
|
registerSessionsExportCommand(sessionsCmd);
|
|
5176
5179
|
registerSessionsRenderCommand(sessionsCmd);
|
|
5180
|
+
registerSessionsTraceCommand(sessionsCmd);
|
|
5177
5181
|
registerSessionsShareCommand(sessionsCmd);
|
|
5178
5182
|
registerSessionsImportCommand(sessionsCmd);
|
|
5179
5183
|
registerSessionsMigrateCommand(sessionsCmd);
|
package/dist/commands/ssh.d.ts
CHANGED
|
@@ -10,10 +10,25 @@
|
|
|
10
10
|
* rsync / `agents sessions --device` resolve the same logical names.
|
|
11
11
|
*/
|
|
12
12
|
import type { Command } from 'commander';
|
|
13
|
+
import { type DeviceRegistry } from '../lib/devices/registry.js';
|
|
13
14
|
import { type FanOutDeviceTarget } from '../lib/devices/fleet.js';
|
|
15
|
+
import { type DeviceStats } from '../lib/devices/health.js';
|
|
14
16
|
import type { AgentId } from '../lib/types.js';
|
|
15
17
|
import { type HostHarnessResult } from '../lib/devices/harness-inventory.js';
|
|
16
18
|
import { type CrabboxBox } from '../lib/crabbox/cli.js';
|
|
19
|
+
/**
|
|
20
|
+
* Render the device list. When `statsMap` is provided, resource columns are
|
|
21
|
+
* appended — a `spec` cell (cores / total RAM / total disk), normalized load,
|
|
22
|
+
* memory and disk-used percentages, a headroom badge, and (in `full` mode)
|
|
23
|
+
* free/total memory — with the per-device role and `description` riding the
|
|
24
|
+
* tail, so it's obvious which boxes have room and what each box is for.
|
|
25
|
+
* Without it (probe skipped) the classic reachability line is used. A fleet
|
|
26
|
+
* capacity summary is appended whenever stats were gathered.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderDeviceTable(reg: DeviceRegistry, names: string[], self: string | undefined, statsMap?: Map<string, DeviceStats>, full?: boolean, interactiveHost?: string, opts?: {
|
|
29
|
+
width?: number;
|
|
30
|
+
ignoredCount?: number;
|
|
31
|
+
}): string[];
|
|
17
32
|
/**
|
|
18
33
|
* Live "Leased boxes" section for `agents devices` (F4, RUSH-1923), computed
|
|
19
34
|
* from `crabboxList()` — these are ephemeral crabbox leases, NEVER written into
|