@phnx-labs/agents-cli 1.20.78 → 1.20.83
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 +595 -0
- package/README.md +26 -26
- 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 +66 -0
- package/dist/commands/doctor.js +596 -33
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +24 -5
- 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/menubar.js +12 -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 +187 -11
- package/dist/commands/routines.d.ts +8 -1
- package/dist/commands/routines.js +241 -77
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +100 -32
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.js +32 -3
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +115 -0
- package/dist/commands/sessions.js +189 -35
- 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/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 +52 -8
- 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 -2
- package/dist/lib/agents.js +72 -63
- 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 +18 -0
- package/dist/lib/daemon.js +47 -6
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -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.d.ts +7 -0
- package/dist/lib/exec.js +69 -11
- 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 +14 -14
- package/dist/lib/git.js +41 -44
- package/dist/lib/hooks.d.ts +71 -0
- package/dist/lib/hooks.js +245 -95
- 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/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/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +24 -0
- package/dist/lib/menubar/install-menubar.js +52 -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 +33 -0
- package/dist/lib/routines.js +122 -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 +49 -10
- package/dist/lib/scheduler.js +16 -4
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +16 -0
- package/dist/lib/secrets/agent.js +105 -3
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +11 -8
- package/dist/lib/secrets/bundles.js +31 -23
- 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/sync.js +3 -3
- package/dist/lib/session/active.d.ts +128 -26
- package/dist/lib/session/active.js +192 -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 +4 -1
- package/dist/lib/session/db.js +65 -8
- package/dist/lib/session/discover.d.ts +4 -0
- package/dist/lib/session/discover.js +13 -0
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- 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/types.d.ts +16 -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/state.js +57 -1
- 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
|
@@ -3,6 +3,66 @@ import type { SessionAgentId, SessionMeta, ViewMode } from '../lib/session/types
|
|
|
3
3
|
import { type ActiveSession } from '../lib/session/active.js';
|
|
4
4
|
import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
|
|
5
5
|
import { type PickedSession } from './sessions-picker.js';
|
|
6
|
+
interface SessionFilterOptions {
|
|
7
|
+
agent?: string;
|
|
8
|
+
project?: string;
|
|
9
|
+
all?: boolean;
|
|
10
|
+
teams?: boolean;
|
|
11
|
+
routine?: boolean;
|
|
12
|
+
since?: string;
|
|
13
|
+
until?: string;
|
|
14
|
+
}
|
|
15
|
+
interface SessionsOptions extends SessionFilterOptions {
|
|
16
|
+
/** Also list sessions from the user's own unmanaged ~/.<agent> installs. */
|
|
17
|
+
unmanaged?: boolean;
|
|
18
|
+
query?: string;
|
|
19
|
+
limit?: string;
|
|
20
|
+
sort?: string;
|
|
21
|
+
json?: boolean;
|
|
22
|
+
markdown?: boolean;
|
|
23
|
+
/** Commander populates this from `--no-redact`: true by default, false when the flag is passed. */
|
|
24
|
+
redact?: boolean;
|
|
25
|
+
include?: string;
|
|
26
|
+
exclude?: string;
|
|
27
|
+
first?: string;
|
|
28
|
+
last?: string;
|
|
29
|
+
artifacts?: boolean;
|
|
30
|
+
artifact?: string;
|
|
31
|
+
active?: boolean;
|
|
32
|
+
/** Emit the on-disk session-scan directories (requires --json); for watchers. */
|
|
33
|
+
roots?: boolean;
|
|
34
|
+
cloud?: boolean;
|
|
35
|
+
host?: string[];
|
|
36
|
+
/** Group the listing by directory and drop the id/version columns. */
|
|
37
|
+
tree?: boolean;
|
|
38
|
+
/** Force the plain flat table instead of the grouped default overview. */
|
|
39
|
+
flat?: boolean;
|
|
40
|
+
/** With --active: show only sessions waiting on user input; exit 1 if any. */
|
|
41
|
+
waiting?: boolean;
|
|
42
|
+
/** Enrich the listing with live glyphs/preview for running rows. Default on;
|
|
43
|
+
* `--no-live` sets this false. Commander's `--no-` convention. */
|
|
44
|
+
live?: boolean;
|
|
45
|
+
/** Force local-only: skip the cross-machine SSH fan-out (both the default
|
|
46
|
+
* listing and --active). */
|
|
47
|
+
local?: boolean;
|
|
48
|
+
/** --device <target...> — alias for --host; resolves against the device registry. */
|
|
49
|
+
device?: string[];
|
|
50
|
+
/** Per-agent shorthands: aliases for `--agent <name>` (prioritized harnesses). */
|
|
51
|
+
claude?: boolean;
|
|
52
|
+
codex?: boolean;
|
|
53
|
+
kimi?: boolean;
|
|
54
|
+
antigravity?: boolean;
|
|
55
|
+
grok?: boolean;
|
|
56
|
+
opencode?: boolean;
|
|
57
|
+
/** Force the printed listing even on a TTY. Commander's `--no-` convention:
|
|
58
|
+
* `--no-interactive` sets this false, opting out of the interactive browser. */
|
|
59
|
+
interactive?: boolean;
|
|
60
|
+
/** Print the canonical `ag sessions …` command for the given flags and exit —
|
|
61
|
+
* the non-interactive twin of the browser's `y` hotkey. */
|
|
62
|
+
printCmd?: boolean;
|
|
63
|
+
/** Print a compact preview of the matched session and exit (no pager). */
|
|
64
|
+
preview?: boolean;
|
|
65
|
+
}
|
|
6
66
|
/**
|
|
7
67
|
* Strip terminal/harness noise from a preview so the column stays a single line
|
|
8
68
|
* of plain prose: OSC title escapes, CSI/SGR ANSI, and the harness wrapper tags
|
|
@@ -10,6 +70,16 @@ import { type PickedSession } from './sessions-picker.js';
|
|
|
10
70
|
* a captured transcript tail. Collapses runs of whitespace.
|
|
11
71
|
*/
|
|
12
72
|
export declare function cleanPreview(text: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Build the live description for an active session: checklist progress (when
|
|
75
|
+
* present) plus the state engine's preview (the latest turn), a user label, or
|
|
76
|
+
* the first-prompt topic. Used by both the flat listing's `doing` cell and as
|
|
77
|
+
* the snippet half of the --active row (identity is layered on in printActiveRow).
|
|
78
|
+
*
|
|
79
|
+
* Covers every ActiveSession context: terminal (interactive), headless, teams,
|
|
80
|
+
* cloud, and sub-agent rows that share the same ActiveSession.todos field.
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildSessionDescription(s: ActiveSession): string;
|
|
13
83
|
/**
|
|
14
84
|
* Identity + checklist + live snippet for an --active / cross-machine row.
|
|
15
85
|
* Surfaces agent-adjacent identity the flat table already has (label, project)
|
|
@@ -40,12 +110,23 @@ export declare function liveGlyphAndPreview(a: ActiveSession | undefined): {
|
|
|
40
110
|
glyph: string;
|
|
41
111
|
preview: string;
|
|
42
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* The one-word live status for a listing row — `working` / `waiting` / `idle`,
|
|
115
|
+
* the same three states the `--active` column shows, so the default list is no
|
|
116
|
+
* longer just a glyph. `waiting` is the actionable "needs you" case (a question /
|
|
117
|
+
* permission / plan-review), kept distinct from `idle` (stopped) and `working`.
|
|
118
|
+
* Empty for a not-live row, and for the rare no-signal `unknown`. Pure +
|
|
119
|
+
* exported for the row tests.
|
|
120
|
+
*/
|
|
121
|
+
export declare function liveStatusWord(a: ActiveSession | undefined): string;
|
|
43
122
|
/**
|
|
44
123
|
* The tracker/PR ref for a session's dedicated column: the ticket id when known,
|
|
45
124
|
* else `PR#<n>`, else empty. Pulled out of the trailing badge blob so refs align
|
|
46
125
|
* into a scannable column instead of jamming against a truncated topic.
|
|
47
126
|
*/
|
|
48
127
|
export declare function ticketLabel(s: Pick<SessionMeta, 'ticketId' | 'prNumber'>): string;
|
|
128
|
+
export declare function linkTicketCell(s: Pick<SessionMeta, 'ticketId' | 'prNumber' | 'prUrl'>, label: string): string;
|
|
129
|
+
export declare function linkCwdCell(s: Pick<SessionMeta, 'cwd' | '_remote'>, label: string): string;
|
|
49
130
|
/**
|
|
50
131
|
* The row shape `agents sessions --active --json` emits. RUSH-1981: a watcher
|
|
51
132
|
* joins active sessions on ticketId + project, but the raw ActiveSession nests
|
|
@@ -58,7 +139,16 @@ export declare function ticketLabel(s: Pick<SessionMeta, 'ticketId' | 'prNumber'
|
|
|
58
139
|
export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<ActiveSession & {
|
|
59
140
|
ticketId: string | null;
|
|
60
141
|
project: string | null;
|
|
142
|
+
prLink: string | null;
|
|
61
143
|
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Compact owner display for the `--active` owner column: the local-part of a
|
|
146
|
+
* resolved actor email/login (`muqsit@getrush.ai` -> `muqsit`), the id as-is
|
|
147
|
+
* when it has no `@`, and `-` when the actor is unresolved (`UNRESOLVED@<host>`)
|
|
148
|
+
* or absent (a launch predating actor stamping). Honest by design — an
|
|
149
|
+
* unresolved local run shows no owner rather than inventing one.
|
|
150
|
+
*/
|
|
151
|
+
export declare function ownerLabel(s: ActiveSession): string;
|
|
62
152
|
/** Grouped + sorted view of active sessions for the --active renderer. */
|
|
63
153
|
export interface ActiveSessionsLayout {
|
|
64
154
|
workspaces: Array<{
|
|
@@ -170,6 +260,27 @@ export declare function gatherActiveSessions(opts?: {
|
|
|
170
260
|
sessions: ActiveSession[];
|
|
171
261
|
remoteDeviceCount: number;
|
|
172
262
|
}>;
|
|
263
|
+
/**
|
|
264
|
+
* A bare interactive fleet listing — no query, no render/filter flag — that the
|
|
265
|
+
* `runSessionBrowser` picker can represent. The single predicate shared by the
|
|
266
|
+
* bare-browser branch and the `--host` early-return guard so they can't drift:
|
|
267
|
+
* when this holds, an explicit `--host`/`--device` scope is folded into the
|
|
268
|
+
* browser (preview-rich, selectable) instead of the legacy per-host raw stream.
|
|
269
|
+
*/
|
|
270
|
+
export declare function isBareBrowserListing(options: SessionsOptions, query: string | undefined): boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Pure flag-gate half of {@link isBareBrowserListing} (TTY-independent, so it is
|
|
273
|
+
* unit-testable): true when no query, render, or filter flag is present that the
|
|
274
|
+
* `runSessionBrowser` picker cannot represent.
|
|
275
|
+
*/
|
|
276
|
+
export declare function hasNoBrowserDisqualifyingFlags(options: SessionsOptions, query: string | undefined): boolean;
|
|
277
|
+
/** One flat table row:
|
|
278
|
+
* shortId · agent · version · model · project · [glyph] label·doing · [ticket] · [wt] · time
|
|
279
|
+
* `doing` is the live preview when running, else the topic. The `ticket` column
|
|
280
|
+
* (tracker/PR ref, pulled out of the badge blob so refs align) is only rendered
|
|
281
|
+
* when `showTicket` — otherwise a listing with no refs would waste a column of
|
|
282
|
+
* dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
|
|
283
|
+
export declare function flatSessionRow(session: SessionMeta, live?: ActiveSession, showTicket?: boolean, cols?: PickerColumns): string;
|
|
173
284
|
/**
|
|
174
285
|
* Group key for the overview: prefer the indexed project name; else fold the cwd
|
|
175
286
|
* to its repo — a worktree (`.../<repo>/.agents/worktrees/<slug>`) folds to the
|
|
@@ -221,6 +332,10 @@ export interface PickerColumns {
|
|
|
221
332
|
/** Total width of the machine column, sized to the widest compacted hostname
|
|
222
333
|
* in the pool (capped). Falls back to PICKER_MACHINE_W when absent. */
|
|
223
334
|
machineWidth?: number;
|
|
335
|
+
/** Render the model column only when at least one row carries a model. */
|
|
336
|
+
showModel?: boolean;
|
|
337
|
+
/** Pool-sized model column width, including one trailing separator cell. */
|
|
338
|
+
modelWidth?: number;
|
|
224
339
|
/** Render the ticket/PR column (only when at least one row carries a ref). */
|
|
225
340
|
showTicket?: boolean;
|
|
226
341
|
/**
|
|
@@ -32,7 +32,7 @@ import { filterTeamSessions } from '../lib/session/team-filter.js';
|
|
|
32
32
|
import { parseSession } from '../lib/session/parse.js';
|
|
33
33
|
import { runRemoteSessions, buildForwardedArgs, ensureWholeIndex } from '../lib/session/remote.js';
|
|
34
34
|
import { formatRelativeTime } from '../lib/session/relative-time.js';
|
|
35
|
-
import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList, linkUrl } from '../lib/session/render.js';
|
|
35
|
+
import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList, linkPath, linkUrl, shortenModel } from '../lib/session/render.js';
|
|
36
36
|
import { linearIssueUrl } from '../lib/session/linear.js';
|
|
37
37
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
38
38
|
import { AGENTS, colorAgent, resolveAgentName } from '../lib/agents.js';
|
|
@@ -182,6 +182,11 @@ function statusColor(status) {
|
|
|
182
182
|
case 'idle': return chalk.gray;
|
|
183
183
|
case 'queued': return chalk.blue;
|
|
184
184
|
case 'input_required': return chalk.yellow;
|
|
185
|
+
// Dead process: dimmed so it recedes from the live rows without being mistaken
|
|
186
|
+
// for the gray `idle` (which reads as "done, waiting for you" — a live state).
|
|
187
|
+
case 'closed': return chalk.dim;
|
|
188
|
+
// Days-stale / dangling: red so a session nobody is driving stands out.
|
|
189
|
+
case 'abandoned': return chalk.red;
|
|
185
190
|
// Alive but un-introspectable (a harness whose transcript we can't parse).
|
|
186
191
|
// Magenta so it never reads as the gray "idle" it used to be faked as.
|
|
187
192
|
case 'unknown': return chalk.magenta;
|
|
@@ -235,7 +240,7 @@ export function cleanPreview(text) {
|
|
|
235
240
|
* Covers every ActiveSession context: terminal (interactive), headless, teams,
|
|
236
241
|
* cloud, and sub-agent rows that share the same ActiveSession.todos field.
|
|
237
242
|
*/
|
|
238
|
-
function buildSessionDescription(s) {
|
|
243
|
+
export function buildSessionDescription(s) {
|
|
239
244
|
const todo = formatTodoCompact(s.todos);
|
|
240
245
|
if (s.context === 'cloud') {
|
|
241
246
|
const base = s.preview || `${s.cloudProvider ?? ''}${s.cloudTaskId ? ` · ${s.cloudTaskId.slice(0, 12)}` : ''}`;
|
|
@@ -243,6 +248,11 @@ function buildSessionDescription(s) {
|
|
|
243
248
|
}
|
|
244
249
|
if (s.context === 'teams') {
|
|
245
250
|
const parts = [s.teamName];
|
|
251
|
+
// Lineage: which orchestrator spun up this team. Prefer the resolved label,
|
|
252
|
+
// else the short session id, so "by whom" is answerable at a glance.
|
|
253
|
+
const orch = s.orchestratorLabel || (s.orchestratorSessionId ? s.orchestratorSessionId.slice(0, 8) : '');
|
|
254
|
+
if (orch)
|
|
255
|
+
parts.push(`by ${orch}`);
|
|
246
256
|
if (todo)
|
|
247
257
|
parts.push(todo);
|
|
248
258
|
if (s.preview)
|
|
@@ -303,6 +313,10 @@ export function formatActiveRowDescription(s) {
|
|
|
303
313
|
}
|
|
304
314
|
/** Short human word for a session's activity (falls back to the coarse status). */
|
|
305
315
|
function activityLabel(s) {
|
|
316
|
+
// Lifecycle status wins over any residual parsed activity — a dead/dangling
|
|
317
|
+
// session must read `closed`/`abandoned`, not the `idle` its stale tail infers.
|
|
318
|
+
if (s.status === 'closed' || s.status === 'abandoned')
|
|
319
|
+
return s.status;
|
|
306
320
|
if (s.activity === 'waiting_input')
|
|
307
321
|
return 'waiting';
|
|
308
322
|
if (s.activity === 'working')
|
|
@@ -335,14 +349,54 @@ export function indexActiveBySessionId(active) {
|
|
|
335
349
|
export function liveGlyphAndPreview(a) {
|
|
336
350
|
if (!a)
|
|
337
351
|
return { glyph: '', preview: '' };
|
|
352
|
+
// `◌` (dotted) = alive but un-introspectable — visually distinct from `○` idle
|
|
353
|
+
// so an opaque harness is never mistaken for a finished one. `⊘` = abandoned /
|
|
354
|
+
// dangling; `×` = closed (dead pid) — both distinct from the `○` idle a live,
|
|
355
|
+
// stopped session shows, so a gone session never masquerades as a resting one.
|
|
356
|
+
if (a.status === 'abandoned')
|
|
357
|
+
return { glyph: statusColor(a.status)('⊘'), preview: buildSessionDescription(a) };
|
|
358
|
+
if (a.status === 'closed')
|
|
359
|
+
return { glyph: statusColor(a.status)('×'), preview: buildSessionDescription(a) };
|
|
338
360
|
const waiting = a.status === 'input_required' || a.activity === 'waiting_input';
|
|
339
361
|
const running = a.status === 'running' || a.activity === 'working';
|
|
340
|
-
// `◌` (dotted) = alive but un-introspectable — visually distinct from `○` idle
|
|
341
|
-
// so an opaque harness is never mistaken for a finished one.
|
|
342
362
|
const unknown = a.status === 'unknown';
|
|
343
|
-
const shape = waiting ? '◐'
|
|
363
|
+
const shape = waiting ? '◐'
|
|
364
|
+
: running ? '●'
|
|
365
|
+
: unknown ? '◌'
|
|
366
|
+
: '○';
|
|
344
367
|
return { glyph: statusColor(a.status)(shape), preview: buildSessionDescription(a) };
|
|
345
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* The one-word live status for a listing row — `working` / `waiting` / `idle`,
|
|
371
|
+
* the same three states the `--active` column shows, so the default list is no
|
|
372
|
+
* longer just a glyph. `waiting` is the actionable "needs you" case (a question /
|
|
373
|
+
* permission / plan-review), kept distinct from `idle` (stopped) and `working`.
|
|
374
|
+
* Empty for a not-live row, and for the rare no-signal `unknown`. Pure +
|
|
375
|
+
* exported for the row tests.
|
|
376
|
+
*/
|
|
377
|
+
export function liveStatusWord(a) {
|
|
378
|
+
if (!a)
|
|
379
|
+
return '';
|
|
380
|
+
// Lifecycle status is definitive — surface it ahead of any parsed activity.
|
|
381
|
+
if (a.status === 'closed' || a.status === 'abandoned')
|
|
382
|
+
return a.status;
|
|
383
|
+
if (a.status === 'input_required' || a.activity === 'waiting_input')
|
|
384
|
+
return 'waiting';
|
|
385
|
+
if (a.status === 'running' || a.activity === 'working')
|
|
386
|
+
return 'working';
|
|
387
|
+
if (a.status === 'idle' || a.activity === 'idle')
|
|
388
|
+
return 'idle';
|
|
389
|
+
if (a.status === 'queued')
|
|
390
|
+
return 'queued';
|
|
391
|
+
return '';
|
|
392
|
+
}
|
|
393
|
+
/** The colored, space-padded status cell for a listing row (empty when not live). */
|
|
394
|
+
function liveStatusCell(live) {
|
|
395
|
+
const word = liveStatusWord(live);
|
|
396
|
+
if (!word || !live)
|
|
397
|
+
return { cell: '', width: 0 };
|
|
398
|
+
return { cell: statusColor(live.status)(padToWidth(word, 8)), width: 8 };
|
|
399
|
+
}
|
|
346
400
|
/**
|
|
347
401
|
* The tracker/PR ref for a session's dedicated column: the ticket id when known,
|
|
348
402
|
* else `PR#<n>`, else empty. Pulled out of the trailing badge blob so refs align
|
|
@@ -351,6 +405,21 @@ export function liveGlyphAndPreview(a) {
|
|
|
351
405
|
export function ticketLabel(s) {
|
|
352
406
|
return s.ticketId ?? (s.prNumber ? `PR#${s.prNumber}` : '');
|
|
353
407
|
}
|
|
408
|
+
function ticketUrl(s) {
|
|
409
|
+
if (s.ticketId)
|
|
410
|
+
return linearIssueUrl(s.ticketId);
|
|
411
|
+
return s.prNumber ? s.prUrl : undefined;
|
|
412
|
+
}
|
|
413
|
+
export function linkTicketCell(s, label) {
|
|
414
|
+
const url = ticketUrl(s);
|
|
415
|
+
return url && label.trim() !== '-' ? linkUrl(url, label) : label;
|
|
416
|
+
}
|
|
417
|
+
export function linkCwdCell(s, label) {
|
|
418
|
+
return s.cwd && !s._remote ? linkPath(s.cwd, label) : label;
|
|
419
|
+
}
|
|
420
|
+
function modelLabel(model) {
|
|
421
|
+
return model ? shortenModel(model) : '-';
|
|
422
|
+
}
|
|
354
423
|
/**
|
|
355
424
|
* The row shape `agents sessions --active --json` emits. RUSH-1981: a watcher
|
|
356
425
|
* joins active sessions on ticketId + project, but the raw ActiveSession nests
|
|
@@ -365,6 +434,7 @@ export function serializeActiveSessionsForJson(sessions) {
|
|
|
365
434
|
...s,
|
|
366
435
|
ticketId: s.ticket?.id ?? null,
|
|
367
436
|
project: s.cwd ? path.basename(s.cwd) : null,
|
|
437
|
+
prLink: s.pr?.url ?? null,
|
|
368
438
|
}));
|
|
369
439
|
}
|
|
370
440
|
/**
|
|
@@ -435,11 +505,12 @@ function locatorBadge(s) {
|
|
|
435
505
|
* terminal width so the row never wraps.
|
|
436
506
|
*/
|
|
437
507
|
function printActiveRow(s, indent) {
|
|
438
|
-
// shortId (8-char) · agent · host · status · badges · identity+todos+snippet
|
|
508
|
+
// shortId (8-char) · agent · host · status · owner · badges · identity+todos+snippet
|
|
439
509
|
const idCol = chalk.dim(padToWidth((s.sessionId?.slice(0, 8)) ?? '-', 9));
|
|
440
510
|
const kindCol = colorAgent(s.kind)(padToWidth(truncateToWidth(s.kind, 8), 9));
|
|
441
511
|
const hostCol = chalk.gray(padToWidth(truncateToWidth(s.host ?? '-', 8), 9));
|
|
442
512
|
const statusCol = statusColor(s.status)(padToWidth(truncateToWidth(activityLabel(s), 8), 9));
|
|
513
|
+
const ownerCol = chalk.cyan(padToWidth(truncateToWidth(ownerLabel(s), 8), 9));
|
|
443
514
|
const fork = s.pidCount && s.pidCount > 1 ? chalk.dim(`×${s.pidCount} `) : '';
|
|
444
515
|
const badges = (fork ? fork : '') + [signalBadges(s), locatorBadge(s)].filter(Boolean).join(' ');
|
|
445
516
|
// Identity (label/project/ticket clickable) + checklist + live snippet.
|
|
@@ -447,10 +518,24 @@ function printActiveRow(s, indent) {
|
|
|
447
518
|
// via the SSH fan-out already populated (including todos when the peer has them).
|
|
448
519
|
const desc = formatActiveRowDescription(s) || '-';
|
|
449
520
|
// Fill the remaining width with the preview so nothing wraps under tmux/SSH.
|
|
450
|
-
const fixed = stringWidth(indent) + 9 + 9 + 9 + 9 + (badges ? stringWidth(badges) + 1 : 0);
|
|
521
|
+
const fixed = stringWidth(indent) + 9 + 9 + 9 + 9 + 9 + (badges ? stringWidth(badges) + 1 : 0);
|
|
451
522
|
const room = Math.max(12, terminalWidth() - fixed - 1);
|
|
452
523
|
const descCol = chalk.white(truncateToWidth(desc, room));
|
|
453
|
-
console.log(indent + idCol + kindCol + hostCol + statusCol + (badges ? badges + ' ' : '') + descCol);
|
|
524
|
+
console.log(indent + idCol + kindCol + hostCol + statusCol + ownerCol + (badges ? badges + ' ' : '') + descCol);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Compact owner display for the `--active` owner column: the local-part of a
|
|
528
|
+
* resolved actor email/login (`muqsit@getrush.ai` -> `muqsit`), the id as-is
|
|
529
|
+
* when it has no `@`, and `-` when the actor is unresolved (`UNRESOLVED@<host>`)
|
|
530
|
+
* or absent (a launch predating actor stamping). Honest by design — an
|
|
531
|
+
* unresolved local run shows no owner rather than inventing one.
|
|
532
|
+
*/
|
|
533
|
+
export function ownerLabel(s) {
|
|
534
|
+
const owner = s.owner;
|
|
535
|
+
if (!owner || owner.startsWith('UNRESOLVED@'))
|
|
536
|
+
return '-';
|
|
537
|
+
const at = owner.indexOf('@');
|
|
538
|
+
return at > 0 ? owner.slice(0, at) : owner;
|
|
454
539
|
}
|
|
455
540
|
/**
|
|
456
541
|
* Short label for an IDE window. The slice key in live-terminals.json is
|
|
@@ -666,8 +751,8 @@ async function runRemoteSessionsJson(hosts) {
|
|
|
666
751
|
process.stdout.write(serializeSessionsJson(sessions));
|
|
667
752
|
}
|
|
668
753
|
/**
|
|
669
|
-
* `running N · idle N · waiting N · queued N ·
|
|
670
|
-
* sessions (zero buckets omitted). Same bucketing as the
|
|
754
|
+
* `running N · idle N · waiting N · queued N · closed N · abandoned N · unknown N`
|
|
755
|
+
* for a bucket of sessions (zero buckets omitted). Same bucketing as the summary so
|
|
671
756
|
* per-group counts reconcile with the `(total)` beside the header — the `unknown`
|
|
672
757
|
* bucket is what keeps an alive-but-opaque row from silently vanishing from the
|
|
673
758
|
* tally. Empty when nothing.
|
|
@@ -677,6 +762,8 @@ function groupTally(sessions) {
|
|
|
677
762
|
const idle = sessions.filter(s => s.status === 'idle').length;
|
|
678
763
|
const waiting = sessions.filter(s => s.status === 'input_required').length;
|
|
679
764
|
const queued = sessions.filter(s => s.status === 'queued').length;
|
|
765
|
+
const closed = sessions.filter(s => s.status === 'closed').length;
|
|
766
|
+
const abandoned = sessions.filter(s => s.status === 'abandoned').length;
|
|
680
767
|
const unknown = sessions.filter(s => s.status === 'unknown').length;
|
|
681
768
|
const parts = [];
|
|
682
769
|
if (running)
|
|
@@ -687,6 +774,10 @@ function groupTally(sessions) {
|
|
|
687
774
|
parts.push(`${waiting} waiting`);
|
|
688
775
|
if (queued)
|
|
689
776
|
parts.push(`${queued} queued`);
|
|
777
|
+
if (closed)
|
|
778
|
+
parts.push(`${closed} closed`);
|
|
779
|
+
if (abandoned)
|
|
780
|
+
parts.push(`${abandoned} abandoned`);
|
|
690
781
|
if (unknown)
|
|
691
782
|
parts.push(`${unknown} unknown`);
|
|
692
783
|
return parts.join(' · ');
|
|
@@ -911,6 +1002,33 @@ function printCrossMachineTip() {
|
|
|
911
1002
|
function useInteractiveBrowser(options) {
|
|
912
1003
|
return options.interactive !== false && !options.json && isInteractiveTerminal();
|
|
913
1004
|
}
|
|
1005
|
+
/**
|
|
1006
|
+
* A bare interactive fleet listing — no query, no render/filter flag — that the
|
|
1007
|
+
* `runSessionBrowser` picker can represent. The single predicate shared by the
|
|
1008
|
+
* bare-browser branch and the `--host` early-return guard so they can't drift:
|
|
1009
|
+
* when this holds, an explicit `--host`/`--device` scope is folded into the
|
|
1010
|
+
* browser (preview-rich, selectable) instead of the legacy per-host raw stream.
|
|
1011
|
+
*/
|
|
1012
|
+
export function isBareBrowserListing(options, query) {
|
|
1013
|
+
return useInteractiveBrowser(options) && hasNoBrowserDisqualifyingFlags(options, query);
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Pure flag-gate half of {@link isBareBrowserListing} (TTY-independent, so it is
|
|
1017
|
+
* unit-testable): true when no query, render, or filter flag is present that the
|
|
1018
|
+
* `runSessionBrowser` picker cannot represent.
|
|
1019
|
+
*/
|
|
1020
|
+
export function hasNoBrowserDisqualifyingFlags(options, query) {
|
|
1021
|
+
return (!query &&
|
|
1022
|
+
!options.routine &&
|
|
1023
|
+
!options.flat &&
|
|
1024
|
+
!options.tree &&
|
|
1025
|
+
!options.markdown &&
|
|
1026
|
+
!options.until &&
|
|
1027
|
+
!options.project &&
|
|
1028
|
+
!options.sort &&
|
|
1029
|
+
!options.artifacts &&
|
|
1030
|
+
options.artifact === undefined);
|
|
1031
|
+
}
|
|
914
1032
|
/** The canonical `ag sessions …` command for a set of flags — the twin of the
|
|
915
1033
|
* browser's `y` hotkey (see --print-cmd). Normalizes to the stable flag form. */
|
|
916
1034
|
function canonicalSessionsCommand(query, options) {
|
|
@@ -958,6 +1076,21 @@ async function renderSessionPreview(query, scope) {
|
|
|
958
1076
|
console.log(chalk.gray(`No session matches "${query}".`));
|
|
959
1077
|
return;
|
|
960
1078
|
}
|
|
1079
|
+
// Lead with the live status when the session is still running, so the preview
|
|
1080
|
+
// says working / waiting / idle up front — not just the historical transcript.
|
|
1081
|
+
let live;
|
|
1082
|
+
try {
|
|
1083
|
+
live = indexActiveBySessionId(await getActiveSessions()).get(session.id);
|
|
1084
|
+
}
|
|
1085
|
+
catch { /* plain preview on any probe failure */ }
|
|
1086
|
+
if (live) {
|
|
1087
|
+
const { glyph } = liveGlyphAndPreview(live);
|
|
1088
|
+
const word = liveStatusWord(live) || live.status;
|
|
1089
|
+
const needsYou = live.status === 'input_required' || live.activity === 'waiting_input';
|
|
1090
|
+
const reason = live.awaitingReason ? ` (${live.awaitingReason.replace('_', ' ')})` : '';
|
|
1091
|
+
const suffix = needsYou ? chalk.yellow(` ← needs you${reason}`) : '';
|
|
1092
|
+
console.log(`${glyph} ${statusColor(live.status)(word)}${suffix}`);
|
|
1093
|
+
}
|
|
961
1094
|
console.log(buildPreview(session));
|
|
962
1095
|
}
|
|
963
1096
|
/** Main action handler for `agents sessions`. Routes to picker, table, or single-session render. */
|
|
@@ -998,14 +1131,21 @@ async function sessionsAction(query, options) {
|
|
|
998
1131
|
await runRemoteSessionsJson(options.host);
|
|
999
1132
|
return;
|
|
1000
1133
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1134
|
+
// A bare interactive `--host`/`--device <box>` listing falls through to the
|
|
1135
|
+
// fleet browser below, which folds the named host(s) into the same merged,
|
|
1136
|
+
// preview-rich, selectable view as the local listing (via gatherRemoteList).
|
|
1137
|
+
// A query, a render/filter flag, or a non-interactive caller keeps the legacy
|
|
1138
|
+
// per-host raw stream under a `── host ──` banner.
|
|
1139
|
+
if (!isBareBrowserListing(options, query)) {
|
|
1140
|
+
try {
|
|
1141
|
+
runRemoteSessions(options.host);
|
|
1142
|
+
}
|
|
1143
|
+
catch (err) {
|
|
1144
|
+
console.error(chalk.red(err.message));
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
return;
|
|
1007
1148
|
}
|
|
1008
|
-
return;
|
|
1009
1149
|
}
|
|
1010
1150
|
// --preview <id/query>: resolve one session and print its compact preview, then
|
|
1011
1151
|
// exit — checked before --active so `--active --preview <id>` peeks the id
|
|
@@ -1059,17 +1199,7 @@ async function sessionsAction(query, options) {
|
|
|
1059
1199
|
// filter the browser can't represent), or --no-interactive keep the existing
|
|
1060
1200
|
// printed/render paths (agents and scripts unaffected). An explicit --since seeds
|
|
1061
1201
|
// the browser's window so the flag is honored, not swallowed.
|
|
1062
|
-
if (
|
|
1063
|
-
!query &&
|
|
1064
|
-
!options.routine &&
|
|
1065
|
-
!options.flat &&
|
|
1066
|
-
!options.tree &&
|
|
1067
|
-
!options.markdown &&
|
|
1068
|
-
!options.until &&
|
|
1069
|
-
!options.project &&
|
|
1070
|
-
!options.sort &&
|
|
1071
|
-
!options.artifacts &&
|
|
1072
|
-
options.artifact === undefined) {
|
|
1202
|
+
if (isBareBrowserListing(options, query)) {
|
|
1073
1203
|
const { runSessionBrowser, bareBrowserSeed } = await import('./sessions-browser.js');
|
|
1074
1204
|
await runSessionBrowser(bareBrowserSeed({
|
|
1075
1205
|
teams: options.teams,
|
|
@@ -1322,12 +1452,12 @@ function metaSignals(s) {
|
|
|
1322
1452
|
};
|
|
1323
1453
|
}
|
|
1324
1454
|
/** One flat table row:
|
|
1325
|
-
* shortId · agent · version · project · [glyph] label·doing · [ticket] · [wt] · time
|
|
1455
|
+
* shortId · agent · version · model · project · [glyph] label·doing · [ticket] · [wt] · time
|
|
1326
1456
|
* `doing` is the live preview when running, else the topic. The `ticket` column
|
|
1327
1457
|
* (tracker/PR ref, pulled out of the badge blob so refs align) is only rendered
|
|
1328
1458
|
* when `showTicket` — otherwise a listing with no refs would waste a column of
|
|
1329
1459
|
* dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
|
|
1330
|
-
function flatSessionRow(session, live, showTicket = false, cols = {}) {
|
|
1460
|
+
export function flatSessionRow(session, live, showTicket = false, cols = {}) {
|
|
1331
1461
|
const agentColor = colorAgent(session.agent);
|
|
1332
1462
|
const when = formatRelativeTime(session.lastActivity ?? session.timestamp);
|
|
1333
1463
|
const project = session.project || '-';
|
|
@@ -1350,19 +1480,31 @@ function flatSessionRow(session, live, showTicket = false, cols = {}) {
|
|
|
1350
1480
|
: '';
|
|
1351
1481
|
const TICKET_W = 10;
|
|
1352
1482
|
const ticketCell = showTicket
|
|
1353
|
-
? chalk.blue(padToWidth(truncateToWidth(ticketLabel(session) || '-', TICKET_W), TICKET_W + 1))
|
|
1483
|
+
? chalk.blue(linkTicketCell(session, padToWidth(truncateToWidth(ticketLabel(session) || '-', TICKET_W), TICKET_W + 1)))
|
|
1354
1484
|
: '';
|
|
1485
|
+
// Live status word (working / waiting / idle) next to the glyph — the default
|
|
1486
|
+
// list is no longer a bare glyph. Empty (zero width) for resting rows.
|
|
1487
|
+
const { cell: statusCell, width: statusW } = liveStatusCell(live);
|
|
1355
1488
|
const glyphW = glyph ? 2 : 0;
|
|
1356
1489
|
const machineW = cols.showMachine ? machineColW : 0;
|
|
1357
1490
|
const ticketW = showTicket ? TICKET_W + 1 : 0;
|
|
1358
1491
|
const wtW = wt ? stringWidth(wt) + 1 : 0;
|
|
1359
|
-
const
|
|
1492
|
+
const width = terminalWidth();
|
|
1493
|
+
const requestedModelW = cols.showModel ? (cols.modelWidth ?? PICKER_MODEL_MAX) : 0;
|
|
1494
|
+
const fixedW = (10 + 9 + 8 + 16) + glyphW + statusW + machineW + ticketW + wtW + stringWidth(when) + 1;
|
|
1495
|
+
const modelSlack = width - fixedW - 16;
|
|
1496
|
+
const modelW = requestedModelW <= modelSlack
|
|
1497
|
+
? requestedModelW
|
|
1498
|
+
: modelSlack >= PICKER_MODEL_MIN ? modelSlack : 0;
|
|
1499
|
+
const topicW = Math.max(16, width - fixedW - modelW);
|
|
1360
1500
|
return (chalk.white(padToWidth(truncateToWidth(session.shortId, 9), 10)) +
|
|
1361
1501
|
agentColor(padToWidth(truncateToWidth(session.agent, 8), 9)) +
|
|
1362
1502
|
chalk.yellow(padToWidth(truncateToWidth(session.version || '-', 7), 8)) +
|
|
1503
|
+
(modelW ? chalk.yellow(padToWidth(truncateToWidth(modelLabel(session.model), modelW - 1), modelW)) : '') +
|
|
1363
1504
|
machineCell +
|
|
1364
|
-
chalk.cyan(padToWidth(truncateToWidth(project, 14), 16)) +
|
|
1505
|
+
chalk.cyan(linkCwdCell(session, padToWidth(truncateToWidth(project, 14), 16))) +
|
|
1365
1506
|
(glyph ? glyph + ' ' : '') +
|
|
1507
|
+
statusCell +
|
|
1366
1508
|
renderTopicCell(label, doing, '', topicW, topicW) +
|
|
1367
1509
|
ticketCell +
|
|
1368
1510
|
(wt ? wt + ' ' : '') +
|
|
@@ -1383,13 +1525,15 @@ function treeSessionRow(session, live) {
|
|
|
1383
1525
|
const badges = signalBadges(metaSignals(session));
|
|
1384
1526
|
const badgeW = badges ? stringWidth(badges) + 1 : 0;
|
|
1385
1527
|
const head = label ? `${label} · ${topic}` : topic;
|
|
1528
|
+
const { cell: statusCell, width: statusW } = liveStatusCell(live);
|
|
1386
1529
|
const glyphW = glyph ? 2 : 0;
|
|
1387
|
-
const topicW = Math.max(12, terminalWidth() - (2 + 9 + 8) - glyphW - badgeW - stringWidth(when) - 1);
|
|
1530
|
+
const topicW = Math.max(12, terminalWidth() - (2 + 9 + 8) - glyphW - statusW - badgeW - stringWidth(when) - 1);
|
|
1388
1531
|
return (' ' +
|
|
1389
1532
|
chalk.dim(padToWidth(session.shortId, 9)) +
|
|
1390
1533
|
agentColor(padToWidth(truncateToWidth(session.agent, 7), 8)) +
|
|
1391
1534
|
(badges ? badges + ' ' : '') +
|
|
1392
1535
|
(glyph ? glyph + ' ' : '') +
|
|
1536
|
+
statusCell +
|
|
1393
1537
|
padToWidth(chalk.white(truncateToWidth(head, topicW)), topicW) +
|
|
1394
1538
|
' ' + chalk.gray(when));
|
|
1395
1539
|
}
|
|
@@ -1506,7 +1650,9 @@ function printSessionTable(sessions, hiddenCount = 0, tree = false, liveIndex) {
|
|
|
1506
1650
|
console.log();
|
|
1507
1651
|
first = false;
|
|
1508
1652
|
const group = byDir.get(key);
|
|
1509
|
-
|
|
1653
|
+
const cwd = group.find((s) => s.cwd && !s._remote)?.cwd;
|
|
1654
|
+
const header = cwd ? linkPath(cwd, shortCwd(key)) : shortCwd(key);
|
|
1655
|
+
console.log(`${chalk.cyan.bold(header)} ${chalk.gray(`(${group.length})`)}`);
|
|
1510
1656
|
for (const s of group)
|
|
1511
1657
|
console.log(treeSessionRow(s, liveIndex?.get(s.id)));
|
|
1512
1658
|
}
|
|
@@ -1702,12 +1848,18 @@ function renderTopicCell(label, topic, query, visibleWidth, paddedWidth) {
|
|
|
1702
1848
|
const PICKER_MACHINE_W = 11;
|
|
1703
1849
|
const PICKER_MACHINE_MIN = 8;
|
|
1704
1850
|
const PICKER_MACHINE_MAX = 18;
|
|
1851
|
+
const PICKER_MODEL_MIN = 6;
|
|
1852
|
+
const PICKER_MODEL_MAX = 13;
|
|
1705
1853
|
/** Column width that shows every compacted hostname in `machines` whole (one
|
|
1706
1854
|
* trailing space for separation), bounded by MIN/MAX. */
|
|
1707
1855
|
function machineColumnWidth(machines, label) {
|
|
1708
1856
|
const widest = machines.reduce((w, m) => Math.max(w, stringWidth(label(m))), 0);
|
|
1709
1857
|
return Math.min(PICKER_MACHINE_MAX, Math.max(PICKER_MACHINE_MIN, widest + 1));
|
|
1710
1858
|
}
|
|
1859
|
+
function modelColumnWidth(sessions) {
|
|
1860
|
+
const widest = sessions.reduce((width, session) => (Math.max(width, session.model ? stringWidth(modelLabel(session.model)) : 0)), 0);
|
|
1861
|
+
return Math.min(PICKER_MODEL_MAX, Math.max(PICKER_MODEL_MIN, widest + 1));
|
|
1862
|
+
}
|
|
1711
1863
|
/**
|
|
1712
1864
|
* Compact display form for machine ids: strip the longest shared dash-delimited
|
|
1713
1865
|
* prefix so "yosemite-s0"/"yosemite-s1" read as "s0"/"s1" while unrelated ids
|
|
@@ -1743,6 +1895,8 @@ export function pickerColumnsFor(sessions) {
|
|
|
1743
1895
|
showMachine: distinct.length > 1,
|
|
1744
1896
|
machineLabel,
|
|
1745
1897
|
machineWidth: machineColumnWidth(distinct, machineLabel),
|
|
1898
|
+
showModel: sessions.some((s) => !!s.model),
|
|
1899
|
+
modelWidth: modelColumnWidth(sessions),
|
|
1746
1900
|
showTicket: sessions.some((s) => ticketLabel(s) !== ''),
|
|
1747
1901
|
};
|
|
1748
1902
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents set` — ergonomic top-level setter for per-agent/version run defaults.
|
|
3
|
+
*
|
|
4
|
+
* `agents set claude@2.1.220 --model opus-5` pins the default model (and/or
|
|
5
|
+
* mode) that `agents run` uses for that agent version. It reads and writes the
|
|
6
|
+
* same store as `agents defaults run set` (agents.yaml -> run.defaults), so the
|
|
7
|
+
* two stay consistent — `set` is just the short front door.
|
|
8
|
+
*
|
|
9
|
+
* agents set # list every configured default
|
|
10
|
+
* agents set claude@2.1.220 # show the default for one version
|
|
11
|
+
* agents set claude@2.1.220 --model opus-5
|
|
12
|
+
* agents set 'claude:*' --mode auto --model opus
|
|
13
|
+
*/
|
|
14
|
+
import type { Command } from 'commander';
|
|
15
|
+
export declare function registerSetCommand(program: Command): void;
|