@phnx-labs/agents-cli 1.22.47 → 1.22.50
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 +503 -17
- package/README.md +76 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +201 -39
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon-test-harness.d.ts +42 -0
- package/dist/commands/daemon-test-harness.js +90 -0
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/device-config-test-harness.d.ts +11 -0
- package/dist/commands/device-config-test-harness.js +59 -0
- package/dist/commands/exec.js +76 -16
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +158 -53
- package/dist/commands/ssh.js +54 -7
- package/dist/commands/teams.d.ts +12 -0
- package/dist/commands/teams.js +44 -67
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +188 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-provider-registry.d.ts +9 -0
- package/dist/lib/account-provider-registry.js +20 -0
- package/dist/lib/accounting/account-pool-collect.d.ts +34 -0
- package/dist/lib/accounting/account-pool-collect.js +71 -0
- package/dist/lib/accounting/account-pool.d.ts +46 -0
- package/dist/lib/accounting/account-pool.js +33 -0
- package/dist/lib/accounting/capacity.d.ts +24 -0
- package/dist/lib/accounting/capacity.js +30 -0
- package/dist/lib/accounting/rotate.d.ts +11 -18
- package/dist/lib/accounting/rotate.js +6 -24
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +2 -8
- package/dist/lib/config-keys.js +2 -13
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +67 -13
- package/dist/lib/exec.js +85 -29
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +11 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +19 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/store.js +6 -3
- package/dist/lib/installations/versions.js +73 -48
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +44 -15
- package/dist/lib/share/publish.js +58 -26
- package/dist/lib/share/worker-template.js +402 -25
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.d.ts +14 -12
- package/dist/lib/teams/agents.js +188 -73
- package/dist/lib/teams/api.d.ts +6 -1
- package/dist/lib/teams/api.js +2 -0
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/teams/scheduler.js +26 -8
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +459 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { type IndexedToolCall } from './tool-calls.js';
|
|
|
12
12
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
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
|
-
export declare const SCHEMA_VERSION =
|
|
15
|
+
export declare const SCHEMA_VERSION = 40;
|
|
16
16
|
/**
|
|
17
17
|
* Bumping this invalidates every cached facet row without touching the schema
|
|
18
18
|
* version, so a change to the extraction logic (a new metric, a corrected bucket)
|
|
@@ -21,6 +21,7 @@ export declare const SCHEMA_VERSION = 39;
|
|
|
21
21
|
*/
|
|
22
22
|
/** Bump when facet extraction changes so cached rows recompute (stalls-by-model v6). */
|
|
23
23
|
export declare const INSIGHTS_EXTRACTOR_VERSION = 6;
|
|
24
|
+
export declare const SESSION_TOPIC_EXTRACTOR_VERSION = 1;
|
|
24
25
|
/** File stat snapshot used to detect changes between scan runs. */
|
|
25
26
|
export interface ScanStamp {
|
|
26
27
|
fileMtimeMs: number;
|
|
@@ -338,6 +339,15 @@ export declare function writeSessionInsights<T>(entries: Array<{
|
|
|
338
339
|
}>): void;
|
|
339
340
|
/** Drop every cached facet row. Backs `agents insights --refresh`. */
|
|
340
341
|
export declare function clearSessionInsights(): void;
|
|
342
|
+
/** Read cached trace topics only when their transcript byte stamps still match. */
|
|
343
|
+
export declare function readSessionTopics<T>(ids: string[]): Map<string, T>;
|
|
344
|
+
/** Persist trace topics against the exact transcript bytes used to classify them. */
|
|
345
|
+
export declare function writeSessionTopics<T>(entries: Array<{
|
|
346
|
+
id: string;
|
|
347
|
+
fileMtimeMs: number | null;
|
|
348
|
+
fileSize: number | null;
|
|
349
|
+
topic: T;
|
|
350
|
+
}>): void;
|
|
341
351
|
/** Read one derived preview only when it matches the transcript bytes on disk. */
|
|
342
352
|
export declare function readSessionPreviewCache<T>(id: string, sourceStamp: {
|
|
343
353
|
fileMtimeMs: number | null;
|
package/dist/lib/session/db.js
CHANGED
|
@@ -18,7 +18,7 @@ import { loadSessionActorIndex, readSessionActorRecord } from './actor-sidecar.j
|
|
|
18
18
|
import { toolCallsFromEvents } from './tool-calls.js';
|
|
19
19
|
import { persistToolCalls, toolEvidenceSourcePath } from './tool-store.js';
|
|
20
20
|
import { buildClaudeAccountIndex, resolveClaudeAccount } from './claude-accounts.js';
|
|
21
|
-
import { extractSkills, extractSlashCommands } from './highlights.js';
|
|
21
|
+
import { extractBackgroundShells, extractSkills, extractSlashCommands, harnessTracksBackgroundShells, isSubAgentTool, } from './highlights.js';
|
|
22
22
|
import { resolveResource } from '../resources.js';
|
|
23
23
|
import { discoverPlugins } from '../plugins/plugins.js';
|
|
24
24
|
import { machineId } from '../machine-id.js';
|
|
@@ -27,7 +27,7 @@ const DB_PATH = getSessionsDbPath();
|
|
|
27
27
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
28
28
|
* assert against the constant instead of hardcoding a number that every bump
|
|
29
29
|
* then has to chase (docs/sessions.md calls the constant the source of truth). */
|
|
30
|
-
export const SCHEMA_VERSION =
|
|
30
|
+
export const SCHEMA_VERSION = 40;
|
|
31
31
|
/**
|
|
32
32
|
* Bump to force `agents sessions backfill resources` to re-derive every
|
|
33
33
|
* session's skill/slash-command tallies on its next run (resource_scan_ledger
|
|
@@ -100,6 +100,8 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
100
100
|
worktree_slug TEXT,
|
|
101
101
|
ticket_id TEXT,
|
|
102
102
|
spawned_team TEXT,
|
|
103
|
+
sub_agent_count INTEGER,
|
|
104
|
+
background_shell_count INTEGER,
|
|
103
105
|
plan TEXT,
|
|
104
106
|
machine TEXT,
|
|
105
107
|
todos TEXT,
|
|
@@ -314,6 +316,17 @@ CREATE TABLE IF NOT EXISTS session_insights (
|
|
|
314
316
|
facets TEXT NOT NULL
|
|
315
317
|
);
|
|
316
318
|
|
|
319
|
+
-- Derived topic classification for traces sync. Like session_insights, this is
|
|
320
|
+
-- a lazy, stamp-validated cache and is intentionally independent of SCHEMA_VERSION.
|
|
321
|
+
CREATE TABLE IF NOT EXISTS session_topics (
|
|
322
|
+
session_id TEXT PRIMARY KEY,
|
|
323
|
+
file_mtime_ms INTEGER,
|
|
324
|
+
file_size INTEGER,
|
|
325
|
+
extractor_version INTEGER NOT NULL,
|
|
326
|
+
computed_at INTEGER NOT NULL,
|
|
327
|
+
topic_json TEXT NOT NULL
|
|
328
|
+
);
|
|
329
|
+
|
|
317
330
|
-- Normalized data behind sessions preview. Like session_insights this is a
|
|
318
331
|
-- lazy, stamp-validated cache: opening one session parses only that transcript,
|
|
319
332
|
-- while subsequent processes reuse the derived preview until its bytes change.
|
|
@@ -397,6 +410,7 @@ CREATE INDEX IF NOT EXISTS idx_computer_sessions_started ON computer_sessions(st
|
|
|
397
410
|
*/
|
|
398
411
|
/** Bump when facet extraction changes so cached rows recompute (stalls-by-model v6). */
|
|
399
412
|
export const INSIGHTS_EXTRACTOR_VERSION = 6;
|
|
413
|
+
export const SESSION_TOPIC_EXTRACTOR_VERSION = 1;
|
|
400
414
|
const PREVIEW_EXTRACTOR_VERSION = 1;
|
|
401
415
|
let dbInstance = null;
|
|
402
416
|
/**
|
|
@@ -1037,6 +1051,27 @@ function migrateSchema(db, fromVersion) {
|
|
|
1037
1051
|
CREATE INDEX IF NOT EXISTS idx_computer_sessions_started ON computer_sessions(started_at DESC);
|
|
1038
1052
|
`);
|
|
1039
1053
|
}
|
|
1054
|
+
if (fromVersion < 40) {
|
|
1055
|
+
// v39 -> v40: persist the fan-out a session left behind — sub-agents spawned
|
|
1056
|
+
// and shells backgrounded (RUSH-3091/3095). Both were recomputed per render
|
|
1057
|
+
// from the transcript, so a REMOTE or unindexed row (rendered from
|
|
1058
|
+
// SessionMeta alone by sessions-picker's formatMetaOnlyBody, which has no
|
|
1059
|
+
// events to derive from) showed neither. Columns are what make them
|
|
1060
|
+
// renderable there.
|
|
1061
|
+
//
|
|
1062
|
+
// New nullable columns, so no ledger flush — the v33->v34 contract that
|
|
1063
|
+
// adding a column keeps warm session ledgers warm. Pre-upgrade rows stay
|
|
1064
|
+
// NULL until re-scanned, and NULL is deliberately distinct from 0: NULL is
|
|
1065
|
+
// "not computed / harness cannot report it", 0 is "scanned, none found".
|
|
1066
|
+
// The renders omit the segment for both, so neither can be misread as a
|
|
1067
|
+
// positive "nothing is running" claim.
|
|
1068
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1069
|
+
if (!cols.has('sub_agent_count'))
|
|
1070
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN sub_agent_count INTEGER`);
|
|
1071
|
+
if (!cols.has('background_shell_count')) {
|
|
1072
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN background_shell_count INTEGER`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1040
1075
|
}
|
|
1041
1076
|
/**
|
|
1042
1077
|
* Stamp `account_key` / `account_org` / `account` on every Claude row from its
|
|
@@ -1554,7 +1589,8 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1554
1589
|
output_tokens, input_tokens, cache_read_tokens, cache_write_tokens,
|
|
1555
1590
|
cost_usd, cost_usd_nocache, duration_ms, model, tool_call_count,
|
|
1556
1591
|
file_path, file_mtime_ms, file_size, scanned_at, is_team_origin,
|
|
1557
|
-
pr_url, pr_number, worktree_slug, ticket_id, spawned_team,
|
|
1592
|
+
pr_url, pr_number, worktree_slug, ticket_id, spawned_team,
|
|
1593
|
+
sub_agent_count, background_shell_count, plan, todos,
|
|
1558
1594
|
recent_directories_touched, linear_project, linear_project_url, machine,
|
|
1559
1595
|
actor, initiated_by, used_browser, used_computer
|
|
1560
1596
|
) VALUES (
|
|
@@ -1564,7 +1600,8 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1564
1600
|
@output_tokens, @input_tokens, @cache_read_tokens, @cache_write_tokens,
|
|
1565
1601
|
@cost_usd, @cost_usd_nocache, @duration_ms, @model, @tool_call_count,
|
|
1566
1602
|
@file_path, @file_mtime_ms, @file_size, @scanned_at, @is_team_origin,
|
|
1567
|
-
@pr_url, @pr_number, @worktree_slug, @ticket_id, @spawned_team,
|
|
1603
|
+
@pr_url, @pr_number, @worktree_slug, @ticket_id, @spawned_team,
|
|
1604
|
+
@sub_agent_count, @background_shell_count, @plan, @todos,
|
|
1568
1605
|
@recent_directories_touched, @linear_project, @linear_project_url, @machine,
|
|
1569
1606
|
@actor, @initiated_by, @used_browser, @used_computer
|
|
1570
1607
|
)
|
|
@@ -1615,6 +1652,8 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1615
1652
|
worktree_slug = excluded.worktree_slug,
|
|
1616
1653
|
ticket_id = excluded.ticket_id,
|
|
1617
1654
|
spawned_team = excluded.spawned_team,
|
|
1655
|
+
sub_agent_count = COALESCE(excluded.sub_agent_count, sessions.sub_agent_count),
|
|
1656
|
+
background_shell_count = COALESCE(excluded.background_shell_count, sessions.background_shell_count),
|
|
1618
1657
|
plan = excluded.plan,
|
|
1619
1658
|
todos = excluded.todos,
|
|
1620
1659
|
recent_directories_touched = excluded.recent_directories_touched,
|
|
@@ -1752,6 +1791,30 @@ function writeResourceUsageFromTallies(sessionId, skills, commands, cwd) {
|
|
|
1752
1791
|
function writeResourceUsage(sessionId, events, cwd) {
|
|
1753
1792
|
writeResourceUsageFromTallies(sessionId, extractSkills(events), extractSlashCommands(events), cwd);
|
|
1754
1793
|
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Fan-out the session left behind, from an ALREADY-parsed transcript (never a
|
|
1796
|
+
* re-parse — every caller here has `events` in hand).
|
|
1797
|
+
*
|
|
1798
|
+
* `backgroundShellCount` is `undefined`, not 0, for a harness that cannot report
|
|
1799
|
+
* backgrounded shells. That distinction is the whole point: 0 asserts "none were
|
|
1800
|
+
* started", while undefined says "this harness does not record it" — and the
|
|
1801
|
+
* renders omit the segment for both, so neither can read as "nothing is running".
|
|
1802
|
+
*/
|
|
1803
|
+
function fanOutCounts(events, agent) {
|
|
1804
|
+
let subAgentCount = 0;
|
|
1805
|
+
for (const e of events) {
|
|
1806
|
+
if (e.type !== 'tool_use' || e._local)
|
|
1807
|
+
continue;
|
|
1808
|
+
if (isSubAgentTool(e.tool || '', e.command || ''))
|
|
1809
|
+
subAgentCount++;
|
|
1810
|
+
}
|
|
1811
|
+
return {
|
|
1812
|
+
subAgentCount,
|
|
1813
|
+
backgroundShellCount: harnessTracksBackgroundShells(agent)
|
|
1814
|
+
? extractBackgroundShells(events).length
|
|
1815
|
+
: undefined,
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1755
1818
|
function enrichCachedSessionMeta(meta) {
|
|
1756
1819
|
if (!meta.filePath)
|
|
1757
1820
|
return meta;
|
|
@@ -1762,6 +1825,7 @@ function enrichCachedSessionMeta(meta) {
|
|
|
1762
1825
|
...meta,
|
|
1763
1826
|
todos: extractTodoProgressFromEvents(events),
|
|
1764
1827
|
recentDirectoriesTouched: extractRecentDirectoriesTouched(events, meta.cwd),
|
|
1828
|
+
...fanOutCounts(events, meta.agent),
|
|
1765
1829
|
};
|
|
1766
1830
|
}
|
|
1767
1831
|
catch {
|
|
@@ -1858,6 +1922,8 @@ export function upsertSession(meta, content, scan) {
|
|
|
1858
1922
|
worktree_slug: meta.worktreeSlug ?? null,
|
|
1859
1923
|
ticket_id: meta.ticketId ?? null,
|
|
1860
1924
|
spawned_team: meta.spawnedTeam ?? null,
|
|
1925
|
+
sub_agent_count: meta.subAgentCount ?? null,
|
|
1926
|
+
background_shell_count: meta.backgroundShellCount ?? null,
|
|
1861
1927
|
plan: meta.plan ?? null,
|
|
1862
1928
|
todos: meta.todos ? JSON.stringify(meta.todos) : null,
|
|
1863
1929
|
recent_directories_touched: meta.recentDirectoriesTouched ? JSON.stringify(meta.recentDirectoriesTouched) : null,
|
|
@@ -1917,8 +1983,16 @@ export function upsertSessionsBatch(entries) {
|
|
|
1917
1983
|
.filter(e => e.scan && e.meta.filePath)
|
|
1918
1984
|
.map(e => [canonicalLedgerKey(e.meta.filePath), e]));
|
|
1919
1985
|
const enrichedEntries = entries.map(entry => {
|
|
1920
|
-
if (entry.meta.agent === 'claude' || entry.meta.agent === 'codex' || !entry.meta.filePath)
|
|
1921
|
-
|
|
1986
|
+
if (entry.meta.agent === 'claude' || entry.meta.agent === 'codex' || !entry.meta.filePath) {
|
|
1987
|
+
// claude/codex keep their resumable-parse optimization (no transcript read
|
|
1988
|
+
// here). When their scanner already handed us normalized events, the counts
|
|
1989
|
+
// are free — take them. Otherwise leave them undefined, which persists NULL
|
|
1990
|
+
// and means "not computed yet", and the next scan that carries events fills
|
|
1991
|
+
// it. Never collapse that to 0: see fanOutCounts.
|
|
1992
|
+
return entry.events
|
|
1993
|
+
? { ...entry, meta: { ...entry.meta, ...fanOutCounts(entry.events, entry.meta.agent) } }
|
|
1994
|
+
: entry;
|
|
1995
|
+
}
|
|
1922
1996
|
try {
|
|
1923
1997
|
const toolSourcePath = toolEvidenceSourcePath(entry.meta.filePath, entry.meta.agent);
|
|
1924
1998
|
const toolScan = toolSourcePath === entry.meta.filePath
|
|
@@ -1938,6 +2012,7 @@ export function upsertSessionsBatch(entries) {
|
|
|
1938
2012
|
...entry.meta,
|
|
1939
2013
|
todos: extractTodoProgressFromEvents(events),
|
|
1940
2014
|
recentDirectoriesTouched: extractRecentDirectoriesTouched(events, entry.meta.cwd),
|
|
2015
|
+
...fanOutCounts(events, entry.meta.agent),
|
|
1941
2016
|
},
|
|
1942
2017
|
toolCalls: toolCallsFromEvents(events),
|
|
1943
2018
|
toolScan,
|
|
@@ -2042,6 +2117,8 @@ export function upsertSessionsBatch(entries) {
|
|
|
2042
2117
|
worktree_slug: meta.worktreeSlug ?? null,
|
|
2043
2118
|
ticket_id: meta.ticketId ?? null,
|
|
2044
2119
|
spawned_team: meta.spawnedTeam ?? null,
|
|
2120
|
+
sub_agent_count: meta.subAgentCount ?? null,
|
|
2121
|
+
background_shell_count: meta.backgroundShellCount ?? null,
|
|
2045
2122
|
plan: meta.plan ?? null,
|
|
2046
2123
|
todos: meta.todos ? JSON.stringify(meta.todos) : null,
|
|
2047
2124
|
recent_directories_touched: meta.recentDirectoriesTouched ? JSON.stringify(meta.recentDirectoriesTouched) : null,
|
|
@@ -2265,6 +2342,8 @@ function rowToMeta(row) {
|
|
|
2265
2342
|
worktreeSlug: row.worktree_slug ?? undefined,
|
|
2266
2343
|
ticketId: row.ticket_id ?? undefined,
|
|
2267
2344
|
spawnedTeam: row.spawned_team ?? undefined,
|
|
2345
|
+
subAgentCount: row.sub_agent_count ?? undefined,
|
|
2346
|
+
backgroundShellCount: row.background_shell_count ?? undefined,
|
|
2268
2347
|
plan: row.plan ?? undefined,
|
|
2269
2348
|
todos: parseJsonColumn(row.todos),
|
|
2270
2349
|
recentDirectoriesTouched: parseJsonColumn(row.recent_directories_touched),
|
|
@@ -2690,6 +2769,59 @@ export function writeSessionInsights(entries) {
|
|
|
2690
2769
|
export function clearSessionInsights() {
|
|
2691
2770
|
getDB().exec(`DELETE FROM session_insights`);
|
|
2692
2771
|
}
|
|
2772
|
+
/** Read cached trace topics only when their transcript byte stamps still match. */
|
|
2773
|
+
export function readSessionTopics(ids) {
|
|
2774
|
+
const db = getDB();
|
|
2775
|
+
const out = new Map();
|
|
2776
|
+
if (ids.length === 0)
|
|
2777
|
+
return out;
|
|
2778
|
+
const CHUNK = 400;
|
|
2779
|
+
for (let i = 0; i < ids.length; i += CHUNK) {
|
|
2780
|
+
const chunk = ids.slice(i, i + CHUNK);
|
|
2781
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
2782
|
+
const rows = db.prepare(`
|
|
2783
|
+
SELECT st.session_id AS id, st.topic_json AS topicJson
|
|
2784
|
+
FROM session_topics st
|
|
2785
|
+
JOIN sessions s ON s.id = st.session_id
|
|
2786
|
+
WHERE st.session_id IN (${placeholders})
|
|
2787
|
+
AND st.extractor_version = ?
|
|
2788
|
+
AND st.file_mtime_ms IS s.file_mtime_ms
|
|
2789
|
+
AND st.file_size IS s.file_size
|
|
2790
|
+
`).all(...chunk, SESSION_TOPIC_EXTRACTOR_VERSION);
|
|
2791
|
+
for (const row of rows) {
|
|
2792
|
+
try {
|
|
2793
|
+
out.set(row.id, JSON.parse(row.topicJson));
|
|
2794
|
+
}
|
|
2795
|
+
catch {
|
|
2796
|
+
// Invalid derived cache data is a miss and self-heals on the next write.
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
return out;
|
|
2801
|
+
}
|
|
2802
|
+
/** Persist trace topics against the exact transcript bytes used to classify them. */
|
|
2803
|
+
export function writeSessionTopics(entries) {
|
|
2804
|
+
if (entries.length === 0)
|
|
2805
|
+
return;
|
|
2806
|
+
const db = getDB();
|
|
2807
|
+
const stmt = db.prepare(`
|
|
2808
|
+
INSERT INTO session_topics
|
|
2809
|
+
(session_id, file_mtime_ms, file_size, extractor_version, computed_at, topic_json)
|
|
2810
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2811
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
2812
|
+
file_mtime_ms = excluded.file_mtime_ms,
|
|
2813
|
+
file_size = excluded.file_size,
|
|
2814
|
+
extractor_version = excluded.extractor_version,
|
|
2815
|
+
computed_at = excluded.computed_at,
|
|
2816
|
+
topic_json = excluded.topic_json
|
|
2817
|
+
`);
|
|
2818
|
+
const now = Date.now();
|
|
2819
|
+
db.transaction(() => {
|
|
2820
|
+
for (const entry of entries) {
|
|
2821
|
+
stmt.run(entry.id, entry.fileMtimeMs, entry.fileSize, SESSION_TOPIC_EXTRACTOR_VERSION, now, JSON.stringify(entry.topic));
|
|
2822
|
+
}
|
|
2823
|
+
})();
|
|
2824
|
+
}
|
|
2693
2825
|
/** Read one derived preview only when it matches the transcript bytes on disk. */
|
|
2694
2826
|
export function readSessionPreviewCache(id, sourceStamp) {
|
|
2695
2827
|
const row = getDB().prepare(`
|
|
@@ -122,6 +122,9 @@ interface ClaudeSessionScan {
|
|
|
122
122
|
name: string;
|
|
123
123
|
count: number;
|
|
124
124
|
}>;
|
|
125
|
+
/** Fan-out left behind (RUSH-3091/3095) — see SessionMeta.subAgentCount. */
|
|
126
|
+
subAgentCount?: number;
|
|
127
|
+
backgroundShellCount?: number;
|
|
125
128
|
/** Slash commands invoked (#12) — see SessionMeta.slashCommandsUsed. */
|
|
126
129
|
slashCommandsUsed?: Array<{
|
|
127
130
|
name: string;
|
|
@@ -481,6 +484,14 @@ interface ClaudeParseState {
|
|
|
481
484
|
/** Slash-command events (user-typed <command-name> wrapper OR a SlashCommand
|
|
482
485
|
* tool_use), held for extractSlashCommands() at finalize (#12). */
|
|
483
486
|
slashCommandEvents: SessionEvent[];
|
|
487
|
+
/**
|
|
488
|
+
* Fan-out tallies (RUSH-3091/3095). Counted as we stream rather than held as
|
|
489
|
+
* events — unlike skills we need only the count, and a busy session can spawn
|
|
490
|
+
* hundreds. `backgroundShells` stays undefined for a harness with no
|
|
491
|
+
* background-shell concept, which must render as absence, never as 0.
|
|
492
|
+
*/
|
|
493
|
+
subAgents: number;
|
|
494
|
+
backgroundShells: number | undefined;
|
|
484
495
|
}
|
|
485
496
|
/** Zero-value accumulator for a fresh (from-byte-0) Claude parse. */
|
|
486
497
|
export declare function initClaudeParseState(): ClaudeParseState;
|
|
@@ -510,7 +521,16 @@ export declare function scanClaudeSession(filePath: string): Promise<ClaudeSessi
|
|
|
510
521
|
* exact even when the recent window is smaller than the true count.
|
|
511
522
|
*/
|
|
512
523
|
export interface ClaudeParserState {
|
|
513
|
-
v:
|
|
524
|
+
v: 4;
|
|
525
|
+
/**
|
|
526
|
+
* Fan-out tallies carried across a RESUMED parse (RUSH-3091/3095). They must
|
|
527
|
+
* live in the durable blob: the resumable parser reads only new bytes, so
|
|
528
|
+
* re-initialising them on hydrate would report a session's tail, not its
|
|
529
|
+
* total. Adding them is why `v` moved 3 -> 4 — an older blob is rejected and
|
|
530
|
+
* the session re-parses from byte 0 with correct totals.
|
|
531
|
+
*/
|
|
532
|
+
subAgents: number;
|
|
533
|
+
backgroundShells: number | undefined;
|
|
514
534
|
offset: number;
|
|
515
535
|
jsonlDroppingOversizedLine?: boolean;
|
|
516
536
|
timestamp?: string;
|
|
@@ -26,8 +26,8 @@ import { getConfigSymlinkVersion } from '../installations/shims.js';
|
|
|
26
26
|
import { SESSION_AGENTS } from './types.js';
|
|
27
27
|
import { deriveShortId } from './short-id.js';
|
|
28
28
|
import { buildClaudeAccountIndex, resolveClaudeAccount } from './claude-accounts.js';
|
|
29
|
-
import { extractSessionTopic, extractSlashCommandName, extractSlashCommandFromToolInput } from './prompt.js';
|
|
30
|
-
import { isSkillInvocation, extractSkills, extractSlashCommands } from './highlights.js';
|
|
29
|
+
import { extractSessionTopic, extractSlashCommandName, extractSlashCommandFromToolInput, classifyUserPrompt } from './prompt.js';
|
|
30
|
+
import { isBackgroundShellStart, isSkillInvocation, extractSkills, extractSlashCommands, isSubAgentTool } from './highlights.js';
|
|
31
31
|
import { parseAntigravity, parseCursor, splitSessionFilePath } from './parse.js';
|
|
32
32
|
import { extractPrUrl, detectWorktree, detectTicket, isPrCreateCommand, detectSpawnedTeam, isTicketCreateTool, extractCreatedTicket, extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from './state.js';
|
|
33
33
|
import { costOfUsage, costOfUsageNoCache } from '../pricing/index.js';
|
|
@@ -1239,6 +1239,8 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1239
1239
|
todos: scan.todos,
|
|
1240
1240
|
recentDirectoriesTouched: scan.recentDirectoriesTouched,
|
|
1241
1241
|
skillsUsed: scan.skillsUsed,
|
|
1242
|
+
subAgentCount: scan.subAgentCount,
|
|
1243
|
+
backgroundShellCount: scan.backgroundShellCount,
|
|
1242
1244
|
slashCommandsUsed: scan.slashCommandsUsed,
|
|
1243
1245
|
};
|
|
1244
1246
|
}
|
|
@@ -1280,6 +1282,8 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1280
1282
|
todos: scan.todos,
|
|
1281
1283
|
recentDirectoriesTouched: scan.recentDirectoriesTouched,
|
|
1282
1284
|
skillsUsed: scan.skillsUsed,
|
|
1285
|
+
subAgentCount: scan.subAgentCount,
|
|
1286
|
+
backgroundShellCount: scan.backgroundShellCount,
|
|
1283
1287
|
slashCommandsUsed: scan.slashCommandsUsed,
|
|
1284
1288
|
};
|
|
1285
1289
|
}
|
|
@@ -2996,6 +3000,8 @@ function extractDroidMessageText(content) {
|
|
|
2996
3000
|
/** Zero-value accumulator for a fresh (from-byte-0) Claude parse. */
|
|
2997
3001
|
export function initClaudeParseState() {
|
|
2998
3002
|
return {
|
|
3003
|
+
subAgents: 0,
|
|
3004
|
+
backgroundShells: 0,
|
|
2999
3005
|
timestamp: undefined,
|
|
3000
3006
|
cwd: undefined,
|
|
3001
3007
|
gitBranch: undefined,
|
|
@@ -3044,6 +3050,14 @@ function foldDerivedToolState(state, event) {
|
|
|
3044
3050
|
// from a full re-parse (see session/db.ts's writeResourceUsage doc comment).
|
|
3045
3051
|
if (isSkillInvocation(event))
|
|
3046
3052
|
state.skillEvents.push(event);
|
|
3053
|
+
// Fan-out tallies (RUSH-3091/3095): counted, not held — we need the number,
|
|
3054
|
+
// and a heavy orchestrator session can spawn hundreds of each.
|
|
3055
|
+
if (state.subAgents !== undefined && isSubAgentTool(event.tool ?? '', event.command ?? '')) {
|
|
3056
|
+
state.subAgents += 1;
|
|
3057
|
+
}
|
|
3058
|
+
if (state.backgroundShells !== undefined && isBackgroundShellStart(event)) {
|
|
3059
|
+
state.backgroundShells += 1;
|
|
3060
|
+
}
|
|
3047
3061
|
if (event.tool === 'SlashCommand') {
|
|
3048
3062
|
const slashCommand = extractSlashCommandFromToolInput(event.args);
|
|
3049
3063
|
if (slashCommand)
|
|
@@ -3254,7 +3268,15 @@ export function finalizeClaudeScan(state) {
|
|
|
3254
3268
|
// A topic is the first meaningful prompt. Harness-owned names travel in the
|
|
3255
3269
|
// separate label field so consumers can replace an early topic once Claude's
|
|
3256
3270
|
// generated title (or a later `/rename`) arrives.
|
|
3257
|
-
|
|
3271
|
+
// Claude's generated `ai-title` is derived from the first turn, so a session
|
|
3272
|
+
// opened with a skill gets named after the injected "Base directory for this
|
|
3273
|
+
// skill: …" line instead of its task — and that name then wins on every
|
|
3274
|
+
// surface for the session's whole life. `classifyUserPrompt` reports `skill`
|
|
3275
|
+
// only for that injected line, so collapse it to `/<skill>`. A `/rename`
|
|
3276
|
+
// (`custom-title`) is the user's own words and is never rewritten.
|
|
3277
|
+
const generated = classifyUserPrompt(state.aiTitle ?? '');
|
|
3278
|
+
const label = state.customTitle
|
|
3279
|
+
|| (generated.kind === 'skill' ? generated.clean : state.aiTitle);
|
|
3258
3280
|
const worktree = detectWorktree(state.cwd, state.gitBranch);
|
|
3259
3281
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3260
3282
|
return {
|
|
@@ -3289,6 +3311,8 @@ export function finalizeClaudeScan(state) {
|
|
|
3289
3311
|
recentDirectoriesTouched: state.recentDirectoriesTouched.length ? state.recentDirectoriesTouched : undefined,
|
|
3290
3312
|
skillsUsed: state.skillEvents.length ? extractSkills(state.skillEvents) : undefined,
|
|
3291
3313
|
slashCommandsUsed: state.slashCommandEvents.length ? extractSlashCommands(state.slashCommandEvents) : undefined,
|
|
3314
|
+
subAgentCount: state.subAgents,
|
|
3315
|
+
backgroundShellCount: state.backgroundShells,
|
|
3292
3316
|
};
|
|
3293
3317
|
}
|
|
3294
3318
|
/** Stream a Claude JSONL file and extract scan-level metadata (timestamp, cwd, topic, tokens). */
|
|
@@ -3330,7 +3354,9 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3330
3354
|
: allIds;
|
|
3331
3355
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3332
3356
|
return {
|
|
3333
|
-
v:
|
|
3357
|
+
v: 4,
|
|
3358
|
+
subAgents: state.subAgents,
|
|
3359
|
+
backgroundShells: state.backgroundShells,
|
|
3334
3360
|
offset,
|
|
3335
3361
|
jsonlDroppingOversizedLine: jsonlDroppingOversizedLine || undefined,
|
|
3336
3362
|
timestamp: state.timestamp,
|
|
@@ -3402,6 +3428,8 @@ export function hydrateClaudeParseState(prior) {
|
|
|
3402
3428
|
seen.add(`pad:${pad++}`);
|
|
3403
3429
|
}
|
|
3404
3430
|
return {
|
|
3431
|
+
subAgents: prior.subAgents,
|
|
3432
|
+
backgroundShells: prior.backgroundShells,
|
|
3405
3433
|
timestamp: prior.timestamp,
|
|
3406
3434
|
cwd: prior.cwd,
|
|
3407
3435
|
gitBranch: prior.gitBranch,
|
|
@@ -3569,7 +3597,7 @@ function parsePriorClaudeState(row) {
|
|
|
3569
3597
|
return null;
|
|
3570
3598
|
try {
|
|
3571
3599
|
const parsed = JSON.parse(row.parserState);
|
|
3572
|
-
if (parsed?.v !==
|
|
3600
|
+
if (parsed?.v !== 4 || typeof parsed.offset !== 'number' || parsed.toolCalls?.v !== 1)
|
|
3573
3601
|
return null;
|
|
3574
3602
|
return parsed;
|
|
3575
3603
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* bounded `.git` walk over a handful of candidate dirs) and is the only
|
|
11
11
|
* non-pure function here.
|
|
12
12
|
*/
|
|
13
|
-
import type { SessionEvent } from './types.js';
|
|
13
|
+
import type { SessionAgentId, SessionEvent } from './types.js';
|
|
14
14
|
import { type FileChange } from './digest.js';
|
|
15
15
|
export interface SkillUse {
|
|
16
16
|
name: string;
|
|
@@ -100,3 +100,51 @@ export declare function extractArtifacts(changes: FileChange[]): ProducedArtifac
|
|
|
100
100
|
* happens to run in, which is a wrong answer, not a degraded one.
|
|
101
101
|
*/
|
|
102
102
|
export declare function extractRepos(events: SessionEvent[], cwd?: string): string[];
|
|
103
|
+
/**
|
|
104
|
+
* True when this harness records backgrounded shells at all. Callers MUST gate
|
|
105
|
+
* on this before rendering a count: for a harness that cannot report it, "0" is
|
|
106
|
+
* a lie ("none running") where the truth is "unknown", so the segment is omitted
|
|
107
|
+
* entirely — the same way the Hooks line renders for Claude only.
|
|
108
|
+
*/
|
|
109
|
+
export declare function harnessTracksBackgroundShells(agent: SessionAgentId): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* True for a tool_use event that starts a background shell. Exported for the
|
|
112
|
+
* same reason {@link isSkillInvocation} is: the incremental parser can select
|
|
113
|
+
* matching events while streaming instead of re-reading the transcript.
|
|
114
|
+
*/
|
|
115
|
+
export declare function isBackgroundShellStart(e: SessionEvent): boolean;
|
|
116
|
+
export interface BackgroundShell {
|
|
117
|
+
/** The command line as invoked, trimmed for display. */
|
|
118
|
+
command: string;
|
|
119
|
+
timestamp: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Shells the session started in the background, oldest first.
|
|
123
|
+
*
|
|
124
|
+
* This is "started", NOT "still running". A transcript records the start and
|
|
125
|
+
* never the death, and inferring liveness from its absence is the same trap
|
|
126
|
+
* `agents devices ps` documents — a killed process leaves no marker, so a naive
|
|
127
|
+
* reader reports "running" forever. Live status belongs to `sessions --active`,
|
|
128
|
+
* which resolves real pids.
|
|
129
|
+
*/
|
|
130
|
+
export declare function extractBackgroundShells(events: SessionEvent[]): BackgroundShell[];
|
|
131
|
+
/**
|
|
132
|
+
* True for a tool call that spawns another agent. Deliberately counts BOTH
|
|
133
|
+
* shapes, because both are fan-out the operator needs to see:
|
|
134
|
+
*
|
|
135
|
+
* in-process an `Agent` / `Task` tool call inside this transcript
|
|
136
|
+
* shelled-out a Bash command that invokes `agents run`, `agents cloud run`,
|
|
137
|
+
* or `agents teams add|start`
|
|
138
|
+
*
|
|
139
|
+
* Moved here from `commands/sessions-picker.ts` so BOTH preview body paths (the
|
|
140
|
+
* digest body and `formatMetaOnlyBody`) share one definition instead of
|
|
141
|
+
* drifting apart. Semantics are unchanged: session f045b577 counts 23
|
|
142
|
+
* in-process + 30 shelled-out = 53.
|
|
143
|
+
*
|
|
144
|
+
* The full summary's `Subagents (N)` section (`render.ts`) deliberately does NOT
|
|
145
|
+
* use this. It builds a described ROSTER, so it needs `args.description` /
|
|
146
|
+
* `args.prompt` and is necessarily limited to in-process `Agent`/`Task` calls —
|
|
147
|
+
* a shelled-out `agents run` carries no such description. Different question,
|
|
148
|
+
* different answer; do not "unify" them.
|
|
149
|
+
*/
|
|
150
|
+
export declare function isSubAgentTool(tool: string, command: string): boolean;
|
|
@@ -294,3 +294,98 @@ export function extractRepos(events, cwd) {
|
|
|
294
294
|
}
|
|
295
295
|
return repos;
|
|
296
296
|
}
|
|
297
|
+
// ── Background shells ─────────────────────────────────────────────────────────
|
|
298
|
+
/**
|
|
299
|
+
* Per-harness shape of "start this shell command in the background", keyed the
|
|
300
|
+
* same way {@link SKILL_TOOL_NAME_BY_AGENT} is: one table entry per harness that
|
|
301
|
+
* has the capability, so adding a harness is a row and never another `else if`.
|
|
302
|
+
*
|
|
303
|
+
* An absent entry means "this harness cannot report it", which the renders must
|
|
304
|
+
* show as ABSENCE, never as a count of zero. Every absence below was probed
|
|
305
|
+
* against that harness's real transcripts on this fleet rather than assumed:
|
|
306
|
+
*
|
|
307
|
+
* codex 0 of 176 session files carry any background marker; its `exec` /
|
|
308
|
+
* `exec_command` record only `cmd` + `workdir`, no shell handle.
|
|
309
|
+
* droid 0 of 126 session files.
|
|
310
|
+
* antigravity 0 of 54 session files.
|
|
311
|
+
* opencode 0 of 79 session files.
|
|
312
|
+
* cursor stores no tool calls locally at all — only `meta.json` and
|
|
313
|
+
* `prompt_history.json`; the calls live server-side.
|
|
314
|
+
*
|
|
315
|
+
* Not probed, because no version home is installed on this fleet to probe:
|
|
316
|
+
* `muse`, `rush`, `hermes`, `openclaw`, `gemini`. They are absent for lack of
|
|
317
|
+
* evidence, NOT on a verified negative — add a row once one is confirmed, and
|
|
318
|
+
* until then absence renders as "unknown", which is the safe direction.
|
|
319
|
+
*/
|
|
320
|
+
const BACKGROUND_SHELL_BY_AGENT = {
|
|
321
|
+
claude: { tool: 'Bash', flag: 'run_in_background' },
|
|
322
|
+
kimi: { tool: 'Bash', flag: 'run_in_background' },
|
|
323
|
+
grok: { tool: 'run_terminal_command', flag: 'background' },
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* True when this harness records backgrounded shells at all. Callers MUST gate
|
|
327
|
+
* on this before rendering a count: for a harness that cannot report it, "0" is
|
|
328
|
+
* a lie ("none running") where the truth is "unknown", so the segment is omitted
|
|
329
|
+
* entirely — the same way the Hooks line renders for Claude only.
|
|
330
|
+
*/
|
|
331
|
+
export function harnessTracksBackgroundShells(agent) {
|
|
332
|
+
return BACKGROUND_SHELL_BY_AGENT[agent] !== undefined;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* True for a tool_use event that starts a background shell. Exported for the
|
|
336
|
+
* same reason {@link isSkillInvocation} is: the incremental parser can select
|
|
337
|
+
* matching events while streaming instead of re-reading the transcript.
|
|
338
|
+
*/
|
|
339
|
+
export function isBackgroundShellStart(e) {
|
|
340
|
+
if (e.type !== 'tool_use' || e._local)
|
|
341
|
+
return false;
|
|
342
|
+
const spec = BACKGROUND_SHELL_BY_AGENT[e.agent];
|
|
343
|
+
return !!spec && e.tool === spec.tool && e.args?.[spec.flag] === true;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Shells the session started in the background, oldest first.
|
|
347
|
+
*
|
|
348
|
+
* This is "started", NOT "still running". A transcript records the start and
|
|
349
|
+
* never the death, and inferring liveness from its absence is the same trap
|
|
350
|
+
* `agents devices ps` documents — a killed process leaves no marker, so a naive
|
|
351
|
+
* reader reports "running" forever. Live status belongs to `sessions --active`,
|
|
352
|
+
* which resolves real pids.
|
|
353
|
+
*/
|
|
354
|
+
export function extractBackgroundShells(events) {
|
|
355
|
+
const out = [];
|
|
356
|
+
for (const e of events) {
|
|
357
|
+
if (!isBackgroundShellStart(e))
|
|
358
|
+
continue;
|
|
359
|
+
const raw = e.command ?? e.args?.command;
|
|
360
|
+
out.push({
|
|
361
|
+
command: typeof raw === 'string' ? raw.trim() : '',
|
|
362
|
+
timestamp: e.timestamp,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
// ── Sub-agents ────────────────────────────────────────────────────────────────
|
|
368
|
+
/**
|
|
369
|
+
* True for a tool call that spawns another agent. Deliberately counts BOTH
|
|
370
|
+
* shapes, because both are fan-out the operator needs to see:
|
|
371
|
+
*
|
|
372
|
+
* in-process an `Agent` / `Task` tool call inside this transcript
|
|
373
|
+
* shelled-out a Bash command that invokes `agents run`, `agents cloud run`,
|
|
374
|
+
* or `agents teams add|start`
|
|
375
|
+
*
|
|
376
|
+
* Moved here from `commands/sessions-picker.ts` so BOTH preview body paths (the
|
|
377
|
+
* digest body and `formatMetaOnlyBody`) share one definition instead of
|
|
378
|
+
* drifting apart. Semantics are unchanged: session f045b577 counts 23
|
|
379
|
+
* in-process + 30 shelled-out = 53.
|
|
380
|
+
*
|
|
381
|
+
* The full summary's `Subagents (N)` section (`render.ts`) deliberately does NOT
|
|
382
|
+
* use this. It builds a described ROSTER, so it needs `args.description` /
|
|
383
|
+
* `args.prompt` and is necessarily limited to in-process `Agent`/`Task` calls —
|
|
384
|
+
* a shelled-out `agents run` carries no such description. Different question,
|
|
385
|
+
* different answer; do not "unify" them.
|
|
386
|
+
*/
|
|
387
|
+
export function isSubAgentTool(tool, command) {
|
|
388
|
+
if (/^(Agent|Task)$/i.test(tool))
|
|
389
|
+
return true;
|
|
390
|
+
return /\b(?:agents|ag)\b[^\n;&|]*\b(?:run|cloud\s+run|teams\s+(?:add|start))\b/.test(command);
|
|
391
|
+
}
|
|
@@ -25,12 +25,26 @@
|
|
|
25
25
|
*/
|
|
26
26
|
/** How a session is connected to the client that should be driving it. */
|
|
27
27
|
export type HostLink =
|
|
28
|
-
/** A client is attached
|
|
28
|
+
/** A client is attached — positively established, not assumed. */
|
|
29
29
|
'connected'
|
|
30
30
|
/** Alive, but nothing is viewing it — the host window is gone and the agent outlived it. */
|
|
31
31
|
| 'no-client'
|
|
32
32
|
/** The host window is gone AND the agent process died with it — an unclean exit. */
|
|
33
|
-
| 'host-gone'
|
|
33
|
+
| 'host-gone'
|
|
34
|
+
/**
|
|
35
|
+
* Alive, and we have NO usable signal either way — no window owns it and it is
|
|
36
|
+
* not tmux-hosted, so neither input this function keys on exists.
|
|
37
|
+
*
|
|
38
|
+
* This case used to return `connected`, which reads as "verified fine" when the
|
|
39
|
+
* truth is "nobody looked". That is the wrong default for a detector: the rows
|
|
40
|
+
* it silently blesses are precisely the ones with no observer — a bare terminal,
|
|
41
|
+
* a team spawn, a cloud task, or any session whose pane lives on another
|
|
42
|
+
* machine (this function's inputs are all local, so a `--device` session is
|
|
43
|
+
* structurally unobservable from the launching box).
|
|
44
|
+
*
|
|
45
|
+
* Callers MUST NOT render this as healthy. It means the question is open.
|
|
46
|
+
*/
|
|
47
|
+
| 'unknown';
|
|
34
48
|
/**
|
|
35
49
|
* How long an IDE window's registry slice may go without a refresh before we
|
|
36
50
|
* treat that window as gone. AGI EXT force-republishes its slice
|
|
@@ -60,5 +60,11 @@ export function classifyHostLink(input) {
|
|
|
60
60
|
// that owned it is gone. The agent outlived its editor.
|
|
61
61
|
if (windowGone)
|
|
62
62
|
return 'no-client';
|
|
63
|
-
|
|
63
|
+
// Positive evidence of a client: tmux counted at least one attached client, or
|
|
64
|
+
// a window is refreshing its registry slice. Either is a real observation.
|
|
65
|
+
if (input.tmuxClients !== undefined || input.windowHeartbeatMs !== undefined)
|
|
66
|
+
return 'connected';
|
|
67
|
+
// Neither input exists, so nothing here observed anything. Say so rather than
|
|
68
|
+
// reporting the healthy answer by default — see {@link HostLink}'s `unknown`.
|
|
69
|
+
return 'unknown';
|
|
64
70
|
}
|