@phnx-labs/agents-cli 1.22.25 → 1.22.27
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 +427 -0
- package/README.md +35 -3
- package/dist/bin/agents +0 -0
- package/dist/browser.js +14 -4
- package/dist/commands/apply.js +52 -8
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +101 -0
- package/dist/commands/browser.js +35 -0
- package/dist/commands/doctor.js +32 -10
- package/dist/commands/exec.js +17 -16
- package/dist/commands/insights.d.ts +25 -19
- package/dist/commands/insights.js +146 -38
- package/dist/commands/output.js +100 -25
- package/dist/commands/reconnect.d.ts +46 -0
- package/dist/commands/reconnect.js +109 -0
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.d.ts +2 -8
- package/dist/commands/secrets.js +29 -105
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +47 -1
- package/dist/commands/sessions.js +349 -37
- package/dist/commands/setup-secrets.d.ts +1 -0
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup.d.ts +26 -3
- package/dist/commands/setup.js +105 -46
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.d.ts +6 -0
- package/dist/commands/teams.js +46 -3
- package/dist/commands/trends.d.ts +8 -0
- package/dist/commands/trends.js +10 -156
- package/dist/index.js +17 -21
- package/dist/lib/agents.d.ts +11 -0
- package/dist/lib/agents.js +29 -2
- package/dist/lib/analytics/dashboard.d.ts +10 -6
- package/dist/lib/analytics/dashboard.js +6 -4
- package/dist/lib/analytics/mix-commands.d.ts +53 -0
- package/dist/lib/analytics/mix-commands.js +229 -0
- package/dist/lib/analytics/recipes.d.ts +19 -14
- package/dist/lib/analytics/recipes.js +4 -2
- package/dist/lib/auth-health.d.ts +47 -3
- package/dist/lib/auth-health.js +74 -15
- package/dist/lib/bench/index.d.ts +4 -0
- package/dist/lib/bench/index.js +4 -0
- package/dist/lib/bench/runner.d.ts +16 -0
- package/dist/lib/bench/runner.js +111 -0
- package/dist/lib/bench/schema.d.ts +5 -0
- package/dist/lib/bench/schema.js +91 -0
- package/dist/lib/bench/storage.d.ts +5 -0
- package/dist/lib/bench/storage.js +32 -0
- package/dist/lib/bench/types.d.ts +40 -0
- package/dist/lib/bench/types.js +1 -0
- package/dist/lib/browser/ipc.d.ts +26 -0
- package/dist/lib/browser/ipc.js +139 -24
- package/dist/lib/browser/profiles.d.ts +11 -0
- package/dist/lib/browser/profiles.js +1 -1
- package/dist/lib/browser/stream.d.ts +14 -0
- package/dist/lib/browser/stream.js +71 -0
- package/dist/lib/channels/owner-sink.d.ts +27 -0
- package/dist/lib/channels/owner-sink.js +93 -0
- package/dist/lib/claude-account-token.d.ts +2 -0
- package/dist/lib/claude-account-token.js +27 -1
- package/dist/lib/crabbox/cli.d.ts +2 -0
- package/dist/lib/crabbox/cli.js +2 -0
- package/dist/lib/crabbox/lease.js +7 -1
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/devices/connect.d.ts +2 -0
- package/dist/lib/devices/connect.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +10 -1
- package/dist/lib/devices/doctor-findings.js +47 -1
- package/dist/lib/devices/harness-inventory.d.ts +97 -0
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/registry.d.ts +2 -0
- package/dist/lib/devices/ssh-config.js +3 -0
- package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
- package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
- package/dist/lib/exec.bench.d.ts +1 -0
- package/dist/lib/exec.bench.js +186 -0
- package/dist/lib/exec.js +18 -8
- package/dist/lib/fleet/apply.d.ts +59 -3
- package/dist/lib/fleet/apply.js +191 -12
- package/dist/lib/fleet/remote-login.d.ts +4 -3
- package/dist/lib/fleet/remote-login.js +11 -9
- package/dist/lib/fleet/types.d.ts +21 -2
- package/dist/lib/gemini-settings.d.ts +0 -1
- package/dist/lib/gemini-settings.js +12 -7
- package/dist/lib/hooks/cache.js +15 -0
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +10 -6
- package/dist/lib/hosts/dispatch.js +7 -3
- package/dist/lib/hosts/passthrough.d.ts +45 -0
- package/dist/lib/hosts/passthrough.js +59 -8
- package/dist/lib/hosts/progress.d.ts +2 -1
- package/dist/lib/hosts/progress.js +4 -3
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +6 -2
- package/dist/lib/hosts/ready.js +33 -8
- package/dist/lib/hosts/reconcile.d.ts +1 -1
- package/dist/lib/hosts/reconcile.js +16 -7
- package/dist/lib/hosts/reconnect.d.ts +56 -21
- package/dist/lib/hosts/reconnect.js +128 -71
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +3 -5
- package/dist/lib/hosts/remote-cmd.d.ts +17 -0
- package/dist/lib/hosts/remote-cmd.js +29 -0
- package/dist/lib/hosts/tasks.d.ts +2 -0
- package/dist/lib/hosts/types.d.ts +1 -0
- package/dist/lib/hosts/types.js +3 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +9 -6
- package/dist/lib/menubar/install-menubar.js +20 -9
- package/dist/lib/models.d.ts +30 -1
- package/dist/lib/models.js +42 -144
- package/dist/lib/pricing/cost.d.ts +9 -0
- package/dist/lib/pricing/cost.js +24 -0
- package/dist/lib/pricing/index.d.ts +1 -1
- package/dist/lib/pricing/index.js +1 -1
- package/dist/lib/redact.js +8 -3
- package/dist/lib/remote-agents-json.d.ts +32 -0
- package/dist/lib/remote-agents-json.js +47 -16
- package/dist/lib/resource-profiles.js +1 -2
- package/dist/lib/routine-notify-owner.d.ts +102 -0
- package/dist/lib/routine-notify-owner.js +232 -0
- package/dist/lib/routines.d.ts +10 -0
- package/dist/lib/routines.js +14 -2
- package/dist/lib/runner.d.ts +9 -4
- package/dist/lib/runner.js +182 -30
- package/dist/lib/sandbox.d.ts +0 -2
- package/dist/lib/sandbox.js +2 -19
- 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.js +3 -2
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/secrets/push.d.ts +94 -0
- package/dist/lib/secrets/push.js +145 -0
- package/dist/lib/secrets/reaper.d.ts +15 -1
- package/dist/lib/secrets/reaper.js +30 -3
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/db.d.ts +38 -3
- package/dist/lib/session/db.js +273 -16
- package/dist/lib/session/discover.d.ts +22 -1
- package/dist/lib/session/discover.js +239 -51
- package/dist/lib/session/insights.d.ts +37 -0
- package/dist/lib/session/insights.js +219 -9
- package/dist/lib/session/recovery.d.ts +17 -1
- package/dist/lib/session/recovery.js +111 -4
- package/dist/lib/session/remote-list.d.ts +10 -9
- package/dist/lib/session/remote-list.js +25 -23
- package/dist/lib/session/resume-owner.d.ts +55 -0
- package/dist/lib/session/resume-owner.js +69 -0
- package/dist/lib/session/team-filter.d.ts +65 -0
- package/dist/lib/session/team-filter.js +98 -3
- package/dist/lib/session/tool-index.js +133 -22
- package/dist/lib/session/tool-store.d.ts +26 -2
- package/dist/lib/session/tool-store.js +36 -17
- package/dist/lib/session/types.d.ts +33 -0
- package/dist/lib/smart-launch.d.ts +31 -5
- package/dist/lib/smart-launch.js +43 -7
- package/dist/lib/ssh-exec.d.ts +27 -0
- package/dist/lib/ssh-exec.js +42 -3
- package/dist/lib/ssh-tunnel.d.ts +3 -2
- package/dist/lib/ssh-tunnel.js +25 -16
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/teams/agents.d.ts +13 -0
- package/dist/lib/teams/agents.js +75 -7
- package/dist/lib/teams/placement-probe.d.ts +21 -0
- package/dist/lib/teams/placement-probe.js +135 -0
- package/dist/lib/teams/scheduler.d.ts +74 -1
- package/dist/lib/teams/scheduler.js +187 -10
- package/dist/lib/tmux/session.d.ts +8 -0
- package/dist/lib/tmux/session.js +22 -0
- package/dist/lib/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -33,16 +33,18 @@ import { stringWidth, truncateToWidth, padToWidth, terminalWidth } from '../lib/
|
|
|
33
33
|
import { inferSessionState } from '../lib/session/state.js';
|
|
34
34
|
import { discoverSessions, queryIndexedSessions, countSessionsInScope, resolveSessionById, isCompleteSessionId, looksLikeSessionId, searchContentIndex, getSessionRoots, scopeToManaged } from '../lib/session/discover.js';
|
|
35
35
|
import { findSessionsById, querySessions, getSessionById } from '../lib/session/db.js';
|
|
36
|
-
import { filterTeamSessions, safeTeamText } from '../lib/session/team-filter.js';
|
|
36
|
+
import { filterTeamSessions, safeTeamText, groupSessionsByTeam, NO_TEAM_GROUP_KEY, } from '../lib/session/team-filter.js';
|
|
37
37
|
import { parseSession } from '../lib/session/parse.js';
|
|
38
38
|
import { runRemoteSessions, buildForwardedArgs, ensureWholeIndex } from '../lib/session/remote.js';
|
|
39
39
|
import { formatRelativeTime, formatCompactAge, sessionAgeParts } from '../lib/session/relative-time.js';
|
|
40
40
|
import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList, linkPath, linkUrl, shortenModel } from '../lib/session/render.js';
|
|
41
41
|
import { linearIssueUrl } from '../lib/session/linear.js';
|
|
42
|
+
import { sessionOwnerDevice, RESUME_PINNED_ENV } from '../lib/session/resume-owner.js';
|
|
42
43
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
43
44
|
import { AGENTS, colorAgent, resolveAgentName } from '../lib/agents.js';
|
|
44
45
|
import { getShimsDir } from '../lib/state.js';
|
|
45
46
|
import { fuzzyMatch, FUZZY_PRESETS } from '../lib/fuzzy.js';
|
|
47
|
+
import { itemPicker } from '../lib/picker.js';
|
|
46
48
|
import { resolveSessionAlias } from '../lib/session/actor-sidecar.js';
|
|
47
49
|
import { resolveVersionAliasLoose } from '../lib/versions.js';
|
|
48
50
|
import { getAgentsInvocation } from '../lib/daemon.js';
|
|
@@ -57,6 +59,7 @@ import { registerSessionsFavoriteCommand } from './sessions-favorite.js';
|
|
|
57
59
|
import { isFavorite, listFavorites } from '../lib/session/favorites.js';
|
|
58
60
|
import { registerGoCommand } from './go.js';
|
|
59
61
|
import { registerFocusCommand } from './focus.js';
|
|
62
|
+
import { registerReconnectCommand } from './reconnect.js';
|
|
60
63
|
import { registerDetachCommand } from './detach.js';
|
|
61
64
|
import { registerAttachCommand } from './attach.js';
|
|
62
65
|
import { registerSessionsInjectCommand } from './sessions-inject.js';
|
|
@@ -66,6 +69,7 @@ import { registerSessionsImportCommand } from './sessions-import.js';
|
|
|
66
69
|
import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
|
|
67
70
|
import { registerSessionsBackfillCommand } from './sessions-backfill.js';
|
|
68
71
|
import { registerSessionsStatsCommand } from './sessions-stats.js';
|
|
72
|
+
import { registerSessionsInsightsCommand } from './insights.js';
|
|
69
73
|
import { registerSessionsOptimizeCommand } from './sessions-optimize.js';
|
|
70
74
|
import { runBrowserSessions } from '../lib/browser/sessions-list.js';
|
|
71
75
|
import { countToolProgramOccurrences, parseToolProgramCountClause, readToolIndexCoverage, searchToolCalls, TOOL_QUERY_MAX_CLAUSE_BYTES, TOOL_QUERY_MAX_CLAUSES, TOOL_QUERY_MAX_RESULT_SESSIONS, serializeToolSearchEnvelope, toolSearchRemoteReceiveBudget, } from '../lib/session/tool-index.js';
|
|
@@ -1440,6 +1444,96 @@ export function hasNoBrowserDisqualifyingFlags(options, query) {
|
|
|
1440
1444
|
// legacy per-host stream, which prints each peer under its own banner.
|
|
1441
1445
|
(options.host?.length ?? 0) <= 1);
|
|
1442
1446
|
}
|
|
1447
|
+
/** Resolve a typed routine selector by exact name, substring, or one unambiguous typo. */
|
|
1448
|
+
export function resolveRoutineName(query, names) {
|
|
1449
|
+
const normalized = query.trim().toLowerCase();
|
|
1450
|
+
if (!normalized)
|
|
1451
|
+
return null;
|
|
1452
|
+
const exact = names.find((name) => name.toLowerCase() === normalized);
|
|
1453
|
+
if (exact)
|
|
1454
|
+
return exact;
|
|
1455
|
+
const containing = names.filter((name) => name.toLowerCase().includes(normalized));
|
|
1456
|
+
if (containing.length === 1)
|
|
1457
|
+
return containing[0];
|
|
1458
|
+
return fuzzyMatch(query, names, FUZZY_PRESETS.dynamic);
|
|
1459
|
+
}
|
|
1460
|
+
export function buildRoutineRunGroups(sessions) {
|
|
1461
|
+
const byRun = new Map();
|
|
1462
|
+
for (const session of sessions) {
|
|
1463
|
+
const runId = session.routineRunId ?? session.timestamp;
|
|
1464
|
+
(byRun.get(runId) ?? byRun.set(runId, []).get(runId)).push(session);
|
|
1465
|
+
}
|
|
1466
|
+
return [...byRun.entries()]
|
|
1467
|
+
.map(([runId, rows]) => ({
|
|
1468
|
+
runId,
|
|
1469
|
+
timestamp: rows.reduce((latest, row) => (row.lastActivity ?? row.timestamp) > latest ? (row.lastActivity ?? row.timestamp) : latest, rows[0].lastActivity ?? rows[0].timestamp),
|
|
1470
|
+
sessions: rows.sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)),
|
|
1471
|
+
}))
|
|
1472
|
+
.sort((a, b) => (a.timestamp < b.timestamp ? 1 : a.timestamp > b.timestamp ? -1 : a.runId.localeCompare(b.runId)));
|
|
1473
|
+
}
|
|
1474
|
+
export function buildRoutineChoices(sessions) {
|
|
1475
|
+
const byName = new Map();
|
|
1476
|
+
for (const session of sessions) {
|
|
1477
|
+
if (!session.routineName)
|
|
1478
|
+
continue;
|
|
1479
|
+
(byName.get(session.routineName) ?? byName.set(session.routineName, []).get(session.routineName)).push(session);
|
|
1480
|
+
}
|
|
1481
|
+
return [...byName.entries()]
|
|
1482
|
+
.map(([name, rows]) => {
|
|
1483
|
+
const runs = buildRoutineRunGroups(rows);
|
|
1484
|
+
return {
|
|
1485
|
+
name,
|
|
1486
|
+
lastRunAt: runs[0].timestamp,
|
|
1487
|
+
runCount: runs.length,
|
|
1488
|
+
latestRunSessionCount: runs[0].sessions.length,
|
|
1489
|
+
};
|
|
1490
|
+
})
|
|
1491
|
+
.sort((a, b) => (a.lastRunAt < b.lastRunAt ? 1 : a.lastRunAt > b.lastRunAt ? -1 : a.name.localeCompare(b.name)));
|
|
1492
|
+
}
|
|
1493
|
+
async function selectRoutineName(sessions) {
|
|
1494
|
+
const choices = buildRoutineChoices(sessions);
|
|
1495
|
+
const picked = await itemPicker({
|
|
1496
|
+
message: 'Select a routine:',
|
|
1497
|
+
items: choices,
|
|
1498
|
+
filter: (query) => {
|
|
1499
|
+
const normalized = query.trim().toLowerCase();
|
|
1500
|
+
if (!normalized)
|
|
1501
|
+
return choices;
|
|
1502
|
+
return choices.filter((choice) => choice.name.toLowerCase().includes(normalized));
|
|
1503
|
+
},
|
|
1504
|
+
labelFor: (choice) => {
|
|
1505
|
+
const runs = `${choice.runCount} run${choice.runCount === 1 ? '' : 's'}`;
|
|
1506
|
+
const sessions = `${choice.latestRunSessionCount} session${choice.latestRunSessionCount === 1 ? '' : 's'} in latest`;
|
|
1507
|
+
return `${choice.name} ${chalk.gray(`${runs} · ${sessions} · ${formatRelativeTime(choice.lastRunAt)}`)}`;
|
|
1508
|
+
},
|
|
1509
|
+
shortIdFor: (choice) => choice.name,
|
|
1510
|
+
emptyMessage: 'No routines match.',
|
|
1511
|
+
enterHint: 'show sessions',
|
|
1512
|
+
});
|
|
1513
|
+
return picked?.item.name ?? null;
|
|
1514
|
+
}
|
|
1515
|
+
export async function filterSessionsByRoutine(sessions, routine, interactive) {
|
|
1516
|
+
const routineNames = [...new Set(sessions.map((session) => session.routineName).filter((name) => !!name))].sort((a, b) => a.localeCompare(b));
|
|
1517
|
+
let selectedRoutine = null;
|
|
1518
|
+
if (typeof routine === 'string') {
|
|
1519
|
+
selectedRoutine = resolveRoutineName(routine, routineNames);
|
|
1520
|
+
if (!selectedRoutine) {
|
|
1521
|
+
console.error(chalk.red(`No routine matches "${routine}".`));
|
|
1522
|
+
if (routineNames.length > 0)
|
|
1523
|
+
console.error(chalk.gray(`Available routines: ${routineNames.join(', ')}`));
|
|
1524
|
+
process.exitCode = 1;
|
|
1525
|
+
return null;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
else if (interactive) {
|
|
1529
|
+
selectedRoutine = await selectRoutineName(sessions);
|
|
1530
|
+
if (!selectedRoutine)
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
return selectedRoutine
|
|
1534
|
+
? sessions.filter((session) => session.routineName === selectedRoutine)
|
|
1535
|
+
: sessions;
|
|
1536
|
+
}
|
|
1443
1537
|
/** The canonical `ag sessions …` command for a set of flags — the twin of the
|
|
1444
1538
|
* browser's `y` hotkey (see --print-cmd). Normalizes to the stable flag form. */
|
|
1445
1539
|
function canonicalSessionsCommand(query, options) {
|
|
@@ -1466,8 +1560,11 @@ function canonicalSessionsCommand(query, options) {
|
|
|
1466
1560
|
a.push('--teams');
|
|
1467
1561
|
if (options.inTeam)
|
|
1468
1562
|
a.push('--in-team', options.inTeam);
|
|
1469
|
-
if (options.routine)
|
|
1563
|
+
if (options.routine) {
|
|
1470
1564
|
a.push('--routine');
|
|
1565
|
+
if (typeof options.routine === 'string')
|
|
1566
|
+
a.push(options.routine);
|
|
1567
|
+
}
|
|
1471
1568
|
if (options.agent)
|
|
1472
1569
|
a.push('-a', options.agent);
|
|
1473
1570
|
for (const h of options.host ?? [])
|
|
@@ -1908,7 +2005,13 @@ limitSource) {
|
|
|
1908
2005
|
// filter the browser can't represent), or --no-interactive keep the existing
|
|
1909
2006
|
// printed/render paths (agents and scripts unaffected). An explicit --since seeds
|
|
1910
2007
|
// the browser's window so the flag is honored, not swallowed.
|
|
1911
|
-
|
|
2008
|
+
//
|
|
2009
|
+
// `--teams` diverts to the printed team-grouped report (printTeamsView, below):
|
|
2010
|
+
// grouping teammates under their team is a shape the flat fuzzy picker cannot
|
|
2011
|
+
// represent, so like --tree it is a printed listing, not an interactive pick.
|
|
2012
|
+
// --teams --flat/--tree keep their inline table rendering; a search query keeps
|
|
2013
|
+
// the picker so `<term> --teams` still searches.
|
|
2014
|
+
if (isBareBrowserListing(options, query) && !options.teams) {
|
|
1912
2015
|
const { runSessionBrowser, bareBrowserSeed } = await import('./sessions-browser.js');
|
|
1913
2016
|
await runSessionBrowser(bareBrowserSeed({
|
|
1914
2017
|
teams: options.teams,
|
|
@@ -1955,13 +2058,13 @@ limitSource) {
|
|
|
1955
2058
|
const wantsRender = !toolEvidenceMode && (mode === 'markdown' || hasAnyFilter(filterOpts));
|
|
1956
2059
|
// Artifact-list or artifact-read paths: widen scope and resolve session globally.
|
|
1957
2060
|
if ((options.artifacts || options.artifact !== undefined) && searchQuery) {
|
|
1958
|
-
await renderArtifactsGlobal(searchQuery, options.artifacts ?? false, options.artifact,
|
|
2061
|
+
await renderArtifactsGlobal(searchQuery, options.artifacts ?? false, options.artifact, artifactLookupScope(options.agent, options.project, options.routine));
|
|
1959
2062
|
return;
|
|
1960
2063
|
}
|
|
1961
2064
|
// When the user explicitly asks to render (via mode flag), resolve the
|
|
1962
2065
|
// query globally so sessions outside the default cwd/30d window are found.
|
|
1963
2066
|
if (wantsRender && searchQuery) {
|
|
1964
|
-
await renderOneSession(searchQuery, mode, { agent: options.agent, project: options.project, filter: filterOpts, redact: options.redact, local: options.local, hosts: options.host });
|
|
2067
|
+
await renderOneSession(searchQuery, mode, { agent: options.agent, project: options.project, routine: options.routine, filter: filterOpts, redact: options.redact, local: options.local, hosts: options.host });
|
|
1965
2068
|
return;
|
|
1966
2069
|
}
|
|
1967
2070
|
// Interactive picker loads a deep pool but shows only recent sessions
|
|
@@ -2144,6 +2247,14 @@ limitSource) {
|
|
|
2144
2247
|
const hiddenCount = options.teams || options.inTeam
|
|
2145
2248
|
? 0
|
|
2146
2249
|
: countSessionsInScope({ ...scope, onlyTeamOrigin: true });
|
|
2250
|
+
// A typed routine scope must apply before smart ID routing. Otherwise an ID
|
|
2251
|
+
// from another routine resolves and renders before the routine filter below.
|
|
2252
|
+
if (typeof options.routine === 'string') {
|
|
2253
|
+
const filteredByRoutine = await filterSessionsByRoutine(sessions, options.routine, false);
|
|
2254
|
+
if (!filteredByRoutine)
|
|
2255
|
+
return;
|
|
2256
|
+
sessions = filteredByRoutine;
|
|
2257
|
+
}
|
|
2147
2258
|
// Smart ID routing: a bare query that resolves to one session renders
|
|
2148
2259
|
// directly. If nothing matches in the scoped window and the query looks
|
|
2149
2260
|
// like a session ID, widen to global scope (incl. Claude /resume history).
|
|
@@ -2161,11 +2272,17 @@ limitSource) {
|
|
|
2161
2272
|
return;
|
|
2162
2273
|
}
|
|
2163
2274
|
if (idMatches.length === 0 && looksLikeSessionId(searchQuery)) {
|
|
2164
|
-
await renderOneSession(searchQuery, mode, { agent: options.agent, project: options.project, filter: filterOpts, redact: options.redact, local: options.local, hosts: options.host });
|
|
2275
|
+
await renderOneSession(searchQuery, mode, { agent: options.agent, project: options.project, routine: options.routine, filter: filterOpts, redact: options.redact, local: options.local, hosts: options.host });
|
|
2165
2276
|
return;
|
|
2166
2277
|
}
|
|
2167
2278
|
}
|
|
2168
2279
|
if (options.json) {
|
|
2280
|
+
if (options.routine === true) {
|
|
2281
|
+
const filteredByRoutine = await filterSessionsByRoutine(sessions, options.routine, false);
|
|
2282
|
+
if (!filteredByRoutine)
|
|
2283
|
+
return;
|
|
2284
|
+
sessions = filteredByRoutine;
|
|
2285
|
+
}
|
|
2169
2286
|
// An id-shaped query resolves by id ONLY — the same rule the render path
|
|
2170
2287
|
// uses (resolveSessionQuery). Without this a `--json <uuid>` (as issued by
|
|
2171
2288
|
// the fleet locate sweep, which runs `sessions <uuid> --json --local` on
|
|
@@ -2208,6 +2325,12 @@ limitSource) {
|
|
|
2208
2325
|
fanSpinner?.stop();
|
|
2209
2326
|
}
|
|
2210
2327
|
}
|
|
2328
|
+
if (options.routine) {
|
|
2329
|
+
const filteredByRoutine = await filterSessionsByRoutine(sessions, options.routine, isInteractive);
|
|
2330
|
+
if (!filteredByRoutine)
|
|
2331
|
+
return;
|
|
2332
|
+
sessions = filteredByRoutine;
|
|
2333
|
+
}
|
|
2211
2334
|
if (sessions.length === 0) {
|
|
2212
2335
|
if (pathFilter) {
|
|
2213
2336
|
console.log(chalk.gray(`No sessions found for ${pathFilter}.`));
|
|
@@ -2223,11 +2346,24 @@ limitSource) {
|
|
|
2223
2346
|
}
|
|
2224
2347
|
return;
|
|
2225
2348
|
}
|
|
2349
|
+
// `--teams` prints the team-grouped report: teammates under their team (with
|
|
2350
|
+
// spawner + spawn time), bare SDK spawns in their own bucket. --flat/--tree
|
|
2351
|
+
// keep their inline table rendering; a search query keeps the picker. Placed
|
|
2352
|
+
// before the project overview and picker so it wins the bare `--teams` case.
|
|
2353
|
+
if (options.teams && !options.flat && !options.tree && !searchQuery && !pathFilter) {
|
|
2354
|
+
const liveIndex = await maybeLiveIndex(options);
|
|
2355
|
+
printTeamsView(sessions, liveIndex, hiddenUnmanaged);
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2226
2358
|
// The grouped project overview is the bare interactive default: a scannable
|
|
2227
2359
|
// dashboard of the whole fleet grouped by project, newest-active first.
|
|
2228
2360
|
// Interact/resume via `agents sessions <project>` or `agents sessions resume`.
|
|
2229
2361
|
if (wantsOverview) {
|
|
2230
2362
|
const liveIndex = await maybeLiveIndex(options);
|
|
2363
|
+
if (options.routine) {
|
|
2364
|
+
printRoutineRunOverview(sessions, liveIndex, { hiddenCount, hiddenUnmanaged });
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2231
2367
|
// Per-project row cap is fixed (--limit carries a default of 50 and drives
|
|
2232
2368
|
// the fetch pool, not the display); `--all` expands every group instead.
|
|
2233
2369
|
printSessionOverview(sessions, hiddenCount, liveIndex, { perProjectCap: OVERVIEW_ROWS_PER_PROJECT, expand: !!options.all, hiddenUnmanaged });
|
|
@@ -2551,6 +2687,25 @@ function printSessionOverview(pool, hiddenCount, liveIndex, opts) {
|
|
|
2551
2687
|
if (opts.hiddenUnmanaged)
|
|
2552
2688
|
console.log(chalk.gray(formatUnmanagedHiddenFooter(opts.hiddenUnmanaged)));
|
|
2553
2689
|
}
|
|
2690
|
+
export function printRoutineRunOverview(sessions, liveIndex, opts = {}) {
|
|
2691
|
+
const groups = buildRoutineRunGroups(sessions);
|
|
2692
|
+
console.log(chalk.gray(`${sessions.length} session${sessions.length === 1 ? '' : 's'} · ${groups.length} routine run${groups.length === 1 ? '' : 's'} · newest run first\n`));
|
|
2693
|
+
let first = true;
|
|
2694
|
+
for (const group of groups) {
|
|
2695
|
+
if (!first)
|
|
2696
|
+
console.log();
|
|
2697
|
+
first = false;
|
|
2698
|
+
console.log(`${chalk.cyan('▸')} ${chalk.cyan.bold(group.runId)} ` +
|
|
2699
|
+
`${chalk.gray(`${group.sessions.length} session${group.sessions.length === 1 ? '' : 's'} · ${formatRelativeTime(group.timestamp)}`)}`);
|
|
2700
|
+
for (const session of group.sessions)
|
|
2701
|
+
console.log(treeSessionRow(session, liveIndex?.get(session.id)));
|
|
2702
|
+
}
|
|
2703
|
+
console.log(chalk.gray('\nGrouped by routine run id and timestamp.'));
|
|
2704
|
+
if (opts.hiddenCount)
|
|
2705
|
+
console.log(chalk.gray(formatTeamHiddenFooter(opts.hiddenCount)));
|
|
2706
|
+
if (opts.hiddenUnmanaged)
|
|
2707
|
+
console.log(chalk.gray(formatUnmanagedHiddenFooter(opts.hiddenUnmanaged)));
|
|
2708
|
+
}
|
|
2554
2709
|
function printSessionTable(sessions, hiddenCount = 0, tree = false, liveIndex) {
|
|
2555
2710
|
if (tree) {
|
|
2556
2711
|
// Group by directory; drop the id/version columns from view. The short id
|
|
@@ -2597,6 +2752,118 @@ function printSessionTable(sessions, hiddenCount = 0, tree = false, liveIndex) {
|
|
|
2597
2752
|
console.log(chalk.gray(formatTeamHiddenFooter(hiddenCount)));
|
|
2598
2753
|
}
|
|
2599
2754
|
}
|
|
2755
|
+
/** Width of the mode cell (plan/edit/auto/skip) in a team-member row. */
|
|
2756
|
+
const TEAM_MODE_W = 5;
|
|
2757
|
+
/** Longest teammate handle folded into a team-member row before it truncates. */
|
|
2758
|
+
const TEAM_HANDLE_W = 16;
|
|
2759
|
+
/**
|
|
2760
|
+
* One row under a team group: `shortId · agent · mode · handle · doing · time`.
|
|
2761
|
+
* Unlike {@link flatSessionRow}/{@link treeSessionRow} it drops the
|
|
2762
|
+
* `[team/handle]` topic prefix — the group header already names the team — and
|
|
2763
|
+
* promotes the teammate's mode and handle to their own cells, the richer
|
|
2764
|
+
* identity the `--teams` view exists to show (RUSH-1997).
|
|
2765
|
+
*/
|
|
2766
|
+
function teamMemberRow(session, live) {
|
|
2767
|
+
const agentColor = colorAgent(session.agent);
|
|
2768
|
+
const origin = session.teamOrigin;
|
|
2769
|
+
const age = sessionAgeParts(session.timestamp, session.lastActivity);
|
|
2770
|
+
const handle = safeTeamText(origin?.handle) ?? session.shortId;
|
|
2771
|
+
const mode = safeTeamText(origin?.mode) ?? '';
|
|
2772
|
+
const { glyph, preview } = liveGlyphAndPreview(live);
|
|
2773
|
+
// Match flatSessionRow: a live preview already folds ActiveSession.todos; a
|
|
2774
|
+
// resting row surfaces SessionMeta.todos when the scan attached it.
|
|
2775
|
+
const restingTodo = !live ? formatTodoCompact(session.todos) : '';
|
|
2776
|
+
const doing = [restingTodo, preview || session.topic || ''].filter(Boolean).join(' · ');
|
|
2777
|
+
const shownHandle = truncateToWidth(handle, TEAM_HANDLE_W);
|
|
2778
|
+
const head = doing ? `${shownHandle} · ${doing}` : shownHandle;
|
|
2779
|
+
const badges = signalBadges(metaSignals(session));
|
|
2780
|
+
const badgeW = badges ? stringWidth(badges) + 1 : 0;
|
|
2781
|
+
const { cell: statusCell, width: statusW } = liveStatusCell(live);
|
|
2782
|
+
const glyphW = glyph ? 2 : 0;
|
|
2783
|
+
const baseTopicW = terminalWidth() - (2 + 9 + 8 + (TEAM_MODE_W + 1)) - glyphW - statusW - badgeW - stringWidth(age.last) - 1;
|
|
2784
|
+
const when = timeCell(age, baseTopicW);
|
|
2785
|
+
const topicW = Math.max(12, baseTopicW - when.extraW);
|
|
2786
|
+
return (' ' +
|
|
2787
|
+
chalk.dim(padToWidth(session.shortId, 9)) +
|
|
2788
|
+
agentColor(padToWidth(truncateToWidth(session.agent, 7), 8)) +
|
|
2789
|
+
chalk.yellow(padToWidth(truncateToWidth(mode || '-', TEAM_MODE_W), TEAM_MODE_W + 1)) +
|
|
2790
|
+
(badges ? badges + ' ' : '') +
|
|
2791
|
+
(glyph ? glyph + ' ' : '') +
|
|
2792
|
+
statusCell +
|
|
2793
|
+
padToWidth(chalk.white(truncateToWidth(head, topicW)), topicW) +
|
|
2794
|
+
' ' +
|
|
2795
|
+
when.text);
|
|
2796
|
+
}
|
|
2797
|
+
/** The header line for one team group (or the residual no-team bucket). */
|
|
2798
|
+
function teamGroupHeader(g, glyph, labelById) {
|
|
2799
|
+
const count = chalk.gray(`(${g.sessions.length})`);
|
|
2800
|
+
if (g.kind === 'noTeam') {
|
|
2801
|
+
// These carry the isTeamOrigin flag but no teammate meta.json — a Task
|
|
2802
|
+
// sub-agent's transcript is never indexed, so in practice this bucket is
|
|
2803
|
+
// headless `agents run` spawns or teammates whose meta aged out. Named
|
|
2804
|
+
// honestly rather than claimed as real teammates.
|
|
2805
|
+
return (chalk.magenta('▸') + ' ' + chalk.magenta.bold(NO_TEAM_GROUP_KEY) + ' ' + count +
|
|
2806
|
+
' ' + chalk.gray('team-flagged spawns with no team record (`agents run`, or aged-out teammates)'));
|
|
2807
|
+
}
|
|
2808
|
+
const bits = [chalk.cyan('▸') + ' ' + chalk.cyan.bold(g.key), count];
|
|
2809
|
+
if (glyph)
|
|
2810
|
+
bits.push(glyph);
|
|
2811
|
+
if (g.spawnerSessionId) {
|
|
2812
|
+
const who = labelById.get(g.spawnerSessionId) ?? g.spawnerSessionId.slice(0, 8);
|
|
2813
|
+
bits.push(chalk.gray(`by ${who}`));
|
|
2814
|
+
}
|
|
2815
|
+
bits.push(chalk.gray(`spawned ${formatRelativeTime(g.firstSpawnTs)}`));
|
|
2816
|
+
return bits.join(' ');
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* The `agents sessions --teams` view: team-origin sessions grouped by team, the
|
|
2820
|
+
* richer display RUSH-1997 asks for. Each named team names its spawner (the
|
|
2821
|
+
* orchestrator session that created it) and spawn time, with its teammates'
|
|
2822
|
+
* mode + handle under it; team-flagged spawns that carry no teammate record fall
|
|
2823
|
+
* into a trailing no-team bucket, so a real teammate and a bare spawn are never
|
|
2824
|
+
* conflated. A printed report like `--tree`, not an interactive pick.
|
|
2825
|
+
*/
|
|
2826
|
+
function printTeamsView(pool, liveIndex, hiddenUnmanaged = 0) {
|
|
2827
|
+
const groups = groupSessionsByTeam(pool);
|
|
2828
|
+
if (groups.length === 0) {
|
|
2829
|
+
console.log(chalk.gray('No team sessions found.'));
|
|
2830
|
+
console.log(chalk.gray('Team sessions are spawned by `agents teams` — see `agents teams status`.'));
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
// Resolve a spawner session id to its human label from the pool. The
|
|
2834
|
+
// orchestrator may or may not be in view; fall back to the short id, the same
|
|
2835
|
+
// degradation the --active teams rows use (active.ts resolveOrchestratorLabels).
|
|
2836
|
+
const labelById = new Map();
|
|
2837
|
+
for (const s of pool) {
|
|
2838
|
+
const label = s.label || s.topic;
|
|
2839
|
+
if (label)
|
|
2840
|
+
labelById.set(s.id, cleanPreview(label));
|
|
2841
|
+
}
|
|
2842
|
+
const total = groups.reduce((n, g) => n + g.sessions.length, 0);
|
|
2843
|
+
const teamCount = groups.filter((g) => g.kind === 'team').length;
|
|
2844
|
+
const noTeam = groups.find((g) => g.kind === 'noTeam');
|
|
2845
|
+
const parts = [
|
|
2846
|
+
`${total} team session${total === 1 ? '' : 's'}`,
|
|
2847
|
+
`${teamCount} team${teamCount === 1 ? '' : 's'}`,
|
|
2848
|
+
];
|
|
2849
|
+
if (noTeam)
|
|
2850
|
+
parts.push(`${noTeam.sessions.length} without a team record`);
|
|
2851
|
+
console.log(chalk.gray(parts.join(' · ') + '\n'));
|
|
2852
|
+
let first = true;
|
|
2853
|
+
for (const g of groups) {
|
|
2854
|
+
if (!first)
|
|
2855
|
+
console.log();
|
|
2856
|
+
first = false;
|
|
2857
|
+
const { glyph } = liveGlyphAndPreview(liveIndex?.get(g.sessions[0].id));
|
|
2858
|
+
console.log(teamGroupHeader(g, glyph, labelById));
|
|
2859
|
+
for (const s of g.sessions)
|
|
2860
|
+
console.log(teamMemberRow(s, liveIndex?.get(s.id)));
|
|
2861
|
+
}
|
|
2862
|
+
console.log();
|
|
2863
|
+
console.log(chalk.gray('newest-active team first · resume any row with `agents sessions resume <id>`'));
|
|
2864
|
+
if (hiddenUnmanaged > 0)
|
|
2865
|
+
console.log(chalk.gray(formatUnmanagedHiddenFooter(hiddenUnmanaged)));
|
|
2866
|
+
}
|
|
2600
2867
|
function buildFilterOptions(options) {
|
|
2601
2868
|
const opts = {};
|
|
2602
2869
|
if (options.include)
|
|
@@ -2971,15 +3238,6 @@ export async function pickSessionInteractive(sessions, message = 'Search session
|
|
|
2971
3238
|
throw err;
|
|
2972
3239
|
}
|
|
2973
3240
|
}
|
|
2974
|
-
/**
|
|
2975
|
-
* The machine a picked session lives on when its transcript is on that peer's
|
|
2976
|
-
* disk (folded in over the live fan-out), else undefined. Keys off `_remote`,
|
|
2977
|
-
* NOT `machine !== local`: a synced mirror is machine-tagged too, but its file
|
|
2978
|
-
* is a local mirror path, so it must be read/resumed locally like any other.
|
|
2979
|
-
*/
|
|
2980
|
-
function remoteMachineOf(session) {
|
|
2981
|
-
return session._remote ? session.machine : undefined;
|
|
2982
|
-
}
|
|
2983
3241
|
/** True when the peer wasn't a dialable device; prints one clear line so a
|
|
2984
3242
|
* remote pick never dead-ends silently. */
|
|
2985
3243
|
function warnNoPeerTarget(machine, session) {
|
|
@@ -3005,31 +3263,50 @@ export async function handlePickedSession(picked) {
|
|
|
3005
3263
|
console.log(chalk.gray(`Watch for it with: agents sessions --active${picked.session.machine ? ` --host ${picked.session.machine}` : ''}`));
|
|
3006
3264
|
return;
|
|
3007
3265
|
}
|
|
3008
|
-
//
|
|
3009
|
-
//
|
|
3010
|
-
//
|
|
3011
|
-
//
|
|
3012
|
-
|
|
3013
|
-
if (
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
warnNoPeerTarget(remote, picked.session);
|
|
3018
|
-
}
|
|
3019
|
-
else {
|
|
3020
|
-
console.log(chalk.gray(`Resuming ${picked.session.shortId} on ${remote} over SSH...`));
|
|
3021
|
-
const rc = await runOnPeer(['sessions', 'resume', picked.session.shortId], remote, { tty: true });
|
|
3266
|
+
// Reading and resuming are on DIFFERENT machines' terms, and conflating them
|
|
3267
|
+
// is what RUSH-2022 was. Reading follows the FILE: a synced mirror sits on
|
|
3268
|
+
// this disk, so only a live fan-out row (`_remote`) has to be read on the
|
|
3269
|
+
// peer. Resuming follows the HARNESS STATE, which is on the owning machine
|
|
3270
|
+
// whatever the transcript's location — a mirror included.
|
|
3271
|
+
if (picked.action === 'view') {
|
|
3272
|
+
const readFrom = picked.session._remote ? picked.session.machine : undefined;
|
|
3273
|
+
if (readFrom) {
|
|
3274
|
+
const rc = await runOnPeer(['sessions', picked.session.shortId, '--markdown'], readFrom);
|
|
3022
3275
|
if (rc === 'no-target')
|
|
3023
|
-
warnNoPeerTarget(
|
|
3276
|
+
warnNoPeerTarget(readFrom, picked.session);
|
|
3277
|
+
return;
|
|
3024
3278
|
}
|
|
3025
|
-
return;
|
|
3026
|
-
}
|
|
3027
|
-
if (picked.action === 'view') {
|
|
3028
3279
|
await renderSession(picked.session, 'summary', {});
|
|
3029
3280
|
return;
|
|
3030
3281
|
}
|
|
3282
|
+
if (await resumeOnOwnerIfRemote(picked.session))
|
|
3283
|
+
return;
|
|
3031
3284
|
await resumeSessionInPlace(picked.session);
|
|
3032
3285
|
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Resume `session` on the machine that owns it, when that is not this one.
|
|
3288
|
+
* Returns true when it handled the resume (hopped, or reported an unreachable
|
|
3289
|
+
* owner), false when the session is local and the caller should take over here.
|
|
3290
|
+
*
|
|
3291
|
+
* The one hop for a foreground, one-session-at-a-time resume — shared by the
|
|
3292
|
+
* `agents sessions` picker and by `sessions resume`'s no-tab-backend path, which
|
|
3293
|
+
* would otherwise reach `resumeSessionInPlace` and be refused (RUSH-2022).
|
|
3294
|
+
*/
|
|
3295
|
+
export async function resumeOnOwnerIfRemote(session) {
|
|
3296
|
+
const owner = sessionOwnerDevice(session);
|
|
3297
|
+
if (!owner)
|
|
3298
|
+
return false;
|
|
3299
|
+
console.log(chalk.gray(`Resuming ${session.shortId} on ${owner} over SSH...`));
|
|
3300
|
+
// `agents resume <id>` is the strict single-session path — one pick, one
|
|
3301
|
+
// resume. (`sessions resume <shortId>` would re-open a picker over there.)
|
|
3302
|
+
const rc = await runOnPeer(['resume', session.id], owner, {
|
|
3303
|
+
tty: true,
|
|
3304
|
+
env: { [RESUME_PINNED_ENV]: '1' },
|
|
3305
|
+
});
|
|
3306
|
+
if (rc === 'no-target')
|
|
3307
|
+
warnNoPeerTarget(owner, session);
|
|
3308
|
+
return true;
|
|
3309
|
+
}
|
|
3033
3310
|
/**
|
|
3034
3311
|
* Resume a session in the current terminal — a foreground takeover of this
|
|
3035
3312
|
* process. Used by the single-select picker and by `sessions resume` when the
|
|
@@ -3038,6 +3315,20 @@ export async function handlePickedSession(picked) {
|
|
|
3038
3315
|
* version-pinned launcher is genuinely missing.
|
|
3039
3316
|
*/
|
|
3040
3317
|
export async function resumeSessionInPlace(session) {
|
|
3318
|
+
// This function is the LOCAL takeover, and every caller is responsible for
|
|
3319
|
+
// routing a peer-owned session before it gets here (the picker above,
|
|
3320
|
+
// `agents resume`, `sessions attach`). Reaching it with one anyway means a
|
|
3321
|
+
// caller skipped that step, so refuse rather than start the harness against
|
|
3322
|
+
// state this box does not have — the `fs.existsSync(session.cwd)` fallback
|
|
3323
|
+
// just below is exactly how that used to pass unnoticed, quietly swapping in
|
|
3324
|
+
// `process.cwd()` when the recorded directory was a peer's path (RUSH-2022).
|
|
3325
|
+
const owner = sessionOwnerDevice(session);
|
|
3326
|
+
if (owner) {
|
|
3327
|
+
console.error(chalk.red(`Session ${session.shortId} belongs to ${owner} — it cannot resume on this machine.`));
|
|
3328
|
+
console.error(chalk.gray(` Resume it there: agents resume ${session.id}`));
|
|
3329
|
+
process.exitCode = 1;
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3041
3332
|
const cwd = session.cwd && fs.existsSync(session.cwd)
|
|
3042
3333
|
? session.cwd
|
|
3043
3334
|
: process.cwd();
|
|
@@ -3457,7 +3748,7 @@ function scoreSessionQuery(session, terms) {
|
|
|
3457
3748
|
* the project you specified AND elsewhere, producing an ambiguity error
|
|
3458
3749
|
* even though the user already pointed at the correct scope.
|
|
3459
3750
|
*/
|
|
3460
|
-
function applyScopeFilters(sessions, scope) {
|
|
3751
|
+
export function applyScopeFilters(sessions, scope) {
|
|
3461
3752
|
let filtered = sessions;
|
|
3462
3753
|
if (scope.project) {
|
|
3463
3754
|
const projectQuery = scope.project.toLowerCase();
|
|
@@ -3480,8 +3771,21 @@ function applyScopeFilters(sessions, scope) {
|
|
|
3480
3771
|
return true;
|
|
3481
3772
|
});
|
|
3482
3773
|
}
|
|
3774
|
+
if (scope.routine) {
|
|
3775
|
+
filtered = filtered.filter((session) => session.origin === 'routine');
|
|
3776
|
+
if (typeof scope.routine === 'string') {
|
|
3777
|
+
const names = [...new Set(filtered.map((session) => session.routineName).filter((name) => !!name))];
|
|
3778
|
+
const selected = resolveRoutineName(scope.routine, names);
|
|
3779
|
+
filtered = selected
|
|
3780
|
+
? filtered.filter((session) => session.routineName === selected)
|
|
3781
|
+
: [];
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3483
3784
|
return filtered;
|
|
3484
3785
|
}
|
|
3786
|
+
export function artifactLookupScope(agent, project, routine) {
|
|
3787
|
+
return { agent, project, routine };
|
|
3788
|
+
}
|
|
3485
3789
|
async function renderArtifactsGlobal(query, listAll, name, scope) {
|
|
3486
3790
|
const spinner = ora().start();
|
|
3487
3791
|
const tracker = createScanProgressTracker(FIND_VERBS, 'session', spinner);
|
|
@@ -3927,9 +4231,9 @@ export function registerSessionsCommands(program) {
|
|
|
3927
4231
|
.option('--opencode', 'Shorthand for --agent opencode')
|
|
3928
4232
|
.option('--all', 'Widen every non-status filter to "all": every directory (not just this project) and all time (no window cap). Status filters like --active still compose; -a/--device/--since still narrow their axis.')
|
|
3929
4233
|
.option('--unmanaged', "Also show sessions from your own ~/.<agent> installs (hidden once agents-cli manages that agent)")
|
|
3930
|
-
.option('--team, --teams', '
|
|
4234
|
+
.option('--team, --teams', 'Show team-spawned sessions (hidden by default), grouped by team — each team names its spawner and spawn time, teammates show their mode + handle, and team-flagged spawns with no teammate record sink into a (no team) bucket. --flat/--tree keep the plain inline table')
|
|
3931
4235
|
.option('--in-team <name>', "Only this team: the session that spawned it plus (with --teams) its teammates. Spans every directory and all time, since a team's worktrees and history sit outside the default window.")
|
|
3932
|
-
.option('--routine', 'Show
|
|
4236
|
+
.option('--routines, --routine [name]', 'Show routine-run sessions; omit the name on a TTY to pick one interactively (fuzzy name matching)')
|
|
3933
4237
|
.option('-p, --project <name>', 'Filter by project name (searches across all directories)')
|
|
3934
4238
|
.option('--skill <name>', 'Only sessions that invoked this skill (matches a bare name or a namespaced plugin skill\'s short name, e.g. --skill design finds rush:design)')
|
|
3935
4239
|
.option('--plugin <name>', 'Only sessions that used a skill/command owned by this plugin')
|
|
@@ -4012,12 +4316,18 @@ export function registerSessionsCommands(program) {
|
|
|
4012
4316
|
# Search across every directory, not just this project
|
|
4013
4317
|
agents sessions "topic" --all
|
|
4014
4318
|
|
|
4319
|
+
# Team-spawned sessions, grouped by team (spawner + spawn time per team,
|
|
4320
|
+
# teammate mode/handle per row; team-flagged spawns with no team record
|
|
4321
|
+
# in a trailing (no team) bucket)
|
|
4322
|
+
agents sessions --teams
|
|
4323
|
+
|
|
4015
4324
|
# Who spawned which team: an orchestrator row carries team:<name>, and a
|
|
4016
4325
|
# teammate row [<team>/<handle>]. --in-team narrows to one team's lineage.
|
|
4017
4326
|
agents sessions --in-team redesign --teams
|
|
4018
4327
|
|
|
4019
4328
|
# Show routine-run sessions and open one by routine run id
|
|
4020
4329
|
agents sessions --routine --all
|
|
4330
|
+
agents sessions --routine nightly-review --all
|
|
4021
4331
|
agents sessions 2026-07-21T10-30-00-000Z
|
|
4022
4332
|
|
|
4023
4333
|
# Export for analysis
|
|
@@ -4064,7 +4374,7 @@ export function registerSessionsCommands(program) {
|
|
|
4064
4374
|
- --first and --last are mutually exclusive.
|
|
4065
4375
|
- A filter flag (--include/--exclude/--first/--last) without --markdown/--json defaults to --markdown output.
|
|
4066
4376
|
- --cloud sources from Rush Cloud captured runs instead of local disk.
|
|
4067
|
-
- --routine shows
|
|
4377
|
+
- --routine [name] shows transcripts archived from routine runs. On a TTY, omit the name to pick a routine; a name accepts exact, substring, or unambiguous typo matches. --routines is an alias. Routine rows also resolve by run id.
|
|
4068
4378
|
- Without --teams, team-spawned sessions are hidden by default.
|
|
4069
4379
|
`,
|
|
4070
4380
|
});
|
|
@@ -4082,6 +4392,7 @@ export function registerSessionsCommands(program) {
|
|
|
4082
4392
|
registerSessionsFavoriteCommand(sessionsCmd);
|
|
4083
4393
|
registerGoCommand(sessionsCmd);
|
|
4084
4394
|
registerFocusCommand(sessionsCmd);
|
|
4395
|
+
registerReconnectCommand(sessionsCmd);
|
|
4085
4396
|
registerDetachCommand(sessionsCmd);
|
|
4086
4397
|
registerAttachCommand(sessionsCmd);
|
|
4087
4398
|
registerSessionsInjectCommand(sessionsCmd);
|
|
@@ -4092,6 +4403,7 @@ export function registerSessionsCommands(program) {
|
|
|
4092
4403
|
registerSessionsMigrationsCommand(sessionsCmd);
|
|
4093
4404
|
registerSessionsBackfillCommand(sessionsCmd);
|
|
4094
4405
|
registerSessionsStatsCommand(sessionsCmd);
|
|
4406
|
+
registerSessionsInsightsCommand(sessionsCmd);
|
|
4095
4407
|
registerSessionsOptimizeCommand(sessionsCmd);
|
|
4096
4408
|
// Observe-umbrella alias (Phase 3): roster → sessions --active.
|
|
4097
4409
|
registerSessionsObserveAliases(program);
|
|
@@ -15,6 +15,7 @@ interface SetupSecretsOptions {
|
|
|
15
15
|
iUnderstand?: boolean;
|
|
16
16
|
force?: boolean;
|
|
17
17
|
}
|
|
18
|
+
export declare function setupSecretsPrefsPath(): string;
|
|
18
19
|
/** The wizard's `--policy` shares the canonical parser with `agents secrets
|
|
19
20
|
* policy`, so it accepts `hold` (and the `daily`/`session` aliases) rather than
|
|
20
21
|
* carrying its own copy that knows only the retired vocabulary. `parsePolicyOpt`
|
|
@@ -15,7 +15,7 @@ import { parsePolicyOpt } from './secrets.js';
|
|
|
15
15
|
function defaultBackendForPlatform(platform = process.platform) {
|
|
16
16
|
return platform === 'darwin' ? 'keychain' : 'file';
|
|
17
17
|
}
|
|
18
|
-
function setupSecretsPrefsPath() {
|
|
18
|
+
export function setupSecretsPrefsPath() {
|
|
19
19
|
return path.join(getHistoryDir(), 'setup', 'secrets.json');
|
|
20
20
|
}
|
|
21
21
|
function parseBackend(raw) {
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* ~/.agents/.system/ and installs agent CLIs with resource syncing.
|
|
6
6
|
*/
|
|
7
7
|
import type { Command } from 'commander';
|
|
8
|
-
|
|
9
|
-
export declare function runSetup(program: Command, options?: {
|
|
8
|
+
interface RunSetupOptions {
|
|
10
9
|
force?: boolean;
|
|
11
10
|
suppressFooter?: boolean;
|
|
12
11
|
systemRepo?: boolean;
|
|
13
|
-
|
|
12
|
+
runHub?: () => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** First-run setup. Clones ~/.agents/.system/ from the system repo if needed. */
|
|
15
|
+
export declare function runSetup(program: Command, options?: RunSetupOptions): Promise<void>;
|
|
14
16
|
/**
|
|
15
17
|
* Ensure the system repo exists before running a command that needs it.
|
|
16
18
|
* If ~/.agents/.system/ is not a git repo AND we're in an interactive TTY,
|
|
@@ -18,5 +20,26 @@ export declare function runSetup(program: Command, options?: {
|
|
|
18
20
|
* error and exit.
|
|
19
21
|
*/
|
|
20
22
|
export declare function ensureInitialized(program: Command): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Interactive "what else do you want to set up?" menu shown after the bare
|
|
25
|
+
* `agents setup` finishes on a TTY. Each pick runs that capability's guided
|
|
26
|
+
* wizard. Never throws — a cancel or an optional wizard's error just skips the
|
|
27
|
+
* rest and lets core setup complete.
|
|
28
|
+
*/
|
|
29
|
+
export type SetupPhase = 'browser' | 'computer' | 'share' | 'secrets' | 'fleet' | 'watchdog' | 'preferences';
|
|
30
|
+
export type SetupStatusState = 'ready' | 'missing' | 'n/a';
|
|
31
|
+
export interface SetupStatusRow {
|
|
32
|
+
phase: 'core' | SetupPhase;
|
|
33
|
+
state: SetupStatusState;
|
|
34
|
+
detail: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function getSetupStatus(): Promise<SetupStatusRow[]>;
|
|
37
|
+
export declare function renderSetupStatus(rows: SetupStatusRow[]): void;
|
|
38
|
+
export declare function runSetupHub(deps?: {
|
|
39
|
+
interactive?: boolean;
|
|
40
|
+
selectPhase?: (rows: SetupStatusRow[]) => Promise<SetupPhase | 'exit'>;
|
|
41
|
+
runPhase?: (phase: SetupPhase) => Promise<void>;
|
|
42
|
+
}): Promise<void>;
|
|
21
43
|
/** Register the `agents setup` command and its capability subcommands. */
|
|
22
44
|
export declare function registerSetupCommand(program: Command): void;
|
|
45
|
+
export {};
|