@phnx-labs/agents-cli 1.21.2 → 1.22.0
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 +101 -0
- package/README.md +32 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/computer-actions.d.ts +4 -0
- package/dist/commands/computer-actions.js +35 -0
- package/dist/commands/computer.js +4 -2
- package/dist/commands/exec.d.ts +27 -0
- package/dist/commands/exec.js +123 -6
- package/dist/commands/models.js +36 -1
- package/dist/commands/perf.d.ts +16 -0
- package/dist/commands/perf.js +11 -1
- package/dist/commands/projects.d.ts +11 -1
- package/dist/commands/projects.js +38 -4
- package/dist/commands/sessions-backfill.d.ts +32 -0
- package/dist/commands/sessions-backfill.js +186 -0
- package/dist/commands/sessions-picker.js +17 -2
- package/dist/commands/sessions.d.ts +22 -1
- package/dist/commands/sessions.js +331 -18
- package/dist/commands/teams.js +1 -1
- package/dist/commands/worktree.d.ts +3 -3
- package/dist/commands/worktree.js +35 -4
- package/dist/index.js +8 -0
- package/dist/lib/browser/service.js +13 -0
- package/dist/lib/computer/dispatch.d.ts +3 -1
- package/dist/lib/computer/dispatch.js +10 -2
- package/dist/lib/daemon.d.ts +5 -1
- package/dist/lib/daemon.js +63 -14
- package/dist/lib/devices/resolve-target.d.ts +6 -0
- package/dist/lib/devices/resolve-target.js +9 -3
- package/dist/lib/event-stream.d.ts +2 -0
- package/dist/lib/event-stream.js +3 -0
- package/dist/lib/events.d.ts +3 -1
- package/dist/lib/events.js +4 -2
- package/dist/lib/exec.js +39 -8
- package/dist/lib/git.d.ts +14 -0
- package/dist/lib/git.js +36 -0
- package/dist/lib/hooks/profile.js +1 -14
- package/dist/lib/hosts/dispatch.d.ts +12 -0
- package/dist/lib/hosts/dispatch.js +23 -6
- package/dist/lib/hosts/reconnect.d.ts +38 -0
- package/dist/lib/hosts/reconnect.js +85 -4
- package/dist/lib/hosts/run-target.js +14 -2
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/model-tiers.d.ts +54 -0
- package/dist/lib/model-tiers.js +229 -0
- package/dist/lib/models.d.ts +3 -0
- package/dist/lib/models.js +44 -7
- package/dist/lib/percentile.d.ts +12 -0
- package/dist/lib/percentile.js +24 -0
- package/dist/lib/perf/db.d.ts +1 -2
- package/dist/lib/perf/db.js +2 -14
- package/dist/lib/plugins.js +12 -1
- package/dist/lib/pricing/prices.json +16 -1
- package/dist/lib/project-focus.d.ts +42 -0
- package/dist/lib/project-focus.js +80 -0
- package/dist/lib/project-schedule.d.ts +75 -0
- package/dist/lib/project-schedule.js +110 -0
- package/dist/lib/redact.d.ts +2 -0
- package/dist/lib/redact.js +22 -0
- package/dist/lib/remote-agents-json.d.ts +2 -0
- package/dist/lib/remote-agents-json.js +3 -3
- package/dist/lib/resources.d.ts +16 -0
- package/dist/lib/resources.js +25 -14
- package/dist/lib/rotate.d.ts +84 -1
- package/dist/lib/rotate.js +155 -5
- package/dist/lib/routines.js +1 -14
- package/dist/lib/runner.d.ts +4 -2
- package/dist/lib/runner.js +21 -5
- 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/session/bash-command.js +60 -9
- package/dist/lib/session/db.d.ts +22 -1
- package/dist/lib/session/db.js +516 -24
- package/dist/lib/session/discover.d.ts +68 -7
- package/dist/lib/session/discover.js +186 -84
- package/dist/lib/session/highlights.d.ts +24 -4
- package/dist/lib/session/highlights.js +52 -7
- package/dist/lib/session/parse.d.ts +8 -1
- package/dist/lib/session/parse.js +102 -35
- package/dist/lib/session/prompt.d.ts +19 -0
- package/dist/lib/session/prompt.js +43 -0
- package/dist/lib/session/remote-list.d.ts +71 -0
- package/dist/lib/session/remote-list.js +410 -2
- package/dist/lib/session/shell-programs.d.ts +15 -0
- package/dist/lib/session/shell-programs.js +359 -0
- package/dist/lib/session/tool-calls.d.ts +88 -0
- package/dist/lib/session/tool-calls.js +612 -0
- package/dist/lib/session/tool-index.d.ts +100 -0
- package/dist/lib/session/tool-index.js +773 -0
- package/dist/lib/session/tool-store.d.ts +15 -0
- package/dist/lib/session/tool-store.js +198 -0
- package/dist/lib/session/types.d.ts +49 -0
- package/dist/lib/state.d.ts +10 -1
- package/dist/lib/state.js +11 -2
- package/dist/lib/teams/remoteWorktree.d.ts +3 -4
- package/dist/lib/teams/remoteWorktree.js +3 -4
- package/dist/lib/teams/worktree.d.ts +11 -1
- package/dist/lib/teams/worktree.js +42 -4
- package/dist/lib/types.d.ts +31 -0
- package/dist/lib/types.js +17 -0
- package/package.json +3 -1
|
@@ -14,6 +14,7 @@ import { spawn } from 'child_process';
|
|
|
14
14
|
import { Option } from 'commander';
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { truncate, padRight } from '../lib/format.js';
|
|
17
|
+
import { sanitizeForTerminal } from '../lib/redact.js';
|
|
17
18
|
import { resolveProjectKey } from '../lib/project-key.js';
|
|
18
19
|
import { listProjectDefs, resolveProjectNameForCwd } from '../lib/projects.js';
|
|
19
20
|
import ora from 'ora';
|
|
@@ -26,10 +27,10 @@ import { mapPanesToTargets, listClients } from '../lib/tmux/session.js';
|
|
|
26
27
|
import { resolveViewingIn, viewingInLabel } from '../lib/session/viewing-in.js';
|
|
27
28
|
import { machineId, normalizeHost } from '../lib/session/sync/config.js';
|
|
28
29
|
import { gatherRemoteActive, NO_FANOUT_ENV } from '../lib/session/remote-active.js';
|
|
29
|
-
import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
|
|
30
|
+
import { gatherRemoteList, gatherRemoteToolProgramCounts, gatherRemoteToolSearch, runOnPeer } from '../lib/session/remote-list.js';
|
|
30
31
|
import { stringWidth, truncateToWidth, padToWidth, terminalWidth } from '../lib/session/width.js';
|
|
31
32
|
import { inferSessionState } from '../lib/session/state.js';
|
|
32
|
-
import { discoverSessions, countSessionsInScope, resolveSessionById, isCompleteSessionId, looksLikeSessionId, searchContentIndex, getSessionRoots } from '../lib/session/discover.js';
|
|
33
|
+
import { discoverSessions, queryIndexedSessions, countSessionsInScope, resolveSessionById, isCompleteSessionId, looksLikeSessionId, searchContentIndex, getSessionRoots, scopeToManaged } from '../lib/session/discover.js';
|
|
33
34
|
import { findSessionsById, querySessions } from '../lib/session/db.js';
|
|
34
35
|
import { filterTeamSessions, safeTeamText } from '../lib/session/team-filter.js';
|
|
35
36
|
import { parseSession } from '../lib/session/parse.js';
|
|
@@ -57,8 +58,13 @@ import { registerSessionsInjectCommand } from './sessions-inject.js';
|
|
|
57
58
|
import { registerSessionsExportCommand } from './sessions-export.js';
|
|
58
59
|
import { registerSessionsImportCommand } from './sessions-import.js';
|
|
59
60
|
import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
|
|
61
|
+
import { registerSessionsBackfillCommand } from './sessions-backfill.js';
|
|
60
62
|
import { runBrowserSessions } from '../lib/browser/sessions-list.js';
|
|
63
|
+
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';
|
|
61
64
|
const SESSION_AGENT_FILTER_HELP = `Filter by agent, e.g. claude, codex, claude@2.0.65`;
|
|
65
|
+
function collectQueryClause(value, previous) {
|
|
66
|
+
return [...previous, value];
|
|
67
|
+
}
|
|
62
68
|
/**
|
|
63
69
|
* The prioritized harnesses that get a boolean shorthand flag (e.g. `--claude`
|
|
64
70
|
* === `--agent claude`). The rest stay reachable via `--agent <name>`, which
|
|
@@ -1172,6 +1178,12 @@ export function hasNoBrowserDisqualifyingFlags(options, query) {
|
|
|
1172
1178
|
!options.markdown &&
|
|
1173
1179
|
!options.until &&
|
|
1174
1180
|
!options.project &&
|
|
1181
|
+
// The interactive browser picker is a fuzzy-search TUI over the discovered
|
|
1182
|
+
// pool, not a SQL-filtered listing — it cannot represent a skill/plugin
|
|
1183
|
+
// scope. Falling through to it here would silently drop the filter and
|
|
1184
|
+
// show the unfiltered pool instead (same reasoning as --project/--sort).
|
|
1185
|
+
!options.skill &&
|
|
1186
|
+
!options.plugin &&
|
|
1175
1187
|
!options.sort &&
|
|
1176
1188
|
!options.artifacts &&
|
|
1177
1189
|
options.artifact === undefined &&
|
|
@@ -1202,6 +1214,10 @@ function canonicalSessionsCommand(query, options) {
|
|
|
1202
1214
|
a.push('--device', h);
|
|
1203
1215
|
if (options.project)
|
|
1204
1216
|
a.push('--project', options.project);
|
|
1217
|
+
if (options.skill)
|
|
1218
|
+
a.push('--skill', options.skill);
|
|
1219
|
+
if (options.plugin)
|
|
1220
|
+
a.push('--plugin', options.plugin);
|
|
1205
1221
|
if (options.all)
|
|
1206
1222
|
a.push('--all');
|
|
1207
1223
|
if (options.since)
|
|
@@ -1286,6 +1302,114 @@ export function formatLiveStatusHeadline(live, favorite = false) {
|
|
|
1286
1302
|
}
|
|
1287
1303
|
return `${star}${glyph} ${statusColor(live.status)(word)}${suffix}`;
|
|
1288
1304
|
}
|
|
1305
|
+
/** Merge local and peer envelopes without changing the versioned JSON shape. */
|
|
1306
|
+
export function mergeToolSearchEnvelopes(local, remotes) {
|
|
1307
|
+
const all = [local, ...remotes];
|
|
1308
|
+
const sessions = new Map();
|
|
1309
|
+
for (const envelope of all) {
|
|
1310
|
+
for (const session of envelope.sessions) {
|
|
1311
|
+
sessions.set(`${session.machine ?? 'local'}\0${session.id}`, session);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
schemaVersion: 1,
|
|
1316
|
+
generatedAt: new Date().toISOString(),
|
|
1317
|
+
query: local.query,
|
|
1318
|
+
coverage: {
|
|
1319
|
+
indexedFiles: all.reduce((n, envelope) => n + envelope.coverage.indexedFiles, 0),
|
|
1320
|
+
indexedCalls: all.reduce((n, envelope) => n + envelope.coverage.indexedCalls, 0),
|
|
1321
|
+
skippedFiles: all.reduce((n, envelope) => n + envelope.coverage.skippedFiles, 0),
|
|
1322
|
+
limitedFiles: all.reduce((n, envelope) => n + envelope.coverage.limitedFiles, 0),
|
|
1323
|
+
remainingFiles: all.reduce((n, envelope) => n + envelope.coverage.remainingFiles, 0),
|
|
1324
|
+
complete: all.every((envelope) => envelope.coverage.complete),
|
|
1325
|
+
},
|
|
1326
|
+
sessions: [...sessions.values()].sort((a, b) => b.timestamp.localeCompare(a.timestamp)),
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
export function mergeToolProgramCountEnvelopes(local, remotes) {
|
|
1330
|
+
const all = [local, ...remotes];
|
|
1331
|
+
return {
|
|
1332
|
+
schemaVersion: 1,
|
|
1333
|
+
kind: 'tool-program-count',
|
|
1334
|
+
generatedAt: new Date().toISOString(),
|
|
1335
|
+
query: local.query,
|
|
1336
|
+
coverage: {
|
|
1337
|
+
indexedFiles: all.reduce((sum, envelope) => sum + envelope.coverage.indexedFiles, 0),
|
|
1338
|
+
indexedCalls: all.reduce((sum, envelope) => sum + envelope.coverage.indexedCalls, 0),
|
|
1339
|
+
skippedFiles: all.reduce((sum, envelope) => sum + envelope.coverage.skippedFiles, 0),
|
|
1340
|
+
limitedFiles: all.reduce((sum, envelope) => sum + envelope.coverage.limitedFiles, 0),
|
|
1341
|
+
remainingFiles: all.reduce((sum, envelope) => sum + envelope.coverage.remainingFiles, 0),
|
|
1342
|
+
complete: all.every((envelope) => envelope.coverage.complete),
|
|
1343
|
+
},
|
|
1344
|
+
totals: {
|
|
1345
|
+
occurrences: all.reduce((sum, envelope) => sum + envelope.totals.occurrences, 0),
|
|
1346
|
+
toolCalls: all.reduce((sum, envelope) => sum + envelope.totals.toolCalls, 0),
|
|
1347
|
+
sessions: all.reduce((sum, envelope) => sum + envelope.totals.sessions, 0),
|
|
1348
|
+
},
|
|
1349
|
+
machines: all.flatMap((envelope) => envelope.machines),
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
/** Partition a fleet query by transcript origin so synced mirrors cannot duplicate results. */
|
|
1353
|
+
export function toolOriginSessions(sessions, machine, originOnly) {
|
|
1354
|
+
return originOnly
|
|
1355
|
+
? sessions.filter((session) => (session.machine ?? machine) === machine)
|
|
1356
|
+
: sessions;
|
|
1357
|
+
}
|
|
1358
|
+
export function printToolProgramCount(envelope) {
|
|
1359
|
+
const { totals } = envelope;
|
|
1360
|
+
const qualifier = envelope.coverage.complete ? '' : 'at least ';
|
|
1361
|
+
console.log(`${envelope.query.program}: ${qualifier}${totals.occurrences.toLocaleString()} static occurrence${totals.occurrences === 1 ? '' : 's'} `
|
|
1362
|
+
+ `in ${totals.toolCalls.toLocaleString()} tool call${totals.toolCalls === 1 ? '' : 's'} `
|
|
1363
|
+
+ `across ${totals.sessions.toLocaleString()} session${totals.sessions === 1 ? '' : 's'}.`);
|
|
1364
|
+
if (!envelope.coverage.complete) {
|
|
1365
|
+
console.log(chalk.yellow(`Partial tool index: ${envelope.coverage.remainingFiles.toLocaleString()} transcript${envelope.coverage.remainingFiles === 1 ? '' : 's'} still need `
|
|
1366
|
+
+ '`agents sessions backfill tools`.'));
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
export function toolSearchFleetSortError(sort, spansDevices) {
|
|
1370
|
+
if (!spansDevices || !sort || sort === 'recent')
|
|
1371
|
+
return undefined;
|
|
1372
|
+
return 'Tool search across devices supports only --sort recent; cost and duration are local-only.';
|
|
1373
|
+
}
|
|
1374
|
+
/** Compact grouped evidence for humans; JSON retains every bounded field. */
|
|
1375
|
+
export function printToolSearch(envelope) {
|
|
1376
|
+
for (const session of envelope.sessions) {
|
|
1377
|
+
const machineName = session.machine
|
|
1378
|
+
? truncate(sanitizeForTerminal(session.machine).replace(/\s+/g, ' '), 80)
|
|
1379
|
+
: '';
|
|
1380
|
+
const machine = machineName ? ` @ ${machineName}` : '';
|
|
1381
|
+
const rawHeading = session.label || session.topic || session.project || session.shortId;
|
|
1382
|
+
const heading = truncate(sanitizeForTerminal(rawHeading).replace(/\s+/g, ' '), Math.max(30, terminalWidth() - 20));
|
|
1383
|
+
console.log(`${chalk.cyan(session.shortId)}${chalk.gray(machine)} ${heading}`);
|
|
1384
|
+
for (const call of session.calls) {
|
|
1385
|
+
const tool = truncate(sanitizeForTerminal(call.tool).replace(/\s+/g, ' '), 80);
|
|
1386
|
+
const safePrograms = call.programs.map((program) => truncate(sanitizeForTerminal(program).replace(/\s+/g, ' '), 80));
|
|
1387
|
+
const programs = safePrograms.length > 0 ? ` [${safePrograms.join(', ')}]` : '';
|
|
1388
|
+
const status = call.outcome === 'unknown' ? '' : ` ${call.outcome}`;
|
|
1389
|
+
const input = truncate(sanitizeForTerminal(call.input).replace(/\s+/g, ' '), Math.max(30, terminalWidth() - 26));
|
|
1390
|
+
console.log(` ${chalk.gray(`#${call.ordinal + 1}`)} ${tool}${programs}${status} ${input}`);
|
|
1391
|
+
const snippet = call.error || call.output;
|
|
1392
|
+
if (snippet) {
|
|
1393
|
+
console.log(` ${chalk.gray(truncate(sanitizeForTerminal(snippet).replace(/\s+/g, ' '), Math.max(30, terminalWidth() - 8)))}`);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
console.log();
|
|
1397
|
+
}
|
|
1398
|
+
const count = envelope.sessions.length;
|
|
1399
|
+
console.log(chalk.gray(`${count} matching session${count === 1 ? '' : 's'}.`));
|
|
1400
|
+
if (!envelope.coverage.complete) {
|
|
1401
|
+
const skipped = envelope.coverage.skippedFiles > 0
|
|
1402
|
+
? ` ${envelope.coverage.skippedFiles} transcript${envelope.coverage.skippedFiles === 1 ? ' was' : 's were'} skipped.`
|
|
1403
|
+
: '';
|
|
1404
|
+
const limited = envelope.coverage.limitedFiles > 0
|
|
1405
|
+
? ` ${envelope.coverage.limitedFiles} transcript${envelope.coverage.limitedFiles === 1 ? ' has' : 's have'} incomplete evidence because a safety limit was reached.`
|
|
1406
|
+
: '';
|
|
1407
|
+
const retry = envelope.coverage.remainingFiles > 0
|
|
1408
|
+
? ' Run `agents sessions backfill tools` to index historical transcripts.'
|
|
1409
|
+
: '';
|
|
1410
|
+
console.log(chalk.yellow(`Tool index coverage is partial: ${envelope.coverage.remainingFiles} transcript${envelope.coverage.remainingFiles === 1 ? '' : 's'} remain.${skipped}${limited}${retry}`));
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1289
1413
|
/** Main action handler for `agents sessions`. Routes to picker, table, or single-session render. */
|
|
1290
1414
|
async function sessionsAction(query, options,
|
|
1291
1415
|
/**
|
|
@@ -1294,9 +1418,70 @@ async function sessionsAction(query, options,
|
|
|
1294
1418
|
* because the default arrives as a string like any typed value.
|
|
1295
1419
|
*/
|
|
1296
1420
|
limitSource) {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1421
|
+
const queryClauses = options.query ?? [];
|
|
1422
|
+
const toolOnly = options.include?.split(',').map((role) => role.trim()).filter(Boolean).join(',') === 'tools';
|
|
1423
|
+
const toolEvidenceMode = toolOnly;
|
|
1424
|
+
if (options.count && !toolOnly) {
|
|
1425
|
+
console.error(chalk.red('--count requires --include tools.'));
|
|
1426
|
+
process.exitCode = 1;
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
if (!toolEvidenceMode) {
|
|
1430
|
+
if (queryClauses.length > 1) {
|
|
1431
|
+
console.error(chalk.red('Repeated --query clauses require --include tools.'));
|
|
1432
|
+
process.exitCode = 1;
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
// Outside tool evidence mode, --query retains its original positional-search
|
|
1436
|
+
// meaning. The collector only makes it repeatable for tool clauses.
|
|
1437
|
+
query = query ?? queryClauses[0];
|
|
1438
|
+
}
|
|
1439
|
+
if (options.fleet && !toolEvidenceMode) {
|
|
1440
|
+
console.error(chalk.red('--fleet applies to tool-call queries: add --include tools.'));
|
|
1441
|
+
process.exitCode = 1;
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
if (toolEvidenceMode && (options.markdown || options.redact === false)) {
|
|
1445
|
+
const incompatible = [
|
|
1446
|
+
options.markdown ? '--markdown' : undefined,
|
|
1447
|
+
options.redact === false ? '--no-redact' : undefined,
|
|
1448
|
+
].filter((flag) => flag !== undefined);
|
|
1449
|
+
console.error(chalk.red(`${incompatible.join(' and ')} cannot be used with --include tools.`));
|
|
1450
|
+
console.error(chalk.gray('Tool evidence is always redacted and byte-bounded; drop the conflicting render flag.'));
|
|
1451
|
+
process.exitCode = 1;
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
if (toolEvidenceMode && queryClauses.length > TOOL_QUERY_MAX_CLAUSES) {
|
|
1455
|
+
console.error(chalk.red(`Tool search accepts at most ${TOOL_QUERY_MAX_CLAUSES} --query clauses.`));
|
|
1456
|
+
process.exitCode = 1;
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
if (toolEvidenceMode && queryClauses.some((clause) => Buffer.byteLength(clause) > TOOL_QUERY_MAX_CLAUSE_BYTES)) {
|
|
1460
|
+
console.error(chalk.red(`Each tool --query clause is limited to ${TOOL_QUERY_MAX_CLAUSE_BYTES} bytes.`));
|
|
1461
|
+
process.exitCode = 1;
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (options.count && (queryClauses.length !== 1 || query !== undefined)) {
|
|
1465
|
+
console.error(chalk.red('--count requires exactly one --query program:<name> clause and no positional query.'));
|
|
1466
|
+
process.exitCode = 1;
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (options.count && (limitSource === 'cli' || limitSource === 'env')) {
|
|
1470
|
+
console.error(chalk.red('--count covers the complete filtered scope and cannot be combined with --limit.'));
|
|
1471
|
+
process.exitCode = 1;
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
let countProgram;
|
|
1475
|
+
if (options.count) {
|
|
1476
|
+
try {
|
|
1477
|
+
countProgram = parseToolProgramCountClause(queryClauses[0]);
|
|
1478
|
+
}
|
|
1479
|
+
catch (error) {
|
|
1480
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
1481
|
+
process.exitCode = 1;
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1300
1485
|
// Normalize convenience flags before any routing reads them: per-agent
|
|
1301
1486
|
// shorthands fold into --agent, and --device is an alias for --host (both
|
|
1302
1487
|
// resolve against the same device registry).
|
|
@@ -1352,13 +1537,19 @@ limitSource) {
|
|
|
1352
1537
|
});
|
|
1353
1538
|
return;
|
|
1354
1539
|
}
|
|
1540
|
+
if (toolEvidenceMode && options.local === true
|
|
1541
|
+
&& options.host && !shouldIncludeLocal(options.host, machineId())) {
|
|
1542
|
+
console.error(chalk.red('--local and --device name opposite scopes: --local skips the SSH fan-out that --device needs.'));
|
|
1543
|
+
console.error(chalk.gray('Drop one — `--device <box>` to read that machine, `--local` to stay on this one.'));
|
|
1544
|
+
process.exit(1);
|
|
1545
|
+
}
|
|
1355
1546
|
// --host WITHOUT --active. `--json` fans the recent listing out and emits ONE
|
|
1356
1547
|
// clean merged SessionMeta[] array (same shape as the local --json path), for
|
|
1357
1548
|
// scripts/extensions that JSON.parse a remote's history. Without --json it
|
|
1358
1549
|
// keeps the legacy per-host stream (each remote's raw stdout under a
|
|
1359
1550
|
// `── host ──` banner). With --active, the hosts are folded into the merged
|
|
1360
1551
|
// machine-grouped view instead (handled below).
|
|
1361
|
-
if (options.host && options.host.length > 0 && !options.active) {
|
|
1552
|
+
if (options.host && options.host.length > 0 && !options.active && !toolEvidenceMode) {
|
|
1362
1553
|
// --local means "skip the SSH fan-out"; --host means "look only over there".
|
|
1363
1554
|
// Together they ask for a peer's sessions without dialing the peer, which can
|
|
1364
1555
|
// only ever be empty — so say that instead of rendering a blank list.
|
|
@@ -1493,7 +1684,7 @@ limitSource) {
|
|
|
1493
1684
|
}
|
|
1494
1685
|
const mode = resolveViewMode(options, filterOpts);
|
|
1495
1686
|
// --markdown or any filter flag forces single-session render.
|
|
1496
|
-
const wantsRender = mode === 'markdown' || hasAnyFilter(filterOpts);
|
|
1687
|
+
const wantsRender = !toolEvidenceMode && (mode === 'markdown' || hasAnyFilter(filterOpts));
|
|
1497
1688
|
// Artifact-list or artifact-read paths: widen scope and resolve session globally.
|
|
1498
1689
|
if ((options.artifacts || options.artifact !== undefined) && searchQuery) {
|
|
1499
1690
|
await renderArtifactsGlobal(searchQuery, options.artifacts ?? false, options.artifact, { agent: options.agent, project: options.project });
|
|
@@ -1526,11 +1717,24 @@ limitSource) {
|
|
|
1526
1717
|
const limit = wantsOverview
|
|
1527
1718
|
? OVERVIEW_POOL_LIMIT
|
|
1528
1719
|
: parseInt(userSetLimit ? options.limit : wantsWholeTeam ? String(WHOLE_TEAM_POOL_LIMIT) : DEFAULT_LIMIT, 10);
|
|
1720
|
+
if (toolEvidenceMode && (!Number.isSafeInteger(limit) || limit < 1 || limit > TOOL_QUERY_MAX_RESULT_SESSIONS)) {
|
|
1721
|
+
console.error(chalk.red(`Tool search --limit must be from 1 to ${TOOL_QUERY_MAX_RESULT_SESSIONS}.`));
|
|
1722
|
+
process.exitCode = 1;
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1529
1725
|
// Overview: recency order across the whole index, no default window; an explicit
|
|
1530
1726
|
// --since still narrows. Non-overview keeps the prior interactive-30d default.
|
|
1531
1727
|
const since = wantsOverview
|
|
1532
1728
|
? options.since
|
|
1533
1729
|
: (options.since ?? (isInteractive && !options.all && !wantsWholeTeam ? '30d' : undefined));
|
|
1730
|
+
const toolSpansDevices = toolEvidenceMode
|
|
1731
|
+
&& (options.fleet || (options.host?.length ?? 0) > 0);
|
|
1732
|
+
const toolSortError = toolSearchFleetSortError(options.sort, toolSpansDevices);
|
|
1733
|
+
if (toolSortError) {
|
|
1734
|
+
console.error(chalk.red(toolSortError));
|
|
1735
|
+
process.exitCode = 1;
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1534
1738
|
const spinner = options.json ? null : ora().start();
|
|
1535
1739
|
const tracker = createScanProgressTracker(LOAD_VERBS, 'sessions', spinner);
|
|
1536
1740
|
try {
|
|
@@ -1546,27 +1750,53 @@ limitSource) {
|
|
|
1546
1750
|
// --in-team spans directories by construction: a team's teammates run in
|
|
1547
1751
|
// their own worktrees, so scoping to the current cwd hides most of the
|
|
1548
1752
|
// lineage the flag exists to show.
|
|
1549
|
-
all: pathFilter ? undefined : options.all || wantsWholeTeam,
|
|
1753
|
+
all: pathFilter ? undefined : options.all || wantsWholeTeam || toolSpansDevices,
|
|
1550
1754
|
cwd: process.cwd(),
|
|
1551
1755
|
// Default overview scopes to the current repo SUBTREE (prefix match), so a
|
|
1552
1756
|
// monorepo shows its sub-projects grouped instead of collapsing to the one
|
|
1553
1757
|
// exact-cwd project. `--all` clears the prefix and spans the whole index.
|
|
1554
|
-
cwdPrefix: pathFilter ?? (wantsOverview && !options.all && !wantsWholeTeam ? process.cwd() : undefined),
|
|
1758
|
+
cwdPrefix: pathFilter ?? (wantsOverview && !options.all && !wantsWholeTeam && !toolSpansDevices ? process.cwd() : undefined),
|
|
1555
1759
|
project: options.project,
|
|
1556
1760
|
since,
|
|
1557
1761
|
until: options.until,
|
|
1558
1762
|
sortBy,
|
|
1559
1763
|
origin: options.routine ? 'routine' : undefined,
|
|
1764
|
+
skipExistenceCheck: toolEvidenceMode,
|
|
1765
|
+
unbounded: toolEvidenceMode,
|
|
1766
|
+
skill: options.skill,
|
|
1767
|
+
plugin: options.plugin,
|
|
1560
1768
|
};
|
|
1561
1769
|
let hiddenUnmanaged = 0;
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
includeUnmanaged: options.unmanaged
|
|
1568
|
-
|
|
1569
|
-
|
|
1770
|
+
const toolSelf = toolEvidenceMode ? machineId() : undefined;
|
|
1771
|
+
const toolIncludesLocal = !toolEvidenceMode
|
|
1772
|
+
|| process.env[NO_FANOUT_ENV] === '1'
|
|
1773
|
+
|| shouldIncludeLocal(options.host, toolSelf);
|
|
1774
|
+
const indexedIdMatches = toolIncludesLocal && toolEvidenceMode && searchQuery && looksLikeSessionId(searchQuery)
|
|
1775
|
+
? scopeToManaged(findSessionsById(searchQuery, { agent, version, project: options.project }), agent ? [agent] : SESSION_AGENTS, { agent, includeUnmanaged: options.unmanaged })
|
|
1776
|
+
: [];
|
|
1777
|
+
let sessions;
|
|
1778
|
+
if (!toolIncludesLocal) {
|
|
1779
|
+
sessions = [];
|
|
1780
|
+
}
|
|
1781
|
+
else if (indexedIdMatches.length > 0) {
|
|
1782
|
+
sessions = indexedIdMatches.map((session) => ({
|
|
1783
|
+
...session,
|
|
1784
|
+
machine: session.machine ?? toolSelf,
|
|
1785
|
+
}));
|
|
1786
|
+
}
|
|
1787
|
+
else {
|
|
1788
|
+
const readOptions = {
|
|
1789
|
+
...scope,
|
|
1790
|
+
limit,
|
|
1791
|
+
excludeTeamOrigin: !options.teams,
|
|
1792
|
+
onProgress: tracker.onProgress,
|
|
1793
|
+
includeUnmanaged: options.unmanaged,
|
|
1794
|
+
onHiddenUnmanaged: (n) => { hiddenUnmanaged = n; },
|
|
1795
|
+
};
|
|
1796
|
+
sessions = toolEvidenceMode
|
|
1797
|
+
? await queryIndexedSessions(readOptions, { resolveLinear: false })
|
|
1798
|
+
: await discoverSessions(readOptions);
|
|
1799
|
+
}
|
|
1570
1800
|
tracker.stop();
|
|
1571
1801
|
spinner?.stop();
|
|
1572
1802
|
// Version filter is pushed down to SQL via scope.version above; no
|
|
@@ -1589,6 +1819,58 @@ limitSource) {
|
|
|
1589
1819
|
const starred = listFavorites();
|
|
1590
1820
|
sessions = sessions.filter((s) => starred.has(s.id));
|
|
1591
1821
|
}
|
|
1822
|
+
if (toolEvidenceMode) {
|
|
1823
|
+
const self = toolSelf;
|
|
1824
|
+
const selectedSessions = searchQuery
|
|
1825
|
+
? filterSessionsByQuery(sessions, searchQuery)
|
|
1826
|
+
: sessions;
|
|
1827
|
+
const localSessions = selectedSessions;
|
|
1828
|
+
const mayFanOut = options.local !== true && process.env[NO_FANOUT_ENV] !== '1';
|
|
1829
|
+
const hosts = remoteHostsToDial(options.host, self);
|
|
1830
|
+
const originOnly = process.env[NO_FANOUT_ENV] === '1'
|
|
1831
|
+
|| (mayFanOut && (options.fleet || (options.host?.length ?? 0) > 0));
|
|
1832
|
+
const querySessions = toolOriginSessions(localSessions, self, originOnly);
|
|
1833
|
+
if (countProgram) {
|
|
1834
|
+
const countCoverage = readToolIndexCoverage(querySessions);
|
|
1835
|
+
let countEnvelope = countToolProgramOccurrences(querySessions, countProgram, countCoverage, self);
|
|
1836
|
+
if (!toolIncludesLocal)
|
|
1837
|
+
countEnvelope.machines = [];
|
|
1838
|
+
if (mayFanOut && (options.fleet || (options.host?.length ?? 0) > 0)
|
|
1839
|
+
&& (!options.host?.length || (hosts && hosts.length > 0))) {
|
|
1840
|
+
const stripped = toolSearchForwardedArgs(process.argv, options.host ?? []);
|
|
1841
|
+
const remote = await gatherRemoteToolProgramCounts(stripped, options.host?.length ? hosts : undefined, countProgram);
|
|
1842
|
+
countEnvelope = mergeToolProgramCountEnvelopes(countEnvelope, remote.envelopes.map((item) => item.envelope));
|
|
1843
|
+
if (remote.unreachable.length > 0)
|
|
1844
|
+
countEnvelope.coverage.complete = false;
|
|
1845
|
+
}
|
|
1846
|
+
if (options.json)
|
|
1847
|
+
process.stdout.write(JSON.stringify(countEnvelope, null, 2) + '\n');
|
|
1848
|
+
else
|
|
1849
|
+
printToolProgramCount(countEnvelope);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
const coverage = readToolIndexCoverage(querySessions);
|
|
1853
|
+
let envelope = searchToolCalls(querySessions, queryClauses, coverage, limit);
|
|
1854
|
+
if (mayFanOut && (options.fleet || (options.host?.length ?? 0) > 0)) {
|
|
1855
|
+
if (!options.host?.length || (hosts && hosts.length > 0)) {
|
|
1856
|
+
const stripped = toolSearchForwardedArgs(process.argv, options.host ?? []);
|
|
1857
|
+
const remote = await gatherRemoteToolSearch(stripped, options.host?.length ? hosts : undefined, toolSearchRemoteReceiveBudget(envelope), queryClauses);
|
|
1858
|
+
envelope = mergeToolSearchEnvelopes(envelope, remote.envelopes.map((item) => item.envelope));
|
|
1859
|
+
if (remote.truncated.length > 0 || remote.unreachable.length > 0) {
|
|
1860
|
+
envelope.coverage.complete = false;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
envelope.sessions = envelope.sessions.slice(0, limit);
|
|
1865
|
+
const serializedEnvelope = serializeToolSearchEnvelope(envelope);
|
|
1866
|
+
if (options.json) {
|
|
1867
|
+
process.stdout.write(serializedEnvelope);
|
|
1868
|
+
}
|
|
1869
|
+
else {
|
|
1870
|
+
printToolSearch(envelope);
|
|
1871
|
+
}
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1592
1874
|
// Under --in-team the visible list is one team, so the whole-index team-origin
|
|
1593
1875
|
// count would be a non-sequitur next to it.
|
|
1594
1876
|
const hiddenCount = options.teams || options.inTeam
|
|
@@ -3154,6 +3436,15 @@ export function metadataResolveForwardedArgs(selector, scope) {
|
|
|
3154
3436
|
args.push('--project', scope.project);
|
|
3155
3437
|
return args;
|
|
3156
3438
|
}
|
|
3439
|
+
/** Build the local-only peer argv for a distributed tool search. */
|
|
3440
|
+
export function toolSearchForwardedArgs(argv, hosts) {
|
|
3441
|
+
const args = ensureWholeIndex(buildForwardedArgs(argv, new Set(hosts)).filter((arg) => arg !== '--fleet'));
|
|
3442
|
+
if (!args.includes('--json'))
|
|
3443
|
+
args.push('--json');
|
|
3444
|
+
if (!args.includes('--local'))
|
|
3445
|
+
args.push('--local');
|
|
3446
|
+
return args;
|
|
3447
|
+
}
|
|
3157
3448
|
/** Resolution must fail closed when any selected peer did not answer. Choosing
|
|
3158
3449
|
* from a partial fleet can turn an unseen candidate into a false unique match. */
|
|
3159
3450
|
export function metadataResolveOutcome(localMatches, remote, selector) {
|
|
@@ -3289,7 +3580,7 @@ export function registerSessionsCommands(program) {
|
|
|
3289
3580
|
const sessionsCmd = program
|
|
3290
3581
|
.command('sessions')
|
|
3291
3582
|
.argument('[query]', 'Session ID, search query, or path (., ../, /path) to filter by project')
|
|
3292
|
-
.option('--query <
|
|
3583
|
+
.option('--query <clause>', 'Search text; repeat with --include tools to require distinct matching calls', collectQueryClause, [])
|
|
3293
3584
|
.option('--resolve <selector>', 'Resolve one full ID, unique prefix, or keyword query to safe session metadata (requires --json; searches the fleet unless --local)')
|
|
3294
3585
|
.addOption(new Option('--resolve-safe-v1 <selector>').hideHelp())
|
|
3295
3586
|
.description('Find, browse, and read agent conversation transcripts across Claude, Codex, Gemini, and OpenCode.')
|
|
@@ -3306,6 +3597,8 @@ export function registerSessionsCommands(program) {
|
|
|
3306
3597
|
.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.")
|
|
3307
3598
|
.option('--routine', 'Show only sessions archived from routine runs')
|
|
3308
3599
|
.option('-p, --project <name>', 'Filter by project name (searches across all directories)')
|
|
3600
|
+
.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)')
|
|
3601
|
+
.option('--plugin <name>', 'Only sessions that used a skill/command owned by this plugin')
|
|
3309
3602
|
.option('--since <time>', 'Only sessions newer than this (e.g., 2h, 7d, 4w, or ISO date)')
|
|
3310
3603
|
.option('--until <time>', 'Only sessions older than this (ISO timestamp)')
|
|
3311
3604
|
.option('-n, --limit <n>', 'Maximum number of sessions to return', DEFAULT_LIMIT)
|
|
@@ -3330,6 +3623,8 @@ export function registerSessionsCommands(program) {
|
|
|
3330
3623
|
.option('--cloud', 'Source sessions from Rush Cloud (captured runs) instead of local disk')
|
|
3331
3624
|
.option('-H, --host <target...>', 'Run this query on remote machine(s) over SSH (host alias or user@host; repeatable)')
|
|
3332
3625
|
.option('--device <target...>', 'Alias for --host (device alias from `agents devices`; repeatable)')
|
|
3626
|
+
.option('--fleet', 'With --include tools: query every registered online compute device and merge compact matches')
|
|
3627
|
+
.option('--count', 'With one program:<name> tool query: count static occurrences, containing calls, and sessions')
|
|
3333
3628
|
.option('--browser', 'List browser-profile captures (screenshots, PDFs, recordings, downloads) instead of agent transcripts — alias of `agents browser sessions`')
|
|
3334
3629
|
.option('--no-interactive', 'Print the listing instead of opening the interactive browser (default on a TTY for the bare listing and --active)')
|
|
3335
3630
|
.option('--print-cmd', 'Print the canonical `ag sessions …` command for the given flags and exit (the twin of the browser’s `y` hotkey)')
|
|
@@ -3366,6 +3661,18 @@ export function registerSessionsCommands(program) {
|
|
|
3366
3661
|
# Export for analysis
|
|
3367
3662
|
agents sessions --since 30d --limit 200 --json > sessions.json
|
|
3368
3663
|
|
|
3664
|
+
# List indexed tool calls in recent Codex sessions on one device
|
|
3665
|
+
agents sessions --include tools --agent codex --device mac-mini --since 7d
|
|
3666
|
+
|
|
3667
|
+
# Each repeated clause must match a different call in the same session
|
|
3668
|
+
agents sessions --include tools --query 'program:git input:merge' --query 'program:gh output:CONFLICT' --fleet --json
|
|
3669
|
+
|
|
3670
|
+
# Count every pre-indexed static git site without reparsing transcripts
|
|
3671
|
+
agents sessions --include tools --query 'program:git' --count --fleet --json
|
|
3672
|
+
|
|
3673
|
+
# Explicitly populate historical tool rows once on every device
|
|
3674
|
+
agents sessions backfill tools --fleet
|
|
3675
|
+
|
|
3369
3676
|
# Resolve one historical selector to metadata only, across the fleet
|
|
3370
3677
|
agents sessions --resolve d3470b57 --json
|
|
3371
3678
|
|
|
@@ -3380,6 +3687,11 @@ export function registerSessionsCommands(program) {
|
|
|
3380
3687
|
- --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.
|
|
3381
3688
|
- --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.
|
|
3382
3689
|
- --include and --exclude are mutually exclusive.
|
|
3690
|
+
- With --include tools, repeat --query for same-session AND across distinct calls. Fields: tool, program, input, output, status, exit, error.
|
|
3691
|
+
- --count accepts exactly one program:<name> clause and reports static source occurrences, containing tool calls, and sessions.
|
|
3692
|
+
- Tool queries read SQLite only. Run 'agents sessions backfill tools' once for historical transcripts; normal scans index new and changed sessions.
|
|
3693
|
+
- Tool evidence is redacted and bounded before it reaches SQLite. --markdown and --no-redact conflict with --include tools.
|
|
3694
|
+
- Tool queries accept 32 clauses (4 KiB each), --limit 1–1,000, and at most 8 MiB of materialized evidence.
|
|
3383
3695
|
- --first and --last are mutually exclusive.
|
|
3384
3696
|
- A filter flag (--include/--exclude/--first/--last) without --markdown/--json defaults to --markdown output.
|
|
3385
3697
|
- --cloud sources from Rush Cloud captured runs instead of local disk.
|
|
@@ -3407,6 +3719,7 @@ export function registerSessionsCommands(program) {
|
|
|
3407
3719
|
registerSessionsImportCommand(sessionsCmd);
|
|
3408
3720
|
registerSessionsMigrateCommand(sessionsCmd);
|
|
3409
3721
|
registerSessionsMigrationsCommand(sessionsCmd);
|
|
3722
|
+
registerSessionsBackfillCommand(sessionsCmd);
|
|
3410
3723
|
}
|
|
3411
3724
|
function formatNoSessionsMessage(showAll, project) {
|
|
3412
3725
|
const projectQuery = project?.trim();
|
package/dist/commands/teams.js
CHANGED
|
@@ -1279,7 +1279,7 @@ export function registerTeamsCommands(program) {
|
|
|
1279
1279
|
.option('-n, --name <name>', 'Friendly name for this teammate (e.g. alice). Required if using --after. Unique within team.')
|
|
1280
1280
|
.option('-m, --mode <mode>', `Permissions: plan (read-only) | edit (can write files) | auto (smart classifier auto-approves safe ops) | skip (bypass all permission prompts). 'full' accepted as alias for skip. Teammates run headless: plan works headless on claude/codex/droid/opencode; kimi/grok/cursor/antigravity have no headless plan mode and auto-downgrade a plan request to auto.`, 'edit')
|
|
1281
1281
|
.option('-e, --effort <effort>', `Reasoning intensity: ${VALID_EFFORTS.join('|')}`, 'medium')
|
|
1282
|
-
.option('--model <model>', '
|
|
1282
|
+
.option('--model <model>', 'Cost tier (cheap|default|best|ultra) or a concrete id (e.g. claude-opus-4-8); tiers resolve per harness+version to a supported model')
|
|
1283
1283
|
.option('--env <key=value>', 'Set an environment variable for this teammate (repeatable for multiple vars)', (val, prev) => [...prev, val], [])
|
|
1284
1284
|
.option('--cwd <dir>', 'Working directory for this teammate (default: current directory)')
|
|
1285
1285
|
.option('--worktree <name>', 'Run this teammate in a dedicated git worktree (required when team has --enable-worktrees)')
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* agents worktree prune -> removes every clean+merged one
|
|
12
12
|
*
|
|
13
13
|
* Worktrees live at <repo>/.agents/worktrees/<terminal-id>, on a branch
|
|
14
|
-
* named agents/<terminal-id>.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* named agents/<terminal-id>. New branches start at the freshly-fetched
|
|
15
|
+
* origin/<default> (not local HEAD), matching lib/teams/worktree.ts and the
|
|
16
|
+
* agent-system rule that keeps coding-agent worktrees under .agents/.
|
|
17
17
|
*/
|
|
18
18
|
import type { Command } from 'commander';
|
|
19
19
|
export declare function registerWorktreeCommands(program: Command): void;
|
|
@@ -67,6 +67,25 @@ async function inspect(root, terminalId) {
|
|
|
67
67
|
}
|
|
68
68
|
return { exists, dirty, aheadOfUpstream, hasUpstream, branchMerged };
|
|
69
69
|
}
|
|
70
|
+
/** Resolve origin/HEAD → default branch name; falls back to `main`. */
|
|
71
|
+
async function defaultBranch(root) {
|
|
72
|
+
try {
|
|
73
|
+
await execFileAsync('git', ['remote', 'set-head', 'origin', '--auto'], { cwd: root });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// offline / no origin
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const { stdout } = await execFileAsync('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { cwd: root });
|
|
80
|
+
const base = stdout.trim().replace(/^origin\//, '');
|
|
81
|
+
if (base)
|
|
82
|
+
return base;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// no origin/HEAD
|
|
86
|
+
}
|
|
87
|
+
return 'main';
|
|
88
|
+
}
|
|
70
89
|
async function provision(root, terminalId) {
|
|
71
90
|
const wt = worktreePathFor(root, terminalId);
|
|
72
91
|
const branch = branchNameFor(terminalId);
|
|
@@ -84,10 +103,22 @@ async function provision(root, terminalId) {
|
|
|
84
103
|
catch {
|
|
85
104
|
branchExists = false;
|
|
86
105
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
if (branchExists) {
|
|
107
|
+
await execFileAsync('git', ['worktree', 'add', wt, branch], { cwd: root });
|
|
108
|
+
return wt;
|
|
109
|
+
}
|
|
110
|
+
// New branch: fetch + base on origin/<default>, never local HEAD (stale trap).
|
|
111
|
+
const base = await defaultBranch(root);
|
|
112
|
+
try {
|
|
113
|
+
await execFileAsync('git', ['fetch', 'origin'], { cwd: root });
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const detail = (err?.stderr || err?.message || String(err)).toString().trim();
|
|
117
|
+
die(`git fetch origin failed in ${root}` +
|
|
118
|
+
(detail ? `: ${detail}` : '') +
|
|
119
|
+
`. Cannot provision a worktree from a stale remote-tracking ref.`);
|
|
120
|
+
}
|
|
121
|
+
await execFileAsync('git', ['worktree', 'add', '-b', branch, wt, `origin/${base}`], { cwd: root });
|
|
91
122
|
return wt;
|
|
92
123
|
}
|
|
93
124
|
async function release(root, terminalId, force) {
|
package/dist/index.js
CHANGED
|
@@ -99,6 +99,7 @@ import { applyGlobalHelpConventions } from './lib/help.js';
|
|
|
99
99
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
100
100
|
import { getCliLaunch } from './lib/cli-entry.js';
|
|
101
101
|
import { emit, emitFriction, redactArgs } from './lib/events.js';
|
|
102
|
+
import { stampProvenance } from './lib/event-provenance.js';
|
|
102
103
|
import { die } from './lib/format.js';
|
|
103
104
|
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
104
105
|
// `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
|
|
@@ -211,12 +212,19 @@ program.hook('postAction', (_thisCommand, actionCommand) => {
|
|
|
211
212
|
}
|
|
212
213
|
// Disposable perf warehouse — fail-soft spool append (no SQLite on this path).
|
|
213
214
|
if (durationMs !== undefined && parts[0] !== 'perf') {
|
|
215
|
+
// sessionId/agent are resolvable here the same way emit() resolves them
|
|
216
|
+
// for command.start/command.end above (the shared provenance floor,
|
|
217
|
+
// event-provenance.ts) — without this, every command.end perf sample
|
|
218
|
+
// was anonymous, unlike the audit log record right next to it.
|
|
219
|
+
const { sessionId, agent } = stampProvenance();
|
|
214
220
|
void import('./lib/perf/spool.js').then(({ recordSample }) => {
|
|
215
221
|
recordSample({
|
|
216
222
|
kind: 'command.end',
|
|
217
223
|
label: command,
|
|
218
224
|
durationMs,
|
|
219
225
|
cwd: process.cwd(),
|
|
226
|
+
sessionId,
|
|
227
|
+
agent,
|
|
220
228
|
});
|
|
221
229
|
}).catch(() => { });
|
|
222
230
|
}
|
|
@@ -486,6 +486,7 @@ export class BrowserService {
|
|
|
486
486
|
const sessionId = await this.getSessionId(conn, cdpTargetId);
|
|
487
487
|
await conn.cdp.send('Page.navigate', { url }, sessionId);
|
|
488
488
|
await this.saveTaskState(task.profile, conn.tasks);
|
|
489
|
+
emit('browser.navigate', { profile: task.profile, task: task.name, url, tabId: currentShortId, created: false });
|
|
489
490
|
return { tabId: currentShortId, url, created: false };
|
|
490
491
|
}
|
|
491
492
|
// No current tab - create one
|
|
@@ -500,6 +501,7 @@ export class BrowserService {
|
|
|
500
501
|
task.tabs[shortId] = cdpTargetId;
|
|
501
502
|
task.currentTabId = shortId;
|
|
502
503
|
await this.saveTaskState(task.profile, conn.tasks);
|
|
504
|
+
emit('browser.navigate', { profile: task.profile, task: task.name, url, tabId: shortId, created: true });
|
|
503
505
|
return { tabId: shortId, url, created: true };
|
|
504
506
|
}
|
|
505
507
|
// Chrome: create new tab
|
|
@@ -511,6 +513,7 @@ export class BrowserService {
|
|
|
511
513
|
task.currentTabId = shortId;
|
|
512
514
|
this.invalidateTargetCache(conn);
|
|
513
515
|
await this.saveTaskState(task.profile, conn.tasks);
|
|
516
|
+
emit('browser.navigate', { profile: task.profile, task: task.name, url, tabId: shortId, created: true });
|
|
514
517
|
return { tabId: shortId, url, created: true };
|
|
515
518
|
}
|
|
516
519
|
async tabAdd(taskId, url, profileName) {
|
|
@@ -705,6 +708,16 @@ export class BrowserService {
|
|
|
705
708
|
await fs.promises.writeFile(finalPath, buffer);
|
|
706
709
|
const dims = (extension === 'png' ? readPngDimensions(buffer) : readJpegDimensions(buffer)) ??
|
|
707
710
|
{ width: 0, height: 0 };
|
|
711
|
+
emit('browser.screenshot', {
|
|
712
|
+
profile: profileName,
|
|
713
|
+
task: task.name,
|
|
714
|
+
tabId: shortId,
|
|
715
|
+
path: finalPath,
|
|
716
|
+
bytes: buffer.length,
|
|
717
|
+
width: dims.width,
|
|
718
|
+
height: dims.height,
|
|
719
|
+
quality,
|
|
720
|
+
});
|
|
708
721
|
return { path: finalPath, bytes: buffer.length, width: dims.width, height: dims.height };
|
|
709
722
|
}
|
|
710
723
|
/**
|