@phnx-labs/agents-cli 1.20.73 → 1.20.76
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 +989 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +31 -1
- package/dist/commands/exec.js +31 -3
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +87 -9
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/setup.js +6 -1
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +51 -11
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +99 -31
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +56 -0
- package/dist/lib/agents.js +133 -7
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +60 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +25 -0
- package/dist/lib/import.js +107 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/project-resources.js +20 -5
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +27 -1
- package/dist/lib/shims.js +88 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +15 -36
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +48 -2
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +22 -2
- package/dist/lib/versions.js +98 -10
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
|
@@ -33,6 +33,19 @@ export declare function liveGlyphAndPreview(a: ActiveSession | undefined): {
|
|
|
33
33
|
* into a scannable column instead of jamming against a truncated topic.
|
|
34
34
|
*/
|
|
35
35
|
export declare function ticketLabel(s: Pick<SessionMeta, 'ticketId' | 'prNumber'>): string;
|
|
36
|
+
/**
|
|
37
|
+
* The row shape `agents sessions --active --json` emits. RUSH-1981: a watcher
|
|
38
|
+
* joins active sessions on ticketId + project, but the raw ActiveSession nests
|
|
39
|
+
* the ticket (`ticket.id`) and carries no `project` at all — so a naive join
|
|
40
|
+
* silently drops every row. Emit both as flat, always-present top-level keys
|
|
41
|
+
* (null when unknown) alongside the raw fields, so every active row is joinable.
|
|
42
|
+
* `project` uses the same derivation SessionMeta does — basename(cwd) (see
|
|
43
|
+
* discover.ts) — so the active view and the history view join identically.
|
|
44
|
+
*/
|
|
45
|
+
export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<ActiveSession & {
|
|
46
|
+
ticketId: string | null;
|
|
47
|
+
project: string | null;
|
|
48
|
+
}>;
|
|
36
49
|
/** Grouped + sorted view of active sessions for the --active renderer. */
|
|
37
50
|
export interface ActiveSessionsLayout {
|
|
38
51
|
workspaces: Array<{
|
|
@@ -204,8 +217,8 @@ export declare function handlePickedSession(picked: PickedSession): Promise<void
|
|
|
204
217
|
* Resume a session in the current terminal — a foreground takeover of this
|
|
205
218
|
* process. Used by the single-select picker and by `sessions resume` when the
|
|
206
219
|
* chosen destination is "in place" (unknown emulator / off-macOS, single pick).
|
|
207
|
-
* Falls back to the current version
|
|
208
|
-
*
|
|
220
|
+
* Falls back to the same resume invocation against the current version when the
|
|
221
|
+
* version-pinned launcher is genuinely missing.
|
|
209
222
|
*/
|
|
210
223
|
export declare function resumeSessionInPlace(session: SessionMeta): Promise<void>;
|
|
211
224
|
/**
|
|
@@ -224,23 +237,33 @@ export declare function resumeSpawnInvocation(cmd: string[], platform?: NodeJS.P
|
|
|
224
237
|
args: string[];
|
|
225
238
|
shell: boolean;
|
|
226
239
|
};
|
|
227
|
-
/**
|
|
228
|
-
* Build the shell command that resumes a picked session.
|
|
229
|
-
*
|
|
230
|
-
* When the session's originating version is known, uses the version-pinned
|
|
231
|
-
* binary (e.g. `claude@2.1.138`) so the resume always runs in the same
|
|
232
|
-
* isolated HOME where the JSONL was written — regardless of which version is
|
|
233
|
-
* currently the default. Falls back to the bare shim when version is unknown.
|
|
234
|
-
*
|
|
235
|
-
* If the versioned binary is missing (version was removed), the ENOENT
|
|
236
|
-
* handler in handlePickedSession retries via buildFallbackCommand.
|
|
237
|
-
*/
|
|
238
240
|
export declare function buildResumeCommand(session: SessionMeta): string[] | null;
|
|
239
241
|
interface AgentFilter {
|
|
240
242
|
agent?: SessionAgentId;
|
|
241
243
|
version?: string;
|
|
242
244
|
}
|
|
243
245
|
export declare function parseAgentFilter(agentName?: string): AgentFilter;
|
|
246
|
+
/**
|
|
247
|
+
* How a `sessions <query>` argument was resolved against the pool.
|
|
248
|
+
*
|
|
249
|
+
* `byId` records that the rows came from an id lookup, so only then does an
|
|
250
|
+
* ambiguous result mean "your id prefix is too short". `completeId` records that
|
|
251
|
+
* the query was a whole session id: it is unique by construction, so a miss is
|
|
252
|
+
* final and must NOT widen into a text/content search.
|
|
253
|
+
*/
|
|
254
|
+
export interface SessionQueryResolution {
|
|
255
|
+
matches: SessionMeta[];
|
|
256
|
+
byId: boolean;
|
|
257
|
+
completeId: boolean;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The single entry point for turning a `sessions <query>` argument into rows.
|
|
261
|
+
*
|
|
262
|
+
* A complete session id resolves by id alone. Anything else keeps the existing
|
|
263
|
+
* ladder: id lookup first (so a short id still wins), then the ranked
|
|
264
|
+
* metadata+content search.
|
|
265
|
+
*/
|
|
266
|
+
export declare function resolveSessionQuery(pool: SessionMeta[], query: string): SessionQueryResolution;
|
|
244
267
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|
|
245
268
|
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined): SessionMeta[];
|
|
246
269
|
/** Register the `agents sessions` command with all its options and help text. */
|
|
@@ -26,14 +26,16 @@ import { gatherRemoteActive, NO_FANOUT_ENV } from '../lib/session/remote-active.
|
|
|
26
26
|
import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
|
|
27
27
|
import { stringWidth, truncateToWidth, padToWidth, terminalWidth } from '../lib/session/width.js';
|
|
28
28
|
import { inferSessionState } from '../lib/session/state.js';
|
|
29
|
-
import { discoverSessions, countSessionsInScope, resolveSessionById, searchContentIndex, getSessionRoots } from '../lib/session/discover.js';
|
|
29
|
+
import { discoverSessions, countSessionsInScope, resolveSessionById, isCompleteSessionId, searchContentIndex, getSessionRoots } from '../lib/session/discover.js';
|
|
30
|
+
import { findSessionsById } from '../lib/session/db.js';
|
|
30
31
|
import { filterTeamSessions } from '../lib/session/team-filter.js';
|
|
31
32
|
import { parseSession } from '../lib/session/parse.js';
|
|
32
33
|
import { runRemoteSessions, buildForwardedArgs, ensureWholeIndex } from '../lib/session/remote.js';
|
|
33
34
|
import { formatRelativeTime } from '../lib/session/relative-time.js';
|
|
34
35
|
import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList } from '../lib/session/render.js';
|
|
35
36
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
36
|
-
import { colorAgent, resolveAgentName } from '../lib/agents.js';
|
|
37
|
+
import { AGENTS, colorAgent, resolveAgentName } from '../lib/agents.js';
|
|
38
|
+
import { getShimsDir } from '../lib/state.js';
|
|
37
39
|
import { fuzzyMatch, FUZZY_PRESETS } from '../lib/fuzzy.js';
|
|
38
40
|
import { resolveVersionAliasLoose } from '../lib/versions.js';
|
|
39
41
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
@@ -44,9 +46,12 @@ import { registerSessionsSyncCommand } from './sessions-sync.js';
|
|
|
44
46
|
import { registerSessionsResumeCommand } from './sessions-resume.js';
|
|
45
47
|
import { registerGoCommand } from './go.js';
|
|
46
48
|
import { registerFocusCommand } from './focus.js';
|
|
49
|
+
import { registerDetachCommand } from './detach.js';
|
|
50
|
+
import { registerAttachCommand } from './attach.js';
|
|
47
51
|
import { registerSessionsInjectCommand } from './sessions-inject.js';
|
|
48
52
|
import { registerSessionsExportCommand } from './sessions-export.js';
|
|
49
53
|
import { registerSessionsImportCommand } from './sessions-import.js';
|
|
54
|
+
import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
|
|
50
55
|
import { runBrowserSessions } from '../lib/browser/sessions-list.js';
|
|
51
56
|
const SESSION_AGENT_FILTER_HELP = `Filter by agent, e.g. claude, codex, claude@2.0.65`;
|
|
52
57
|
/**
|
|
@@ -176,6 +181,9 @@ function statusColor(status) {
|
|
|
176
181
|
case 'idle': return chalk.gray;
|
|
177
182
|
case 'queued': return chalk.blue;
|
|
178
183
|
case 'input_required': return chalk.yellow;
|
|
184
|
+
// Alive but un-introspectable (a harness whose transcript we can't parse).
|
|
185
|
+
// Magenta so it never reads as the gray "idle" it used to be faked as.
|
|
186
|
+
case 'unknown': return chalk.magenta;
|
|
179
187
|
}
|
|
180
188
|
}
|
|
181
189
|
function contextColor(context) {
|
|
@@ -274,7 +282,10 @@ export function liveGlyphAndPreview(a) {
|
|
|
274
282
|
return { glyph: '', preview: '' };
|
|
275
283
|
const waiting = a.status === 'input_required' || a.activity === 'waiting_input';
|
|
276
284
|
const running = a.status === 'running' || a.activity === 'working';
|
|
277
|
-
|
|
285
|
+
// `◌` (dotted) = alive but un-introspectable — visually distinct from `○` idle
|
|
286
|
+
// so an opaque harness is never mistaken for a finished one.
|
|
287
|
+
const unknown = a.status === 'unknown';
|
|
288
|
+
const shape = waiting ? '◐' : running ? '●' : unknown ? '◌' : '○';
|
|
278
289
|
return { glyph: statusColor(a.status)(shape), preview: buildSessionDescription(a) };
|
|
279
290
|
}
|
|
280
291
|
/**
|
|
@@ -285,6 +296,22 @@ export function liveGlyphAndPreview(a) {
|
|
|
285
296
|
export function ticketLabel(s) {
|
|
286
297
|
return s.ticketId ?? (s.prNumber ? `PR#${s.prNumber}` : '');
|
|
287
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* The row shape `agents sessions --active --json` emits. RUSH-1981: a watcher
|
|
301
|
+
* joins active sessions on ticketId + project, but the raw ActiveSession nests
|
|
302
|
+
* the ticket (`ticket.id`) and carries no `project` at all — so a naive join
|
|
303
|
+
* silently drops every row. Emit both as flat, always-present top-level keys
|
|
304
|
+
* (null when unknown) alongside the raw fields, so every active row is joinable.
|
|
305
|
+
* `project` uses the same derivation SessionMeta does — basename(cwd) (see
|
|
306
|
+
* discover.ts) — so the active view and the history view join identically.
|
|
307
|
+
*/
|
|
308
|
+
export function serializeActiveSessionsForJson(sessions) {
|
|
309
|
+
return sessions.map((s) => ({
|
|
310
|
+
...s,
|
|
311
|
+
ticketId: s.ticket?.id ?? null,
|
|
312
|
+
project: s.cwd ? path.basename(s.cwd) : null,
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
288
315
|
/**
|
|
289
316
|
* Compact, colour-coded badges for the durable/awaiting signals. Text-only (no
|
|
290
317
|
* emoji, per repo convention): `plan` / `ask` / `perm` for why it's waiting,
|
|
@@ -573,15 +600,18 @@ async function runRemoteSessionsJson(hosts) {
|
|
|
573
600
|
process.stdout.write(serializeSessionsJson(sessions));
|
|
574
601
|
}
|
|
575
602
|
/**
|
|
576
|
-
* `running N · idle N · waiting N · queued N` for a bucket of
|
|
577
|
-
* buckets omitted). Same bucketing as the grand-total summary so
|
|
578
|
-
* counts reconcile with the `(total)` beside the header
|
|
603
|
+
* `running N · idle N · waiting N · queued N · unknown N` for a bucket of
|
|
604
|
+
* sessions (zero buckets omitted). Same bucketing as the grand-total summary so
|
|
605
|
+
* per-group counts reconcile with the `(total)` beside the header — the `unknown`
|
|
606
|
+
* bucket is what keeps an alive-but-opaque row from silently vanishing from the
|
|
607
|
+
* tally. Empty when nothing.
|
|
579
608
|
*/
|
|
580
609
|
function groupTally(sessions) {
|
|
581
610
|
const running = sessions.filter(s => s.status === 'running').length;
|
|
582
611
|
const idle = sessions.filter(s => s.status === 'idle').length;
|
|
583
612
|
const waiting = sessions.filter(s => s.status === 'input_required').length;
|
|
584
613
|
const queued = sessions.filter(s => s.status === 'queued').length;
|
|
614
|
+
const unknown = sessions.filter(s => s.status === 'unknown').length;
|
|
585
615
|
const parts = [];
|
|
586
616
|
if (running)
|
|
587
617
|
parts.push(`${running} running`);
|
|
@@ -591,6 +621,8 @@ function groupTally(sessions) {
|
|
|
591
621
|
parts.push(`${waiting} waiting`);
|
|
592
622
|
if (queued)
|
|
593
623
|
parts.push(`${queued} queued`);
|
|
624
|
+
if (unknown)
|
|
625
|
+
parts.push(`${unknown} unknown`);
|
|
594
626
|
return parts.join(' · ');
|
|
595
627
|
}
|
|
596
628
|
/** Print one machine's workspace tree, indented under its machine header. */
|
|
@@ -747,7 +779,7 @@ async function renderActiveSessions(asJson, waitingOnly = false, opts = {}) {
|
|
|
747
779
|
// present so a supervising agent or hook can poll it as a gate.
|
|
748
780
|
const sessions = waitingOnly ? merged.filter(s => s.status === 'input_required') : merged;
|
|
749
781
|
if (asJson) {
|
|
750
|
-
process.stdout.write(JSON.stringify(sessions, null, 2) + '\n');
|
|
782
|
+
process.stdout.write(JSON.stringify(serializeActiveSessionsForJson(sessions), null, 2) + '\n');
|
|
751
783
|
if (waitingOnly && sessions.length > 0)
|
|
752
784
|
process.exitCode = 1;
|
|
753
785
|
return;
|
|
@@ -835,10 +867,15 @@ function canonicalSessionsCommand(query, options) {
|
|
|
835
867
|
async function renderSessionPreview(query, scope) {
|
|
836
868
|
const discovered = await discoverSessions({ all: true, cwd: process.cwd(), limit: 5000 });
|
|
837
869
|
const pool = applyScopeFilters(discovered, scope);
|
|
838
|
-
const matches =
|
|
839
|
-
const session =
|
|
870
|
+
const { matches, completeId } = resolveSessionQuery(pool, query);
|
|
871
|
+
const session = matches[0];
|
|
840
872
|
if (!session) {
|
|
841
|
-
|
|
873
|
+
// A complete id that missed is not "no match for this text" — say which, and
|
|
874
|
+
// give the same fleet pointer the render paths give.
|
|
875
|
+
if (completeId)
|
|
876
|
+
notFoundByIdMessage(query).forEach(l => console.log(l));
|
|
877
|
+
else
|
|
878
|
+
console.log(chalk.gray(`No session matches "${query}".`));
|
|
842
879
|
return;
|
|
843
880
|
}
|
|
844
881
|
console.log(buildPreview(session));
|
|
@@ -1043,11 +1080,14 @@ async function sessionsAction(query, options) {
|
|
|
1043
1080
|
sortBy,
|
|
1044
1081
|
origin: options.routine ? 'routine' : undefined,
|
|
1045
1082
|
};
|
|
1083
|
+
let hiddenUnmanaged = 0;
|
|
1046
1084
|
let sessions = await discoverSessions({
|
|
1047
1085
|
...scope,
|
|
1048
1086
|
limit,
|
|
1049
1087
|
excludeTeamOrigin: !options.teams,
|
|
1050
1088
|
onProgress: tracker.onProgress,
|
|
1089
|
+
includeUnmanaged: options.unmanaged,
|
|
1090
|
+
onHiddenUnmanaged: (n) => { hiddenUnmanaged = n; },
|
|
1051
1091
|
});
|
|
1052
1092
|
tracker.stop();
|
|
1053
1093
|
spinner?.stop();
|
|
@@ -1121,6 +1161,9 @@ async function sessionsAction(query, options) {
|
|
|
1121
1161
|
if (hiddenCount > 0) {
|
|
1122
1162
|
console.log(chalk.gray(formatTeamHiddenFooter(hiddenCount)));
|
|
1123
1163
|
}
|
|
1164
|
+
if (hiddenUnmanaged > 0) {
|
|
1165
|
+
console.log(chalk.gray(formatUnmanagedHiddenFooter(hiddenUnmanaged)));
|
|
1166
|
+
}
|
|
1124
1167
|
return;
|
|
1125
1168
|
}
|
|
1126
1169
|
// The grouped project overview is the bare interactive default: a scannable
|
|
@@ -1130,7 +1173,7 @@ async function sessionsAction(query, options) {
|
|
|
1130
1173
|
const liveIndex = await maybeLiveIndex(options);
|
|
1131
1174
|
// Per-project row cap is fixed (--limit carries a default of 50 and drives
|
|
1132
1175
|
// the fetch pool, not the display); `--all` expands every group instead.
|
|
1133
|
-
printSessionOverview(sessions, hiddenCount, liveIndex, { perProjectCap: OVERVIEW_ROWS_PER_PROJECT, expand: !!options.all });
|
|
1176
|
+
printSessionOverview(sessions, hiddenCount, liveIndex, { perProjectCap: OVERVIEW_ROWS_PER_PROJECT, expand: !!options.all, hiddenUnmanaged });
|
|
1134
1177
|
return;
|
|
1135
1178
|
}
|
|
1136
1179
|
// --tree / --flat are printed listings, not an interactive pick — render them
|
|
@@ -1150,6 +1193,10 @@ async function sessionsAction(query, options) {
|
|
|
1150
1193
|
const filtered = searchQuery ? filterSessionsByQuery(sessions, searchQuery) : sessions;
|
|
1151
1194
|
const liveIndex = await maybeLiveIndex(options);
|
|
1152
1195
|
printSessionTable(filtered, hiddenCount, options.tree === true, liveIndex);
|
|
1196
|
+
// Every listing path must say what it dropped — a hidden default that stays
|
|
1197
|
+
// silent in one render mode is the failure this footer exists to prevent.
|
|
1198
|
+
if (hiddenUnmanaged > 0)
|
|
1199
|
+
console.log(chalk.gray(formatUnmanagedHiddenFooter(hiddenUnmanaged)));
|
|
1153
1200
|
}
|
|
1154
1201
|
catch (err) {
|
|
1155
1202
|
tracker.stop();
|
|
@@ -1158,8 +1205,14 @@ async function sessionsAction(query, options) {
|
|
|
1158
1205
|
process.exit(1);
|
|
1159
1206
|
}
|
|
1160
1207
|
}
|
|
1208
|
+
/** Whether a query should route to the single-session render rather than the
|
|
1209
|
+
* listing. The hex-ish test catches a bare id prefix; `isCompleteSessionId`
|
|
1210
|
+
* additionally catches the prefixed whole ids (`session_…`, `ses_…`) that the
|
|
1211
|
+
* hex test rejects — without it those never reach the id-only resolution and
|
|
1212
|
+
* still content-search. */
|
|
1161
1213
|
function looksLikeSessionId(query) {
|
|
1162
|
-
|
|
1214
|
+
const trimmed = query.trim();
|
|
1215
|
+
return /^[0-9a-f-]{6,}$/i.test(trimmed) || isCompleteSessionId(trimmed);
|
|
1163
1216
|
}
|
|
1164
1217
|
function teamTag(session) {
|
|
1165
1218
|
const origin = session.teamOrigin;
|
|
@@ -1337,6 +1390,8 @@ function printSessionOverview(pool, hiddenCount, liveIndex, opts) {
|
|
|
1337
1390
|
console.log(parts.join(chalk.gray(' · ')));
|
|
1338
1391
|
if (hiddenCount > 0)
|
|
1339
1392
|
console.log(chalk.gray(formatTeamHiddenFooter(hiddenCount)));
|
|
1393
|
+
if (opts.hiddenUnmanaged)
|
|
1394
|
+
console.log(chalk.gray(formatUnmanagedHiddenFooter(opts.hiddenUnmanaged)));
|
|
1340
1395
|
}
|
|
1341
1396
|
function printSessionTable(sessions, hiddenCount = 0, tree = false, liveIndex) {
|
|
1342
1397
|
if (tree) {
|
|
@@ -1720,8 +1775,8 @@ export async function handlePickedSession(picked) {
|
|
|
1720
1775
|
* Resume a session in the current terminal — a foreground takeover of this
|
|
1721
1776
|
* process. Used by the single-select picker and by `sessions resume` when the
|
|
1722
1777
|
* chosen destination is "in place" (unknown emulator / off-macOS, single pick).
|
|
1723
|
-
* Falls back to the current version
|
|
1724
|
-
*
|
|
1778
|
+
* Falls back to the same resume invocation against the current version when the
|
|
1779
|
+
* version-pinned launcher is genuinely missing.
|
|
1725
1780
|
*/
|
|
1726
1781
|
export async function resumeSessionInPlace(session) {
|
|
1727
1782
|
const cwd = session.cwd && fs.existsSync(session.cwd)
|
|
@@ -1738,11 +1793,17 @@ export async function resumeSessionInPlace(session) {
|
|
|
1738
1793
|
// agent shim is a `.cmd`/`.ps1` and, under the shell needed to run it (see
|
|
1739
1794
|
// spawnResumeCommand), a missing command exits non-zero rather than emitting
|
|
1740
1795
|
// an ENOENT `error` event — so detect a removed version here instead of
|
|
1741
|
-
// relying on that event, keeping the
|
|
1742
|
-
|
|
1796
|
+
// relying on that event, keeping the fallback working on every OS.
|
|
1797
|
+
// `resume[0]` is an absolute alias path when one exists on disk, so only a bare
|
|
1798
|
+
// name still needs a PATH lookup. Checking existsSync first is what keeps an
|
|
1799
|
+
// isolated install (shims deliberately off PATH) out of the fallback.
|
|
1800
|
+
const launcherFound = path.isAbsolute(resume[0])
|
|
1801
|
+
? fs.existsSync(resume[0])
|
|
1802
|
+
: !!findExecutable(resume[0]);
|
|
1803
|
+
if (!launcherFound && session.version) {
|
|
1743
1804
|
const fallback = buildFallbackCommand(session);
|
|
1744
1805
|
if (fallback) {
|
|
1745
|
-
console.log(chalk.gray(`Version ${session.version} is not installed.
|
|
1806
|
+
console.log(chalk.gray(`Version ${session.version} is not installed. Resuming with the current version instead...`));
|
|
1746
1807
|
await spawnResumeCommand(fallback, cwd);
|
|
1747
1808
|
return;
|
|
1748
1809
|
}
|
|
@@ -1819,18 +1880,55 @@ function spawnResumeCommand(cmd, cwd) {
|
|
|
1819
1880
|
* If the versioned binary is missing (version was removed), the ENOENT
|
|
1820
1881
|
* handler in handlePickedSession retries via buildFallbackCommand.
|
|
1821
1882
|
*/
|
|
1883
|
+
/**
|
|
1884
|
+
* The agent's own resume invocation, given whichever launcher we resolved.
|
|
1885
|
+
* Keeping the verb in one place is what lets the version-pinned and fallback
|
|
1886
|
+
* paths stay in agreement — they previously drifted into `/continue`, which is
|
|
1887
|
+
* not a command either CLI has.
|
|
1888
|
+
*/
|
|
1889
|
+
function resumeArgv(agent, id, launcher) {
|
|
1890
|
+
switch (agent) {
|
|
1891
|
+
case 'claude': return [launcher, '--resume', id];
|
|
1892
|
+
case 'codex': return [launcher, 'resume', id];
|
|
1893
|
+
case 'opencode': return [launcher, '--session', id];
|
|
1894
|
+
default: return null;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Absolute path of the on-disk versioned alias, or null when it isn't there.
|
|
1899
|
+
*
|
|
1900
|
+
* Resume must not resolve `<cli>@<version>` by bare name. The shims directory is
|
|
1901
|
+
* deliberately absent from PATH for an isolated install — that is precisely what
|
|
1902
|
+
* `--isolated` promises — so a PATH lookup can never find the alias, and resume
|
|
1903
|
+
* degraded to the fallback 100% of the time for isolated copies. Mirrors the
|
|
1904
|
+
* resolution `buildExecCommand` already does for `agents run`.
|
|
1905
|
+
*/
|
|
1906
|
+
function versionedAliasIfPresent(agent, version) {
|
|
1907
|
+
const cli = AGENTS[agent]?.cliCommand ?? agent;
|
|
1908
|
+
const base = path.join(getShimsDir(), `${cli}@${version}`);
|
|
1909
|
+
if (process.platform === 'win32' && fs.existsSync(`${base}.cmd`))
|
|
1910
|
+
return `${base}.cmd`;
|
|
1911
|
+
if (fs.existsSync(base))
|
|
1912
|
+
return base;
|
|
1913
|
+
return null;
|
|
1914
|
+
}
|
|
1822
1915
|
export function buildResumeCommand(session) {
|
|
1823
1916
|
switch (session.agent) {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
return [`claude@${session.version}`, '--resume', session.id];
|
|
1827
|
-
return ['claude', '--resume', session.id];
|
|
1828
|
-
case 'codex':
|
|
1829
|
-
if (session.version)
|
|
1830
|
-
return [`codex@${session.version}`, 'resume', session.id];
|
|
1831
|
-
return ['codex', 'resume', session.id];
|
|
1917
|
+
// opencode sessions are shared across versions, so resume is deliberately NOT
|
|
1918
|
+
// version-pinned — it always goes through the plain launcher.
|
|
1832
1919
|
case 'opencode':
|
|
1833
|
-
return
|
|
1920
|
+
return resumeArgv('opencode', session.id, 'opencode');
|
|
1921
|
+
case 'claude':
|
|
1922
|
+
case 'codex': {
|
|
1923
|
+
const cli = AGENTS[session.agent]?.cliCommand ?? session.agent;
|
|
1924
|
+
if (session.version) {
|
|
1925
|
+
const alias = versionedAliasIfPresent(session.agent, session.version);
|
|
1926
|
+
// Absolute path when the alias exists; otherwise the bare versioned name,
|
|
1927
|
+
// which still resolves for a non-isolated install whose shims are on PATH.
|
|
1928
|
+
return resumeArgv(session.agent, session.id, alias ?? `${cli}@${session.version}`);
|
|
1929
|
+
}
|
|
1930
|
+
return resumeArgv(session.agent, session.id, cli);
|
|
1931
|
+
}
|
|
1834
1932
|
case 'gemini':
|
|
1835
1933
|
case 'antigravity':
|
|
1836
1934
|
case 'openclaw':
|
|
@@ -1843,13 +1941,18 @@ export function buildResumeCommand(session) {
|
|
|
1843
1941
|
return null;
|
|
1844
1942
|
}
|
|
1845
1943
|
}
|
|
1846
|
-
/**
|
|
1944
|
+
/**
|
|
1945
|
+
* Fallback when the pinned version really is gone: the same resume invocation
|
|
1946
|
+
* against the current version.
|
|
1947
|
+
*
|
|
1948
|
+
* This used to spawn `<cli> "/continue <id>"`, feeding a slash command into the
|
|
1949
|
+
* TUI as a prompt. Neither CLI has `/continue` — codex documents `/resume` — so
|
|
1950
|
+
* the agent received an unrecognised command and the session was not resumed at
|
|
1951
|
+
* all. Reusing resumeArgv keeps the two paths from drifting apart again.
|
|
1952
|
+
*/
|
|
1847
1953
|
function buildFallbackCommand(session) {
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
case 'codex': return ['codex', `/continue ${session.id}`];
|
|
1851
|
-
default: return null;
|
|
1852
|
-
}
|
|
1954
|
+
const cli = AGENTS[session.agent]?.cliCommand ?? session.agent;
|
|
1955
|
+
return resumeArgv(session.agent, session.id, cli);
|
|
1853
1956
|
}
|
|
1854
1957
|
// ---------------------------------------------------------------------------
|
|
1855
1958
|
// Cloud session source (--cloud)
|
|
@@ -1956,6 +2059,51 @@ function formatSearchMessage(options) {
|
|
|
1956
2059
|
return 'Search sessions:';
|
|
1957
2060
|
return `Search sessions (${filters.join(', ')}):`;
|
|
1958
2061
|
}
|
|
2062
|
+
/**
|
|
2063
|
+
* The single entry point for turning a `sessions <query>` argument into rows.
|
|
2064
|
+
*
|
|
2065
|
+
* A complete session id resolves by id alone. Anything else keeps the existing
|
|
2066
|
+
* ladder: id lookup first (so a short id still wins), then the ranked
|
|
2067
|
+
* metadata+content search.
|
|
2068
|
+
*/
|
|
2069
|
+
export function resolveSessionQuery(pool, query) {
|
|
2070
|
+
// Normalize ONCE here. isCompleteSessionId trims but resolveSessionById does
|
|
2071
|
+
// not, so a padded id ("<uuid> ", e.g. pasted from a terminal) would classify
|
|
2072
|
+
// as complete and then miss the id lookup — reporting a session that IS on
|
|
2073
|
+
// this machine as absent.
|
|
2074
|
+
const normalized = query.trim();
|
|
2075
|
+
const completeId = isCompleteSessionId(normalized);
|
|
2076
|
+
const byIdMatches = resolveSessionById(pool, normalized);
|
|
2077
|
+
if (byIdMatches.length > 0)
|
|
2078
|
+
return { matches: byIdMatches, byId: true, completeId };
|
|
2079
|
+
if (completeId) {
|
|
2080
|
+
// The pool is only what discoverSessions walked — a minority of the index
|
|
2081
|
+
// (measured: 2,798 of 7,614 rows), because it re-reads live agent homes and
|
|
2082
|
+
// skips whole classes of indexed session. Declaring absence from the pool
|
|
2083
|
+
// alone denied 1,315 sessions whose transcript is on this disk right now.
|
|
2084
|
+
// Ask the index itself, the same authoritative lookup `fork` and `exec` use.
|
|
2085
|
+
return { matches: findSessionsById(normalized), byId: true, completeId };
|
|
2086
|
+
}
|
|
2087
|
+
return { matches: filterSessionsByQuery(pool, normalized), byId: false, completeId };
|
|
2088
|
+
}
|
|
2089
|
+
/** Explain an ambiguous resolution. Only a short id can be lengthened: a complete
|
|
2090
|
+
* id is already maximal, and a search phrase was never an id to begin with. */
|
|
2091
|
+
function ambiguityHint(byId, completeId) {
|
|
2092
|
+
if (completeId)
|
|
2093
|
+
return 'That is already a complete id — these rows share it as a prefix.';
|
|
2094
|
+
return byId
|
|
2095
|
+
? 'Pass a longer ID to narrow it down.'
|
|
2096
|
+
: 'That matched on text, not an id. Pass a session id, or narrow the search.';
|
|
2097
|
+
}
|
|
2098
|
+
/** Explain a complete-id miss, which no local rephrasing can fix. Echoes the
|
|
2099
|
+
* normalized id so a pasted, padded argument doesn't produce an unrunnable hint. */
|
|
2100
|
+
function notFoundByIdMessage(query) {
|
|
2101
|
+
const id = query.trim();
|
|
2102
|
+
return [
|
|
2103
|
+
chalk.red(`No session with id ${id} on this machine.`),
|
|
2104
|
+
chalk.gray(`Search the fleet with: agents sessions ${id} --device <host>`),
|
|
2105
|
+
];
|
|
2106
|
+
}
|
|
1959
2107
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|
|
1960
2108
|
export function filterSessionsByQuery(sessions, query) {
|
|
1961
2109
|
const trimmed = query?.trim().toLowerCase() || '';
|
|
@@ -2086,11 +2234,13 @@ async function renderArtifactsGlobal(query, listAll, name, scope) {
|
|
|
2086
2234
|
});
|
|
2087
2235
|
tracker.stop();
|
|
2088
2236
|
const allSessions = applyScopeFilters(discovered, scope);
|
|
2089
|
-
const matches =
|
|
2090
|
-
const queryMatches = matches.length > 0 ? matches : filterSessionsByQuery(allSessions, query);
|
|
2237
|
+
const { matches: queryMatches, byId, completeId } = resolveSessionQuery(allSessions, query);
|
|
2091
2238
|
if (queryMatches.length === 0) {
|
|
2092
2239
|
spinner.stop();
|
|
2093
|
-
|
|
2240
|
+
if (completeId)
|
|
2241
|
+
notFoundByIdMessage(query).forEach(l => console.error(l));
|
|
2242
|
+
else
|
|
2243
|
+
console.error(chalk.red(`No session found matching: ${query}`));
|
|
2094
2244
|
process.exit(1);
|
|
2095
2245
|
}
|
|
2096
2246
|
if (queryMatches.length > 1) {
|
|
@@ -2099,7 +2249,7 @@ async function renderArtifactsGlobal(query, listAll, name, scope) {
|
|
|
2099
2249
|
for (const m of queryMatches.slice(0, 10)) {
|
|
2100
2250
|
console.error(chalk.cyan(` ${m.shortId} ${m.id} ${m.label ?? m.topic ?? ''}`));
|
|
2101
2251
|
}
|
|
2102
|
-
console.error(chalk.gray(
|
|
2252
|
+
console.error(chalk.gray(ambiguityHint(byId, completeId)));
|
|
2103
2253
|
process.exit(1);
|
|
2104
2254
|
}
|
|
2105
2255
|
spinner.stop();
|
|
@@ -2127,13 +2277,20 @@ async function renderOneSession(query, mode, scope) {
|
|
|
2127
2277
|
tracker.stop();
|
|
2128
2278
|
const allSessions = applyScopeFilters(discovered, scope);
|
|
2129
2279
|
let session;
|
|
2130
|
-
const
|
|
2131
|
-
let queryMatches =
|
|
2132
|
-
|
|
2280
|
+
const resolution = resolveSessionQuery(allSessions, query);
|
|
2281
|
+
let queryMatches = resolution.matches;
|
|
2282
|
+
let byId = resolution.byId;
|
|
2283
|
+
const completeId = resolution.completeId;
|
|
2284
|
+
// Widen to the transcript content index only for a genuine search phrase. A
|
|
2285
|
+
// complete id is unique, so widening could only ever surface a DIFFERENT
|
|
2286
|
+
// session that happens to mention the id — which is what made `sessions
|
|
2287
|
+
// <uuid>` render an unrelated transcript.
|
|
2288
|
+
if (queryMatches.length === 0 && !completeId) {
|
|
2133
2289
|
const contentResults = searchContentIndex(allSessions, query);
|
|
2134
2290
|
if (contentResults.size > 0) {
|
|
2135
2291
|
const matchedSessions = Array.from(contentResults.values())
|
|
2136
2292
|
.sort((a, b) => (b._bm25Score ?? 0) - (a._bm25Score ?? 0));
|
|
2293
|
+
byId = false;
|
|
2137
2294
|
if (matchedSessions.length === 1) {
|
|
2138
2295
|
session = matchedSessions[0];
|
|
2139
2296
|
}
|
|
@@ -2155,6 +2312,10 @@ async function renderOneSession(query, mode, scope) {
|
|
|
2155
2312
|
process.exit(1);
|
|
2156
2313
|
}
|
|
2157
2314
|
}
|
|
2315
|
+
else if (completeId) {
|
|
2316
|
+
notFoundByIdMessage(query).forEach(l => console.error(l));
|
|
2317
|
+
process.exit(1);
|
|
2318
|
+
}
|
|
2158
2319
|
else {
|
|
2159
2320
|
console.error(chalk.red(`No session found matching: ${query}`));
|
|
2160
2321
|
console.error(chalk.gray('Run "agents sessions" to browse sessions.'));
|
|
@@ -2168,7 +2329,7 @@ async function renderOneSession(query, mode, scope) {
|
|
|
2168
2329
|
for (const match of queryMatches.slice(0, 10)) {
|
|
2169
2330
|
console.error(chalk.cyan(` ${match.shortId} ${match.id} ${match.label ?? match.topic ?? ''}`));
|
|
2170
2331
|
}
|
|
2171
|
-
console.error(chalk.gray(
|
|
2332
|
+
console.error(chalk.gray(ambiguityHint(byId, completeId)));
|
|
2172
2333
|
process.exit(1);
|
|
2173
2334
|
}
|
|
2174
2335
|
else {
|
|
@@ -2205,6 +2366,7 @@ export function registerSessionsCommands(program) {
|
|
|
2205
2366
|
.option('--grok', 'Shorthand for --agent grok')
|
|
2206
2367
|
.option('--opencode', 'Shorthand for --agent opencode')
|
|
2207
2368
|
.option('--all', 'Include sessions from every directory (not just current project)')
|
|
2369
|
+
.option('--unmanaged', "Also show sessions from your own ~/.<agent> installs (hidden once agents-cli manages that agent)")
|
|
2208
2370
|
.option('--teams', 'Include team-spawned sessions (hidden by default)')
|
|
2209
2371
|
.option('--routine', 'Show only sessions archived from routine runs')
|
|
2210
2372
|
.option('-p, --project <name>', 'Filter by project name (searches across all directories)')
|
|
@@ -2293,9 +2455,13 @@ export function registerSessionsCommands(program) {
|
|
|
2293
2455
|
registerSessionsResumeCommand(sessionsCmd);
|
|
2294
2456
|
registerGoCommand(sessionsCmd);
|
|
2295
2457
|
registerFocusCommand(sessionsCmd);
|
|
2458
|
+
registerDetachCommand(sessionsCmd);
|
|
2459
|
+
registerAttachCommand(sessionsCmd);
|
|
2296
2460
|
registerSessionsInjectCommand(sessionsCmd);
|
|
2297
2461
|
registerSessionsExportCommand(sessionsCmd);
|
|
2298
2462
|
registerSessionsImportCommand(sessionsCmd);
|
|
2463
|
+
registerSessionsMigrateCommand(sessionsCmd);
|
|
2464
|
+
registerSessionsMigrationsCommand(sessionsCmd);
|
|
2299
2465
|
}
|
|
2300
2466
|
function formatNoSessionsMessage(showAll, project) {
|
|
2301
2467
|
const projectQuery = project?.trim();
|
|
@@ -2307,6 +2473,10 @@ function formatNoSessionsMessage(showAll, project) {
|
|
|
2307
2473
|
const command = 'agents sessions --all';
|
|
2308
2474
|
return `No sessions found for ${process.cwd()}. Run "${command}" to see sessions from every directory.`;
|
|
2309
2475
|
}
|
|
2476
|
+
function formatUnmanagedHiddenFooter(hiddenCount) {
|
|
2477
|
+
const noun = hiddenCount === 1 ? 'session' : 'sessions';
|
|
2478
|
+
return `(${hiddenCount} ${noun} from your own unmanaged installs hidden — use --unmanaged to show)`;
|
|
2479
|
+
}
|
|
2310
2480
|
function formatTeamHiddenFooter(hiddenCount) {
|
|
2311
2481
|
const noun = hiddenCount === 1 ? 'team session' : 'team sessions';
|
|
2312
2482
|
return `(${hiddenCount} ${noun} hidden — use --teams to show, or \`agents teams status\`)`;
|
|
@@ -37,7 +37,7 @@ export async function runComputerWizard() {
|
|
|
37
37
|
return false;
|
|
38
38
|
}
|
|
39
39
|
// 1. Download + verify + install the signed, notarized helper.
|
|
40
|
-
console.log(chalk.bold('Installing the Computer
|
|
40
|
+
console.log(chalk.bold('Installing the Agents Computer helper...'));
|
|
41
41
|
try {
|
|
42
42
|
await installComputerHelperMacLocal();
|
|
43
43
|
}
|
|
@@ -57,7 +57,7 @@ export async function runComputerWizard() {
|
|
|
57
57
|
}
|
|
58
58
|
// 3. Guide the two permission grants if not already trusted.
|
|
59
59
|
if (!trusted) {
|
|
60
|
-
console.log(chalk.bold('\nGrant two permissions to "Computer
|
|
60
|
+
console.log(chalk.bold('\nGrant two permissions to "Agents Computer" (one-time):'));
|
|
61
61
|
console.log(' 1. ' + chalk.cyan('Accessibility') + chalk.dim(' — lets it click/type'));
|
|
62
62
|
console.log(' 2. ' + chalk.cyan('Screen Recording') + chalk.dim(' — lets it screenshot windows'));
|
|
63
63
|
console.log(chalk.dim('\nOpening System Settings > Privacy & Security ...'));
|
package/dist/commands/setup.js
CHANGED
|
@@ -16,7 +16,7 @@ import { isGitRepo, cloneIntoExisting, pullRepo } from '../lib/git.js';
|
|
|
16
16
|
import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
|
|
17
17
|
import { AGENTS, agentConfigDirName, getUnmanagedAgentInstalls, countSessionFiles, agentLabel } from '../lib/agents.js';
|
|
18
18
|
import { setGlobalDefault } from '../lib/versions.js';
|
|
19
|
-
import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPath, getPathSetupInstructions } from '../lib/shims.js';
|
|
19
|
+
import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPath, getPathSetupInstructions, assertIsolationBoundary } from '../lib/shims.js';
|
|
20
20
|
import { setHelpSections } from '../lib/help.js';
|
|
21
21
|
import { registerSetupBrowserCommand, runBrowserWizard } from './setup-browser.js';
|
|
22
22
|
import { registerSetupComputerCommand, runComputerWizard } from './setup-computer.js';
|
|
@@ -29,6 +29,11 @@ const HOME = os.homedir();
|
|
|
29
29
|
*/
|
|
30
30
|
async function importAgent(agentId, version) {
|
|
31
31
|
const agent = AGENTS[agentId];
|
|
32
|
+
// setup has its own hand-rolled adoption (rename + symlink inline, rather than
|
|
33
|
+
// calling switchConfigSymlink), so the primitive gates never see it. Check the
|
|
34
|
+
// boundary here, before the first mkdirSync — which would otherwise create the
|
|
35
|
+
// scaffolding this very check reads.
|
|
36
|
+
assertIsolationBoundary(agentId, 'adopt your existing install');
|
|
32
37
|
const configDir = agent.configDir;
|
|
33
38
|
const versionsDir = getVersionsDir();
|
|
34
39
|
const versionHome = path.join(versionsDir, agentId, version, 'home');
|