@phnx-labs/agents-cli 1.20.77 → 1.20.82
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 +589 -0
- package/README.md +27 -25
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +9 -0
- package/dist/commands/activity.js +153 -24
- package/dist/commands/apply.js +11 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/commands.d.ts +1 -1
- package/dist/commands/commands.js +7 -6
- package/dist/commands/defaults.js +3 -11
- package/dist/commands/doctor.d.ts +74 -0
- package/dist/commands/doctor.js +718 -40
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -8
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +87 -11
- package/dist/commands/harness.js +2 -2
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +8 -4
- package/dist/commands/logs.js +6 -0
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -6
- package/dist/commands/permissions.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +4 -4
- package/dist/commands/repo.d.ts +78 -0
- package/dist/commands/repo.js +195 -11
- package/dist/commands/routines.js +16 -4
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +108 -34
- package/dist/commands/sessions-browser.d.ts +45 -3
- package/dist/commands/sessions-browser.js +118 -12
- package/dist/commands/sessions-export.d.ts +3 -0
- package/dist/commands/sessions-export.js +13 -10
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.d.ts +15 -1
- package/dist/commands/sessions-picker.js +90 -7
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +126 -1
- package/dist/commands/sessions.js +365 -42
- package/dist/commands/set.d.ts +15 -0
- package/dist/commands/set.js +79 -0
- package/dist/commands/setup-fleet.d.ts +21 -0
- package/dist/commands/setup-fleet.js +201 -0
- package/dist/commands/setup-secrets.d.ts +20 -0
- package/dist/commands/setup-secrets.js +223 -0
- package/dist/commands/setup.js +17 -3
- package/dist/commands/skills.js +1 -1
- package/dist/commands/ssh.js +9 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/teams-picker.js +0 -1
- package/dist/commands/teams.d.ts +33 -1
- package/dist/commands/teams.js +208 -44
- package/dist/commands/versions.js +10 -5
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +11 -0
- package/dist/index.js +8 -13
- package/dist/lib/acp/harnesses.js +0 -7
- package/dist/lib/activity.d.ts +156 -0
- package/dist/lib/activity.js +282 -0
- package/dist/lib/agents.d.ts +9 -23
- package/dist/lib/agents.js +72 -100
- package/dist/lib/auto-pull.d.ts +16 -8
- package/dist/lib/auto-pull.js +23 -28
- package/dist/lib/browser/service.js +3 -0
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/capabilities.js +6 -2
- package/dist/lib/crabbox/lease.js +2 -2
- package/dist/lib/crabbox/setup-copy.d.ts +4 -4
- package/dist/lib/crabbox/setup-copy.js +4 -4
- package/dist/lib/daemon.d.ts +26 -40
- package/dist/lib/daemon.js +63 -123
- package/dist/lib/devices/fleet-divergence.d.ts +101 -0
- package/dist/lib/devices/fleet-divergence.js +188 -0
- package/dist/lib/devices/fleet-inventory.d.ts +19 -0
- package/dist/lib/devices/fleet-inventory.js +57 -0
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -1
- package/dist/lib/devices/health-report.d.ts +10 -2
- package/dist/lib/devices/health-report.js +32 -1
- package/dist/lib/doctor-diff.d.ts +20 -0
- package/dist/lib/doctor-diff.js +6 -1
- package/dist/lib/drift.d.ts +31 -11
- package/dist/lib/drift.js +58 -7
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +10 -1
- package/dist/lib/exec.js +35 -8
- package/dist/lib/feed-post.d.ts +28 -1
- package/dist/lib/feed-post.js +110 -2
- package/dist/lib/fleet/apply.js +8 -1
- package/dist/lib/fleet/auth-sync.d.ts +18 -1
- package/dist/lib/fleet/auth-sync.js +25 -11
- package/dist/lib/fleet/remote-login.js +5 -0
- package/dist/lib/git.d.ts +27 -14
- package/dist/lib/git.js +138 -43
- package/dist/lib/hooks.d.ts +42 -0
- package/dist/lib/hooks.js +137 -93
- package/dist/lib/hosts/dispatch.d.ts +28 -1
- package/dist/lib/hosts/dispatch.js +33 -5
- package/dist/lib/hosts/option.js +2 -2
- package/dist/lib/hosts/passthrough.d.ts +22 -1
- package/dist/lib/hosts/passthrough.js +238 -13
- package/dist/lib/hosts/remote-cmd.js +2 -0
- package/dist/lib/hosts/run-target.d.ts +2 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/hq/floor.d.ts +1 -1
- package/dist/lib/hq/floor.js +6 -1
- package/dist/lib/mcp.js +3 -71
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +2 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
- package/dist/lib/menubar/notify-desktop.d.ts +15 -0
- package/dist/lib/menubar/notify-desktop.js +41 -5
- package/dist/lib/merged-resources.d.ts +11 -0
- package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
- package/dist/lib/permissions.d.ts +0 -32
- package/dist/lib/permissions.js +4 -157
- package/dist/lib/picker.js +1 -1
- package/dist/lib/platform/process.d.ts +16 -5
- package/dist/lib/platform/process.js +54 -0
- package/dist/lib/plugins.d.ts +0 -8
- package/dist/lib/plugins.js +4 -110
- package/dist/lib/project-resources.js +4 -1
- package/dist/lib/refresh.d.ts +5 -3
- package/dist/lib/refresh.js +7 -5
- package/dist/lib/resources/commands.js +4 -1
- package/dist/lib/resources/mcp.js +0 -4
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +1 -5
- package/dist/lib/resources/rules.js +4 -1
- package/dist/lib/resources/skills.js +4 -1
- package/dist/lib/resources/subagents.js +4 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +20 -0
- package/dist/lib/routines.js +6 -0
- package/dist/lib/run-defaults.d.ts +1 -0
- package/dist/lib/run-defaults.js +9 -0
- package/dist/lib/runner.d.ts +0 -12
- package/dist/lib/runner.js +71 -29
- package/dist/lib/sandbox.js +0 -16
- 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/secrets/agent.d.ts +18 -0
- package/dist/lib/secrets/agent.js +131 -17
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +12 -9
- package/dist/lib/secrets/bundles.js +47 -31
- package/dist/lib/secrets/index.d.ts +2 -1
- package/dist/lib/secrets/index.js +8 -2
- package/dist/lib/secrets/remote.d.ts +75 -2
- package/dist/lib/secrets/remote.js +139 -5
- package/dist/lib/secrets/scope.d.ts +26 -0
- package/dist/lib/secrets/scope.js +29 -0
- package/dist/lib/secrets/session-store.d.ts +10 -0
- package/dist/lib/secrets/session-store.js +64 -11
- package/dist/lib/secrets/sync.js +3 -3
- package/dist/lib/session/active.d.ts +117 -26
- package/dist/lib/session/active.js +199 -53
- package/dist/lib/session/actor-sidecar.d.ts +23 -0
- package/dist/lib/session/actor-sidecar.js +101 -0
- package/dist/lib/session/db.d.ts +41 -1
- package/dist/lib/session/db.js +173 -8
- package/dist/lib/session/digest.js +126 -21
- package/dist/lib/session/discover.d.ts +19 -2
- package/dist/lib/session/discover.js +32 -11
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- package/dist/lib/session/origin-machine.d.ts +18 -0
- package/dist/lib/session/origin-machine.js +34 -0
- package/dist/lib/session/pid-registry.d.ts +13 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/state.d.ts +1 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/session/types.d.ts +16 -0
- package/dist/lib/smart-launch.d.ts +86 -0
- package/dist/lib/smart-launch.js +172 -0
- package/dist/lib/staleness/detectors/commands.js +2 -2
- package/dist/lib/staleness/detectors/permissions.js +1 -45
- package/dist/lib/staleness/writers/commands.js +4 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -2
- package/dist/lib/startup/command-registry.js +2 -4
- package/dist/lib/subagents-registry.js +5 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -12
- package/dist/lib/teams/agents.d.ts +0 -2
- package/dist/lib/teams/agents.js +10 -27
- package/dist/lib/teams/index.d.ts +1 -1
- package/dist/lib/teams/index.js +1 -1
- package/dist/lib/types.d.ts +17 -9
- package/dist/lib/usage.d.ts +28 -5
- package/dist/lib/usage.js +271 -8
- package/dist/lib/versions.js +7 -1
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +25 -13
- package/package.json +1 -2
- package/dist/commands/check.d.ts +0 -15
- package/dist/commands/check.js +0 -180
- package/dist/commands/resources.d.ts +0 -5
- package/dist/lib/secrets/account-token.d.ts +0 -20
- package/dist/lib/secrets/account-token.js +0 -64
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import * as fs from 'fs';
|
|
21
21
|
import * as path from 'path';
|
|
22
22
|
import chalk from 'chalk';
|
|
23
|
-
import { discoverSessions, resolveSessionById,
|
|
23
|
+
import { discoverSessions, resolveSessionById, looksLikeSessionId } from '../lib/session/discover.js';
|
|
24
24
|
import { findSessionsById } from '../lib/session/db.js';
|
|
25
25
|
import { filterSessionsByQuery, parseAgentFilter } from './sessions.js';
|
|
26
26
|
import { listLocalTranscripts, SYNC_AGENTS } from '../lib/session/sync/agents.js';
|
|
@@ -222,8 +222,8 @@ function resolveAgentShorthand(g) {
|
|
|
222
222
|
return 'antigravity';
|
|
223
223
|
return undefined;
|
|
224
224
|
}
|
|
225
|
-
/** ids > query > everything-in-scope. */
|
|
226
|
-
function selectSessions(metas, selectors) {
|
|
225
|
+
/** ids > query > everything-in-scope. Exported for the id-resolution test. */
|
|
226
|
+
export function selectSessions(metas, selectors) {
|
|
227
227
|
if (selectors.length === 0)
|
|
228
228
|
return metas;
|
|
229
229
|
const byId = [];
|
|
@@ -231,9 +231,11 @@ function selectSessions(metas, selectors) {
|
|
|
231
231
|
for (const sel of selectors) {
|
|
232
232
|
const trimmed = sel.trim();
|
|
233
233
|
// Same reason as resolveSessionQuery: the discovered pool is a minority of
|
|
234
|
-
// the index, so
|
|
234
|
+
// the index, so an id-shaped selector absent from it may still be indexed
|
|
235
|
+
// here. Gate on looksLikeSessionId, not isCompleteSessionId, so a SHORT id
|
|
236
|
+
// ("d3470b57") also resolves through the index instead of falling to content.
|
|
235
237
|
const hits = resolveSessionById(metas, trimmed);
|
|
236
|
-
const resolved = hits.length > 0 || !
|
|
238
|
+
const resolved = hits.length > 0 || !looksLikeSessionId(trimmed) ? hits : findSessionsById(trimmed);
|
|
237
239
|
if (resolved.length > 0)
|
|
238
240
|
byId.push(...resolved);
|
|
239
241
|
else
|
|
@@ -243,11 +245,12 @@ function selectSessions(metas, selectors) {
|
|
|
243
245
|
const seen = new Set();
|
|
244
246
|
return byId.filter(s => (seen.has(s.id) ? false : (seen.add(s.id), true)));
|
|
245
247
|
}
|
|
246
|
-
// A selector that is a
|
|
247
|
-
// the id is unique, so the text query below could only bundle
|
|
248
|
-
// merely mention it. Bundling those would ship unrelated
|
|
249
|
-
// whoever receives the export, so select nothing and let the
|
|
250
|
-
|
|
248
|
+
// A selector that is an id (complete OR a short hex id/prefix) and still missed
|
|
249
|
+
// cannot be widened: the id is unique, so the text query below could only bundle
|
|
250
|
+
// sessions that merely mention it. Bundling those would ship unrelated
|
|
251
|
+
// transcripts to whoever receives the export, so select nothing and let the
|
|
252
|
+
// caller report it — a short id like "d3470b57" must never content-search.
|
|
253
|
+
const missingIds = unmatched.filter(looksLikeSessionId);
|
|
251
254
|
if (missingIds.length > 0) {
|
|
252
255
|
process.stderr.write(chalk.red(`No session with id ${missingIds.join(', ')} on this machine.\n`));
|
|
253
256
|
return [];
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* (`injectIntoTerminal`, src/lib/terminal/inject.ts), so a native watchdog
|
|
5
5
|
* (RUSH-1415) can shell out to nudge a stalled agent with "continue".
|
|
6
6
|
*
|
|
7
|
-
* Resolution: find the active session by id,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Resolution: find the active session by id, then resolve its exact split through
|
|
8
|
+
* the SAME canonical resolver the watchdog uses — `resolveInjectTargetForSession`
|
|
9
|
+
* (lib/terminal/resolve.ts), precedence tmux > iterm > vscodium > pty. Sharing one
|
|
10
|
+
* resolver keeps the manual unblock path and the watchdog in agreement on which
|
|
11
|
+
* sessions are addressable (a prior duplicate resolver read only `provenance.reply`
|
|
12
|
+
* and could not address a VSCodium/Cursor terminal the watchdog handled fine).
|
|
13
|
+
* `--pane`/`--pty` target a backend directly when the handle is already known.
|
|
11
14
|
*/
|
|
12
15
|
import type { Command } from 'commander';
|
|
13
16
|
/** Attach the `inject` subcommand to an existing `sessions` command. */
|
|
@@ -4,33 +4,32 @@
|
|
|
4
4
|
* (`injectIntoTerminal`, src/lib/terminal/inject.ts), so a native watchdog
|
|
5
5
|
* (RUSH-1415) can shell out to nudge a stalled agent with "continue".
|
|
6
6
|
*
|
|
7
|
-
* Resolution: find the active session by id,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Resolution: find the active session by id, then resolve its exact split through
|
|
8
|
+
* the SAME canonical resolver the watchdog uses — `resolveInjectTargetForSession`
|
|
9
|
+
* (lib/terminal/resolve.ts), precedence tmux > iterm > vscodium > pty. Sharing one
|
|
10
|
+
* resolver keeps the manual unblock path and the watchdog in agreement on which
|
|
11
|
+
* sessions are addressable (a prior duplicate resolver read only `provenance.reply`
|
|
12
|
+
* and could not address a VSCodium/Cursor terminal the watchdog handled fine).
|
|
13
|
+
* `--pane`/`--pty` target a backend directly when the handle is already known.
|
|
11
14
|
*/
|
|
12
15
|
import chalk from 'chalk';
|
|
13
16
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
14
|
-
import {
|
|
17
|
+
import { resolveInjectTargetForSession } from '../lib/terminal/resolve.js';
|
|
15
18
|
import { injectIntoTerminal } from '../lib/terminal/index.js';
|
|
16
19
|
import { setHelpSections } from '../lib/help.js';
|
|
17
|
-
/** Resolve a session id (short or full) to an addressable terminal target
|
|
20
|
+
/** Resolve a session id (short or full) to an addressable terminal target, via the
|
|
21
|
+
* same resolver the watchdog uses so both agree on what is reachable. */
|
|
18
22
|
async function resolveTarget(sessionId) {
|
|
19
23
|
const sessions = await getActiveSessions();
|
|
20
24
|
const match = sessions.find((s) => s.sessionId === sessionId || (s.sessionId != null && s.sessionId.startsWith(sessionId)));
|
|
21
25
|
if (!match)
|
|
22
26
|
return { target: null, reason: `No active session matches "${sessionId}".` };
|
|
23
|
-
const
|
|
24
|
-
if (!
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
reason: `Session "${sessionId}" has no addressable reply rail (not running under tmux). Relaunch it under a tmux/pty rail to inject.`,
|
|
28
|
-
};
|
|
27
|
+
const resolution = resolveInjectTargetForSession(match);
|
|
28
|
+
if (!resolution.addressable) {
|
|
29
|
+
// Surface the resolver's precise reason (host/rail), not a generic "not tmux".
|
|
30
|
+
return { target: null, reason: `Session "${sessionId}": ${resolution.reason}` };
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
if (!target)
|
|
32
|
-
return { target: null, reason: `Session "${sessionId}" reply rail is not injectable.` };
|
|
33
|
-
return { target };
|
|
32
|
+
return { target: resolution.target };
|
|
34
33
|
}
|
|
35
34
|
async function runInject(sessionId, text, options) {
|
|
36
35
|
// Direct-target shortcuts skip the session lookup — the watchdog often already
|
|
@@ -97,8 +96,9 @@ export function registerSessionsInjectCommand(sessionsCmd) {
|
|
|
97
96
|
notes: `
|
|
98
97
|
- Ink-TUI Enter semantics: by default the text and Enter are two separate
|
|
99
98
|
writes, which is what Claude's Ink TUI needs. --combined fuses them.
|
|
100
|
-
-
|
|
101
|
-
|
|
99
|
+
- A session is addressable by id when it resolves to a precise split —
|
|
100
|
+
tmux, iTerm, a VSCodium/Cursor/VS Code integrated terminal, or a pty
|
|
101
|
+
(resolveInjectTargetForSession). Use --pane/--pty for direct targeting.
|
|
102
102
|
- Built on the Terminal Engine (src/lib/terminal): --host runs the tmux /
|
|
103
103
|
AppleScript spec over the same SSH transport the launch engine uses.
|
|
104
104
|
`,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { SessionMeta, TodoProgress } from '../lib/session/types.js';
|
|
1
|
+
import type { SessionEvent, SessionMeta, TodoProgress } from '../lib/session/types.js';
|
|
2
|
+
import { classifyFileChanges } from '../lib/session/digest.js';
|
|
2
3
|
/**
|
|
3
4
|
* Compact checklist tally for list rows and previews (RUSH-2045).
|
|
4
5
|
* Example: `✓6/8 · A5 wiring runner`. Empty string when there is no list.
|
|
@@ -30,5 +31,18 @@ export interface SessionPickerConfig {
|
|
|
30
31
|
}
|
|
31
32
|
/** Build a cached multi-line preview string for display in the session picker. */
|
|
32
33
|
export declare function buildPreview(session: SessionMeta): string;
|
|
34
|
+
/** Optional dirs-touched field the parser teammate may attach; we prefer it. */
|
|
35
|
+
type SessionMetaWithDirs = SessionMeta & {
|
|
36
|
+
dirsTouched?: string[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Unique directories the session touched, compact and human-readable.
|
|
40
|
+
* Prefer `session.dirsTouched` when the parser teammate has populated it;
|
|
41
|
+
* otherwise derive from file-change + tool paths already available here.
|
|
42
|
+
*/
|
|
43
|
+
export declare function directoriesTouched(session: SessionMetaWithDirs, events: SessionEvent[], changes: ReturnType<typeof classifyFileChanges>): string[];
|
|
44
|
+
/** Relativize a file path to its parent dir, short enough for one preview line. */
|
|
45
|
+
export declare function relativizeDir(filePath: string, cwd?: string): string | undefined;
|
|
33
46
|
/** Show an interactive session picker and return the selected session with its action (resume or view). */
|
|
34
47
|
export declare function sessionPicker(config: SessionPickerConfig): Promise<PickedSession | null>;
|
|
48
|
+
export {};
|
|
@@ -11,9 +11,9 @@ import chalk from 'chalk';
|
|
|
11
11
|
import { truncate, humanDuration } from '../lib/format.js';
|
|
12
12
|
import { parseSession, sanitizeForTerminal } from '../lib/session/parse.js';
|
|
13
13
|
import { cleanSessionPrompt, extractSessionTopic } from '../lib/session/prompt.js';
|
|
14
|
-
import { linkPath, linkUrl, relativeToCwd } from '../lib/session/render.js';
|
|
14
|
+
import { linkPath, linkUrl, relativeToCwd, shortenModel } from '../lib/session/render.js';
|
|
15
15
|
import { linearIssueUrl } from '../lib/session/linear.js';
|
|
16
|
-
import { extractTodoProgress } from '../lib/session/state.js';
|
|
16
|
+
import { extractTodoProgress, WORKTREE_RE } from '../lib/session/state.js';
|
|
17
17
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
18
18
|
import { itemPicker } from '../lib/picker.js';
|
|
19
19
|
import { classifyFileChanges, changeCounts, toolHistogram, detectTestResult } from '../lib/session/digest.js';
|
|
@@ -137,7 +137,7 @@ function displayAgent(agent) {
|
|
|
137
137
|
}
|
|
138
138
|
const DOT = chalk.gray(' · ');
|
|
139
139
|
function formatHeader(session, events) {
|
|
140
|
-
const model = extractModel(events);
|
|
140
|
+
const model = extractModel(events) || session.model;
|
|
141
141
|
const { startedAgo, duration } = extractTiming(events);
|
|
142
142
|
const totalMessages = session.messageCount ?? countMessages(events);
|
|
143
143
|
const totalTokens = session.tokenCount;
|
|
@@ -147,7 +147,7 @@ function formatHeader(session, events) {
|
|
|
147
147
|
if (session.shortId)
|
|
148
148
|
line1.push(chalk.dim(session.shortId));
|
|
149
149
|
if (model)
|
|
150
|
-
line1.push(chalk.bold.white(model));
|
|
150
|
+
line1.push(chalk.bold.white(shortenModel(model)));
|
|
151
151
|
if (session.account)
|
|
152
152
|
line1.push(chalk.gray(session.account));
|
|
153
153
|
// Line 2: cwd · project · branch · started X ago · lasted Y
|
|
@@ -285,6 +285,8 @@ function formatCompactPreview(events, session) {
|
|
|
285
285
|
let planFile = '';
|
|
286
286
|
/** Latest checklist from the transcript (Claude TodoWrite / Codex update_plan). */
|
|
287
287
|
let latestTodos;
|
|
288
|
+
let subAgentCount = 0;
|
|
289
|
+
const toolTags = new Set();
|
|
288
290
|
for (const event of events) {
|
|
289
291
|
if (event.type === 'message') {
|
|
290
292
|
if (event.role === 'user' && !firstUser && event.content) {
|
|
@@ -298,6 +300,11 @@ function formatCompactPreview(events, session) {
|
|
|
298
300
|
}
|
|
299
301
|
else if (event.type === 'tool_use' && !event._local) {
|
|
300
302
|
const tool = event.tool || '';
|
|
303
|
+
const command = event.command || '';
|
|
304
|
+
for (const tag of classifySessionTool(tool, command))
|
|
305
|
+
toolTags.add(tag);
|
|
306
|
+
if (isSubAgentTool(tool, command))
|
|
307
|
+
subAgentCount++;
|
|
301
308
|
const p = event.path || event.args?.file_path || event.args?.path || '';
|
|
302
309
|
if (['Read', 'read_file', 'view_file', 'cat_file', 'get_file'].includes(tool) && p) {
|
|
303
310
|
filesRead.add(p);
|
|
@@ -369,6 +376,13 @@ function formatCompactPreview(events, session) {
|
|
|
369
376
|
if (activity.length) {
|
|
370
377
|
lines.push(chalk.cyan('Changes: ') + activity.join(chalk.gray(' · ')));
|
|
371
378
|
}
|
|
379
|
+
const metadata = [
|
|
380
|
+
...toolTags,
|
|
381
|
+
subAgentCount ? `${subAgentCount} sub-agent${subAgentCount === 1 ? '' : 's'}` : '',
|
|
382
|
+
].filter(Boolean);
|
|
383
|
+
if (metadata.length) {
|
|
384
|
+
lines.push(chalk.cyan('Meta: ') + chalk.gray(metadata.join(' · ')));
|
|
385
|
+
}
|
|
372
386
|
// Tool mix (top 4) — what kind of work this was.
|
|
373
387
|
const hist = toolHistogram(toolCounts, 4);
|
|
374
388
|
if (hist.length) {
|
|
@@ -403,12 +417,27 @@ function formatCompactPreview(events, session) {
|
|
|
403
417
|
}
|
|
404
418
|
return lines.map(l => ' ' + l).join('\n');
|
|
405
419
|
}
|
|
420
|
+
function classifySessionTool(tool, command) {
|
|
421
|
+
const toolName = tool.toLowerCase();
|
|
422
|
+
const commandUses = (surface) => (new RegExp(`\\b(?:agents|ag)\\b[^\\n;&|]*\\b${surface}\\b`).test(command.toLowerCase()));
|
|
423
|
+
const tags = [];
|
|
424
|
+
if (/browser|webfetch|websearch/.test(toolName) || commandUses('browser'))
|
|
425
|
+
tags.push('browser');
|
|
426
|
+
if (/computer/.test(toolName) || commandUses('computer'))
|
|
427
|
+
tags.push('computer');
|
|
428
|
+
return tags;
|
|
429
|
+
}
|
|
430
|
+
function isSubAgentTool(tool, command) {
|
|
431
|
+
if (/^(Agent|Task)$/i.test(tool))
|
|
432
|
+
return true;
|
|
433
|
+
return /\b(?:agents|ag)\b[^\n;&|]*\b(?:run|cloud\s+run|teams\s+(?:add|start))\b/.test(command);
|
|
434
|
+
}
|
|
406
435
|
/**
|
|
407
436
|
* Unique directories the session touched, compact and human-readable.
|
|
408
437
|
* Prefer `session.dirsTouched` when the parser teammate has populated it;
|
|
409
438
|
* otherwise derive from file-change + tool paths already available here.
|
|
410
439
|
*/
|
|
411
|
-
function directoriesTouched(session, events, changes) {
|
|
440
|
+
export function directoriesTouched(session, events, changes) {
|
|
412
441
|
const fromMeta = session.dirsTouched;
|
|
413
442
|
if (Array.isArray(fromMeta) && fromMeta.length > 0) {
|
|
414
443
|
return fromMeta
|
|
@@ -437,19 +466,73 @@ function directoriesTouched(session, events, changes) {
|
|
|
437
466
|
.map(([d]) => d)
|
|
438
467
|
.slice(0, DIRS_TOUCHED_MAX);
|
|
439
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Claude names its per-project transcript store `~/.claude/projects/<slug>`, where
|
|
471
|
+
* `<slug>` is the cwd with every `/` AND `.` replaced by `-` — so `/Users/me/app`
|
|
472
|
+
* → `-Users-me-app` and `.agents/worktrees/x` → `--agents-worktrees-x`. That slug
|
|
473
|
+
* leaks into transcript paths as a leading segment (`<slug>/<session-id>/…`).
|
|
474
|
+
*
|
|
475
|
+
* The encoding is LOSSY and irreversible (`-`, `/`, and `.` all collapse to `-`),
|
|
476
|
+
* so we never try to decode it back to a path. Instead we ENCODE the comparison
|
|
477
|
+
* targets (cwd, the worktree marker) into the same slug space and match there. See
|
|
478
|
+
* {@link relativizeDir}. `encodeClaudeSlug` mirrors Claude's own transform.
|
|
479
|
+
*/
|
|
480
|
+
function encodeClaudeSlug(absPath) {
|
|
481
|
+
return absPath.replace(/[/.]/g, '-');
|
|
482
|
+
}
|
|
483
|
+
/** The `.agents/worktrees/<name>` marker, Claude-slug-encoded (`/.` → `--`). */
|
|
484
|
+
const SLUG_WORKTREE_RE = /--agents-worktrees-(.+)$/;
|
|
440
485
|
/** Relativize a file path to its parent dir, short enough for one preview line. */
|
|
441
|
-
function relativizeDir(filePath, cwd) {
|
|
486
|
+
export function relativizeDir(filePath, cwd) {
|
|
442
487
|
const norm = filePath.replace(/\\/g, '/');
|
|
443
488
|
if (!norm || norm.includes('node_modules') || norm.includes('/.git/') || norm.includes('/plans/')) {
|
|
444
489
|
return undefined;
|
|
445
490
|
}
|
|
446
491
|
let dir = path.posix.dirname(norm);
|
|
492
|
+
// Claude project-slug form: a leading `-`-segment (`-home-me-…`) that carries
|
|
493
|
+
// the cwd, lossily encoded. Handle it in SLUG SPACE — never lossy-decode to a
|
|
494
|
+
// fake path. The slug is the first path segment; any real `/`-subdirs after it
|
|
495
|
+
// are Claude's internal storage (`<session-id>/scratchpad|tasks`), not code.
|
|
496
|
+
if (dir.startsWith('-')) {
|
|
497
|
+
const slash = dir.indexOf('/');
|
|
498
|
+
const slug = slash === -1 ? dir : dir.slice(0, slash);
|
|
499
|
+
// CWD FIRST: if the slug is (or is under) this session's own cwd, the leaked
|
|
500
|
+
// path is Claude's internal projects-storage scratch (`<id>/scratchpad`) —
|
|
501
|
+
// not a meaningful code dir — so drop it like node_modules. Precedence over
|
|
502
|
+
// the worktree collapse so a session editing its OWN worktree isn't relabeled.
|
|
503
|
+
if (cwd) {
|
|
504
|
+
const cwdSlug = encodeClaudeSlug(cwd.replace(/\\/g, '/').replace(/\/$/, ''));
|
|
505
|
+
if (slug === cwdSlug || slug.startsWith(cwdSlug + '-'))
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
// Only a DIFFERENT worktree than cwd reaches here: a worktree encodes its
|
|
509
|
+
// `/.agents/worktrees/<name>` marker as `--agents-worktrees-<name>`, so
|
|
510
|
+
// collapse to the worktree name to disambiguate. (The name may contain `-`;
|
|
511
|
+
// we can't losslessly re-split it, so show the whole encoded remainder.)
|
|
512
|
+
const wtSlug = slug.match(SLUG_WORKTREE_RE);
|
|
513
|
+
if (wtSlug)
|
|
514
|
+
return `⧉ ${wtSlug[1]}`;
|
|
515
|
+
// An unattributable slug: don't invent a `/`-joined fake path. Show only the
|
|
516
|
+
// trailing `-`-group as a minimal, honest token.
|
|
517
|
+
const segs = slug.split('-').filter(Boolean);
|
|
518
|
+
return segs.length ? segs[segs.length - 1] : undefined;
|
|
519
|
+
}
|
|
520
|
+
// CWD FIRST for real paths too: strip the session-cwd prefix so a session
|
|
521
|
+
// editing its OWN worktree renders the concise relative remainder (`src/lib`),
|
|
522
|
+
// not the longer `⧉ <slug>/…` collapse.
|
|
447
523
|
if (cwd) {
|
|
448
524
|
const base = cwd.replace(/\\/g, '/').replace(/\/$/, '');
|
|
449
525
|
if (dir === base)
|
|
450
526
|
return '.';
|
|
451
527
|
if (dir.startsWith(base + '/'))
|
|
452
|
-
|
|
528
|
+
return dir.slice(base.length + 1);
|
|
529
|
+
}
|
|
530
|
+
// No cwd match. If the dir is in a (different) git worktree, collapse to the
|
|
531
|
+
// worktree NAME + in-worktree remainder to disambiguate; else home→`~` + trim.
|
|
532
|
+
const wt = dir.match(WORKTREE_RE);
|
|
533
|
+
if (wt) {
|
|
534
|
+
const after = dir.slice(dir.indexOf(wt[0]) + wt[0].length).replace(/^\//, '');
|
|
535
|
+
return after ? `⧉ ${wt[1]}/${after}` : `⧉ ${wt[1]}`;
|
|
453
536
|
}
|
|
454
537
|
// Collapse home prefix.
|
|
455
538
|
const home = (process.env.HOME || '').replace(/\\/g, '/');
|
|
@@ -1,2 +1,18 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
import { type Packing } from '../lib/terminal/index.js';
|
|
3
|
+
interface ResumeOptions {
|
|
4
|
+
agent?: string;
|
|
5
|
+
all?: boolean;
|
|
6
|
+
teams?: boolean;
|
|
7
|
+
since?: string;
|
|
8
|
+
limit?: string;
|
|
9
|
+
host?: string;
|
|
10
|
+
iterm?: boolean;
|
|
11
|
+
ghostty?: boolean;
|
|
12
|
+
tmux?: boolean;
|
|
13
|
+
vscodium?: boolean;
|
|
14
|
+
splits?: boolean;
|
|
15
|
+
}
|
|
2
16
|
export declare function registerSessionsResumeCommand(sessionsCmd: Command): void;
|
|
17
|
+
export declare function resolveResumePacking(options: Pick<ResumeOptions, 'splits'>): Packing;
|
|
18
|
+
export {};
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Unlike the single-select picker behind bare `agents sessions` (which resumes
|
|
6
6
|
* one session in place), this opens a checkbox picker, then asks where the
|
|
7
|
-
* chosen sessions should resume. By default
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* chosen sessions should resume. By default each session opens in its own tab in
|
|
8
|
+
* the terminal you're in — iTerm / Ghostty / tmux, locally or on a remote host
|
|
9
|
+
* via --host. `--splits` opts into packing two sessions side by side per tab.
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import chalk from 'chalk';
|
|
@@ -36,25 +36,24 @@ export function registerSessionsResumeCommand(sessionsCmd) {
|
|
|
36
36
|
.option('--ghostty', 'Force the Ghostty backend')
|
|
37
37
|
.option('--tmux', 'Force the tmux backend')
|
|
38
38
|
.option('--vscodium', 'Force the VSCodium agent-terminal backend (swarm-ext)')
|
|
39
|
-
.option('--
|
|
39
|
+
.option('--splits', 'Pack two sessions side by side per tab (default: one tab per session)');
|
|
40
40
|
setHelpSections(cmd, {
|
|
41
41
|
examples: `
|
|
42
|
-
# Pick several sessions;
|
|
42
|
+
# Pick several sessions; each opens in its own tab
|
|
43
43
|
agents sessions resume
|
|
44
44
|
|
|
45
45
|
# Pre-filter the pool before selecting (space in the filter → use [query])
|
|
46
46
|
agents sessions resume "auth middleware"
|
|
47
47
|
|
|
48
|
-
# Force a backend /
|
|
48
|
+
# Force a backend / side-by-side splits / a remote host
|
|
49
49
|
agents sessions resume --ghostty
|
|
50
50
|
agents sessions resume --vscodium
|
|
51
|
-
agents sessions resume --
|
|
51
|
+
agents sessions resume --splits
|
|
52
52
|
agents sessions resume --host zion --tmux
|
|
53
53
|
`,
|
|
54
54
|
notes: `
|
|
55
55
|
- space toggles a session, enter confirms; tab toggles the preview pane.
|
|
56
|
-
- Layout:
|
|
57
|
-
- VSCodium defaults to one editor tab per session (matches swarm-ext's native agent-terminal UX); the others default to two-per-tab.
|
|
56
|
+
- Layout: one tab per session by default. --splits packs session pairs side by side in each tab.
|
|
58
57
|
- Backend: auto-detected from the terminal you're in (iTerm / Ghostty / tmux); override with --iterm/--ghostty/--tmux/--vscodium.
|
|
59
58
|
- --vscodium opens each session as an agent terminal tab in VSCodium via the swarm-ext extension (works with --host too).
|
|
60
59
|
- --host <alias> resumes on a remote machine over the same SSH transport as 'sessions --host' (defaults to tmux).
|
|
@@ -153,10 +152,9 @@ async function sessionsResumeAction(query, options) {
|
|
|
153
152
|
await resumeSessionInPlace(it.session);
|
|
154
153
|
return;
|
|
155
154
|
}
|
|
156
|
-
// 5b. Fan out through the engine.
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
const packing = options.tabs || backend === 'vscodium-agent' ? 'tabs' : 'two-per-tab';
|
|
155
|
+
// 5b. Fan out through the engine. Full-width tabs are the default for batch
|
|
156
|
+
// recovery; callers can explicitly opt into pairs of side-by-side panes.
|
|
157
|
+
const packing = resolveResumePacking(options);
|
|
160
158
|
const where = options.host ? `${backend} on ${options.host}` : backend;
|
|
161
159
|
console.log(chalk.gray(`Opening ${items.length} session${items.length === 1 ? '' : 's'} in ${where} (${packing})…`));
|
|
162
160
|
const results = await openSurfaces(items.map((it) => ({ cwd: it.cwd, command: it.command })), { backend, host: options.host, packing });
|
|
@@ -174,6 +172,9 @@ async function sessionsResumeAction(query, options) {
|
|
|
174
172
|
});
|
|
175
173
|
console.log(chalk.gray(`\nOpened ${opened}/${items.length} in ${where}.`));
|
|
176
174
|
}
|
|
175
|
+
export function resolveResumePacking(options) {
|
|
176
|
+
return options.splits ? 'two-per-tab' : 'tabs';
|
|
177
|
+
}
|
|
177
178
|
/**
|
|
178
179
|
* Decide which backend to launch into. Returns a concrete backend, `'inplace'`
|
|
179
180
|
* (resume in the current process — no GUI/tmux available), or `'cancel'` (the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { SessionAgentId, SessionMeta, ViewMode } from '../lib/session/types.js';
|
|
3
3
|
import { type ActiveSession } from '../lib/session/active.js';
|
|
4
|
+
import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
|
|
4
5
|
import { type PickedSession } from './sessions-picker.js';
|
|
5
6
|
/**
|
|
6
7
|
* Strip terminal/harness noise from a preview so the column stays a single line
|
|
@@ -39,12 +40,23 @@ export declare function liveGlyphAndPreview(a: ActiveSession | undefined): {
|
|
|
39
40
|
glyph: string;
|
|
40
41
|
preview: string;
|
|
41
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* The one-word live status for a listing row — `working` / `waiting` / `idle`,
|
|
45
|
+
* the same three states the `--active` column shows, so the default list is no
|
|
46
|
+
* longer just a glyph. `waiting` is the actionable "needs you" case (a question /
|
|
47
|
+
* permission / plan-review), kept distinct from `idle` (stopped) and `working`.
|
|
48
|
+
* Empty for a not-live row, and for the rare no-signal `unknown`. Pure +
|
|
49
|
+
* exported for the row tests.
|
|
50
|
+
*/
|
|
51
|
+
export declare function liveStatusWord(a: ActiveSession | undefined): string;
|
|
42
52
|
/**
|
|
43
53
|
* The tracker/PR ref for a session's dedicated column: the ticket id when known,
|
|
44
54
|
* else `PR#<n>`, else empty. Pulled out of the trailing badge blob so refs align
|
|
45
55
|
* into a scannable column instead of jamming against a truncated topic.
|
|
46
56
|
*/
|
|
47
57
|
export declare function ticketLabel(s: Pick<SessionMeta, 'ticketId' | 'prNumber'>): string;
|
|
58
|
+
export declare function linkTicketCell(s: Pick<SessionMeta, 'ticketId' | 'prNumber' | 'prUrl'>, label: string): string;
|
|
59
|
+
export declare function linkCwdCell(s: Pick<SessionMeta, 'cwd' | '_remote'>, label: string): string;
|
|
48
60
|
/**
|
|
49
61
|
* The row shape `agents sessions --active --json` emits. RUSH-1981: a watcher
|
|
50
62
|
* joins active sessions on ticketId + project, but the raw ActiveSession nests
|
|
@@ -57,7 +69,16 @@ export declare function ticketLabel(s: Pick<SessionMeta, 'ticketId' | 'prNumber'
|
|
|
57
69
|
export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<ActiveSession & {
|
|
58
70
|
ticketId: string | null;
|
|
59
71
|
project: string | null;
|
|
72
|
+
prLink: string | null;
|
|
60
73
|
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Compact owner display for the `--active` owner column: the local-part of a
|
|
76
|
+
* resolved actor email/login (`muqsit@getrush.ai` -> `muqsit`), the id as-is
|
|
77
|
+
* when it has no `@`, and `-` when the actor is unresolved (`UNRESOLVED@<host>`)
|
|
78
|
+
* or absent (a launch predating actor stamping). Honest by design — an
|
|
79
|
+
* unresolved local run shows no owner rather than inventing one.
|
|
80
|
+
*/
|
|
81
|
+
export declare function ownerLabel(s: ActiveSession): string;
|
|
61
82
|
/** Grouped + sorted view of active sessions for the --active renderer. */
|
|
62
83
|
export interface ActiveSessionsLayout {
|
|
63
84
|
workspaces: Array<{
|
|
@@ -150,6 +171,32 @@ export declare function shouldIncludeLocal(hosts: string[] | undefined, self: st
|
|
|
150
171
|
* Exported for unit testing.
|
|
151
172
|
*/
|
|
152
173
|
export declare function remoteHostsToDial(hosts: string[] | undefined, self: string): string[] | undefined;
|
|
174
|
+
/**
|
|
175
|
+
* The fleet-wide live-session set behind every `--active` surface. Local sessions
|
|
176
|
+
* come from `getActiveSessions()` and (unless `--local`) the registered online
|
|
177
|
+
* devices from `ag devices` are folded in over SSH. An explicit `--host`/`--device`
|
|
178
|
+
* list SCOPES the sweep to exactly those machines — the local machine is included
|
|
179
|
+
* only when it is itself named — so `--host` is a filter, not an addition.
|
|
180
|
+
*
|
|
181
|
+
* This is the single gather: the static renderer AND the interactive browser both
|
|
182
|
+
* call it, so the browser can never disagree with `--active --json` about which
|
|
183
|
+
* sessions are live (it used to call the local-only `getActiveSessions()` directly
|
|
184
|
+
* and silently hid every remote session).
|
|
185
|
+
*/
|
|
186
|
+
export declare function gatherActiveSessions(opts?: {
|
|
187
|
+
local?: boolean;
|
|
188
|
+
hosts?: string[];
|
|
189
|
+
}): Promise<{
|
|
190
|
+
sessions: ActiveSession[];
|
|
191
|
+
remoteDeviceCount: number;
|
|
192
|
+
}>;
|
|
193
|
+
/** One flat table row:
|
|
194
|
+
* shortId · agent · version · model · project · [glyph] label·doing · [ticket] · [wt] · time
|
|
195
|
+
* `doing` is the live preview when running, else the topic. The `ticket` column
|
|
196
|
+
* (tracker/PR ref, pulled out of the badge blob so refs align) is only rendered
|
|
197
|
+
* when `showTicket` — otherwise a listing with no refs would waste a column of
|
|
198
|
+
* dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
|
|
199
|
+
export declare function flatSessionRow(session: SessionMeta, live?: ActiveSession, showTicket?: boolean, cols?: PickerColumns): string;
|
|
153
200
|
/**
|
|
154
201
|
* Group key for the overview: prefer the indexed project name; else fold the cwd
|
|
155
202
|
* to its repo — a worktree (`.../<repo>/.agents/worktrees/<slug>`) folds to the
|
|
@@ -201,8 +248,19 @@ export interface PickerColumns {
|
|
|
201
248
|
/** Total width of the machine column, sized to the widest compacted hostname
|
|
202
249
|
* in the pool (capped). Falls back to PICKER_MACHINE_W when absent. */
|
|
203
250
|
machineWidth?: number;
|
|
251
|
+
/** Render the model column only when at least one row carries a model. */
|
|
252
|
+
showModel?: boolean;
|
|
253
|
+
/** Pool-sized model column width, including one trailing separator cell. */
|
|
254
|
+
modelWidth?: number;
|
|
204
255
|
/** Render the ticket/PR column (only when at least one row carries a ref). */
|
|
205
256
|
showTicket?: boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Render the host-program column — which terminal/editor the session is running
|
|
259
|
+
* in (`ghostty`, `codium`, `tmux→ghostty`, …). Live-only: it comes from the
|
|
260
|
+
* active-session scan, so it is set by the running-filtered browser and stays
|
|
261
|
+
* off for a plain transcript listing, where no row has a host.
|
|
262
|
+
*/
|
|
263
|
+
showHost?: boolean;
|
|
206
264
|
/**
|
|
207
265
|
* Cells the picker prepends before each row: 2 for the single-select cursor
|
|
208
266
|
* ('> '), 6 for the multi-select cursor + checkbox ('> [x] '). Reserved from
|
|
@@ -223,13 +281,30 @@ export declare function machineLabeler(machines: string[]): (m: string) => strin
|
|
|
223
281
|
* than one box, the ticket column only when some row carries a PR/ticket ref.
|
|
224
282
|
*/
|
|
225
283
|
export declare function pickerColumnsFor(sessions: SessionMeta[]): PickerColumns;
|
|
226
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Which program a live session is running in, for the picker's host column: the
|
|
286
|
+
* immediate host app (`codium`, `ghostty`, `tmux`, `iterm`, …) and — when a tmux
|
|
287
|
+
* session is currently being watched through a different app — the app it is
|
|
288
|
+
* viewed in, as `tmux→ghostty`. A tmux session with no attached client stays a
|
|
289
|
+
* bare `tmux`, which is exactly "running detached". Empty when the session has no
|
|
290
|
+
* resolvable host (cloud rows, an unreadable process env).
|
|
291
|
+
*/
|
|
292
|
+
export declare function liveHostLabel(a: ActiveSession | undefined): string;
|
|
293
|
+
export declare function formatPickerLabel(s: SessionMeta, query: string, cols?: PickerColumns, ssh?: SshOriginTag, host?: string): string;
|
|
227
294
|
/**
|
|
228
295
|
* Pick a hint to show above the picker. Deterministic (keys off the pool size)
|
|
229
296
|
* so it stays fixed across the picker's re-renders within a single run.
|
|
230
297
|
*/
|
|
231
298
|
export declare function formatPickerTip(sessions: SessionMeta[]): string;
|
|
232
299
|
export declare function pickSessionInteractive(sessions: SessionMeta[], message?: string, initialSearch?: string, hiddenCount?: number, enterHint?: string): Promise<PickedSession | null>;
|
|
300
|
+
/**
|
|
301
|
+
* Row-id prefix for a live session whose agent has not reported a session id yet
|
|
302
|
+
* (a booting harness, a queued teammate). The browser lists these so nothing live
|
|
303
|
+
* is hidden, but they address no transcript — {@link isIdlessLiveRow} is what the
|
|
304
|
+
* pick handler checks before trying to read or resume one.
|
|
305
|
+
*/
|
|
306
|
+
export declare const LIVE_ROW_PREFIX = "live:";
|
|
307
|
+
export declare function isIdlessLiveRow(s: SessionMeta): boolean;
|
|
233
308
|
export declare function handlePickedSession(picked: PickedSession): Promise<void>;
|
|
234
309
|
/**
|
|
235
310
|
* Resume a session in the current terminal — a foreground takeover of this
|
|
@@ -285,6 +360,56 @@ export interface SessionQueryResolution {
|
|
|
285
360
|
export declare function resolveSessionQuery(pool: SessionMeta[], query: string): SessionQueryResolution;
|
|
286
361
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|
|
287
362
|
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined): SessionMeta[];
|
|
363
|
+
/**
|
|
364
|
+
* Whether a missed local id lookup should widen to a cross-machine sweep.
|
|
365
|
+
*
|
|
366
|
+
* Gate (all must hold):
|
|
367
|
+
* - the query is id-shaped (`looksLikeSessionId`) — only an identifier resolves
|
|
368
|
+
* across the fleet; a search phrase never does.
|
|
369
|
+
* - not `--local` — the caller opted out of cross-machine lookup (deterministic
|
|
370
|
+
* local behavior for scripts, RUSH-2024 acceptance: "--local still restricts").
|
|
371
|
+
* - `AGENTS_SESSIONS_LOCAL` is unset — we are not ourselves a peer answering a
|
|
372
|
+
* parent's sweep, so a locate can never recurse (RUSH-2024: avoid double-fan-out).
|
|
373
|
+
*
|
|
374
|
+
* Pure + exported so the gate is unit-tested without driving discovery / SSH.
|
|
375
|
+
*/
|
|
376
|
+
export declare function shouldFanOutForId(query: string, local: boolean | undefined): boolean;
|
|
377
|
+
/** Injectable SSH/peer boundary so the fleet-resolve logic is unit-testable
|
|
378
|
+
* without a live tailnet. Production wires these to the real remote-list infra. */
|
|
379
|
+
export interface FleetResolveDeps {
|
|
380
|
+
gatherRemoteList: typeof gatherRemoteList;
|
|
381
|
+
runOnPeer: typeof runOnPeer;
|
|
382
|
+
}
|
|
383
|
+
/** One distinct machine that reported the id, plus its winning row. */
|
|
384
|
+
interface FleetHit {
|
|
385
|
+
machine: string;
|
|
386
|
+
session: SessionMeta;
|
|
387
|
+
}
|
|
388
|
+
/** Group a fleet sweep's rows to the DISTINCT machines that hold the id. Each
|
|
389
|
+
* peer answered `sessions <id> --json --local`, which (post-fix) id-resolves and
|
|
390
|
+
* so returns the matching row(s); a peer with a synced MIRROR of the same id can
|
|
391
|
+
* emit more than one row, so we keep the first per machine. Rows the peer somehow
|
|
392
|
+
* returned that do NOT match the id (defensive against version skew) are dropped
|
|
393
|
+
* so a stray content hit can never masquerade as an exact resolution. */
|
|
394
|
+
export declare function fleetHitsById(rows: SessionMeta[], id: string): FleetHit[];
|
|
395
|
+
/**
|
|
396
|
+
* Locate a full session id across the online fleet and render it from the machine
|
|
397
|
+
* that holds it. The local disk already missed; this fans `sessions <id> --json
|
|
398
|
+
* --all` out to every registered online peer (or the explicit `hosts` set),
|
|
399
|
+
* groups the rows to distinct machines, then:
|
|
400
|
+
*
|
|
401
|
+
* - exactly one machine → delegate rendering to that peer via `runOnPeer`
|
|
402
|
+
* (its transcript and agent binary live there — a local `--host` hop would
|
|
403
|
+
* re-discover locally and dead-end), returning `'rendered'`.
|
|
404
|
+
* - more than one machine → print the conflict with machine labels so the user
|
|
405
|
+
* can disambiguate with `--device <host>`, returning `'conflict'`.
|
|
406
|
+
* - none → `'not-found'`, letting the caller print the local
|
|
407
|
+
* "no session on this machine" message.
|
|
408
|
+
*
|
|
409
|
+
* No fuzzy/content fallback: the sweep forwards a UUID, each peer id-resolves it,
|
|
410
|
+
* and `fleetHitsById` drops anything that isn't an exact id match.
|
|
411
|
+
*/
|
|
412
|
+
export declare function resolveSessionAcrossFleet(id: string, mode: ViewMode, hosts?: string[], deps?: FleetResolveDeps): Promise<'rendered' | 'conflict' | 'not-found'>;
|
|
288
413
|
/** Register the `agents sessions` command with all its options and help text. */
|
|
289
414
|
export declare function registerSessionsCommands(program: Command): void;
|
|
290
415
|
export {};
|