@phnx-labs/agents-cli 1.20.76 → 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 +162 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/exec.js +58 -16
- package/dist/commands/routines.js +271 -13
- package/dist/commands/secrets.js +59 -17
- package/dist/commands/sessions-browser.js +11 -1
- package/dist/commands/sessions-picker.d.ts +14 -1
- package/dist/commands/sessions-picker.js +168 -15
- package/dist/commands/sessions.d.ts +23 -4
- package/dist/commands/sessions.js +128 -38
- package/dist/commands/ssh.d.ts +13 -0
- package/dist/commands/ssh.js +39 -2
- package/dist/index.js +2 -1
- package/dist/lib/activity.d.ts +5 -4
- package/dist/lib/activity.js +450 -36
- package/dist/lib/cloud/session-index.js +2 -2
- package/dist/lib/daemon.d.ts +10 -1
- package/dist/lib/daemon.js +99 -12
- package/dist/lib/devices/fleet.d.ts +16 -1
- package/dist/lib/devices/fleet.js +10 -2
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +16 -0
- package/dist/lib/exec.js +27 -4
- package/dist/lib/feed.d.ts +7 -1
- package/dist/lib/feed.js +96 -6
- package/dist/lib/heal.d.ts +7 -4
- package/dist/lib/heal.js +10 -22
- package/dist/lib/hosts/dispatch.d.ts +9 -0
- package/dist/lib/hosts/dispatch.js +24 -4
- package/dist/lib/hosts/passthrough.js +7 -2
- package/dist/lib/hosts/remote-cmd.d.ts +11 -0
- package/dist/lib/hosts/remote-cmd.js +31 -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 +4 -0
- package/dist/lib/hosts/run-target.js +5 -1
- package/dist/lib/hosts/session-index.js +3 -3
- package/dist/lib/menubar/install-menubar.d.ts +9 -0
- package/dist/lib/menubar/install-menubar.js +14 -0
- package/dist/lib/menubar/notify-desktop.d.ts +44 -0
- package/dist/lib/menubar/notify-desktop.js +78 -0
- package/dist/lib/overdue.d.ts +4 -5
- package/dist/lib/overdue.js +8 -41
- 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 +68 -0
- package/dist/lib/routines.js +74 -7
- package/dist/lib/runner.d.ts +12 -2
- package/dist/lib/runner.js +150 -25
- 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 +9 -4
- package/dist/lib/secrets/agent.js +30 -20
- package/dist/lib/secrets/bundles.d.ts +20 -6
- package/dist/lib/secrets/bundles.js +70 -34
- package/dist/lib/secrets/index.d.ts +11 -2
- package/dist/lib/secrets/index.js +21 -9
- package/dist/lib/secrets/session-store.d.ts +6 -2
- package/dist/lib/secrets/session-store.js +65 -15
- package/dist/lib/secrets/unlock-hints.d.ts +27 -0
- package/dist/lib/secrets/unlock-hints.js +36 -0
- package/dist/lib/session/active.d.ts +10 -0
- package/dist/lib/session/active.js +67 -4
- package/dist/lib/session/db.d.ts +6 -0
- package/dist/lib/session/db.js +83 -6
- package/dist/lib/session/discover.d.ts +13 -1
- package/dist/lib/session/discover.js +91 -3
- package/dist/lib/session/parse.js +11 -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.js +18 -16
- package/dist/lib/session/state.d.ts +4 -0
- package/dist/lib/session/state.js +93 -11
- package/dist/lib/session/types.d.ts +25 -1
- package/dist/lib/session/types.js +8 -0
- package/dist/lib/state.d.ts +7 -3
- package/dist/lib/state.js +20 -5
- package/dist/lib/types.d.ts +10 -0
- package/package.json +1 -1
|
@@ -6,12 +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
14
|
import { linkPath, linkUrl, relativeToCwd } from '../lib/session/render.js';
|
|
14
15
|
import { linearIssueUrl } from '../lib/session/linear.js';
|
|
16
|
+
import { extractTodoProgress } from '../lib/session/state.js';
|
|
15
17
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
16
18
|
import { itemPicker } from '../lib/picker.js';
|
|
17
19
|
import { classifyFileChanges, changeCounts, toolHistogram, detectTestResult } from '../lib/session/digest.js';
|
|
@@ -23,6 +25,32 @@ import { classifyFileChanges, changeCounts, toolHistogram, detectTestResult } fr
|
|
|
23
25
|
function remoteMachineOf(session) {
|
|
24
26
|
return session._remote ? session.machine : undefined;
|
|
25
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
|
+
}
|
|
26
54
|
/**
|
|
27
55
|
* SessionMeta originates in discover.ts (gitBranch, cwd, label, etc. read from
|
|
28
56
|
* untrusted session files). parseSession sanitizes event payloads at its
|
|
@@ -31,6 +59,17 @@ function remoteMachineOf(session) {
|
|
|
31
59
|
*/
|
|
32
60
|
function sanitizeMeta(s) {
|
|
33
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;
|
|
34
73
|
return {
|
|
35
74
|
...s,
|
|
36
75
|
id: sanitizeForTerminal(s.id),
|
|
@@ -45,6 +84,7 @@ function sanitizeMeta(s) {
|
|
|
45
84
|
label: clean(s.label),
|
|
46
85
|
ticketId: clean(s.ticketId),
|
|
47
86
|
prUrl: clean(s.prUrl),
|
|
87
|
+
todos,
|
|
48
88
|
};
|
|
49
89
|
}
|
|
50
90
|
const previewCache = new Map();
|
|
@@ -62,7 +102,8 @@ export function buildPreview(session) {
|
|
|
62
102
|
if (remote) {
|
|
63
103
|
const note = ' ' + chalk.gray(`on `) + chalk.bold.white(remote)
|
|
64
104
|
+ chalk.gray(` — enter to resume there, or space then enter to read it over SSH`);
|
|
65
|
-
const
|
|
105
|
+
const metaBody = formatMetaOnlyBody(safe);
|
|
106
|
+
const output = [formatHeader(safe, []), '', note, metaBody].filter(Boolean).join('\n');
|
|
66
107
|
previewCache.set(cacheKey, output);
|
|
67
108
|
return output;
|
|
68
109
|
}
|
|
@@ -70,7 +111,8 @@ export function buildPreview(session) {
|
|
|
70
111
|
// entry (e.g. `sessions go`). Show the header + a clean note, not a parse error.
|
|
71
112
|
if (!session.filePath || !fs.existsSync(session.filePath)) {
|
|
72
113
|
const note = ' ' + chalk.gray('Live session — full transcript not indexed here.');
|
|
73
|
-
const
|
|
114
|
+
const metaBody = formatMetaOnlyBody(safe);
|
|
115
|
+
const output = [formatHeader(safe, []), '', note, metaBody].filter(Boolean).join('\n');
|
|
74
116
|
previewCache.set(cacheKey, output);
|
|
75
117
|
return output;
|
|
76
118
|
}
|
|
@@ -99,19 +141,27 @@ function formatHeader(session, events) {
|
|
|
99
141
|
const { startedAgo, duration } = extractTiming(events);
|
|
100
142
|
const totalMessages = session.messageCount ?? countMessages(events);
|
|
101
143
|
const totalTokens = session.tokenCount;
|
|
102
|
-
// Line 1: Agent v version · model · account
|
|
144
|
+
// Line 1: Agent v version · shortId · model · account
|
|
103
145
|
const line1 = [];
|
|
104
146
|
line1.push(chalk.gray(`${displayAgent(session.agent)}${session.version ? ` v${session.version}` : ''}`));
|
|
147
|
+
if (session.shortId)
|
|
148
|
+
line1.push(chalk.dim(session.shortId));
|
|
105
149
|
if (model)
|
|
106
150
|
line1.push(chalk.bold.white(model));
|
|
107
151
|
if (session.account)
|
|
108
152
|
line1.push(chalk.gray(session.account));
|
|
109
|
-
// 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.
|
|
110
156
|
const line2 = [];
|
|
111
157
|
if (session.cwd) {
|
|
112
158
|
const label = relativeToCwd(session.cwd);
|
|
113
159
|
line2.push(chalk.bold.white(linkPath(session.cwd, label)));
|
|
114
160
|
}
|
|
161
|
+
if (session.project) {
|
|
162
|
+
const repoUrl = githubRepoUrlFromCwd(session.cwd);
|
|
163
|
+
line2.push(chalk.cyan(repoUrl ? linkUrl(repoUrl, session.project) : session.project));
|
|
164
|
+
}
|
|
115
165
|
if (session.gitBranch)
|
|
116
166
|
line2.push(chalk.cyan(session.gitBranch));
|
|
117
167
|
if (startedAgo)
|
|
@@ -128,7 +178,10 @@ function formatHeader(session, events) {
|
|
|
128
178
|
}
|
|
129
179
|
if (session.label)
|
|
130
180
|
line3.push(chalk.white(session.label));
|
|
131
|
-
|
|
181
|
+
if (session.filePath)
|
|
182
|
+
line3.push(chalk.gray(linkPath(session.filePath, session.id)));
|
|
183
|
+
else
|
|
184
|
+
line3.push(chalk.gray(session.id));
|
|
132
185
|
// Line 4: ticket + PR — clickable when a URL is resolvable (OSC 8 hyperlink),
|
|
133
186
|
// plain text otherwise. Only rendered when the session carries either.
|
|
134
187
|
const line4 = [];
|
|
@@ -147,6 +200,24 @@ function formatHeader(session, events) {
|
|
|
147
200
|
...(line4.length ? [line4.join(DOT)] : []),
|
|
148
201
|
].join('\n');
|
|
149
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
|
+
}
|
|
150
221
|
function extractModel(events) {
|
|
151
222
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
152
223
|
const m = events[i].model;
|
|
@@ -204,6 +275,7 @@ function stripTags(text) {
|
|
|
204
275
|
const LAST_RESPONSE_MAX_LINES = 15;
|
|
205
276
|
const LAST_RESPONSE_MAX_LINES_WITH_TODOS = 8;
|
|
206
277
|
const TODOS_MAX_ITEMS = 5;
|
|
278
|
+
const DIRS_TOUCHED_MAX = 5;
|
|
207
279
|
function formatCompactPreview(events, session) {
|
|
208
280
|
let firstUser = '';
|
|
209
281
|
let lastAssistant = '';
|
|
@@ -211,7 +283,8 @@ function formatCompactPreview(events, session) {
|
|
|
211
283
|
const toolCounts = {};
|
|
212
284
|
let toolCalls = 0;
|
|
213
285
|
let planFile = '';
|
|
214
|
-
|
|
286
|
+
/** Latest checklist from the transcript (Claude TodoWrite / Codex update_plan). */
|
|
287
|
+
let latestTodos;
|
|
215
288
|
for (const event of events) {
|
|
216
289
|
if (event.type === 'message') {
|
|
217
290
|
if (event.role === 'user' && !firstUser && event.content) {
|
|
@@ -232,25 +305,53 @@ function formatCompactPreview(events, session) {
|
|
|
232
305
|
if (!planFile && p && /\/plans\/[^/]+\.md$/.test(p)) {
|
|
233
306
|
planFile = p;
|
|
234
307
|
}
|
|
235
|
-
|
|
236
|
-
|
|
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;
|
|
237
314
|
}
|
|
238
315
|
if (tool)
|
|
239
316
|
toolCounts[tool] = (toolCounts[tool] ?? 0) + 1;
|
|
240
317
|
toolCalls++;
|
|
241
318
|
}
|
|
242
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;
|
|
243
324
|
// Digest signals folded into the preview: change lifecycle, tool mix, tests.
|
|
244
325
|
const changes = classifyFileChanges(events);
|
|
245
326
|
const chg = changeCounts(changes);
|
|
246
327
|
const lines = [];
|
|
247
328
|
const termWidth = process.stdout.columns || 80;
|
|
329
|
+
// Originating user prompt (first non-system user turn).
|
|
248
330
|
if (firstUser) {
|
|
249
331
|
const first = extractSessionTopic(firstUser) || cleanSessionPrompt(firstUser).split('\n').find(l => l.trim()) || '';
|
|
250
332
|
if (first) {
|
|
251
333
|
lines.push(chalk.cyan('Prompt: ') + chalk.white(truncate(first.trim(), termWidth - 12)));
|
|
252
334
|
}
|
|
253
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
|
+
}
|
|
254
355
|
const activity = [];
|
|
255
356
|
const changed = chg.created + chg.modified + chg.deleted;
|
|
256
357
|
if (changed) {
|
|
@@ -287,14 +388,8 @@ function formatCompactPreview(events, session) {
|
|
|
287
388
|
const basename = planFile.split('/').pop() || planFile;
|
|
288
389
|
lines.push(chalk.cyan('Plan: ') + chalk.white(linkPath(planFile, basename)));
|
|
289
390
|
}
|
|
290
|
-
const todosRendered = latestTodos ? renderTodos(latestTodos, termWidth) : [];
|
|
291
|
-
if (todosRendered.length > 0) {
|
|
292
|
-
lines.push(chalk.cyan('Todos:'));
|
|
293
|
-
for (const l of todosRendered)
|
|
294
|
-
lines.push(' ' + l);
|
|
295
|
-
}
|
|
296
391
|
if (lastAssistant) {
|
|
297
|
-
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;
|
|
298
393
|
const rendered = renderLastResponse(lastAssistant, maxLines);
|
|
299
394
|
if (rendered.length > 0) {
|
|
300
395
|
lines.push('');
|
|
@@ -308,6 +403,64 @@ function formatCompactPreview(events, session) {
|
|
|
308
403
|
}
|
|
309
404
|
return lines.map(l => ' ' + l).join('\n');
|
|
310
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
|
+
}
|
|
311
464
|
function renderLastResponse(content, maxLines = LAST_RESPONSE_MAX_LINES) {
|
|
312
465
|
const cleaned = stripTags(content).trim();
|
|
313
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.
|
|
@@ -175,6 +187,12 @@ export declare function renderSessionLog(session: SessionMeta, mode?: ViewMode):
|
|
|
175
187
|
*/
|
|
176
188
|
export declare function renderSessionLogJson(session: SessionMeta): Promise<void>;
|
|
177
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
|
+
}
|
|
178
196
|
export interface PickerColumns {
|
|
179
197
|
/** Render the machine column (only when the pool spans more than one machine). */
|
|
180
198
|
showMachine?: boolean;
|
|
@@ -205,7 +223,7 @@ export declare function machineLabeler(machines: string[]): (m: string) => strin
|
|
|
205
223
|
* than one box, the ticket column only when some row carries a PR/ticket ref.
|
|
206
224
|
*/
|
|
207
225
|
export declare function pickerColumnsFor(sessions: SessionMeta[]): PickerColumns;
|
|
208
|
-
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;
|
|
209
227
|
/**
|
|
210
228
|
* Pick a hint to show above the picker. Deterministic (keys off the pool size)
|
|
211
229
|
* so it stays fixed across the picker's re-renders within a single run.
|
|
@@ -259,9 +277,10 @@ export interface SessionQueryResolution {
|
|
|
259
277
|
/**
|
|
260
278
|
* The single entry point for turning a `sessions <query>` argument into rows.
|
|
261
279
|
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
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.
|
|
265
284
|
*/
|
|
266
285
|
export declare function resolveSessionQuery(pool: SessionMeta[], query: string): SessionQueryResolution;
|
|
267
286
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|