@phnx-labs/agents-cli 1.22.43 → 1.22.45
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 +85 -0
- package/README.md +14 -12
- package/dist/bootstrap.js +3 -6
- package/dist/cli/command-registry.d.ts +0 -7
- package/dist/cli/command-registry.js +1 -21
- package/dist/commands/accounts.d.ts +1 -9
- package/dist/commands/accounts.js +12 -90
- package/dist/commands/apply.d.ts +7 -10
- package/dist/commands/apply.js +15 -35
- package/dist/commands/artifacts.js +4 -5
- package/dist/commands/audit.d.ts +6 -6
- package/dist/commands/audit.js +12 -12
- package/dist/commands/beta.d.ts +7 -1
- package/dist/commands/beta.js +16 -9
- package/dist/commands/commands.js +1 -1
- package/dist/commands/doctor.js +1 -4
- package/dist/commands/events.d.ts +2 -2
- package/dist/commands/events.js +6 -5
- package/dist/commands/exec.d.ts +1 -1
- package/dist/commands/exec.js +8 -4
- package/dist/commands/factory.d.ts +1 -1
- package/dist/commands/fleet-capture.d.ts +1 -1
- package/dist/commands/fleet-capture.js +4 -5
- package/dist/commands/harness.js +1 -49
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +85 -156
- package/dist/commands/menubar.js +58 -1
- package/dist/commands/output.js +2 -2
- package/dist/commands/prune.js +5 -4
- package/dist/commands/routines.test-fixture.d.ts +86 -0
- package/dist/commands/routines.test-fixture.js +345 -0
- package/dist/commands/sessions.test-fixture.d.ts +24 -0
- package/dist/commands/sessions.test-fixture.js +224 -0
- package/dist/commands/setup.js +5 -1
- package/dist/commands/share.d.ts +9 -10
- package/dist/commands/share.js +68 -50
- package/dist/commands/skills.js +1 -1
- package/dist/commands/ssh.js +15 -62
- package/dist/commands/subagents.js +1 -1
- package/dist/commands/view.js +5 -2
- package/dist/lib/analytics/mix-commands.d.ts +6 -28
- package/dist/lib/analytics/mix-commands.js +14 -40
- package/dist/lib/audit/log.d.ts +2 -2
- package/dist/lib/audit/log.js +2 -2
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/domain-skills.d.ts +26 -6
- package/dist/lib/browser/domain-skills.js +81 -43
- package/dist/lib/daemon/daemon.js +11 -0
- package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
- package/dist/lib/daemon/daemon.test-fixture.js +71 -0
- package/dist/lib/daemon-ticks.d.ts +38 -1
- package/dist/lib/daemon-ticks.js +52 -5
- package/dist/lib/device-config.js +1 -2
- package/dist/lib/devices/discovery-policy.d.ts +3 -3
- package/dist/lib/devices/discovery-policy.js +3 -3
- package/dist/lib/devices/fleet.d.ts +4 -8
- package/dist/lib/devices/fleet.js +3 -15
- package/dist/lib/devices/health-report.js +3 -3
- package/dist/lib/devices/pool.d.ts +0 -6
- package/dist/lib/devices/pool.js +0 -6
- package/dist/lib/devices/registry.d.ts +0 -15
- package/dist/lib/devices/registry.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +12 -0
- package/dist/lib/devices/stats-cache.js +24 -5
- package/dist/lib/fleet/capture.d.ts +1 -1
- package/dist/lib/fleet/manifest.js +1 -1
- package/dist/lib/fleet/remote-login.d.ts +2 -2
- package/dist/lib/fleet/remote-login.js +3 -4
- package/dist/lib/hosts/dispatch.d.ts +2 -0
- package/dist/lib/hosts/dispatch.js +8 -1
- package/dist/lib/hosts/passthrough.js +0 -1
- package/dist/lib/hosts/providers/devices.js +1 -13
- package/dist/lib/hosts/ready.d.ts +12 -3
- package/dist/lib/hosts/ready.js +27 -12
- package/dist/lib/hosts/registry.d.ts +4 -5
- package/dist/lib/hosts/registry.js +3 -7
- package/dist/lib/installations/versions.js +21 -2
- package/dist/lib/menubar/install-menubar.d.ts +109 -0
- package/dist/lib/menubar/install-menubar.js +199 -4
- package/dist/lib/remote-agents-json.js +1 -7
- package/dist/lib/routines-placement.d.ts +1 -1
- package/dist/lib/routines-placement.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/session/active.d.ts +4 -16
- package/dist/lib/session/active.js +2 -6
- package/dist/lib/session/db.d.ts +13 -98
- package/dist/lib/session/db.js +4 -11
- package/dist/lib/session/discover.d.ts +5 -81
- package/dist/lib/session/discover.js +5 -5
- package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote/remote-bundle.js +61 -0
- package/dist/lib/session/remote/remote-list.d.ts +148 -0
- package/dist/lib/session/remote/remote-list.js +607 -0
- package/dist/lib/session/remote/remote.d.ts +132 -0
- package/dist/lib/session/remote/remote.js +314 -0
- package/dist/lib/session/remote/watch.d.ts +101 -0
- package/dist/lib/session/remote/watch.js +241 -0
- package/dist/lib/session/remote-bundle.d.ts +1 -12
- package/dist/lib/session/remote-bundle.js +3 -61
- package/dist/lib/session/remote-list.d.ts +1 -148
- package/dist/lib/session/remote-list.js +3 -607
- package/dist/lib/session/remote.d.ts +1 -132
- package/dist/lib/session/remote.js +3 -314
- package/dist/lib/session/watch.d.ts +1 -101
- package/dist/lib/session/watch.js +3 -242
- package/dist/lib/share/config.d.ts +12 -2
- package/dist/lib/share/config.js +47 -11
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/publish.js +1 -1
- package/dist/lib/share/worker-template.js +2 -2
- package/dist/lib/smart-launch.d.ts +4 -4
- package/dist/lib/smart-launch.js +8 -18
- package/dist/lib/staleness/index.d.ts +3 -1
- package/dist/lib/staleness/index.js +21 -1
- package/dist/lib/staleness/types.d.ts +11 -0
- package/dist/lib/staleness/writers/commands.js +13 -4
- package/dist/lib/staleness/writers/hooks.js +3 -1
- package/dist/lib/staleness/writers/mcp.d.ts +0 -7
- package/dist/lib/staleness/writers/mcp.js +16 -1
- package/dist/lib/staleness/writers/rules.js +1 -1
- package/dist/lib/staleness/writers/skills.js +3 -1
- package/dist/lib/staleness/writers/subagents.d.ts +0 -9
- package/dist/lib/staleness/writers/subagents.js +18 -1
- package/dist/lib/staleness/writers/types.d.ts +10 -0
- package/dist/lib/startup/command-registry.d.ts +8 -1
- package/dist/lib/startup/command-registry.js +21 -6
- package/dist/lib/startup/root-command.d.ts +18 -1
- package/dist/lib/startup/root-command.js +18 -1
- package/dist/lib/state.js +7 -0
- package/dist/lib/teams/placement-probe.js +27 -12
- package/dist/lib/teams/scheduler.d.ts +3 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/view-types.d.ts +3 -0
- package/package.json +2 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/auth.d.ts +0 -9
- package/dist/commands/auth.js +0 -108
- package/dist/commands/org.d.ts +0 -6
- package/dist/commands/org.js +0 -175
- package/dist/commands/serve.d.ts +0 -10
- package/dist/commands/serve.js +0 -68
- package/dist/commands/trends.d.ts +0 -10
- package/dist/commands/trends.js +0 -12
- package/dist/lib/entitlement.d.ts +0 -31
- package/dist/lib/entitlement.js +0 -137
- package/dist/lib/prix-account.d.ts +0 -158
- package/dist/lib/prix-account.js +0 -214
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/serve/control.d.ts +0 -95
- package/dist/lib/serve/control.js +0 -260
- package/dist/lib/serve/data.d.ts +0 -81
- package/dist/lib/serve/data.js +0 -91
- package/dist/lib/serve/page.d.ts +0 -7
- package/dist/lib/serve/page.js +0 -140
- package/dist/lib/serve/server.d.ts +0 -80
- package/dist/lib/serve/server.js +0 -145
- package/dist/lib/serve/stream.d.ts +0 -43
- package/dist/lib/serve/stream.js +0 -116
- package/dist/lib/serve/token.d.ts +0 -35
- package/dist/lib/serve/token.js +0 -85
|
@@ -59,7 +59,7 @@ export declare function attributedSetLostPids(prev: Set<number>, next: Set<numbe
|
|
|
59
59
|
* the same pid-reuse guard {@link isPidAlive} uses elsewhere.
|
|
60
60
|
*/
|
|
61
61
|
export declare function filterCachedUnattributed(sessions: ActiveSession[], attributed: Set<number>, alive: (pid: number, startedAtMs?: number) => boolean): ActiveSession[];
|
|
62
|
-
|
|
62
|
+
type ActiveContext = 'terminal' | 'teams' | 'cloud' | 'headless';
|
|
63
63
|
/** The SessionMeta fields the live-row backfill reads — the enrichment a running process cannot report. */
|
|
64
64
|
export type BackfillMeta = Pick<SessionMeta, 'version' | 'timestamp' | 'label' | 'ticketId' | 'prUrl' | 'prNumber' | 'origin' | 'routineName'>;
|
|
65
65
|
export declare function backfillActiveRowsFromMeta(sessions: ActiveSession[], metaById: Map<string, BackfillMeta>): void;
|
|
@@ -335,7 +335,7 @@ export interface ActiveSession {
|
|
|
335
335
|
paneId?: string;
|
|
336
336
|
}
|
|
337
337
|
export declare function activeStatusFromCloudStatus(status: CloudTaskStatus): ActiveStatus;
|
|
338
|
-
|
|
338
|
+
interface ActiveQueryOptions {
|
|
339
339
|
/** Skip the `ps` scan for ad-hoc headless agents. */
|
|
340
340
|
skipHeadless?: boolean;
|
|
341
341
|
/**
|
|
@@ -359,14 +359,6 @@ export interface ActiveQueryOptions {
|
|
|
359
359
|
export declare class TmuxDiscoveryDegradedError extends Error {
|
|
360
360
|
constructor(message: string);
|
|
361
361
|
}
|
|
362
|
-
/**
|
|
363
|
-
* A live process can only borrow an indexed session file if that transcript
|
|
364
|
-
* has been touched recently enough to plausibly belong to the process. This is
|
|
365
|
-
* deliberately wider than ACTIVE_MTIME_WINDOW_MS: an inactive-but-live CLI can
|
|
366
|
-
* be idle for longer than 2 minutes, but it must not attach to a weeks-old
|
|
367
|
-
* transcript just because a GUI app service with the same basename is alive.
|
|
368
|
-
*/
|
|
369
|
-
export declare const ACTIVE_SESSION_STALE_MS: number;
|
|
370
362
|
/**
|
|
371
363
|
* A session whose transcript hasn't been written in this long is ABANDONED /
|
|
372
364
|
* dangling — the framework can no longer treat it as live work, whether its PID
|
|
@@ -420,8 +412,6 @@ export declare function resolveNamesToSessionIds(sessionNames: string[], deps: {
|
|
|
420
412
|
* the start time can't be read, we keep the existence answer.
|
|
421
413
|
*/
|
|
422
414
|
export declare function isPidAlive(pid: number, startedAtMs?: number): boolean;
|
|
423
|
-
/** Test seam: drop the Claude session-file path memo. */
|
|
424
|
-
export declare function clearClaudeSessionFileCacheForTest(): void;
|
|
425
415
|
/**
|
|
426
416
|
* Resolve a Claude transcript for `cwd` across the given project roots, newest
|
|
427
417
|
* mtime winning when the same id/cwd resolves in more than one version home
|
|
@@ -553,8 +543,6 @@ export declare function listTeamsActive(opts?: {
|
|
|
553
543
|
}): Promise<ActiveSession[]>;
|
|
554
544
|
/** Live editor-terminal agents across every IDE window. */
|
|
555
545
|
export declare function listTerminalsActive(): Promise<ActiveSession[]>;
|
|
556
|
-
/** Cloud tasks still in a non-terminal state. `tasks.db` may not exist; that's fine. */
|
|
557
|
-
export declare function listCloudActive(): ActiveSession[];
|
|
558
546
|
interface ProcRow {
|
|
559
547
|
pid: number;
|
|
560
548
|
ppid: number;
|
|
@@ -628,7 +616,7 @@ export declare function foldSubordinateAgents(candidates: AgentCandidate[], ppid
|
|
|
628
616
|
*/
|
|
629
617
|
export declare function listUnattributedActive(attributed: Set<number>): Promise<ActiveSession[]>;
|
|
630
618
|
/** One tmux pane's resolved agent identity for the authoritative source. */
|
|
631
|
-
|
|
619
|
+
interface PaneIdentity {
|
|
632
620
|
agent: string;
|
|
633
621
|
/** Exact session id when resolvable (launch registry, or the hook join). */
|
|
634
622
|
sessionId?: string;
|
|
@@ -679,7 +667,7 @@ export declare function listTmuxAgentSessions(): Promise<ActiveSession[]>;
|
|
|
679
667
|
*/
|
|
680
668
|
export declare function getActiveSessions(opts?: ActiveQueryOptions): Promise<ActiveSession[]>;
|
|
681
669
|
/** Local discovery sources probed by {@link describeActiveDiscoveryHealth}. */
|
|
682
|
-
|
|
670
|
+
interface ActiveDiscoveryHealth {
|
|
683
671
|
/** Sources that failed on the probe (currently only `'tmux'`). Empty = nothing degraded locally. */
|
|
684
672
|
degradedSources: string[];
|
|
685
673
|
}
|
|
@@ -262,7 +262,7 @@ export class TmuxDiscoveryDegradedError extends Error {
|
|
|
262
262
|
* be idle for longer than 2 minutes, but it must not attach to a weeks-old
|
|
263
263
|
* transcript just because a GUI app service with the same basename is alive.
|
|
264
264
|
*/
|
|
265
|
-
|
|
265
|
+
const ACTIVE_SESSION_STALE_MS = 24 * 60 * 60_000;
|
|
266
266
|
/**
|
|
267
267
|
* A session whose transcript hasn't been written in this long is ABANDONED /
|
|
268
268
|
* dangling — the framework can no longer treat it as live work, whether its PID
|
|
@@ -541,10 +541,6 @@ function claudeProjectDirName(cwd) {
|
|
|
541
541
|
*/
|
|
542
542
|
const CLAUDE_SESSION_FILE_CACHE_MAX = 256;
|
|
543
543
|
const claudeSessionFileCache = new Map();
|
|
544
|
-
/** Test seam: drop the Claude session-file path memo. */
|
|
545
|
-
export function clearClaudeSessionFileCacheForTest() {
|
|
546
|
-
claudeSessionFileCache.clear();
|
|
547
|
-
}
|
|
548
544
|
/**
|
|
549
545
|
* Locate the active Claude session file for a process. If we know the session
|
|
550
546
|
* UUID (from terminal env or team parent), prefer the exact match. Otherwise
|
|
@@ -1094,7 +1090,7 @@ export async function listTerminalsActive() {
|
|
|
1094
1090
|
});
|
|
1095
1091
|
}
|
|
1096
1092
|
/** Cloud tasks still in a non-terminal state. `tasks.db` may not exist; that's fine. */
|
|
1097
|
-
|
|
1093
|
+
function listCloudActive() {
|
|
1098
1094
|
let tasks;
|
|
1099
1095
|
try {
|
|
1100
1096
|
tasks = listActiveTasks();
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -13,13 +13,6 @@ import { type IndexedToolCall } from './tool-calls.js';
|
|
|
13
13
|
* assert against the constant instead of hardcoding a number that every bump
|
|
14
14
|
* then has to chase (docs/sessions.md calls the constant the source of truth). */
|
|
15
15
|
export declare const SCHEMA_VERSION = 39;
|
|
16
|
-
/**
|
|
17
|
-
* Bump to force `agents sessions backfill resources` to re-derive every
|
|
18
|
-
* session's skill/slash-command tallies on its next run (resource_scan_ledger
|
|
19
|
-
* rows with a lower version are treated as stale — the same mechanism
|
|
20
|
-
* TOOL_INDEX_VERSION gives the tool backfill).
|
|
21
|
-
*/
|
|
22
|
-
export declare const RESOURCE_INDEX_VERSION = 1;
|
|
23
16
|
/**
|
|
24
17
|
* Bumping this invalidates every cached facet row without touching the schema
|
|
25
18
|
* version, so a change to the extraction logic (a new metric, a corrected bucket)
|
|
@@ -28,67 +21,6 @@ export declare const RESOURCE_INDEX_VERSION = 1;
|
|
|
28
21
|
*/
|
|
29
22
|
/** Bump when facet extraction changes so cached rows recompute (stalls-by-model v6). */
|
|
30
23
|
export declare const INSIGHTS_EXTRACTOR_VERSION = 6;
|
|
31
|
-
export declare const PREVIEW_EXTRACTOR_VERSION = 1;
|
|
32
|
-
/** Raw row shape returned from the sessions table. */
|
|
33
|
-
export interface SessionRow {
|
|
34
|
-
id: string;
|
|
35
|
-
short_id: string;
|
|
36
|
-
agent: string;
|
|
37
|
-
origin: string | null;
|
|
38
|
-
routine_name: string | null;
|
|
39
|
-
routine_run_id: string | null;
|
|
40
|
-
version: string | null;
|
|
41
|
-
account: string | null;
|
|
42
|
-
account_key: string | null;
|
|
43
|
-
account_org: string | null;
|
|
44
|
-
mode: string | null;
|
|
45
|
-
timestamp: string;
|
|
46
|
-
last_activity: string | null;
|
|
47
|
-
project: string | null;
|
|
48
|
-
cwd: string | null;
|
|
49
|
-
git_branch: string | null;
|
|
50
|
-
topic: string | null;
|
|
51
|
-
label: string | null;
|
|
52
|
-
message_count: number | null;
|
|
53
|
-
token_count: number | null;
|
|
54
|
-
output_tokens: number | null;
|
|
55
|
-
input_tokens: number | null;
|
|
56
|
-
cache_read_tokens: number | null;
|
|
57
|
-
cache_write_tokens: number | null;
|
|
58
|
-
cost_usd: number | null;
|
|
59
|
-
cost_usd_nocache: number | null;
|
|
60
|
-
duration_ms: number | null;
|
|
61
|
-
model: string | null;
|
|
62
|
-
tool_call_count: number | null;
|
|
63
|
-
file_path: string;
|
|
64
|
-
file_mtime_ms: number | null;
|
|
65
|
-
file_size: number | null;
|
|
66
|
-
scanned_at: number | null;
|
|
67
|
-
is_team_origin: number;
|
|
68
|
-
pr_url: string | null;
|
|
69
|
-
pr_number: number | null;
|
|
70
|
-
worktree_slug: string | null;
|
|
71
|
-
ticket_id: string | null;
|
|
72
|
-
spawned_team: string | null;
|
|
73
|
-
plan: string | null;
|
|
74
|
-
machine: string | null;
|
|
75
|
-
todos: string | null;
|
|
76
|
-
recent_directories_touched: string | null;
|
|
77
|
-
linear_project: string | null;
|
|
78
|
-
linear_project_url: string | null;
|
|
79
|
-
actor: string | null;
|
|
80
|
-
initiated_by: string | null;
|
|
81
|
-
/** NULL means "not yet computed" (a row scanned before this field existed) — see rowToMeta. */
|
|
82
|
-
used_browser: number | null;
|
|
83
|
-
used_computer: number | null;
|
|
84
|
-
/**
|
|
85
|
-
* Epoch ms the transcript file was first confirmed gone while content survived
|
|
86
|
-
* (RUSH-2436); NULL = live/never archived. Optional because the scanner upsert
|
|
87
|
-
* write-payload never sets it — the INSERT column list omits it, so a rescan
|
|
88
|
-
* preserves the sticky stamp; it is written only by querySessions.
|
|
89
|
-
*/
|
|
90
|
-
archived_at?: number | null;
|
|
91
|
-
}
|
|
92
24
|
/** File stat snapshot used to detect changes between scan runs. */
|
|
93
25
|
export interface ScanStamp {
|
|
94
26
|
fileMtimeMs: number;
|
|
@@ -143,7 +75,7 @@ export interface QueryOptions {
|
|
|
143
75
|
export declare function getDB(): Database.Database;
|
|
144
76
|
/** Close the cached database connection. */
|
|
145
77
|
export declare function closeDB(): void;
|
|
146
|
-
|
|
78
|
+
interface FtsOptimizeResult {
|
|
147
79
|
table: string;
|
|
148
80
|
segmentsBefore: number;
|
|
149
81
|
segmentsAfter: number;
|
|
@@ -224,7 +156,7 @@ export declare function getScanStampsForPaths(filePaths: string[]): Map<string,
|
|
|
224
156
|
* written by the Claude scan (B-2) and consumed on the next scan to decide
|
|
225
157
|
* full-vs-incremental and to hydrate the resume.
|
|
226
158
|
*/
|
|
227
|
-
|
|
159
|
+
interface ParserStateRow {
|
|
228
160
|
parserState: string | null;
|
|
229
161
|
contentText: string | null;
|
|
230
162
|
fileMtimeMs: number;
|
|
@@ -348,7 +280,7 @@ export declare function querySessions(options?: QueryOptions): SessionMeta[];
|
|
|
348
280
|
/** Count sessions matching the given filter options. */
|
|
349
281
|
export declare function countSessions(options?: QueryOptions): number;
|
|
350
282
|
/** One grouped row in a cost/duration rollup. */
|
|
351
|
-
|
|
283
|
+
interface UsageRollupRow {
|
|
352
284
|
/**
|
|
353
285
|
* Grouping key value: the agent id, project name, ISO date (YYYY-MM-DD), or
|
|
354
286
|
* account identity (`claude:org=<uuid>` / `unattributed:<reason>`).
|
|
@@ -454,7 +386,7 @@ export type UsageRollupGroup = 'agent' | 'project' | 'day' | 'account';
|
|
|
454
386
|
* Account rotation is NOT done here — that stays on live rate-limit windows
|
|
455
387
|
* via `--strategy balanced` / rotate.ts.
|
|
456
388
|
*/
|
|
457
|
-
|
|
389
|
+
type AffinityGroup = 'machine' | 'agent' | 'machine_agent';
|
|
458
390
|
export interface AffinityRow {
|
|
459
391
|
/** Group key: machine name, agent id, or "machine\\tagent". */
|
|
460
392
|
key: string;
|
|
@@ -540,7 +472,7 @@ export declare function resourceUsageCoverage(): {
|
|
|
540
472
|
total: number;
|
|
541
473
|
};
|
|
542
474
|
/** Outcome of a resource-usage backfill run. */
|
|
543
|
-
|
|
475
|
+
interface ResourceBackfillResult {
|
|
544
476
|
/** Sessions considered (matched the filter, had a real transcript). */
|
|
545
477
|
scanned: number;
|
|
546
478
|
/** Sessions (re)parsed and written this run. */
|
|
@@ -585,7 +517,7 @@ export interface TeamSpawner {
|
|
|
585
517
|
*/
|
|
586
518
|
export declare function teamSpawners(): Map<string, TeamSpawner>;
|
|
587
519
|
/** A session with its cost, for the top-N-by-cost listing. */
|
|
588
|
-
|
|
520
|
+
interface TopCostSession {
|
|
589
521
|
meta: SessionMeta;
|
|
590
522
|
costUsd: number;
|
|
591
523
|
durationMs: number;
|
|
@@ -638,7 +570,7 @@ export declare function findSessionsById(idQuery: string, scope?: Pick<QueryOpti
|
|
|
638
570
|
*/
|
|
639
571
|
export declare function findSessionsByShortIds(shortIds: string[]): Map<string, SessionMeta>;
|
|
640
572
|
/** A single full-text search result with ranking score. */
|
|
641
|
-
|
|
573
|
+
interface FtsHit {
|
|
642
574
|
sessionId: string;
|
|
643
575
|
score: number;
|
|
644
576
|
matchedTerms: string[];
|
|
@@ -667,11 +599,6 @@ export declare function buildFtsQuery(input: string): {
|
|
|
667
599
|
* so "higher = better" is consistent across all tiers.
|
|
668
600
|
*/
|
|
669
601
|
export declare function ftsSearch(input: string, limit?: number): FtsHit[];
|
|
670
|
-
/** Return the total row counts for the sessions and FTS5 tables (diagnostic). */
|
|
671
|
-
export declare function getRowCount(): {
|
|
672
|
-
sessions: number;
|
|
673
|
-
textRows: number;
|
|
674
|
-
};
|
|
675
602
|
/**
|
|
676
603
|
* Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
|
|
677
604
|
* it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
|
|
@@ -683,14 +610,14 @@ export declare function getRowCount(): {
|
|
|
683
610
|
*/
|
|
684
611
|
export declare function updateSessionFilePaths(oldPrefix: string, newPrefix: string): number;
|
|
685
612
|
/** Per-kind capture tallies for a browser task. Counts only -- never the bytes. */
|
|
686
|
-
|
|
613
|
+
interface BrowserCaptureCounts {
|
|
687
614
|
screenshot: number;
|
|
688
615
|
pdf: number;
|
|
689
616
|
recording: number;
|
|
690
617
|
download: number;
|
|
691
618
|
}
|
|
692
619
|
/** One durable browser-task row. `machine` defaults to this device. */
|
|
693
|
-
|
|
620
|
+
interface BrowserSessionRecord {
|
|
694
621
|
task: string;
|
|
695
622
|
profile: string;
|
|
696
623
|
sessionId?: string;
|
|
@@ -704,7 +631,7 @@ export interface BrowserSessionRecord {
|
|
|
704
631
|
capturesRemote?: string;
|
|
705
632
|
}
|
|
706
633
|
/** One durable computer-use invocation row. `machine` defaults to this device. */
|
|
707
|
-
|
|
634
|
+
interface ComputerSessionRecord {
|
|
708
635
|
invocationId: string;
|
|
709
636
|
sessionId?: string;
|
|
710
637
|
launchId?: string;
|
|
@@ -743,7 +670,7 @@ export declare function recordBrowserSession(record: BrowserSessionRecord): void
|
|
|
743
670
|
*/
|
|
744
671
|
export declare function recordComputerSession(record: ComputerSessionRecord): void;
|
|
745
672
|
/** A stored browser row as read back, with counts rehydrated. */
|
|
746
|
-
|
|
673
|
+
interface StoredBrowserSession extends Required<Pick<BrowserSessionRecord, 'task' | 'profile'>> {
|
|
747
674
|
sessionId?: string;
|
|
748
675
|
launchId?: string;
|
|
749
676
|
actor?: string;
|
|
@@ -771,7 +698,7 @@ export declare function listBrowserSessionRecords(profile?: string, opts?: {
|
|
|
771
698
|
/** One stored browser task by its (profile, task) key, or null. */
|
|
772
699
|
export declare function getBrowserSessionRecord(profile: string, task: string): StoredBrowserSession | null;
|
|
773
700
|
/** A stored computer-use invocation as read back. */
|
|
774
|
-
|
|
701
|
+
interface StoredComputerSession {
|
|
775
702
|
invocationId: string;
|
|
776
703
|
sessionId?: string;
|
|
777
704
|
launchId?: string;
|
|
@@ -782,19 +709,6 @@ export interface StoredComputerSession {
|
|
|
782
709
|
actionCount: number;
|
|
783
710
|
taskPreview?: string;
|
|
784
711
|
}
|
|
785
|
-
/**
|
|
786
|
-
* Retention for the tool-session tables.
|
|
787
|
-
*
|
|
788
|
-
* These are the durable answer to a ledger that prunes at 7 days, so they are
|
|
789
|
-
* deliberately long-lived — but "durable" is not "unbounded". `computer_sessions`
|
|
790
|
-
* takes one row per `agents computer` CLI PROCESS (`COMPUTER_INVOCATION_ID` is
|
|
791
|
-
* minted per process), and an agent driving a desktop invokes that hundreds of
|
|
792
|
-
* times a day, so an unbounded table would grow without limit and be read in
|
|
793
|
-
* full on every listing.
|
|
794
|
-
*/
|
|
795
|
-
export declare const TOOL_SESSION_MAX_AGE_DAYS = 365;
|
|
796
|
-
/** Default ceiling on rows one listing will read. */
|
|
797
|
-
export declare const TOOL_SESSION_LIST_LIMIT = 2000;
|
|
798
712
|
/**
|
|
799
713
|
* Drop tool-session rows past {@link TOOL_SESSION_MAX_AGE_DAYS}.
|
|
800
714
|
*
|
|
@@ -813,3 +727,4 @@ export declare function listComputerSessionRecords(opts?: {
|
|
|
813
727
|
limit?: number;
|
|
814
728
|
startedBeforeMs?: number;
|
|
815
729
|
}): StoredComputerSession[];
|
|
730
|
+
export {};
|
package/dist/lib/session/db.js
CHANGED
|
@@ -34,7 +34,7 @@ export const SCHEMA_VERSION = 39;
|
|
|
34
34
|
* rows with a lower version are treated as stale — the same mechanism
|
|
35
35
|
* TOOL_INDEX_VERSION gives the tool backfill).
|
|
36
36
|
*/
|
|
37
|
-
|
|
37
|
+
const RESOURCE_INDEX_VERSION = 1;
|
|
38
38
|
/**
|
|
39
39
|
* Canonicalize a file path for use as a scan_ledger key. The same physical
|
|
40
40
|
* session file is reachable via multiple aliases — `~/.claude/projects/x.jsonl`
|
|
@@ -397,7 +397,7 @@ CREATE INDEX IF NOT EXISTS idx_computer_sessions_started ON computer_sessions(st
|
|
|
397
397
|
*/
|
|
398
398
|
/** Bump when facet extraction changes so cached rows recompute (stalls-by-model v6). */
|
|
399
399
|
export const INSIGHTS_EXTRACTOR_VERSION = 6;
|
|
400
|
-
|
|
400
|
+
const PREVIEW_EXTRACTOR_VERSION = 1;
|
|
401
401
|
let dbInstance = null;
|
|
402
402
|
/**
|
|
403
403
|
* Apply schema migrations from `fromVersion` → SCHEMA_VERSION. The new
|
|
@@ -3365,13 +3365,6 @@ export function ftsSearch(input, limit = 200) {
|
|
|
3365
3365
|
hits.sort((a, b) => b.score - a.score);
|
|
3366
3366
|
return hits.slice(0, limit);
|
|
3367
3367
|
}
|
|
3368
|
-
/** Return the total row counts for the sessions and FTS5 tables (diagnostic). */
|
|
3369
|
-
export function getRowCount() {
|
|
3370
|
-
const db = getDB();
|
|
3371
|
-
const sessions = db.prepare(`SELECT COUNT(*) AS c FROM sessions`).get().c;
|
|
3372
|
-
const textRows = db.prepare(`SELECT COUNT(*) AS c FROM session_text`).get().c;
|
|
3373
|
-
return { sessions, textRows };
|
|
3374
|
-
}
|
|
3375
3368
|
/**
|
|
3376
3369
|
* Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
|
|
3377
3370
|
* it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
|
|
@@ -3520,9 +3513,9 @@ export function getBrowserSessionRecord(profile, task) {
|
|
|
3520
3513
|
* times a day, so an unbounded table would grow without limit and be read in
|
|
3521
3514
|
* full on every listing.
|
|
3522
3515
|
*/
|
|
3523
|
-
|
|
3516
|
+
const TOOL_SESSION_MAX_AGE_DAYS = 365;
|
|
3524
3517
|
/** Default ceiling on rows one listing will read. */
|
|
3525
|
-
|
|
3518
|
+
const TOOL_SESSION_LIST_LIMIT = 2000;
|
|
3526
3519
|
/**
|
|
3527
3520
|
* Drop tool-session rows past {@link TOOL_SESSION_MAX_AGE_DAYS}.
|
|
3528
3521
|
*
|
|
@@ -10,8 +10,6 @@ import type { SessionAgentId, SessionEvent, SessionMeta, TodoProgress } from './
|
|
|
10
10
|
export { machineForSessionFile } from './origin-machine.js';
|
|
11
11
|
import { type ScanStamp } from './db.js';
|
|
12
12
|
import { ToolCallCollector, type IndexedToolCall, type ToolCallCollectorSnapshot } from './tool-calls.js';
|
|
13
|
-
/** One JSONL record may not force an unbounded string allocation. */
|
|
14
|
-
export declare const SESSION_JSONL_LINE_MAX_BYTES: number;
|
|
15
13
|
/** Options controlling which sessions to discover and how to report progress. */
|
|
16
14
|
export interface DiscoverOptions {
|
|
17
15
|
agent?: SessionAgentId;
|
|
@@ -172,7 +170,7 @@ interface CodexSessionScan {
|
|
|
172
170
|
*/
|
|
173
171
|
export declare function discoverSessions(options?: DiscoverOptions): Promise<SessionMeta[]>;
|
|
174
172
|
/** What one incremental scan actually did. */
|
|
175
|
-
|
|
173
|
+
interface IncrementalScanResult {
|
|
176
174
|
/** True when this process won the single-flight claim and ran the scan. */
|
|
177
175
|
claimed: boolean;
|
|
178
176
|
/**
|
|
@@ -362,7 +360,7 @@ export declare function filterChangedFiles(filePaths: string[]): Array<{
|
|
|
362
360
|
scan: ScanStamp;
|
|
363
361
|
}>;
|
|
364
362
|
/** A path already stat'd by the walk — mtime is the raw (unfloored) fs value. */
|
|
365
|
-
|
|
363
|
+
interface PreStatEntry {
|
|
366
364
|
filePath: string;
|
|
367
365
|
fileMtimeMs: number;
|
|
368
366
|
fileSize: number;
|
|
@@ -378,64 +376,13 @@ export declare function filterChangedEntries(entries: PreStatEntry[]): Array<{
|
|
|
378
376
|
scan: ScanStamp;
|
|
379
377
|
}>;
|
|
380
378
|
export declare function shouldDeferRecentAppend(prev: ScanStamp, current: ScanStamp, nowMs: number, debounceMs?: number): boolean;
|
|
381
|
-
/** One leaf directory of transcripts to change-detect, plus its live-root flag. */
|
|
382
|
-
export interface LeafDir {
|
|
383
|
-
/** Absolute path to the directory that directly holds transcript files. */
|
|
384
|
-
dirPath: string;
|
|
385
|
-
/**
|
|
386
|
-
* True if this dir is under the agent's LIVE `~/.<agent>` root — the only tree
|
|
387
|
-
* an agent process appends to live. Every file in such a dir is treated as
|
|
388
|
-
* hot (always re-stat'd), so an in-place append is never missed there.
|
|
389
|
-
*/
|
|
390
|
-
isLiveRoot: boolean;
|
|
391
|
-
}
|
|
392
|
-
/** The changed files a leaf-dir walk surfaced, ready to parse + upsert. */
|
|
393
|
-
export interface LeafDirScan {
|
|
394
|
-
/** Files whose (mtime, size) changed vs the ledger — the parse set. */
|
|
395
|
-
changed: Array<{
|
|
396
|
-
filePath: string;
|
|
397
|
-
scan: ScanStamp;
|
|
398
|
-
}>;
|
|
399
|
-
/**
|
|
400
|
-
* Every transcript file seen across all leaf dirs (changed or not), in
|
|
401
|
-
* live-root-first order, each tagged with whether its dir is a live root.
|
|
402
|
-
* Lets a caller restore cross-root, session-id precedence (prefer the live
|
|
403
|
-
* copy of a session over a frozen backup copy) independent of which copy
|
|
404
|
-
* happened to be flagged "changed" this run.
|
|
405
|
-
*/
|
|
406
|
-
allFiles: Array<{
|
|
407
|
-
filePath: string;
|
|
408
|
-
isLiveRoot: boolean;
|
|
409
|
-
}>;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Walk a set of leaf transcript directories and return the files that changed,
|
|
413
|
-
* skipping the per-file `stat` of directories whose (mtime, entry_count) matches
|
|
414
|
-
* the dir_ledger.
|
|
415
|
-
*
|
|
416
|
-
* Per leaf dir:
|
|
417
|
-
* - `stat` the dir once and `readdir` it (one cheap syscall) to get the entry
|
|
418
|
-
* count and the file list.
|
|
419
|
-
* - If the dir matches the dir_ledger (floored mtime AND entry_count), no file
|
|
420
|
-
* was created / deleted / renamed since we last walked it. We then stat ONLY
|
|
421
|
-
* the hot files (live-root files, or files scanned within HOT_FILE_WINDOW_MS)
|
|
422
|
-
* and run just those through the ledger compare — so an in-place append to a
|
|
423
|
-
* still-live session is still caught, while immutable backup/version dirs
|
|
424
|
-
* collapse to a single dir stat and zero per-file stats.
|
|
425
|
-
* - Else (changed dir, or no ledger row) we stat every file (today's full
|
|
426
|
-
* walk) and record the fresh dir stamp so the next run can short-circuit.
|
|
427
|
-
*
|
|
428
|
-
* The kill-switch (`AGENTS_SESSIONS_NO_DIR_LEDGER=1`) forces the full-walk branch
|
|
429
|
-
* for every dir and never consults or records the dir_ledger.
|
|
430
|
-
*/
|
|
431
|
-
export declare function collectChangedFilesInLeafDirs(leafDirs: LeafDir[], ext: string): LeafDirScan;
|
|
432
379
|
/**
|
|
433
380
|
* Collect all directories to scan for an agent's sessions. Deduplicates by
|
|
434
381
|
* realpath to avoid double-counting symlinked version homes.
|
|
435
382
|
*/
|
|
436
383
|
export declare function getAgentSessionDirs(agent: string, subdir: string): string[];
|
|
437
384
|
/** A session-agent's on-disk watch roots (every version home + backup mirror). */
|
|
438
|
-
|
|
385
|
+
interface SessionRoots {
|
|
439
386
|
agent: SessionAgentId;
|
|
440
387
|
/** Absolute directories that hold this agent's transcripts, existing right now. */
|
|
441
388
|
dirs: string[];
|
|
@@ -492,7 +439,7 @@ export declare function readCodexMeta(filePath: string, resolveAccount?: () => s
|
|
|
492
439
|
* both a full parse and a resumable incremental parse (see
|
|
493
440
|
* {@link scanClaudeSessionIncremental}).
|
|
494
441
|
*/
|
|
495
|
-
|
|
442
|
+
interface ClaudeParseState {
|
|
496
443
|
timestamp?: string;
|
|
497
444
|
cwd?: string;
|
|
498
445
|
gitBranch?: string;
|
|
@@ -680,7 +627,7 @@ export declare function __resetClaudeScanBranchCountsForTest(): void;
|
|
|
680
627
|
* fold ({@link applyCodexLine}) runs for both a full parse and an incremental
|
|
681
628
|
* resume. Mirrors {@link ClaudeParseState}.
|
|
682
629
|
*/
|
|
683
|
-
|
|
630
|
+
interface CodexParseState {
|
|
684
631
|
sessionId?: string;
|
|
685
632
|
timestamp?: string;
|
|
686
633
|
cwd?: string;
|
|
@@ -712,18 +659,6 @@ export interface CodexParseState {
|
|
|
712
659
|
}
|
|
713
660
|
/** Zero-value accumulator for a fresh (from-byte-0) Codex parse. */
|
|
714
661
|
export declare function initCodexParseState(): CodexParseState;
|
|
715
|
-
/**
|
|
716
|
-
* Fold one parsed Codex line into the accumulator — the exact loop body
|
|
717
|
-
* {@link scanCodexSession} used to run inline, extracted verbatim and mutating
|
|
718
|
-
* `state.*` in place. `parsed` is the already-`JSON.parse`d line (the
|
|
719
|
-
* malformed-line skip happens in the caller, as before).
|
|
720
|
-
*/
|
|
721
|
-
export declare function applyCodexLine(state: CodexParseState, parsed: any): void;
|
|
722
|
-
/**
|
|
723
|
-
* Build the {@link CodexSessionScan} return object from an accumulator — the
|
|
724
|
-
* exact return-building {@link scanCodexSession} used to run inline.
|
|
725
|
-
*/
|
|
726
|
-
export declare function finalizeCodexScan(state: CodexParseState): CodexSessionScan;
|
|
727
662
|
/**
|
|
728
663
|
* SERIALIZED continuation blob persisted in `scan_ledger.parser_state` for a
|
|
729
664
|
* Codex rollout. Carries everything {@link hydrateCodexParseState} needs to
|
|
@@ -769,17 +704,6 @@ export interface CodexParserState {
|
|
|
769
704
|
* incremental replay equals a full parse.
|
|
770
705
|
*/
|
|
771
706
|
export declare function serializeCodexParserState(state: CodexParseState, offset: number, jsonlDroppingOversizedLine?: boolean): CodexParserState;
|
|
772
|
-
/**
|
|
773
|
-
* Rebuild a live {@link CodexParseState} from a persisted continuation so that
|
|
774
|
-
* applying the appended lines yields the same accumulator a full parse would.
|
|
775
|
-
*
|
|
776
|
-
* `userTexts` is rehydrated as a single joined blob from `contentText`: only
|
|
777
|
-
* `userTexts.join('\n')` (detectTicket + contentText) and `userTexts.length > 0`
|
|
778
|
-
* are ever read downstream, and both are preserved by a one-element array
|
|
779
|
-
* holding the joined content. Topic is first-wins and already persisted, so a
|
|
780
|
-
* collapsed userTexts never changes it.
|
|
781
|
-
*/
|
|
782
|
-
export declare function hydrateCodexParseState(prior: CodexParserState): CodexParseState;
|
|
783
707
|
/**
|
|
784
708
|
* Resume a Codex parse from `fromOffset` bytes into the file, folding only the
|
|
785
709
|
* newly-appended lines into `prior`. Returns the finalized scan, the next
|
|
@@ -55,7 +55,7 @@ const MUSE_SESSIONS_DIR = path.join(HOME, '.local', 'share', 'muse', 'sessions')
|
|
|
55
55
|
const OPENCLAW_TTL_MS = 60_000;
|
|
56
56
|
const ACTIVE_APPEND_RESCAN_DEBOUNCE_MS = 5_000;
|
|
57
57
|
/** One JSONL record may not force an unbounded string allocation. */
|
|
58
|
-
|
|
58
|
+
const SESSION_JSONL_LINE_MAX_BYTES = 1024 * 1024;
|
|
59
59
|
/**
|
|
60
60
|
* Stream one appended JSONL range, applying only newline-terminated records.
|
|
61
61
|
* Memory is bounded to one record. An ordinary unterminated tail remains for the
|
|
@@ -728,7 +728,7 @@ export function shouldDeferRecentAppend(prev, current, nowMs, debounceMs = ACTIV
|
|
|
728
728
|
* The kill-switch (`AGENTS_SESSIONS_NO_DIR_LEDGER=1`) forces the full-walk branch
|
|
729
729
|
* for every dir and never consults or records the dir_ledger.
|
|
730
730
|
*/
|
|
731
|
-
|
|
731
|
+
function collectChangedFilesInLeafDirs(leafDirs, ext) {
|
|
732
732
|
const disabled = dirLedgerDisabled();
|
|
733
733
|
const dirStamps = disabled ? new Map() : getDirLedgerForPaths(leafDirs.map(d => d.dirPath));
|
|
734
734
|
const now = Date.now();
|
|
@@ -3619,7 +3619,7 @@ export function initCodexParseState() {
|
|
|
3619
3619
|
* `state.*` in place. `parsed` is the already-`JSON.parse`d line (the
|
|
3620
3620
|
* malformed-line skip happens in the caller, as before).
|
|
3621
3621
|
*/
|
|
3622
|
-
|
|
3622
|
+
function applyCodexLine(state, parsed) {
|
|
3623
3623
|
collectCodexToolCalls(state.toolCollector, parsed);
|
|
3624
3624
|
// PR signal, structurally: a Codex `function_call` whose command is
|
|
3625
3625
|
// `gh pr create`, then the pull URL from a `function_call_output`.
|
|
@@ -3730,7 +3730,7 @@ export function applyCodexLine(state, parsed) {
|
|
|
3730
3730
|
* Build the {@link CodexSessionScan} return object from an accumulator — the
|
|
3731
3731
|
* exact return-building {@link scanCodexSession} used to run inline.
|
|
3732
3732
|
*/
|
|
3733
|
-
|
|
3733
|
+
function finalizeCodexScan(state) {
|
|
3734
3734
|
// Codex reports one cumulative snapshot: uncached input, cached (cache-read)
|
|
3735
3735
|
// input, and output+reasoning. It has no cache-write bucket. Derive the burn
|
|
3736
3736
|
// split and both costs (actual + no-cache) from that final snapshot (RUSH-2287).
|
|
@@ -3869,7 +3869,7 @@ export function serializeCodexParserState(state, offset, jsonlDroppingOversizedL
|
|
|
3869
3869
|
* holding the joined content. Topic is first-wins and already persisted, so a
|
|
3870
3870
|
* collapsed userTexts never changes it.
|
|
3871
3871
|
*/
|
|
3872
|
-
|
|
3872
|
+
function hydrateCodexParseState(prior) {
|
|
3873
3873
|
return {
|
|
3874
3874
|
sessionId: prior.sessionId,
|
|
3875
3875
|
timestamp: prior.timestamp,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ParsedBundle } from '../bundle.js';
|
|
2
|
+
export interface RemotePullResult {
|
|
3
|
+
bundles: ParsedBundle[];
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Run `agents sessions export …exportArgs --stdout` on each host and parse the
|
|
8
|
+
* streamed bundle. A host that fails (unreachable, remote error, bad output) is
|
|
9
|
+
* collected in `errors` and skipped — one asleep peer never aborts the pull.
|
|
10
|
+
* `exportArgs` must NOT contain --device (the remote export runs for itself only).
|
|
11
|
+
*/
|
|
12
|
+
export declare function pullBundlesFromHosts(hosts: string[], exportArgs: string[]): Promise<RemotePullResult>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-device session transfer over the EXISTING SSH fleet transport
|
|
3
|
+
* (RUSH-1712) — no R2, no daemon. `agents sessions export --device <h>` and
|
|
4
|
+
* `agents sessions import --from-host <h>` both run `agents sessions export
|
|
5
|
+
* … --stdout` ON the peer and stream the bundle back over the same SSH path the
|
|
6
|
+
* cross-machine listing already uses (resolveExplicitTargets + ssh-exec), then
|
|
7
|
+
* either write it (export) or import it (import) locally.
|
|
8
|
+
*
|
|
9
|
+
* This deliberately reuses ssh-exec / resolve-target rather than adding a second
|
|
10
|
+
* transport: the raw form `agents ssh boxA 'agents sessions export --stdout' |
|
|
11
|
+
* agents sessions import -` works with plain export/import; this module is just
|
|
12
|
+
* the one-shot wrapper around it.
|
|
13
|
+
*/
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import { sshExec } from '../../ssh-exec.js';
|
|
16
|
+
import { shellQuote } from '../../ssh-exec.js';
|
|
17
|
+
import { resolveExplicitTargets } from '../../devices/resolve-target.js';
|
|
18
|
+
import { remoteShellFor, buildWindowsAgentsCommand } from '../../hosts/remote-cmd.js';
|
|
19
|
+
import { parseBundle } from '../bundle.js';
|
|
20
|
+
/** Remote export can traverse many sessions; give it a generous ceiling. */
|
|
21
|
+
const REMOTE_EXPORT_TIMEOUT_MS = 300_000;
|
|
22
|
+
/** Build `agents <args>` for the peer's login shell (bash or PowerShell). */
|
|
23
|
+
function remoteAgentsCommand(args, os) {
|
|
24
|
+
if (remoteShellFor(os) === 'powershell') {
|
|
25
|
+
return buildWindowsAgentsCommand({ args });
|
|
26
|
+
}
|
|
27
|
+
const inner = ['agents', ...args].map((t, i) => (i === 0 ? t : shellQuote(t))).join(' ');
|
|
28
|
+
return `bash -lc ${shellQuote(inner)}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run `agents sessions export …exportArgs --stdout` on each host and parse the
|
|
32
|
+
* streamed bundle. A host that fails (unreachable, remote error, bad output) is
|
|
33
|
+
* collected in `errors` and skipped — one asleep peer never aborts the pull.
|
|
34
|
+
* `exportArgs` must NOT contain --device (the remote export runs for itself only).
|
|
35
|
+
*/
|
|
36
|
+
export async function pullBundlesFromHosts(hosts, exportArgs) {
|
|
37
|
+
const targets = await resolveExplicitTargets(hosts);
|
|
38
|
+
const bundles = [];
|
|
39
|
+
const errors = [];
|
|
40
|
+
for (const t of targets) {
|
|
41
|
+
const cmd = remoteAgentsCommand(['sessions', 'export', ...exportArgs, '--stdout'], t.os);
|
|
42
|
+
process.stderr.write(chalk.dim(`Pulling sessions from ${t.name}…\n`));
|
|
43
|
+
const res = sshExec(t.target, cmd, { timeoutMs: REMOTE_EXPORT_TIMEOUT_MS, extraSshArgs: t.extraSshArgs });
|
|
44
|
+
if (res.timedOut) {
|
|
45
|
+
errors.push(`${t.name}: timed out after ${Math.round(REMOTE_EXPORT_TIMEOUT_MS / 1000)}s`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (res.code !== 0) {
|
|
49
|
+
const tail = res.stderr.trim().split('\n').filter(Boolean).pop();
|
|
50
|
+
errors.push(`${t.name}: remote export failed (${res.code ?? 'ssh error'})${tail ? ': ' + tail : ''}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
bundles.push(parseBundle(res.stdout));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
errors.push(`${t.name}: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { bundles, errors };
|
|
61
|
+
}
|