@phnx-labs/agents-cli 1.20.74 → 1.20.77
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 +1055 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +18 -0
- package/dist/commands/exec.js +83 -13
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +38 -8
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +286 -15
- package/dist/commands/secrets.js +102 -37
- package/dist/commands/sessions-browser.js +12 -2
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.d.ts +14 -1
- package/dist/commands/sessions-picker.js +184 -16
- package/dist/commands/sessions.d.ts +56 -14
- package/dist/commands/sessions.js +322 -62
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/ssh.d.ts +13 -0
- package/dist/commands/ssh.js +110 -37
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +15 -6
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +24 -4
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +17 -6
- package/dist/lib/activity.js +466 -40
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +21 -0
- package/dist/lib/agents.js +70 -6
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.js +4 -0
- package/dist/lib/daemon.d.ts +10 -1
- package/dist/lib/daemon.js +114 -11
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +37 -1
- package/dist/lib/devices/fleet.js +36 -2
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +82 -0
- package/dist/lib/exec.js +218 -6
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/feed.d.ts +7 -1
- package/dist/lib/feed.js +96 -6
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/heal.d.ts +7 -4
- package/dist/lib/heal.js +10 -22
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +16 -0
- package/dist/lib/hosts/dispatch.js +26 -4
- package/dist/lib/hosts/passthrough.js +9 -2
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.d.ts +11 -0
- package/dist/lib/hosts/remote-cmd.js +32 -8
- package/dist/lib/hosts/remote-session-id.d.ts +45 -0
- package/dist/lib/hosts/remote-session-id.js +84 -0
- package/dist/lib/hosts/run-target.d.ts +17 -5
- package/dist/lib/hosts/run-target.js +30 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +40 -5
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +2 -0
- package/dist/lib/import.js +35 -2
- package/dist/lib/menubar/install-menubar.d.ts +29 -0
- package/dist/lib/menubar/install-menubar.js +59 -1
- package/dist/lib/menubar/notify-desktop.d.ts +44 -0
- package/dist/lib/menubar/notify-desktop.js +78 -0
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +6 -2
- package/dist/lib/overdue.js +10 -36
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routine-notify.d.ts +76 -0
- package/dist/lib/routine-notify.js +190 -0
- package/dist/lib/routines-placement.d.ts +38 -0
- package/dist/lib/routines-placement.js +79 -0
- package/dist/lib/routines-project.d.ts +97 -0
- package/dist/lib/routines-project.js +349 -0
- package/dist/lib/routines.d.ts +94 -0
- package/dist/lib/routines.js +93 -9
- package/dist/lib/runner.d.ts +12 -2
- package/dist/lib/runner.js +242 -20
- package/dist/lib/sandbox.js +10 -0
- package/dist/lib/secrets/account-token.d.ts +20 -0
- package/dist/lib/secrets/account-token.js +64 -0
- package/dist/lib/secrets/agent.d.ts +74 -4
- package/dist/lib/secrets/agent.js +181 -107
- package/dist/lib/secrets/bundles.d.ts +54 -6
- package/dist/lib/secrets/bundles.js +230 -34
- package/dist/lib/secrets/index.d.ts +26 -3
- package/dist/lib/secrets/index.js +42 -12
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/session-store.d.ts +6 -2
- package/dist/lib/secrets/session-store.js +65 -15
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/secrets/unlock-hints.d.ts +27 -0
- package/dist/lib/secrets/unlock-hints.js +36 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +124 -7
- package/dist/lib/session/active.js +390 -56
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +54 -0
- package/dist/lib/session/db.js +320 -19
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +478 -3
- package/dist/lib/session/discover.js +1740 -472
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +141 -32
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/prompt.d.ts +7 -0
- package/dist/lib/session/prompt.js +37 -0
- package/dist/lib/session/provenance.d.ts +7 -0
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +40 -26
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.d.ts +4 -0
- package/dist/lib/session/state.js +99 -13
- package/dist/lib/session/types.d.ts +25 -1
- package/dist/lib/session/types.js +8 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +20 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +3 -0
- package/dist/lib/state.d.ts +8 -4
- package/dist/lib/state.js +21 -37
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +47 -2
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +9 -2
- package/dist/lib/versions.js +37 -8
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
* response) and delegates to the generic `itemPicker` for the interactive UI.
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
9
10
|
import chalk from 'chalk';
|
|
10
11
|
import { truncate, humanDuration } from '../lib/format.js';
|
|
11
12
|
import { parseSession, sanitizeForTerminal } from '../lib/session/parse.js';
|
|
12
13
|
import { cleanSessionPrompt, extractSessionTopic } from '../lib/session/prompt.js';
|
|
13
|
-
import { linkPath, relativeToCwd } from '../lib/session/render.js';
|
|
14
|
+
import { linkPath, linkUrl, relativeToCwd } from '../lib/session/render.js';
|
|
15
|
+
import { linearIssueUrl } from '../lib/session/linear.js';
|
|
16
|
+
import { extractTodoProgress } from '../lib/session/state.js';
|
|
14
17
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
15
18
|
import { itemPicker } from '../lib/picker.js';
|
|
16
19
|
import { classifyFileChanges, changeCounts, toolHistogram, detectTestResult } from '../lib/session/digest.js';
|
|
@@ -22,6 +25,32 @@ import { classifyFileChanges, changeCounts, toolHistogram, detectTestResult } fr
|
|
|
22
25
|
function remoteMachineOf(session) {
|
|
23
26
|
return session._remote ? session.machine : undefined;
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Compact checklist tally for list rows and previews (RUSH-2045).
|
|
30
|
+
* Example: `✓6/8 · A5 wiring runner`. Empty string when there is no list.
|
|
31
|
+
* Consumes `SessionMeta.todos` / `ActiveSession.todos` as populated by the
|
|
32
|
+
* state engine — does not re-parse transcripts.
|
|
33
|
+
*/
|
|
34
|
+
export function formatTodoCompact(todos) {
|
|
35
|
+
if (!todos || !Number.isFinite(todos.total) || todos.total < 1)
|
|
36
|
+
return '';
|
|
37
|
+
const done = Number.isFinite(todos.done) ? Math.max(0, todos.done) : 0;
|
|
38
|
+
const tally = `✓${done}/${todos.total}`;
|
|
39
|
+
const step = todos.activeForm?.replace(/\s+/g, ' ').trim();
|
|
40
|
+
return step ? `${tally} · ${step}` : tally;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Best-effort GitHub repo URL from a checkout path shaped like
|
|
44
|
+
* `…/github.com/<owner>/<repo>/…`. Used to make the project name clickable
|
|
45
|
+
* when no Linear project URL is available.
|
|
46
|
+
*/
|
|
47
|
+
export function githubRepoUrlFromCwd(cwd) {
|
|
48
|
+
if (!cwd)
|
|
49
|
+
return undefined;
|
|
50
|
+
const norm = cwd.replace(/\\/g, '/');
|
|
51
|
+
const m = norm.match(/\/github\.com\/([^/]+\/[^/]+)/);
|
|
52
|
+
return m ? `https://github.com/${m[1]}` : undefined;
|
|
53
|
+
}
|
|
25
54
|
/**
|
|
26
55
|
* SessionMeta originates in discover.ts (gitBranch, cwd, label, etc. read from
|
|
27
56
|
* untrusted session files). parseSession sanitizes event payloads at its
|
|
@@ -30,6 +59,17 @@ function remoteMachineOf(session) {
|
|
|
30
59
|
*/
|
|
31
60
|
function sanitizeMeta(s) {
|
|
32
61
|
const clean = (v) => (v == null ? v : sanitizeForTerminal(v));
|
|
62
|
+
const todos = s.todos
|
|
63
|
+
? {
|
|
64
|
+
...s.todos,
|
|
65
|
+
activeForm: clean(s.todos.activeForm),
|
|
66
|
+
items: s.todos.items.map((it) => ({
|
|
67
|
+
...it,
|
|
68
|
+
content: sanitizeForTerminal(it.content),
|
|
69
|
+
activeForm: clean(it.activeForm),
|
|
70
|
+
})),
|
|
71
|
+
}
|
|
72
|
+
: s.todos;
|
|
33
73
|
return {
|
|
34
74
|
...s,
|
|
35
75
|
id: sanitizeForTerminal(s.id),
|
|
@@ -42,6 +82,9 @@ function sanitizeMeta(s) {
|
|
|
42
82
|
account: clean(s.account),
|
|
43
83
|
topic: clean(s.topic),
|
|
44
84
|
label: clean(s.label),
|
|
85
|
+
ticketId: clean(s.ticketId),
|
|
86
|
+
prUrl: clean(s.prUrl),
|
|
87
|
+
todos,
|
|
45
88
|
};
|
|
46
89
|
}
|
|
47
90
|
const previewCache = new Map();
|
|
@@ -59,7 +102,8 @@ export function buildPreview(session) {
|
|
|
59
102
|
if (remote) {
|
|
60
103
|
const note = ' ' + chalk.gray(`on `) + chalk.bold.white(remote)
|
|
61
104
|
+ chalk.gray(` — enter to resume there, or space then enter to read it over SSH`);
|
|
62
|
-
const
|
|
105
|
+
const metaBody = formatMetaOnlyBody(safe);
|
|
106
|
+
const output = [formatHeader(safe, []), '', note, metaBody].filter(Boolean).join('\n');
|
|
63
107
|
previewCache.set(cacheKey, output);
|
|
64
108
|
return output;
|
|
65
109
|
}
|
|
@@ -67,7 +111,8 @@ export function buildPreview(session) {
|
|
|
67
111
|
// entry (e.g. `sessions go`). Show the header + a clean note, not a parse error.
|
|
68
112
|
if (!session.filePath || !fs.existsSync(session.filePath)) {
|
|
69
113
|
const note = ' ' + chalk.gray('Live session — full transcript not indexed here.');
|
|
70
|
-
const
|
|
114
|
+
const metaBody = formatMetaOnlyBody(safe);
|
|
115
|
+
const output = [formatHeader(safe, []), '', note, metaBody].filter(Boolean).join('\n');
|
|
71
116
|
previewCache.set(cacheKey, output);
|
|
72
117
|
return output;
|
|
73
118
|
}
|
|
@@ -96,19 +141,27 @@ function formatHeader(session, events) {
|
|
|
96
141
|
const { startedAgo, duration } = extractTiming(events);
|
|
97
142
|
const totalMessages = session.messageCount ?? countMessages(events);
|
|
98
143
|
const totalTokens = session.tokenCount;
|
|
99
|
-
// Line 1: Agent v version · model · account
|
|
144
|
+
// Line 1: Agent v version · shortId · model · account
|
|
100
145
|
const line1 = [];
|
|
101
146
|
line1.push(chalk.gray(`${displayAgent(session.agent)}${session.version ? ` v${session.version}` : ''}`));
|
|
147
|
+
if (session.shortId)
|
|
148
|
+
line1.push(chalk.dim(session.shortId));
|
|
102
149
|
if (model)
|
|
103
150
|
line1.push(chalk.bold.white(model));
|
|
104
151
|
if (session.account)
|
|
105
152
|
line1.push(chalk.gray(session.account));
|
|
106
|
-
// Line 2: cwd · branch · started X ago · lasted Y
|
|
153
|
+
// Line 2: cwd · project · branch · started X ago · lasted Y
|
|
154
|
+
// Project is clickable: Linear issue URL is for tickets (line 4); for the
|
|
155
|
+
// project name prefer a GitHub repo URL derived from the checkout path.
|
|
107
156
|
const line2 = [];
|
|
108
157
|
if (session.cwd) {
|
|
109
158
|
const label = relativeToCwd(session.cwd);
|
|
110
159
|
line2.push(chalk.bold.white(linkPath(session.cwd, label)));
|
|
111
160
|
}
|
|
161
|
+
if (session.project) {
|
|
162
|
+
const repoUrl = githubRepoUrlFromCwd(session.cwd);
|
|
163
|
+
line2.push(chalk.cyan(repoUrl ? linkUrl(repoUrl, session.project) : session.project));
|
|
164
|
+
}
|
|
112
165
|
if (session.gitBranch)
|
|
113
166
|
line2.push(chalk.cyan(session.gitBranch));
|
|
114
167
|
if (startedAgo)
|
|
@@ -125,13 +178,46 @@ function formatHeader(session, events) {
|
|
|
125
178
|
}
|
|
126
179
|
if (session.label)
|
|
127
180
|
line3.push(chalk.white(session.label));
|
|
128
|
-
|
|
181
|
+
if (session.filePath)
|
|
182
|
+
line3.push(chalk.gray(linkPath(session.filePath, session.id)));
|
|
183
|
+
else
|
|
184
|
+
line3.push(chalk.gray(session.id));
|
|
185
|
+
// Line 4: ticket + PR — clickable when a URL is resolvable (OSC 8 hyperlink),
|
|
186
|
+
// plain text otherwise. Only rendered when the session carries either.
|
|
187
|
+
const line4 = [];
|
|
188
|
+
if (session.ticketId) {
|
|
189
|
+
const url = linearIssueUrl(session.ticketId);
|
|
190
|
+
line4.push(chalk.blue(url ? linkUrl(url, session.ticketId) : session.ticketId));
|
|
191
|
+
}
|
|
192
|
+
if (session.prUrl) {
|
|
193
|
+
const label = session.prNumber ? `PR#${session.prNumber}` : 'PR';
|
|
194
|
+
line4.push(chalk.blue(linkUrl(session.prUrl, label)));
|
|
195
|
+
}
|
|
129
196
|
return [
|
|
130
197
|
line1.join(DOT),
|
|
131
198
|
line2.join(DOT),
|
|
132
199
|
line3.join(DOT),
|
|
200
|
+
...(line4.length ? [line4.join(DOT)] : []),
|
|
133
201
|
].join('\n');
|
|
134
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Body lines available from SessionMeta alone (no transcript parse) — used for
|
|
205
|
+
* remote / unindexed sessions so checklist progress still surfaces when the
|
|
206
|
+
* parser teammate (or a prior scan) has populated `session.todos`.
|
|
207
|
+
*/
|
|
208
|
+
function formatMetaOnlyBody(session) {
|
|
209
|
+
const lines = [];
|
|
210
|
+
if (session.topic) {
|
|
211
|
+
lines.push(chalk.cyan('Prompt: ') + chalk.white(truncate(session.topic.trim(), (process.stdout.columns || 80) - 12)));
|
|
212
|
+
}
|
|
213
|
+
const compact = formatTodoCompact(session.todos);
|
|
214
|
+
if (compact) {
|
|
215
|
+
lines.push(chalk.cyan('Todos: ') + chalk.white(compact));
|
|
216
|
+
}
|
|
217
|
+
if (lines.length === 0)
|
|
218
|
+
return '';
|
|
219
|
+
return lines.map(l => ' ' + l).join('\n');
|
|
220
|
+
}
|
|
135
221
|
function extractModel(events) {
|
|
136
222
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
137
223
|
const m = events[i].model;
|
|
@@ -189,6 +275,7 @@ function stripTags(text) {
|
|
|
189
275
|
const LAST_RESPONSE_MAX_LINES = 15;
|
|
190
276
|
const LAST_RESPONSE_MAX_LINES_WITH_TODOS = 8;
|
|
191
277
|
const TODOS_MAX_ITEMS = 5;
|
|
278
|
+
const DIRS_TOUCHED_MAX = 5;
|
|
192
279
|
function formatCompactPreview(events, session) {
|
|
193
280
|
let firstUser = '';
|
|
194
281
|
let lastAssistant = '';
|
|
@@ -196,7 +283,8 @@ function formatCompactPreview(events, session) {
|
|
|
196
283
|
const toolCounts = {};
|
|
197
284
|
let toolCalls = 0;
|
|
198
285
|
let planFile = '';
|
|
199
|
-
|
|
286
|
+
/** Latest checklist from the transcript (Claude TodoWrite / Codex update_plan). */
|
|
287
|
+
let latestTodos;
|
|
200
288
|
for (const event of events) {
|
|
201
289
|
if (event.type === 'message') {
|
|
202
290
|
if (event.role === 'user' && !firstUser && event.content) {
|
|
@@ -217,25 +305,53 @@ function formatCompactPreview(events, session) {
|
|
|
217
305
|
if (!planFile && p && /\/plans\/[^/]+\.md$/.test(p)) {
|
|
218
306
|
planFile = p;
|
|
219
307
|
}
|
|
220
|
-
|
|
221
|
-
|
|
308
|
+
// Claude TodoWrite (`todos`) and Codex update_plan (`plan`) — same source as
|
|
309
|
+
// extractTodoProgress in the state engine. Prefer the most recent write.
|
|
310
|
+
if (tool === 'TodoWrite' || tool === 'update_plan') {
|
|
311
|
+
const progress = extractTodoProgress(event.args);
|
|
312
|
+
if (progress)
|
|
313
|
+
latestTodos = progress;
|
|
222
314
|
}
|
|
223
315
|
if (tool)
|
|
224
316
|
toolCounts[tool] = (toolCounts[tool] ?? 0) + 1;
|
|
225
317
|
toolCalls++;
|
|
226
318
|
}
|
|
227
319
|
}
|
|
320
|
+
// Prefer the transcript-derived list when we just re-parsed the file (freshest
|
|
321
|
+
// checklist write); fall back to SessionMeta.todos for rows where the scan/
|
|
322
|
+
// fan-out attached progress but the event stream has no TodoWrite yet.
|
|
323
|
+
const todos = latestTodos ?? session.todos;
|
|
228
324
|
// Digest signals folded into the preview: change lifecycle, tool mix, tests.
|
|
229
325
|
const changes = classifyFileChanges(events);
|
|
230
326
|
const chg = changeCounts(changes);
|
|
231
327
|
const lines = [];
|
|
232
328
|
const termWidth = process.stdout.columns || 80;
|
|
329
|
+
// Originating user prompt (first non-system user turn).
|
|
233
330
|
if (firstUser) {
|
|
234
331
|
const first = extractSessionTopic(firstUser) || cleanSessionPrompt(firstUser).split('\n').find(l => l.trim()) || '';
|
|
235
332
|
if (first) {
|
|
236
333
|
lines.push(chalk.cyan('Prompt: ') + chalk.white(truncate(first.trim(), termWidth - 12)));
|
|
237
334
|
}
|
|
238
335
|
}
|
|
336
|
+
else if (session.topic) {
|
|
337
|
+
lines.push(chalk.cyan('Prompt: ') + chalk.white(truncate(session.topic.trim(), termWidth - 12)));
|
|
338
|
+
}
|
|
339
|
+
// Compact checklist: ✓done/total · current step (RUSH-2045).
|
|
340
|
+
const compact = formatTodoCompact(todos);
|
|
341
|
+
if (compact) {
|
|
342
|
+
lines.push(chalk.cyan('Todos: ') + chalk.white(compact));
|
|
343
|
+
}
|
|
344
|
+
const todosRendered = todos?.items?.length ? renderTodos(todos.items, termWidth) : [];
|
|
345
|
+
if (todosRendered.length > 0) {
|
|
346
|
+
for (const l of todosRendered)
|
|
347
|
+
lines.push(' ' + l);
|
|
348
|
+
}
|
|
349
|
+
// Recent activity = directories touched (not raw tool calls). Prefer a
|
|
350
|
+
// parser-supplied dirsTouched when present; else derive from event paths.
|
|
351
|
+
const dirs = directoriesTouched(session, events, changes);
|
|
352
|
+
if (dirs.length) {
|
|
353
|
+
lines.push(chalk.cyan('Dirs: ') + chalk.white(dirs.join(chalk.gray(' · '))));
|
|
354
|
+
}
|
|
239
355
|
const activity = [];
|
|
240
356
|
const changed = chg.created + chg.modified + chg.deleted;
|
|
241
357
|
if (changed) {
|
|
@@ -272,14 +388,8 @@ function formatCompactPreview(events, session) {
|
|
|
272
388
|
const basename = planFile.split('/').pop() || planFile;
|
|
273
389
|
lines.push(chalk.cyan('Plan: ') + chalk.white(linkPath(planFile, basename)));
|
|
274
390
|
}
|
|
275
|
-
const todosRendered = latestTodos ? renderTodos(latestTodos, termWidth) : [];
|
|
276
|
-
if (todosRendered.length > 0) {
|
|
277
|
-
lines.push(chalk.cyan('Todos:'));
|
|
278
|
-
for (const l of todosRendered)
|
|
279
|
-
lines.push(' ' + l);
|
|
280
|
-
}
|
|
281
391
|
if (lastAssistant) {
|
|
282
|
-
const maxLines = todosRendered.length > 0 ? LAST_RESPONSE_MAX_LINES_WITH_TODOS : LAST_RESPONSE_MAX_LINES;
|
|
392
|
+
const maxLines = todosRendered.length > 0 || compact ? LAST_RESPONSE_MAX_LINES_WITH_TODOS : LAST_RESPONSE_MAX_LINES;
|
|
283
393
|
const rendered = renderLastResponse(lastAssistant, maxLines);
|
|
284
394
|
if (rendered.length > 0) {
|
|
285
395
|
lines.push('');
|
|
@@ -293,6 +403,64 @@ function formatCompactPreview(events, session) {
|
|
|
293
403
|
}
|
|
294
404
|
return lines.map(l => ' ' + l).join('\n');
|
|
295
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Unique directories the session touched, compact and human-readable.
|
|
408
|
+
* Prefer `session.dirsTouched` when the parser teammate has populated it;
|
|
409
|
+
* otherwise derive from file-change + tool paths already available here.
|
|
410
|
+
*/
|
|
411
|
+
function directoriesTouched(session, events, changes) {
|
|
412
|
+
const fromMeta = session.dirsTouched;
|
|
413
|
+
if (Array.isArray(fromMeta) && fromMeta.length > 0) {
|
|
414
|
+
return fromMeta
|
|
415
|
+
.map((d) => sanitizeForTerminal(String(d).trim()))
|
|
416
|
+
.filter(Boolean)
|
|
417
|
+
.slice(0, DIRS_TOUCHED_MAX);
|
|
418
|
+
}
|
|
419
|
+
const counts = new Map();
|
|
420
|
+
const bump = (raw) => {
|
|
421
|
+
const dir = relativizeDir(raw, session.cwd);
|
|
422
|
+
if (!dir)
|
|
423
|
+
return;
|
|
424
|
+
counts.set(dir, (counts.get(dir) ?? 0) + 1);
|
|
425
|
+
};
|
|
426
|
+
for (const ch of changes)
|
|
427
|
+
bump(ch.path);
|
|
428
|
+
for (const event of events) {
|
|
429
|
+
if (event.type !== 'tool_use' || event._local)
|
|
430
|
+
continue;
|
|
431
|
+
const p = event.path || event.args?.file_path || event.args?.path || '';
|
|
432
|
+
if (p)
|
|
433
|
+
bump(p);
|
|
434
|
+
}
|
|
435
|
+
return [...counts.entries()]
|
|
436
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
437
|
+
.map(([d]) => d)
|
|
438
|
+
.slice(0, DIRS_TOUCHED_MAX);
|
|
439
|
+
}
|
|
440
|
+
/** Relativize a file path to its parent dir, short enough for one preview line. */
|
|
441
|
+
function relativizeDir(filePath, cwd) {
|
|
442
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
443
|
+
if (!norm || norm.includes('node_modules') || norm.includes('/.git/') || norm.includes('/plans/')) {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
let dir = path.posix.dirname(norm);
|
|
447
|
+
if (cwd) {
|
|
448
|
+
const base = cwd.replace(/\\/g, '/').replace(/\/$/, '');
|
|
449
|
+
if (dir === base)
|
|
450
|
+
return '.';
|
|
451
|
+
if (dir.startsWith(base + '/'))
|
|
452
|
+
dir = dir.slice(base.length + 1);
|
|
453
|
+
}
|
|
454
|
+
// Collapse home prefix.
|
|
455
|
+
const home = (process.env.HOME || '').replace(/\\/g, '/');
|
|
456
|
+
if (home && dir.startsWith(home + '/'))
|
|
457
|
+
dir = '~' + dir.slice(home.length);
|
|
458
|
+
// Drop ultra-deep absolute noise; keep last 3 segments.
|
|
459
|
+
const parts = dir.split('/').filter(Boolean);
|
|
460
|
+
if (parts.length > 3 && dir.startsWith('/'))
|
|
461
|
+
dir = parts.slice(-3).join('/');
|
|
462
|
+
return dir || undefined;
|
|
463
|
+
}
|
|
296
464
|
function renderLastResponse(content, maxLines = LAST_RESPONSE_MAX_LINES) {
|
|
297
465
|
const cleaned = stripTags(content).trim();
|
|
298
466
|
if (!cleaned)
|
|
@@ -9,6 +9,18 @@ import { type PickedSession } from './sessions-picker.js';
|
|
|
9
9
|
* a captured transcript tail. Collapses runs of whitespace.
|
|
10
10
|
*/
|
|
11
11
|
export declare function cleanPreview(text: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Identity + checklist + live snippet for an --active / cross-machine row.
|
|
14
|
+
* Surfaces agent-adjacent identity the flat table already has (label, project)
|
|
15
|
+
* with a clickable project when a GitHub URL is resolvable, then the checklist
|
|
16
|
+
* tally and the latest-turn snippet.
|
|
17
|
+
*
|
|
18
|
+
* Free-text fields are cleaned individually; OSC 8 hyperlinks are applied
|
|
19
|
+
* *after* cleaning so `cleanPreview` does not strip the clickable targets
|
|
20
|
+
* (RUSH-2045 review). Ticket is clickable via {@link signalBadges}, not here,
|
|
21
|
+
* so the id is not printed twice.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatActiveRowDescription(s: ActiveSession): string;
|
|
12
24
|
/**
|
|
13
25
|
* Index live sessions by their full session UUID so a historical `SessionMeta`
|
|
14
26
|
* row (`meta.id`) can be matched to the session that is still running now.
|
|
@@ -33,6 +45,19 @@ export declare function liveGlyphAndPreview(a: ActiveSession | undefined): {
|
|
|
33
45
|
* into a scannable column instead of jamming against a truncated topic.
|
|
34
46
|
*/
|
|
35
47
|
export declare function ticketLabel(s: Pick<SessionMeta, 'ticketId' | 'prNumber'>): string;
|
|
48
|
+
/**
|
|
49
|
+
* The row shape `agents sessions --active --json` emits. RUSH-1981: a watcher
|
|
50
|
+
* joins active sessions on ticketId + project, but the raw ActiveSession nests
|
|
51
|
+
* the ticket (`ticket.id`) and carries no `project` at all — so a naive join
|
|
52
|
+
* silently drops every row. Emit both as flat, always-present top-level keys
|
|
53
|
+
* (null when unknown) alongside the raw fields, so every active row is joinable.
|
|
54
|
+
* `project` uses the same derivation SessionMeta does — basename(cwd) (see
|
|
55
|
+
* discover.ts) — so the active view and the history view join identically.
|
|
56
|
+
*/
|
|
57
|
+
export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<ActiveSession & {
|
|
58
|
+
ticketId: string | null;
|
|
59
|
+
project: string | null;
|
|
60
|
+
}>;
|
|
36
61
|
/** Grouped + sorted view of active sessions for the --active renderer. */
|
|
37
62
|
export interface ActiveSessionsLayout {
|
|
38
63
|
workspaces: Array<{
|
|
@@ -162,6 +187,12 @@ export declare function renderSessionLog(session: SessionMeta, mode?: ViewMode):
|
|
|
162
187
|
*/
|
|
163
188
|
export declare function renderSessionLogJson(session: SessionMeta): Promise<void>;
|
|
164
189
|
/** Column-visibility flags for the picker row, computed once over the whole pool. */
|
|
190
|
+
/** The SSH-launch origin for a picker row, resolved from the live session's
|
|
191
|
+
* provenance (transport 'ssh'). `device` is set when the client IP matched a
|
|
192
|
+
* registered device; absent for an unresolved IP (renders a bare `ssh`). */
|
|
193
|
+
export interface SshOriginTag {
|
|
194
|
+
device?: string;
|
|
195
|
+
}
|
|
165
196
|
export interface PickerColumns {
|
|
166
197
|
/** Render the machine column (only when the pool spans more than one machine). */
|
|
167
198
|
showMachine?: boolean;
|
|
@@ -192,7 +223,7 @@ export declare function machineLabeler(machines: string[]): (m: string) => strin
|
|
|
192
223
|
* than one box, the ticket column only when some row carries a PR/ticket ref.
|
|
193
224
|
*/
|
|
194
225
|
export declare function pickerColumnsFor(sessions: SessionMeta[]): PickerColumns;
|
|
195
|
-
export declare function formatPickerLabel(s: SessionMeta, query: string, cols?: PickerColumns): string;
|
|
226
|
+
export declare function formatPickerLabel(s: SessionMeta, query: string, cols?: PickerColumns, ssh?: SshOriginTag): string;
|
|
196
227
|
/**
|
|
197
228
|
* Pick a hint to show above the picker. Deterministic (keys off the pool size)
|
|
198
229
|
* so it stays fixed across the picker's re-renders within a single run.
|
|
@@ -204,8 +235,8 @@ export declare function handlePickedSession(picked: PickedSession): Promise<void
|
|
|
204
235
|
* Resume a session in the current terminal — a foreground takeover of this
|
|
205
236
|
* process. Used by the single-select picker and by `sessions resume` when the
|
|
206
237
|
* chosen destination is "in place" (unknown emulator / off-macOS, single pick).
|
|
207
|
-
* Falls back to the current version
|
|
208
|
-
*
|
|
238
|
+
* Falls back to the same resume invocation against the current version when the
|
|
239
|
+
* version-pinned launcher is genuinely missing.
|
|
209
240
|
*/
|
|
210
241
|
export declare function resumeSessionInPlace(session: SessionMeta): Promise<void>;
|
|
211
242
|
/**
|
|
@@ -224,23 +255,34 @@ export declare function resumeSpawnInvocation(cmd: string[], platform?: NodeJS.P
|
|
|
224
255
|
args: string[];
|
|
225
256
|
shell: boolean;
|
|
226
257
|
};
|
|
227
|
-
/**
|
|
228
|
-
* Build the shell command that resumes a picked session.
|
|
229
|
-
*
|
|
230
|
-
* When the session's originating version is known, uses the version-pinned
|
|
231
|
-
* binary (e.g. `claude@2.1.138`) so the resume always runs in the same
|
|
232
|
-
* isolated HOME where the JSONL was written — regardless of which version is
|
|
233
|
-
* currently the default. Falls back to the bare shim when version is unknown.
|
|
234
|
-
*
|
|
235
|
-
* If the versioned binary is missing (version was removed), the ENOENT
|
|
236
|
-
* handler in handlePickedSession retries via buildFallbackCommand.
|
|
237
|
-
*/
|
|
238
258
|
export declare function buildResumeCommand(session: SessionMeta): string[] | null;
|
|
239
259
|
interface AgentFilter {
|
|
240
260
|
agent?: SessionAgentId;
|
|
241
261
|
version?: string;
|
|
242
262
|
}
|
|
243
263
|
export declare function parseAgentFilter(agentName?: string): AgentFilter;
|
|
264
|
+
/**
|
|
265
|
+
* How a `sessions <query>` argument was resolved against the pool.
|
|
266
|
+
*
|
|
267
|
+
* `byId` records that the rows came from an id lookup, so only then does an
|
|
268
|
+
* ambiguous result mean "your id prefix is too short". `completeId` records that
|
|
269
|
+
* the query was a whole session id: it is unique by construction, so a miss is
|
|
270
|
+
* final and must NOT widen into a text/content search.
|
|
271
|
+
*/
|
|
272
|
+
export interface SessionQueryResolution {
|
|
273
|
+
matches: SessionMeta[];
|
|
274
|
+
byId: boolean;
|
|
275
|
+
completeId: boolean;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The single entry point for turning a `sessions <query>` argument into rows.
|
|
279
|
+
*
|
|
280
|
+
* An id-shaped query — a complete id OR a hex short-id/prefix (looksLikeSessionId)
|
|
281
|
+
* — resolves by id alone, through the index, and never falls back to content
|
|
282
|
+
* search (a bare id must not surface every transcript that merely mentions it).
|
|
283
|
+
* A genuine search phrase keeps the ranked metadata+content search.
|
|
284
|
+
*/
|
|
285
|
+
export declare function resolveSessionQuery(pool: SessionMeta[], query: string): SessionQueryResolution;
|
|
244
286
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|
|
245
287
|
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined): SessionMeta[];
|
|
246
288
|
/** Register the `agents sessions` command with all its options and help text. */
|