@phnx-labs/agents-cli 1.22.29 → 1.22.30
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 +82 -0
- package/README.md +39 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/accounts.d.ts +13 -0
- package/dist/commands/accounts.js +32 -0
- package/dist/commands/daemon.d.ts +18 -0
- package/dist/commands/daemon.js +581 -0
- package/dist/commands/exec.js +66 -20
- package/dist/commands/routines.js +29 -11
- package/dist/commands/secrets.d.ts +17 -0
- package/dist/commands/secrets.js +30 -15
- package/dist/commands/sessions-browser.js +6 -6
- package/dist/commands/sessions-favorite.d.ts +7 -7
- package/dist/commands/sessions-favorite.js +30 -30
- package/dist/commands/sessions-picker.d.ts +33 -1
- package/dist/commands/sessions-picker.js +102 -27
- package/dist/commands/sessions.d.ts +12 -1
- package/dist/commands/sessions.js +259 -20
- package/dist/commands/view.d.ts +11 -0
- package/dist/commands/view.js +56 -29
- package/dist/index.js +37 -2
- package/dist/lib/account-labels.d.ts +24 -0
- package/dist/lib/account-labels.js +72 -0
- package/dist/lib/agents.d.ts +32 -1
- package/dist/lib/agents.js +96 -31
- package/dist/lib/daemon-health.d.ts +24 -0
- package/dist/lib/daemon-health.js +84 -0
- package/dist/lib/daemon-ticks.d.ts +81 -0
- package/dist/lib/daemon-ticks.js +190 -0
- package/dist/lib/daemon.d.ts +68 -18
- package/dist/lib/daemon.js +303 -338
- package/dist/lib/device-config.d.ts +10 -0
- package/dist/lib/device-config.js +27 -0
- package/dist/lib/exec.d.ts +27 -0
- package/dist/lib/exec.js +49 -2
- package/dist/lib/hosts/dispatch.d.ts +4 -0
- package/dist/lib/hosts/dispatch.js +4 -0
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +1 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/import.js +7 -6
- package/dist/lib/memory-cache.d.ts +19 -0
- package/dist/lib/memory-cache.js +31 -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/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +7 -2
- package/dist/lib/routine-activation.d.ts +2 -0
- package/dist/lib/routine-activation.js +16 -0
- package/dist/lib/runner.d.ts +18 -0
- package/dist/lib/runner.js +52 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +19 -0
- package/dist/lib/secrets/agent.js +32 -2
- package/dist/lib/secrets/scope.d.ts +3 -3
- package/dist/lib/secrets/scope.js +3 -3
- package/dist/lib/session/db.d.ts +15 -0
- package/dist/lib/session/db.js +90 -15
- package/dist/lib/session/discover.js +91 -39
- package/dist/lib/session/favorites.d.ts +2 -2
- package/dist/lib/session/favorites.js +2 -2
- package/dist/lib/session/parse.d.ts +63 -0
- package/dist/lib/session/parse.js +165 -20
- package/dist/lib/session/session-cache.d.ts +9 -6
- package/dist/lib/session/session-cache.js +23 -6
- package/dist/lib/shims.js +12 -0
- package/dist/lib/startup/command-registry.d.ts +15 -1
- package/dist/lib/startup/command-registry.js +49 -0
- package/dist/lib/usage-refresh.js +3 -2
- package/dist/lib/usage.d.ts +12 -10
- package/dist/lib/usage.js +63 -144
- package/package.json +4 -1
|
@@ -46,11 +46,11 @@ import { getShimsDir } from '../lib/state.js';
|
|
|
46
46
|
import { fuzzyMatch, FUZZY_PRESETS } from '../lib/fuzzy.js';
|
|
47
47
|
import { itemPicker } from '../lib/picker.js';
|
|
48
48
|
import { resolveSessionAlias } from '../lib/session/actor-sidecar.js';
|
|
49
|
-
import { resolveVersionAliasLoose } from '../lib/versions.js';
|
|
49
|
+
import { listInstalledVersions, resolveVersionAliasLoose } from '../lib/versions.js';
|
|
50
50
|
import { getAgentsInvocation } from '../lib/daemon.js';
|
|
51
51
|
import { sessionRecoveryRunArgs } from '../lib/session/recovery.js';
|
|
52
52
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
53
|
-
import { sessionPicker, buildPreview, formatTodoCompact, githubRepoUrlFromCwd, } from './sessions-picker.js';
|
|
53
|
+
import { sessionPicker, buildPreview, loadSessionPreviewDigest, formatTodoCompact, githubRepoUrlFromCwd, } from './sessions-picker.js';
|
|
54
54
|
import { setHelpSections } from '../lib/help.js';
|
|
55
55
|
import { registerSessionsTailCommand } from './sessions-tail.js';
|
|
56
56
|
import { registerSessionsResumeCommand } from './sessions-resume.js';
|
|
@@ -95,6 +95,44 @@ function applyAgentShorthands(options) {
|
|
|
95
95
|
if (hit)
|
|
96
96
|
options.agent = hit;
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Treat a positional `agent@version` as a structured filter only when it names
|
|
100
|
+
* a real installed version. Everything else remains ordinary free-text search.
|
|
101
|
+
*/
|
|
102
|
+
export function parseInstalledAgentVersionQuery(query, installedVersions = (agent) => (agent in AGENTS ? listInstalledVersions(agent) : [])) {
|
|
103
|
+
const trimmed = query?.trim();
|
|
104
|
+
if (!trimmed)
|
|
105
|
+
return undefined;
|
|
106
|
+
const at = trimmed.indexOf('@');
|
|
107
|
+
if (at <= 0 || at !== trimmed.lastIndexOf('@') || at === trimmed.length - 1)
|
|
108
|
+
return undefined;
|
|
109
|
+
const agentName = trimmed.slice(0, at).toLowerCase();
|
|
110
|
+
if (!SESSION_AGENTS.includes(agentName))
|
|
111
|
+
return undefined;
|
|
112
|
+
const agent = agentName;
|
|
113
|
+
const version = trimmed.slice(at + 1);
|
|
114
|
+
return installedVersions(agent).includes(version) ? `${agent}@${version}` : undefined;
|
|
115
|
+
}
|
|
116
|
+
function applyVersionFilters(query, options) {
|
|
117
|
+
const explicitVersion = options.version ?? options.sessionVersion;
|
|
118
|
+
if (explicitVersion) {
|
|
119
|
+
if (!options.agent) {
|
|
120
|
+
throw new Error('--version requires --agent (for example: --agent claude --version 2.1.181).');
|
|
121
|
+
}
|
|
122
|
+
if (options.agent.includes('@')) {
|
|
123
|
+
throw new Error('Pass the version either in --agent <agent@version> or with --version, not both.');
|
|
124
|
+
}
|
|
125
|
+
options.agent = `${options.agent}@${explicitVersion}`;
|
|
126
|
+
}
|
|
127
|
+
if (!options.agent) {
|
|
128
|
+
const positionalFilter = parseInstalledAgentVersionQuery(query);
|
|
129
|
+
if (positionalFilter) {
|
|
130
|
+
options.agent = positionalFilter;
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return query;
|
|
135
|
+
}
|
|
98
136
|
const CLAUDE_RESUME_MATCH_WINDOW_MS = 10 * 60_000;
|
|
99
137
|
const LOAD_VERBS = ['Loading', 'Scanning', 'Gathering', 'Indexing', 'Reading'];
|
|
100
138
|
const FIND_VERBS = ['Finding', 'Searching', 'Locating', 'Matching'];
|
|
@@ -1629,28 +1667,107 @@ function canonicalSessionsCommand(query, options) {
|
|
|
1629
1667
|
/** Resolve a session by id/query globally and print its compact preview (no pager).
|
|
1630
1668
|
* Backs `--preview` — the fast path for the "peek before resume" hot loop. */
|
|
1631
1669
|
export async function renderSessionPreview(query, scope) {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
if (
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1670
|
+
let outcome = await resolveSessionMetadataValue(query, scope);
|
|
1671
|
+
// A just-created transcript may not have reached the incremental index yet.
|
|
1672
|
+
// Keep the indexed path hot, but repair a local cold miss once before saying
|
|
1673
|
+
// it does not exist. This also preserves Claude history-alias discovery.
|
|
1674
|
+
if (outcome.kind !== 'resolved'
|
|
1675
|
+
&& (!scope.hosts?.length || shouldIncludeLocal(scope.hosts, machineId()))) {
|
|
1676
|
+
const discovered = applyScopeFilters(await discoverSessions({ all: true, cwd: process.cwd(), limit: 5000 }), scope);
|
|
1677
|
+
const localMatches = resolveSessionQuery(discovered, query, { indexFallback: false }).matches
|
|
1678
|
+
.map(session => ({ ...session, machine: session.machine || machineId() }));
|
|
1679
|
+
const exact = localMatches.find(session => selectorAllowsEarlyExit(query)
|
|
1680
|
+
&& session.id.toLowerCase() === query.trim().toLowerCase());
|
|
1681
|
+
if (exact)
|
|
1682
|
+
outcome = { kind: 'resolved', session: exact };
|
|
1683
|
+
else if (outcome.kind === 'not-found' && localMatches.length > 0) {
|
|
1684
|
+
outcome = metadataResolveOutcome(localMatches, { sessions: [], unreachable: [] }, query);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
if (outcome.kind === 'partial') {
|
|
1688
|
+
console.error(chalk.red(`Partial session resolution: ${outcome.failedPeers.join(', ')} did not answer.`));
|
|
1689
|
+
console.error(chalk.gray('No preview was rendered because the short ID may be ambiguous on an unreachable peer.'));
|
|
1690
|
+
process.exitCode = 2;
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
if (outcome.kind === 'not-found') {
|
|
1694
|
+
notFoundByIdMessage(query).forEach(l => console.error(l));
|
|
1695
|
+
process.exitCode = 1;
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
if (outcome.kind === 'ambiguous') {
|
|
1699
|
+
console.error(chalk.red(`Multiple sessions match "${query}" across the fleet:`));
|
|
1700
|
+
for (const candidate of outcome.candidates) {
|
|
1701
|
+
const match = candidate.hits[0].session;
|
|
1702
|
+
const machines = candidate.hits.map(hit => hit.machine).join(', ');
|
|
1703
|
+
console.error(chalk.cyan(` ${match.shortId} ${match.id}`) + chalk.gray(` ${machines} ${match.agent}${match.version ? ` ${match.version}` : ''}`));
|
|
1704
|
+
}
|
|
1705
|
+
console.error(chalk.gray('Pass the full session ID to narrow it down.'));
|
|
1706
|
+
process.exitCode = 1;
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
const session = outcome.session;
|
|
1710
|
+
if (session._remote && session.machine && session.machine !== machineId()) {
|
|
1711
|
+
const args = ['sessions', 'preview', session.id, '--local'];
|
|
1712
|
+
if (scope.json)
|
|
1713
|
+
args.push('--json');
|
|
1714
|
+
const rendered = await runOnPeer(args, session.machine);
|
|
1715
|
+
if (rendered === 'no-target') {
|
|
1716
|
+
console.error(chalk.red(`Session ${session.id} is on ${session.machine}, but that device is not reachable.`));
|
|
1717
|
+
process.exitCode = 1;
|
|
1718
|
+
}
|
|
1643
1719
|
return;
|
|
1644
1720
|
}
|
|
1645
1721
|
// Lead with the live status when the session is still running, so the preview
|
|
1646
1722
|
// says working / waiting / idle up front — not just the historical transcript.
|
|
1647
|
-
//
|
|
1648
|
-
//
|
|
1723
|
+
// The shared snapshot is accepted for at most 15 seconds. The durable preview
|
|
1724
|
+
// below contains no live status, so a long-lived process can never keep a
|
|
1725
|
+
// stale working/waiting headline in its transcript cache.
|
|
1649
1726
|
let live;
|
|
1650
1727
|
try {
|
|
1651
|
-
|
|
1728
|
+
const loaded = await loadLocalActiveSessions();
|
|
1729
|
+
live = indexActiveBySessionId(loaded.sessions).get(session.id);
|
|
1652
1730
|
}
|
|
1653
1731
|
catch { /* plain preview on any probe failure */ }
|
|
1732
|
+
if (scope.json) {
|
|
1733
|
+
const { digest, error } = loadSessionPreviewDigest(session);
|
|
1734
|
+
console.log(JSON.stringify({
|
|
1735
|
+
schemaVersion: 1,
|
|
1736
|
+
session: {
|
|
1737
|
+
id: session.id,
|
|
1738
|
+
shortId: session.shortId,
|
|
1739
|
+
agent: session.agent,
|
|
1740
|
+
version: session.version,
|
|
1741
|
+
model: session.model,
|
|
1742
|
+
account: session.account,
|
|
1743
|
+
machine: session.machine ?? machineId(),
|
|
1744
|
+
cwd: session.cwd,
|
|
1745
|
+
project: session.project,
|
|
1746
|
+
gitBranch: session.gitBranch,
|
|
1747
|
+
createdAt: session.timestamp,
|
|
1748
|
+
lastActivity: session.lastActivity,
|
|
1749
|
+
durationMs: session.durationMs,
|
|
1750
|
+
messageCount: session.messageCount,
|
|
1751
|
+
tokenCount: session.tokenCount,
|
|
1752
|
+
costUsd: session.costUsd,
|
|
1753
|
+
label: session.label,
|
|
1754
|
+
ticketId: session.ticketId,
|
|
1755
|
+
prUrl: session.prUrl,
|
|
1756
|
+
},
|
|
1757
|
+
active: live ? {
|
|
1758
|
+
status: live.status,
|
|
1759
|
+
activity: live.activity,
|
|
1760
|
+
awaitingReason: live.awaitingReason,
|
|
1761
|
+
lastActivityMs: live.lastActivityMs,
|
|
1762
|
+
startedAtMs: live.startedAtMs,
|
|
1763
|
+
pid: live.pid,
|
|
1764
|
+
host: live.host,
|
|
1765
|
+
} : null,
|
|
1766
|
+
preview: digest ?? null,
|
|
1767
|
+
error: error ?? null,
|
|
1768
|
+
}));
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1654
1771
|
const headline = formatLiveStatusHeadline(live, isFavorite(session.id));
|
|
1655
1772
|
if (headline)
|
|
1656
1773
|
console.log(headline);
|
|
@@ -1879,6 +1996,14 @@ limitSource) {
|
|
|
1879
1996
|
// shorthands fold into --agent, and --device is an alias for --host (both
|
|
1880
1997
|
// resolve against the same device registry).
|
|
1881
1998
|
applyAgentShorthands(options);
|
|
1999
|
+
try {
|
|
2000
|
+
query = applyVersionFilters(query, options);
|
|
2001
|
+
}
|
|
2002
|
+
catch (error) {
|
|
2003
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
2004
|
+
process.exitCode = 1;
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
1882
2007
|
// --device / --devices both alias --host. A bare `all` / `fleet` sentinel means
|
|
1883
2008
|
// "search every peer" — which is already the default — so it resolves to no
|
|
1884
2009
|
// explicit host set rather than erroring on a device literally named "all".
|
|
@@ -1984,7 +2109,7 @@ limitSource) {
|
|
|
1984
2109
|
console.error(chalk.red('--preview requires a session id or query.'));
|
|
1985
2110
|
process.exit(1);
|
|
1986
2111
|
}
|
|
1987
|
-
await renderSessionPreview(query, { agent: options.agent, project: options.project, local: options.local });
|
|
2112
|
+
await renderSessionPreview(query, { agent: options.agent, project: options.project, local: options.local, hosts: options.host });
|
|
1988
2113
|
return;
|
|
1989
2114
|
}
|
|
1990
2115
|
if (liveOnly) {
|
|
@@ -2095,6 +2220,12 @@ limitSource) {
|
|
|
2095
2220
|
await renderArtifactsGlobal(searchQuery, options.artifacts ?? false, options.artifact, artifactLookupScope(options.agent, options.project, options.routine));
|
|
2096
2221
|
return;
|
|
2097
2222
|
}
|
|
2223
|
+
// A supplied ID is already the lookup key. Resolve it directly from SQLite
|
|
2224
|
+
// and, when needed, the peer indexes before any broad transcript discovery.
|
|
2225
|
+
if (!toolEvidenceMode && searchQuery && looksLikeSessionId(searchQuery)) {
|
|
2226
|
+
await renderOneSession(searchQuery, mode, { agent: options.agent, project: options.project, routine: options.routine, filter: filterOpts, redact: options.redact, local: options.local, hosts: options.host });
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2098
2229
|
// When the user explicitly asks to render (via mode flag), resolve the
|
|
2099
2230
|
// query globally so sessions outside the default cwd/30d window are found.
|
|
2100
2231
|
if (wantsRender && searchQuery) {
|
|
@@ -3694,6 +3825,11 @@ export function filterSessionsByQuery(sessions, query) {
|
|
|
3694
3825
|
const trimmed = query?.trim().toLowerCase() || '';
|
|
3695
3826
|
if (!trimmed)
|
|
3696
3827
|
return sessions;
|
|
3828
|
+
const installedAgentVersion = parseInstalledAgentVersionQuery(trimmed);
|
|
3829
|
+
if (installedAgentVersion) {
|
|
3830
|
+
const { agent, version } = parseAgentFilter(installedAgentVersion);
|
|
3831
|
+
return sessions.filter((session) => session.agent === agent && session.version === version);
|
|
3832
|
+
}
|
|
3697
3833
|
const terms = trimmed.split(/\s+/).filter(Boolean);
|
|
3698
3834
|
const contentIndex = searchContentIndex(sessions, trimmed);
|
|
3699
3835
|
// If the query exactly matches a session label, short-circuit the structural
|
|
@@ -3863,6 +3999,53 @@ async function renderArtifactsGlobal(query, listAll, name, scope) {
|
|
|
3863
3999
|
}
|
|
3864
4000
|
}
|
|
3865
4001
|
async function renderOneSession(query, mode, scope) {
|
|
4002
|
+
if (looksLikeSessionId(query)) {
|
|
4003
|
+
const outcome = await resolveSessionMetadataValue(query, scope);
|
|
4004
|
+
if (outcome.kind === 'partial') {
|
|
4005
|
+
console.error(chalk.red(`Partial session resolution: ${outcome.failedPeers.join(', ')} did not answer.`));
|
|
4006
|
+
process.exit(2);
|
|
4007
|
+
}
|
|
4008
|
+
// An index miss can be a transcript created since the last incremental
|
|
4009
|
+
// scan, or a Claude history alias. Fall through to the established
|
|
4010
|
+
// discovery/history resolver below only on that cold miss.
|
|
4011
|
+
if (outcome.kind === 'ambiguous') {
|
|
4012
|
+
console.error(chalk.red(`Multiple sessions match "${query}" across the fleet:`));
|
|
4013
|
+
for (const candidate of outcome.candidates) {
|
|
4014
|
+
const match = candidate.hits[0].session;
|
|
4015
|
+
const machines = candidate.hits.map(hit => hit.machine).join(', ');
|
|
4016
|
+
console.error(chalk.cyan(` ${match.shortId} ${match.id}`) + chalk.gray(` ${machines} ${match.agent}${match.version ? ` ${match.version}` : ''}`));
|
|
4017
|
+
}
|
|
4018
|
+
console.error(chalk.gray('Pass the full session ID to narrow it down.'));
|
|
4019
|
+
process.exit(1);
|
|
4020
|
+
}
|
|
4021
|
+
if (outcome.kind === 'resolved') {
|
|
4022
|
+
const resolved = outcome.session;
|
|
4023
|
+
if (resolved._remote && resolved.machine && resolved.machine !== machineId()) {
|
|
4024
|
+
const args = ['sessions', resolved.id, '--local'];
|
|
4025
|
+
const flag = modeFlag(mode);
|
|
4026
|
+
if (flag)
|
|
4027
|
+
args.push(flag);
|
|
4028
|
+
if (scope.filter.include?.length)
|
|
4029
|
+
args.push('--include', scope.filter.include.join(','));
|
|
4030
|
+
if (scope.filter.exclude?.length)
|
|
4031
|
+
args.push('--exclude', scope.filter.exclude.join(','));
|
|
4032
|
+
if (scope.filter.first !== undefined)
|
|
4033
|
+
args.push('--first', String(scope.filter.first));
|
|
4034
|
+
if (scope.filter.last !== undefined)
|
|
4035
|
+
args.push('--last', String(scope.filter.last));
|
|
4036
|
+
if (scope.redact === false)
|
|
4037
|
+
args.push('--no-redact');
|
|
4038
|
+
const rendered = await runOnPeer(args, resolved.machine);
|
|
4039
|
+
if (rendered === 'no-target') {
|
|
4040
|
+
console.error(chalk.red(`Session ${resolved.id} is on ${resolved.machine}, but that device is not reachable.`));
|
|
4041
|
+
process.exit(1);
|
|
4042
|
+
}
|
|
4043
|
+
return;
|
|
4044
|
+
}
|
|
4045
|
+
await renderSession(resolved, mode, scope.filter, { redact: scope.redact });
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
3866
4049
|
const spinner = ora().start();
|
|
3867
4050
|
const tracker = createScanProgressTracker(FIND_VERBS, 'session', spinner);
|
|
3868
4051
|
try {
|
|
@@ -4070,6 +4253,16 @@ function resolveIndexedMetadataRows(indexed, selector) {
|
|
|
4070
4253
|
}
|
|
4071
4254
|
return resolveSessionQuery(indexed, selector, { indexFallback: false }).matches;
|
|
4072
4255
|
}
|
|
4256
|
+
/**
|
|
4257
|
+
* ID-shaped selectors go straight to SQLite's primary-key/short-id lookup.
|
|
4258
|
+
* Keyword and label selectors still need the broader metadata pool.
|
|
4259
|
+
*/
|
|
4260
|
+
function indexedRowsForSelector(selector, scope) {
|
|
4261
|
+
const indexed = looksLikeSessionId(selector)
|
|
4262
|
+
? findSessionsById(selector)
|
|
4263
|
+
: querySessions();
|
|
4264
|
+
return applyScopeFilters(indexed, scope);
|
|
4265
|
+
}
|
|
4073
4266
|
/** Fixed peer argv for the metadata resolver. Scope flags compose identically on
|
|
4074
4267
|
* every host; `--all` removes the SSH login cwd/time window, not agent/project filters. */
|
|
4075
4268
|
export function metadataResolveForwardedArgs(selector, scope) {
|
|
@@ -4121,7 +4314,7 @@ export function metadataResolveOutcome(localMatches, remote, selector) {
|
|
|
4121
4314
|
export async function resolveSessionMetadataValue(selector, scope = {}, deps = { gatherRemoteList }) {
|
|
4122
4315
|
const localMachine = machineId();
|
|
4123
4316
|
const includeLocal = !scope.hosts?.length || shouldIncludeLocal(scope.hosts, localMachine);
|
|
4124
|
-
const indexed = includeLocal ?
|
|
4317
|
+
const indexed = includeLocal ? indexedRowsForSelector(selector, scope) : [];
|
|
4125
4318
|
const localMatches = resolveIndexedMetadataRows(indexed, selector)
|
|
4126
4319
|
.map(session => ({ ...session, machine: session.machine || localMachine }));
|
|
4127
4320
|
// A full-UUID local hit resolves with ZERO SSH: a UUID is globally unique, so
|
|
@@ -4158,7 +4351,7 @@ export async function resolveSessionMetadataValue(selector, scope = {}, deps = {
|
|
|
4158
4351
|
async function resolveSessionMetadata(selector, scope, deps = { gatherRemoteList }) {
|
|
4159
4352
|
const localMachine = machineId();
|
|
4160
4353
|
const includeLocal = !scope.hosts?.length || shouldIncludeLocal(scope.hosts, localMachine);
|
|
4161
|
-
const indexed = includeLocal ?
|
|
4354
|
+
const indexed = includeLocal ? indexedRowsForSelector(selector, scope) : [];
|
|
4162
4355
|
const localMatches = resolveIndexedMetadataRows(indexed, selector)
|
|
4163
4356
|
.map(session => ({ ...session, machine: session.machine || localMachine }));
|
|
4164
4357
|
// A peer is already inside gatherRemoteList. Return all local candidates so
|
|
@@ -4257,6 +4450,8 @@ export function registerSessionsCommands(program) {
|
|
|
4257
4450
|
.addOption(new Option('--resolve-safe-v1 <selector>').hideHelp())
|
|
4258
4451
|
.description('Find, browse, and read agent conversation transcripts. Live roster: `agents sessions --active` (alias: `agents roster`).')
|
|
4259
4452
|
.option('-a, --agent <agent>', 'Filter by agent type and version (e.g., claude, codex@0.116.0)')
|
|
4453
|
+
.addOption(new Option('--session-version <version>', 'Internal spelling for the public sessions --version filter')
|
|
4454
|
+
.hideHelp())
|
|
4260
4455
|
.option('--claude', 'Shorthand for --agent claude')
|
|
4261
4456
|
.option('--codex', 'Shorthand for --agent codex')
|
|
4262
4457
|
.option('--kimi', 'Shorthand for --agent kimi')
|
|
@@ -4297,7 +4492,7 @@ export function registerSessionsCommands(program) {
|
|
|
4297
4492
|
.option('--abandoned', 'Show sessions with no transcript progress for the abandonment window (implies --active)')
|
|
4298
4493
|
.option('--queued', 'Show queued sessions that have not started running (implies --active)')
|
|
4299
4494
|
.option('--unknown', 'Show sessions whose live state cannot be determined (implies --active)')
|
|
4300
|
-
.option('--favorites', 'Show only favorited
|
|
4495
|
+
.option('--favorites', 'Show only favorited sessions — favorite them with `*` in the browser or `agents sessions favorite <id>`')
|
|
4301
4496
|
.option('--tree', 'Group the listing by directory; drops the id/version columns for readability')
|
|
4302
4497
|
.option('--flat', 'Plain flat table (one row per session) instead of the grouped project overview')
|
|
4303
4498
|
.option('--no-live', 'Do not enrich the listing with live status/preview for running sessions')
|
|
@@ -4350,6 +4545,10 @@ export function registerSessionsCommands(program) {
|
|
|
4350
4545
|
# Search across every directory, not just this project
|
|
4351
4546
|
agents sessions "topic" --all
|
|
4352
4547
|
|
|
4548
|
+
# Filter one installed harness version (equivalent forms)
|
|
4549
|
+
agents sessions claude@2.1.181
|
|
4550
|
+
agents sessions --agent claude --version 2.1.181
|
|
4551
|
+
|
|
4353
4552
|
# Team-spawned sessions, grouped by team (spawner + spawn time per team,
|
|
4354
4553
|
# teammate mode/handle per row; team-flagged spawns with no team record
|
|
4355
4554
|
# in a trailing (no team) bucket)
|
|
@@ -4397,6 +4596,7 @@ export function registerSessionsCommands(program) {
|
|
|
4397
4596
|
resume [query] multi-select history → open tabs (or run --resume <id>)
|
|
4398
4597
|
- The interactive listing and every live-status flag fold in your other online machines automatically (live over SSH, no sync) — each row is labelled by host, this machine first. Use --local to skip the fan-out; single-id lookups stay local.
|
|
4399
4598
|
- --all is not a device flag: it widens historical directory and time filters. Fleet collection is already the default. A status flag (--working/--idle/--waiting/--orphan/--crashed/--closed/--abandoned/--queued/--unknown) implies --active; combine status flags for a union.
|
|
4599
|
+
- --version <version> requires --agent and is equivalent to --agent <agent@version>.
|
|
4400
4600
|
- --host runs the query on the remote's own index over SSH (host alias or user@host); repeat or pass several to fan out. SSH access is the only auth.
|
|
4401
4601
|
- --in-team matches both ends of the lineage: the session that ran 'agents teams create/add', and (with --teams) that team's teammates. In the interactive list, 't' cycles the same filter over the teams in view.
|
|
4402
4602
|
- --include and --exclude are mutually exclusive.
|
|
@@ -4420,6 +4620,45 @@ export function registerSessionsCommands(program) {
|
|
|
4420
4620
|
}
|
|
4421
4621
|
await sessionsAction(query, options, command.getOptionValueSource('limit'));
|
|
4422
4622
|
});
|
|
4623
|
+
const previewCmd = sessionsCmd
|
|
4624
|
+
.command('preview')
|
|
4625
|
+
.argument('<id>', 'Full session ID or displayed 8-character short ID')
|
|
4626
|
+
.description('Show one rich session card without rendering the full transcript')
|
|
4627
|
+
.option('-a, --agent <agent>', 'Narrow the ID to one agent type/version')
|
|
4628
|
+
.option('-p, --project <name>', 'Narrow the ID to one project')
|
|
4629
|
+
.option('--local', 'Only this machine; do not resolve the ID across the fleet')
|
|
4630
|
+
.option('-H, --host <target...>', 'Resolve only on the named device(s)')
|
|
4631
|
+
.option('--device <target...>', 'Alias for --host')
|
|
4632
|
+
.option('--json', 'Output the session preview as JSON');
|
|
4633
|
+
setHelpSections(previewCmd, {
|
|
4634
|
+
examples: `
|
|
4635
|
+
# Preview by the 8-character ID shown in agents sessions
|
|
4636
|
+
agents sessions preview 407b8dd5
|
|
4637
|
+
|
|
4638
|
+
# A full UUID resolves on the first device that owns it
|
|
4639
|
+
agents sessions preview c70ecdea-6210-4039-9845-246a3a7a9942
|
|
4640
|
+
|
|
4641
|
+
# Stay on this machine or restrict the authoritative lookup to one peer
|
|
4642
|
+
agents sessions preview 407b8dd5 --local
|
|
4643
|
+
agents sessions preview 407b8dd5 --device zion
|
|
4644
|
+
`,
|
|
4645
|
+
notes: `
|
|
4646
|
+
- Full UUIDs are globally unique and may stop the fleet lookup at the first exact hit.
|
|
4647
|
+
- Short IDs wait for every selected device so ambiguity is never hidden.
|
|
4648
|
+
- Active status is refreshed through the bounded live-state TTL; transcript-derived details use the durable session index.
|
|
4649
|
+
`,
|
|
4650
|
+
});
|
|
4651
|
+
previewCmd.action(async (id) => {
|
|
4652
|
+
const options = previewCmd.optsWithGlobals();
|
|
4653
|
+
const hosts = [...(options.host ?? []), ...(options.device ?? [])];
|
|
4654
|
+
await renderSessionPreview(id, {
|
|
4655
|
+
agent: options.agent,
|
|
4656
|
+
project: options.project,
|
|
4657
|
+
local: options.local,
|
|
4658
|
+
hosts: hosts.length > 0 ? hosts : undefined,
|
|
4659
|
+
json: options.json,
|
|
4660
|
+
});
|
|
4661
|
+
});
|
|
4423
4662
|
registerSessionsTailCommand(sessionsCmd);
|
|
4424
4663
|
registerSessionsResumeCommand(sessionsCmd);
|
|
4425
4664
|
registerSessionsForkCommand(sessionsCmd);
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -15,6 +15,17 @@ import { type ProfileSummary } from '../lib/profiles.js';
|
|
|
15
15
|
import { type ByokUsageResult } from '../lib/byok-usage.js';
|
|
16
16
|
/** Shared account identity formatter, re-exported for the view-specific tests. */
|
|
17
17
|
export declare const accountColumnLabel: typeof accountDisplayLabel;
|
|
18
|
+
export interface AccountOrderedVersion {
|
|
19
|
+
version: string;
|
|
20
|
+
email: string | null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Human `agents view` row order: selected default first, then email-bearing
|
|
24
|
+
* accounts alphabetically, then installs whose account has no email. Version
|
|
25
|
+
* descending is the deterministic tie-breaker and preserves the old order for
|
|
26
|
+
* every non-email harness.
|
|
27
|
+
*/
|
|
28
|
+
export declare function compareAccountOrderedVersions(a: AccountOrderedVersion, b: AccountOrderedVersion, globalDefault: string | null): number;
|
|
18
29
|
/**
|
|
19
30
|
* Join fixed view columns with a consistent two-space gutter. Empty trailing
|
|
20
31
|
* columns are dropped so a row without an auth chip does not grow a dangling
|
package/dist/commands/view.js
CHANGED
|
@@ -33,6 +33,30 @@ import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js
|
|
|
33
33
|
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
34
34
|
/** Shared account identity formatter, re-exported for the view-specific tests. */
|
|
35
35
|
export const accountColumnLabel = accountDisplayLabel;
|
|
36
|
+
/**
|
|
37
|
+
* Human `agents view` row order: selected default first, then email-bearing
|
|
38
|
+
* accounts alphabetically, then installs whose account has no email. Version
|
|
39
|
+
* descending is the deterministic tie-breaker and preserves the old order for
|
|
40
|
+
* every non-email harness.
|
|
41
|
+
*/
|
|
42
|
+
export function compareAccountOrderedVersions(a, b, globalDefault) {
|
|
43
|
+
const aIsDefault = a.version === globalDefault;
|
|
44
|
+
const bIsDefault = b.version === globalDefault;
|
|
45
|
+
if (aIsDefault !== bIsDefault)
|
|
46
|
+
return aIsDefault ? -1 : 1;
|
|
47
|
+
const aEmail = a.email?.toLowerCase() ?? null;
|
|
48
|
+
const bEmail = b.email?.toLowerCase() ?? null;
|
|
49
|
+
if (aEmail !== null && bEmail === null)
|
|
50
|
+
return -1;
|
|
51
|
+
if (aEmail === null && bEmail !== null)
|
|
52
|
+
return 1;
|
|
53
|
+
if (aEmail !== null && bEmail !== null) {
|
|
54
|
+
const emailOrder = aEmail.localeCompare(bEmail);
|
|
55
|
+
if (emailOrder !== 0)
|
|
56
|
+
return emailOrder;
|
|
57
|
+
}
|
|
58
|
+
return compareVersions(b.version, a.version);
|
|
59
|
+
}
|
|
36
60
|
/**
|
|
37
61
|
* Overview (`agents view` with no agent filter) caps compact usage windows so
|
|
38
62
|
* multi-meter agents (Antigravity's four model quotas, Droid's three buckets)
|
|
@@ -56,15 +80,12 @@ export function joinViewColumns(cols) {
|
|
|
56
80
|
return cols.slice(0, end).join(' ');
|
|
57
81
|
}
|
|
58
82
|
/**
|
|
59
|
-
* Custom harnesses (the `~/.agents/profiles/*.yml` bundles), sorted by name
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* malformed file.
|
|
83
|
+
* Custom harnesses (the `~/.agents/profiles/*.yml` bundles), sorted by name.
|
|
84
|
+
* YAMLs that fail validation are silently skipped by `listProfiles`, so this
|
|
85
|
+
* never throws on a malformed file.
|
|
63
86
|
*/
|
|
64
|
-
function getHarnesses(
|
|
65
|
-
return listProfiles()
|
|
66
|
-
.filter((profile) => !filterAgentId || profile.host.agent === filterAgentId)
|
|
67
|
-
.map(profileSummary);
|
|
87
|
+
function getHarnesses() {
|
|
88
|
+
return listProfiles().map(profileSummary);
|
|
68
89
|
}
|
|
69
90
|
/** Version-first label: "<version> (forked from <host>[, tracks default])" */
|
|
70
91
|
function harnessVersionLabel(harness, globalDefault) {
|
|
@@ -307,7 +328,9 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
307
328
|
.filter((agentId) => !hasNonIsolatedVersion(agentId))
|
|
308
329
|
.map(async (agentId) => [agentId, await getUnmanagedCliState(agentId)])));
|
|
309
330
|
const showPaths = !!filterAgentId;
|
|
310
|
-
|
|
331
|
+
// A filtered native view is about that native harness's installed versions.
|
|
332
|
+
// Custom forks are standalone agent types and only belong in the overview.
|
|
333
|
+
const harnesses = filterAgentId ? [] : getHarnesses();
|
|
311
334
|
// Auto-heal stale versioned aliases. Pre-v2 aliases (e.g. pre-CLAUDE_CONFIG_DIR
|
|
312
335
|
// claude shims) silently route login through the default version's symlinked
|
|
313
336
|
// home, so `agents view` would never reflect the right account. Regenerate on
|
|
@@ -524,14 +547,16 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
524
547
|
? chalk.yellow(' (no default)')
|
|
525
548
|
: '';
|
|
526
549
|
console.log(` ${chalk.bold(agentLabel(agentId))}${strategyLabel}${noDefaultLabel}`);
|
|
527
|
-
//
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
})
|
|
550
|
+
// Account information is already loaded above. Keep the selected default
|
|
551
|
+
// first, then make multi-account installs scannable by email. Harnesses
|
|
552
|
+
// without email identities retain their prior version-descending order.
|
|
553
|
+
const sortedVersions = versions
|
|
554
|
+
.map((version) => ({
|
|
555
|
+
version,
|
|
556
|
+
email: infoMap.get(`${agentId}:${version}`)?.email ?? null,
|
|
557
|
+
}))
|
|
558
|
+
.sort((a, b) => compareAccountOrderedVersions(a, b, globalDefault))
|
|
559
|
+
.map(({ version }) => version);
|
|
535
560
|
for (const version of sortedVersions) {
|
|
536
561
|
const isDefault = version === globalDefault;
|
|
537
562
|
const isolated = !isDefault && isVersionIsolated(agentId, version);
|
|
@@ -1273,7 +1298,9 @@ export async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1273
1298
|
else
|
|
1274
1299
|
byAgent.set(agentId, [entry]);
|
|
1275
1300
|
}
|
|
1276
|
-
|
|
1301
|
+
// Keep filtered native JSON consistent with the text view: custom forks are
|
|
1302
|
+
// not children of the native harness they execute through.
|
|
1303
|
+
const harnesses = filterAgentId ? [] : getHarnesses();
|
|
1277
1304
|
const out = [];
|
|
1278
1305
|
for (const agentId of agentsToShow) {
|
|
1279
1306
|
const versions = byAgent.get(agentId) ?? [];
|
|
@@ -1544,19 +1571,19 @@ export async function viewAction(agentArg, options) {
|
|
|
1544
1571
|
// Parse agent@version syntax
|
|
1545
1572
|
const parts = agentArg.split('@');
|
|
1546
1573
|
const agentName = parts[0];
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
if (
|
|
1552
|
-
|
|
1553
|
-
if (json) {
|
|
1554
|
-
console.log(JSON.stringify(harness, null, 2));
|
|
1555
|
-
return;
|
|
1556
|
-
}
|
|
1557
|
-
renderHarnessDetail(agentName);
|
|
1574
|
+
// Match run resolution: an exact custom harness name wins over a native id or
|
|
1575
|
+
// alias with the same spelling, so every fork remains independently viewable.
|
|
1576
|
+
if (profileExists(agentName)) {
|
|
1577
|
+
const harness = profileSummary(readProfile(agentName));
|
|
1578
|
+
if (json) {
|
|
1579
|
+
console.log(JSON.stringify(harness, null, 2));
|
|
1558
1580
|
return;
|
|
1559
1581
|
}
|
|
1582
|
+
renderHarnessDetail(agentName);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
const agentId = resolveAgentName(agentName);
|
|
1586
|
+
if (!agentId) {
|
|
1560
1587
|
if (json) {
|
|
1561
1588
|
console.log(JSON.stringify({ error: formatAgentError(agentName) }));
|
|
1562
1589
|
process.exit(1);
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ if (IS_DEV_BUILD) {
|
|
|
94
94
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
95
95
|
// first byte of output), the registry maps a command name to a thunk that
|
|
96
96
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
97
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadModes, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadSnapshot, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadInsights, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadHumans, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadBench, loadShare, loadSend, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
97
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadDaemon, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadModes, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadSnapshot, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadInsights, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadHumans, loadAccounts, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadBench, loadShare, loadSend, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
98
98
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
99
99
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
100
100
|
import { getCliLaunch } from './lib/cli-entry.js';
|
|
@@ -120,6 +120,22 @@ if (process.argv[2] === '__daemon-run') {
|
|
|
120
120
|
await runDaemon();
|
|
121
121
|
process.exit(process.exitCode ?? 0);
|
|
122
122
|
}
|
|
123
|
+
// One-shot invocation of a migrated daemon housekeeping tick (RUSH-2353). The
|
|
124
|
+
// shipped system routines (`watchdog`, `device-probe`, `fleet-cache-warm`, ...)
|
|
125
|
+
// run this as their `command:` instead of the daemon holding a setInterval —
|
|
126
|
+
// same tick body, now scheduled/tracked/pinnable through the routines system.
|
|
127
|
+
if (process.argv[2] === '__daemon-tick') {
|
|
128
|
+
const name = process.argv[3] || '';
|
|
129
|
+
const { runDaemonTick } = await import('./lib/daemon-ticks.js');
|
|
130
|
+
try {
|
|
131
|
+
await runDaemonTick(name);
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
process.stderr.write(`[agents] daemon tick '${name}' failed: ${err.message}\n`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
123
139
|
// White-label: the shim for a brand (e.g. `jack`) exports AGENTS_BRAND, so the
|
|
124
140
|
// CLI presents its own name/help/errors as the brand. Unbranded (AGENTS_BRAND
|
|
125
141
|
// unset) resolves to 'agents' and everything below is byte-identical to before.
|
|
@@ -303,6 +319,7 @@ Run and dispatch:
|
|
|
303
319
|
defaults Configure run defaults by agent/version selector
|
|
304
320
|
teams Coordinate multiple agents on shared work
|
|
305
321
|
routines Run agents on a cron schedule (scheduler auto-starts)
|
|
322
|
+
daemon Runtime status/control for the always-on daemon (secrets broker, browser IPC, scheduler)
|
|
306
323
|
webhook Receive signed GitHub/Linear webhooks for trigger routines
|
|
307
324
|
funnel Expose a webhook receiver through Tailscale Funnel
|
|
308
325
|
sessions Browse, search, and replay past runs (live-search in TTY; grouped by workspace)
|
|
@@ -929,6 +946,7 @@ async function registerAllEagerCommands() {
|
|
|
929
946
|
await reg(loadExport);
|
|
930
947
|
await reg(loadPackages);
|
|
931
948
|
await reg(loadRoutines);
|
|
949
|
+
await reg(loadDaemon);
|
|
932
950
|
await reg(loadMonitors);
|
|
933
951
|
await reg(loadProjects);
|
|
934
952
|
await reg(loadRun);
|
|
@@ -980,6 +998,7 @@ async function registerAllEagerCommands() {
|
|
|
980
998
|
await reg(loadWebhook);
|
|
981
999
|
await reg(loadFunnel);
|
|
982
1000
|
await reg(loadHumans);
|
|
1001
|
+
await reg(loadAccounts);
|
|
983
1002
|
registerHqTombstoneCommand(program);
|
|
984
1003
|
await reg(loadFeed);
|
|
985
1004
|
await reg(loadMailboxes);
|
|
@@ -1060,6 +1079,22 @@ program.on('command:*', (operands) => {
|
|
|
1060
1079
|
// and the doc flags (--version/--help/-h) drive both the registration strategy
|
|
1061
1080
|
// and whether the update check + background sync run at all.
|
|
1062
1081
|
const passedArgs = process.argv.slice(2);
|
|
1082
|
+
// Commander owns `--version` on the root command and otherwise intercepts it
|
|
1083
|
+
// even after `sessions`, before the subcommand can parse its version filter.
|
|
1084
|
+
// Rewrite only that value-taking nested form; bare `agents --version` and every
|
|
1085
|
+
// other command retain the root documentation flag unchanged.
|
|
1086
|
+
if (passedArgs[0] === 'sessions') {
|
|
1087
|
+
const nestedVersionIndex = passedArgs.indexOf('--version', 1);
|
|
1088
|
+
if (nestedVersionIndex >= 0) {
|
|
1089
|
+
const nestedVersion = passedArgs[nestedVersionIndex + 1];
|
|
1090
|
+
if (!nestedVersion || nestedVersion.startsWith('-')) {
|
|
1091
|
+
console.error("error: option '--version <version>' argument missing");
|
|
1092
|
+
process.exit(1);
|
|
1093
|
+
}
|
|
1094
|
+
passedArgs[nestedVersionIndex] = '--session-version';
|
|
1095
|
+
process.argv[nestedVersionIndex + 2] = '--session-version';
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1063
1098
|
const requestedCommand = passedArgs.find((arg) => !arg.startsWith('-'));
|
|
1064
1099
|
const verboseStartup = passedArgs.includes('--verbose');
|
|
1065
1100
|
// Help and version output are pure documentation — they must never gate on
|
|
@@ -1212,7 +1247,7 @@ if (process.env.AGENTS_SKIP_MIGRATION !== '1') {
|
|
|
1212
1247
|
// Bumping the suffix re-runs migrations for every user; binary releases that
|
|
1213
1248
|
// don't change the schema must NOT re-run (they would destroy user content
|
|
1214
1249
|
// when migration steps overlap with user-authored paths). See issue #20.
|
|
1215
|
-
const sentinelValue = '
|
|
1250
|
+
const sentinelValue = 'v17';
|
|
1216
1251
|
let needRun = true;
|
|
1217
1252
|
try {
|
|
1218
1253
|
if (fs.existsSync(sentinel) && fs.readFileSync(sentinel, 'utf-8').trim() === sentinelValue) {
|