@phnx-labs/agents-cli 1.22.46 → 1.22.49
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 +663 -0
- package/LICENSE +102 -182
- package/README.md +96 -49
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +243 -25
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +711 -62
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +26 -24
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +107 -53
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- 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/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +7 -6
- 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 +4 -3
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +5 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +243 -18
- package/dist/commands/teams.js +34 -2
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +32 -62
- package/dist/commands/watchdog.js +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +84 -6
- package/dist/lib/accounting/usage.js +293 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +25 -15
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +40 -9
- package/dist/lib/browser/profiles.d.ts +104 -65
- package/dist/lib/browser/profiles.js +277 -165
- 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 +28 -2
- package/dist/lib/browser/remote-control.js +35 -3
- 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 +44 -11
- package/dist/lib/browser/runtime-state.js +98 -15
- package/dist/lib/browser/service.d.ts +65 -9
- package/dist/lib/browser/service.js +196 -61
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/browser/types.d.ts +12 -1
- 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/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- 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 +6 -11
- package/dist/lib/config-keys.js +18 -19
- 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/runner.js +15 -2
- 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 +22 -20
- package/dist/lib/device-config.d.ts +25 -10
- package/dist/lib/device-config.js +90 -39
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +26 -0
- package/dist/lib/devices/stats-cache.js +59 -7
- 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 +70 -14
- package/dist/lib/exec.js +110 -38
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- 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 +19 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- 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 +35 -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/hosts/registry.js +7 -0
- 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/versions.js +25 -2
- 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 +44 -7
- package/dist/lib/menubar/install-menubar.js +105 -23
- 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/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- 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/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.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.d.ts +67 -0
- package/dist/lib/session/active.js +86 -2
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +142 -8
- package/dist/lib/session/discover.d.ts +23 -1
- package/dist/lib/session/discover.js +44 -10
- 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/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- 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.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +17 -3
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +93 -1
- 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-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +19 -0
- package/dist/lib/session/trajectory-html.js +770 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +284 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +384 -0
- 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 +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- 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.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +6 -3
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +2 -2
- package/dist/lib/state.js +11 -5
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- 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.d.ts +46 -13
- package/dist/lib/tmux/session.js +204 -27
- 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 +447 -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 +34 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +18 -4
- package/dist/lib/usage-refresh.js +68 -19
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +5 -7
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
- 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 -24
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CloudTaskStatus } from '../cloud/types.js';
|
|
2
2
|
import { type PidSessionEntry } from './pid-registry.js';
|
|
3
3
|
import { type HookSessionIndex } from './hook-sessions.js';
|
|
4
|
+
import { type UserPromptKind } from './prompt.js';
|
|
4
5
|
import { type SessionState, type SessionActivity, type AwaitingReason, type StructuredQuestion, type TodoProgress, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
|
|
5
6
|
import { type SessionAgentId, type SessionAttachment, type SessionMeta } from './types.js';
|
|
6
7
|
import { type SessionProvenance } from './provenance.js';
|
|
@@ -99,6 +100,15 @@ export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required' | 'c
|
|
|
99
100
|
| 'orphaned'
|
|
100
101
|
/** The host window died and took the agent with it — an unclean exit, not a normal close. */
|
|
101
102
|
| 'crashed' | 'unknown';
|
|
103
|
+
/**
|
|
104
|
+
* Which rung of the recap ladder produced a row's shown {@link ActiveSession.title}
|
|
105
|
+
* (RUSH-3011), best-first:
|
|
106
|
+
* - `label` — a `/rename` or harness-set label (incl. an agent-generated
|
|
107
|
+
* title, which lands in `label`); always wins.
|
|
108
|
+
* - `last` — the last assistant line from the transcript tail; agent-derived.
|
|
109
|
+
* - `prompt` — the first-user-prompt topic; the last-resort fallback.
|
|
110
|
+
*/
|
|
111
|
+
export type RecapSource = 'label' | 'last' | 'prompt';
|
|
102
112
|
export interface ActiveSession {
|
|
103
113
|
context: ActiveContext;
|
|
104
114
|
kind: string;
|
|
@@ -115,6 +125,30 @@ export interface ActiveSession {
|
|
|
115
125
|
name?: string;
|
|
116
126
|
/** First meaningful line of the initial prompt (extracted topic). */
|
|
117
127
|
topic?: string;
|
|
128
|
+
/**
|
|
129
|
+
* The row's shown title — WHAT the session is, best-source-wins (RUSH-3011).
|
|
130
|
+
* The ladder ({@link deriveSessionRecap}): a `/rename` or harness `label` →
|
|
131
|
+
* the last agent line (`lastAgentLine`) → the first-prompt `topic`. A session
|
|
132
|
+
* whose agent did work shows an agent-derived line, not the stale first
|
|
133
|
+
* prompt. Folded on at the end of {@link getActiveSessions}; `recapSource`
|
|
134
|
+
* names which rung produced it.
|
|
135
|
+
*/
|
|
136
|
+
title?: string;
|
|
137
|
+
/** Which ladder rung produced {@link title}. */
|
|
138
|
+
recapSource?: RecapSource;
|
|
139
|
+
/**
|
|
140
|
+
* The first user turn cleaned for a "You" line — a screenshot path folds to
|
|
141
|
+
* `[image]`, a pasted `$ cmd` to the command, a `/skill` install path to
|
|
142
|
+
* `/<name>`, so path noise never shows. See {@link classifyUserPrompt}.
|
|
143
|
+
*/
|
|
144
|
+
userPromptClean?: string;
|
|
145
|
+
/** What kind of first turn {@link userPromptClean} was. */
|
|
146
|
+
userPromptKind?: UserPromptKind;
|
|
147
|
+
/**
|
|
148
|
+
* The most recent assistant line (from the transcript tail) — the free,
|
|
149
|
+
* always-current signal of what the agent last said/did. Ladder rung 3.
|
|
150
|
+
*/
|
|
151
|
+
lastAgentLine?: string;
|
|
118
152
|
/** Live preview: the latest turn (agent message or tool action), from the state engine. */
|
|
119
153
|
preview?: string;
|
|
120
154
|
/** Inferred activity: working / waiting_input / idle (from the transcript tail). */
|
|
@@ -787,6 +821,39 @@ export declare function sessionProcessIsLocal(s: Pick<ActiveSession, 'machine' |
|
|
|
787
821
|
*/
|
|
788
822
|
export declare function sessionProcessHost(s: Pick<ActiveSession, 'machine' | 'offloadedFrom'>, self: string): string | undefined;
|
|
789
823
|
export declare function foldHostLink(rows: ActiveSession[]): void;
|
|
824
|
+
/**
|
|
825
|
+
* The recap ladder (RUSH-3011): compute a row's shown {@link ActiveSession.title}
|
|
826
|
+
* + {@link RecapSource} from the best available source, plus the cleaned first
|
|
827
|
+
* prompt (`userPromptClean`/`userPromptKind`) and the `lastAgentLine`. Pure over
|
|
828
|
+
* one row; exported for tests and folded in by {@link foldRecap}.
|
|
829
|
+
*
|
|
830
|
+
* Ladder, best-first: a `/rename`/harness `label` → the last assistant line →
|
|
831
|
+
* the first-prompt topic. The `last` rung is agent-derived, so a session that
|
|
832
|
+
* produced work stops showing its stale first prompt as the title. (`topic` is
|
|
833
|
+
* the row's already-extracted first line, so image detection here is
|
|
834
|
+
* path-based; a pure-attachment turn with no first-line text stays on `prompt`.)
|
|
835
|
+
*/
|
|
836
|
+
export declare function deriveSessionRecap(row: Pick<ActiveSession, 'label' | 'topic' | 'tail'>): {
|
|
837
|
+
title?: string;
|
|
838
|
+
recapSource?: RecapSource;
|
|
839
|
+
userPromptClean?: string;
|
|
840
|
+
userPromptKind?: UserPromptKind;
|
|
841
|
+
lastAgentLine?: string;
|
|
842
|
+
};
|
|
843
|
+
/** Fold the recap ladder onto every row (see {@link deriveSessionRecap}). */
|
|
844
|
+
export declare function foldRecap(rows: ActiveSession[]): void;
|
|
845
|
+
/**
|
|
846
|
+
* True when a crash-leaked orphan is genuinely DEAD and should be reaped from the
|
|
847
|
+
* reconnectable set rather than shown as resumable forever (RUSH-3011 / issue #3b).
|
|
848
|
+
*
|
|
849
|
+
* The gate is `abandoned` (no transcript write in {@link ABANDONED_STALE_MS}) AND
|
|
850
|
+
* a dead pid — exactly "past the stale threshold whose pid is gone". A live pid
|
|
851
|
+
* (an idle-but-unfinished session, the highest-risk state) is NEVER reaped, and
|
|
852
|
+
* neither is a recently-`closed`/`crashed` session that just exited (still
|
|
853
|
+
* resumable). `pidAlive` absent (a cloud row or an older peer that can't prove
|
|
854
|
+
* death) also stays un-reaped — reaping is fail-safe, never a guess.
|
|
855
|
+
*/
|
|
856
|
+
export declare function isReapableOrphan(row: Pick<ActiveSession, 'status' | 'pidAlive'>): boolean;
|
|
790
857
|
/**
|
|
791
858
|
* Resolve each teams row's `orchestratorLabel` from the orchestrator's own row,
|
|
792
859
|
* when that orchestrator session is itself in the active set (it usually is — the
|
|
@@ -30,7 +30,7 @@ import { loadHookSessionIndex, resolveHookSessionRecord, readStateSessionRecord
|
|
|
30
30
|
import { buildClaudeLabelMap, getAgentSessionDirs } from './discover.js';
|
|
31
31
|
import { buildRunNameMap } from './run-names.js';
|
|
32
32
|
import { latestSessionFileForCwd, findSessionsByShortIds, findSessionMachinesByIds, getSessionById } from './db.js';
|
|
33
|
-
import { extractSessionTopic } from './prompt.js';
|
|
33
|
+
import { extractSessionTopic, classifyUserPrompt } from './prompt.js';
|
|
34
34
|
import { readSessionTailWithRaw } from './tail.js';
|
|
35
35
|
import { parseSession } from './parse.js';
|
|
36
36
|
import { computeTokPerSec } from './throughput.js';
|
|
@@ -1831,6 +1831,9 @@ export async function getActiveSessions(opts = {}) {
|
|
|
1831
1831
|
// the EXECUTION host rather than the dispatcher (RUSH-2479).
|
|
1832
1832
|
foldExecutionMachine(merged, recordedMachineLookup(merged), machineId());
|
|
1833
1833
|
annotateOrchestratorLabels(merged);
|
|
1834
|
+
// Last: derive the shown title (recap ladder) from label/tail/topic now that
|
|
1835
|
+
// every row's live tail + label are resolved (RUSH-3011).
|
|
1836
|
+
foldRecap(merged);
|
|
1834
1837
|
return merged;
|
|
1835
1838
|
}
|
|
1836
1839
|
/**
|
|
@@ -2065,15 +2068,96 @@ export function foldHostLink(rows) {
|
|
|
2065
2068
|
// disproves. A stored record never yields `attached` (it is background or
|
|
2066
2069
|
// parked), so clearing only that value drops the derived lie and leaves a
|
|
2067
2070
|
// real detach record untouched.
|
|
2068
|
-
|
|
2071
|
+
// Only a POSITIVE loss signal disproves a derived `attached`. `unknown` means
|
|
2072
|
+
// we never looked — a bare terminal with no IDE window and no tmux has
|
|
2073
|
+
// neither input — so it must not clear presence, or every plain-terminal
|
|
2074
|
+
// session the user is sitting in would lose its `attached` marker.
|
|
2075
|
+
if ((link === 'no-client' || link === 'host-gone') && s.presence === 'attached') {
|
|
2069
2076
|
s.presence = undefined;
|
|
2077
|
+
}
|
|
2070
2078
|
if (link === 'host-gone' && s.status === 'closed')
|
|
2071
2079
|
s.status = 'crashed';
|
|
2080
|
+
// Only idle/input_required are promoted — a `running` session keeps its
|
|
2081
|
+
// status (SES-18a). Extending this to a running agent with no client was
|
|
2082
|
+
// tried and reverted: since RUSH-3125 wraps every remote interactive run in
|
|
2083
|
+
// a detached tmux pane, "running with zero attached clients" is the NORMAL
|
|
2084
|
+
// steady state between check-ins, and that path writes no detach record, so
|
|
2085
|
+
// `deliberatelyDetached` is false for it. Promoting it would relabel every
|
|
2086
|
+
// remote agent as orphaned whenever nobody is looking — the over-reporting
|
|
2087
|
+
// this file's header calls worthless. Telling a stranded agent from a
|
|
2088
|
+
// healthy unattended one needs to know a client was EXPECTED and LOST, which
|
|
2089
|
+
// no signal available here carries; that belongs with the peer-side pane
|
|
2090
|
+
// ownership work, not this function.
|
|
2072
2091
|
else if (link === 'no-client' && (s.status === 'idle' || s.status === 'input_required')) {
|
|
2073
2092
|
s.status = 'orphaned';
|
|
2074
2093
|
}
|
|
2075
2094
|
}
|
|
2076
2095
|
}
|
|
2096
|
+
/** One display line, trimmed and capped, or undefined when empty. */
|
|
2097
|
+
function recapLine(s, max = 120) {
|
|
2098
|
+
const t = s?.replace(/\s+/g, ' ').trim();
|
|
2099
|
+
if (!t)
|
|
2100
|
+
return undefined;
|
|
2101
|
+
return t.length > max ? t.slice(0, max - 1).trimEnd() + '…' : t;
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* The recap ladder (RUSH-3011): compute a row's shown {@link ActiveSession.title}
|
|
2105
|
+
* + {@link RecapSource} from the best available source, plus the cleaned first
|
|
2106
|
+
* prompt (`userPromptClean`/`userPromptKind`) and the `lastAgentLine`. Pure over
|
|
2107
|
+
* one row; exported for tests and folded in by {@link foldRecap}.
|
|
2108
|
+
*
|
|
2109
|
+
* Ladder, best-first: a `/rename`/harness `label` → the last assistant line →
|
|
2110
|
+
* the first-prompt topic. The `last` rung is agent-derived, so a session that
|
|
2111
|
+
* produced work stops showing its stale first prompt as the title. (`topic` is
|
|
2112
|
+
* the row's already-extracted first line, so image detection here is
|
|
2113
|
+
* path-based; a pure-attachment turn with no first-line text stays on `prompt`.)
|
|
2114
|
+
*/
|
|
2115
|
+
export function deriveSessionRecap(row) {
|
|
2116
|
+
const lastAgentLine = recapLine(row.tail?.length ? row.tail[row.tail.length - 1] : undefined);
|
|
2117
|
+
const { clean: userPromptClean, kind: userPromptKind } = classifyUserPrompt(row.topic ?? '');
|
|
2118
|
+
const label = recapLine(row.label);
|
|
2119
|
+
const prompt = recapLine(userPromptClean || row.topic);
|
|
2120
|
+
let title;
|
|
2121
|
+
let recapSource;
|
|
2122
|
+
if (label) {
|
|
2123
|
+
title = label;
|
|
2124
|
+
recapSource = 'label';
|
|
2125
|
+
}
|
|
2126
|
+
else if (lastAgentLine) {
|
|
2127
|
+
title = lastAgentLine;
|
|
2128
|
+
recapSource = 'last';
|
|
2129
|
+
}
|
|
2130
|
+
else if (prompt) {
|
|
2131
|
+
title = prompt;
|
|
2132
|
+
recapSource = 'prompt';
|
|
2133
|
+
}
|
|
2134
|
+
return { title, recapSource, userPromptClean: recapLine(userPromptClean), userPromptKind, lastAgentLine };
|
|
2135
|
+
}
|
|
2136
|
+
/** Fold the recap ladder onto every row (see {@link deriveSessionRecap}). */
|
|
2137
|
+
export function foldRecap(rows) {
|
|
2138
|
+
for (const s of rows) {
|
|
2139
|
+
const recap = deriveSessionRecap(s);
|
|
2140
|
+
s.title = recap.title;
|
|
2141
|
+
s.recapSource = recap.recapSource;
|
|
2142
|
+
s.userPromptClean = recap.userPromptClean;
|
|
2143
|
+
s.userPromptKind = recap.userPromptKind;
|
|
2144
|
+
s.lastAgentLine = recap.lastAgentLine;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* True when a crash-leaked orphan is genuinely DEAD and should be reaped from the
|
|
2149
|
+
* reconnectable set rather than shown as resumable forever (RUSH-3011 / issue #3b).
|
|
2150
|
+
*
|
|
2151
|
+
* The gate is `abandoned` (no transcript write in {@link ABANDONED_STALE_MS}) AND
|
|
2152
|
+
* a dead pid — exactly "past the stale threshold whose pid is gone". A live pid
|
|
2153
|
+
* (an idle-but-unfinished session, the highest-risk state) is NEVER reaped, and
|
|
2154
|
+
* neither is a recently-`closed`/`crashed` session that just exited (still
|
|
2155
|
+
* resumable). `pidAlive` absent (a cloud row or an older peer that can't prove
|
|
2156
|
+
* death) also stays un-reaped — reaping is fail-safe, never a guess.
|
|
2157
|
+
*/
|
|
2158
|
+
export function isReapableOrphan(row) {
|
|
2159
|
+
return row.status === 'abandoned' && row.pidAlive === false;
|
|
2160
|
+
}
|
|
2077
2161
|
/**
|
|
2078
2162
|
* Resolve each teams row's `orchestratorLabel` from the orchestrator's own row,
|
|
2079
2163
|
* when that orchestrator session is itself in the active set (it usually is — the
|
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,
|
|
@@ -2112,8 +2189,10 @@ export function syncLabels(labelMap) {
|
|
|
2112
2189
|
.prepare(`SELECT id, label FROM sessions WHERE id IN (${placeholders})`)
|
|
2113
2190
|
.all(...chunk);
|
|
2114
2191
|
for (const row of rows) {
|
|
2115
|
-
const live = labelMap.get(row.id)
|
|
2116
|
-
|
|
2192
|
+
const live = labelMap.get(row.id)?.trim() || null;
|
|
2193
|
+
// A missing/empty live label means "no refinement yet", not "erase the
|
|
2194
|
+
// generated title or launch handle already stored for this session".
|
|
2195
|
+
if (live && live !== (row.label ?? '')) {
|
|
2117
2196
|
updates.push({ id: row.id, label: live });
|
|
2118
2197
|
}
|
|
2119
2198
|
}
|
|
@@ -2263,6 +2342,8 @@ function rowToMeta(row) {
|
|
|
2263
2342
|
worktreeSlug: row.worktree_slug ?? undefined,
|
|
2264
2343
|
ticketId: row.ticket_id ?? undefined,
|
|
2265
2344
|
spawnedTeam: row.spawned_team ?? undefined,
|
|
2345
|
+
subAgentCount: row.sub_agent_count ?? undefined,
|
|
2346
|
+
backgroundShellCount: row.background_shell_count ?? undefined,
|
|
2266
2347
|
plan: row.plan ?? undefined,
|
|
2267
2348
|
todos: parseJsonColumn(row.todos),
|
|
2268
2349
|
recentDirectoriesTouched: parseJsonColumn(row.recent_directories_touched),
|
|
@@ -2688,6 +2769,59 @@ export function writeSessionInsights(entries) {
|
|
|
2688
2769
|
export function clearSessionInsights() {
|
|
2689
2770
|
getDB().exec(`DELETE FROM session_insights`);
|
|
2690
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
|
+
}
|
|
2691
2825
|
/** Read one derived preview only when it matches the transcript bytes on disk. */
|
|
2692
2826
|
export function readSessionPreviewCache(id, sourceStamp) {
|
|
2693
2827
|
const row = getDB().prepare(`
|
|
@@ -78,6 +78,8 @@ interface ClaudeSessionScan {
|
|
|
78
78
|
version?: string;
|
|
79
79
|
model?: string;
|
|
80
80
|
topic?: string;
|
|
81
|
+
/** Harness-owned session name (`/rename` or Claude's generated `ai-title`). */
|
|
82
|
+
label?: string;
|
|
81
83
|
messageCount: number;
|
|
82
84
|
tokenCount?: number;
|
|
83
85
|
/** Real generated (output) tokens, excluding cache-read/-write context. */
|
|
@@ -120,6 +122,9 @@ interface ClaudeSessionScan {
|
|
|
120
122
|
name: string;
|
|
121
123
|
count: number;
|
|
122
124
|
}>;
|
|
125
|
+
/** Fan-out left behind (RUSH-3091/3095) — see SessionMeta.subAgentCount. */
|
|
126
|
+
subAgentCount?: number;
|
|
127
|
+
backgroundShellCount?: number;
|
|
123
128
|
/** Slash commands invoked (#12) — see SessionMeta.slashCommandsUsed. */
|
|
124
129
|
slashCommandsUsed?: Array<{
|
|
125
130
|
name: string;
|
|
@@ -479,6 +484,14 @@ interface ClaudeParseState {
|
|
|
479
484
|
/** Slash-command events (user-typed <command-name> wrapper OR a SlashCommand
|
|
480
485
|
* tool_use), held for extractSlashCommands() at finalize (#12). */
|
|
481
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;
|
|
482
495
|
}
|
|
483
496
|
/** Zero-value accumulator for a fresh (from-byte-0) Claude parse. */
|
|
484
497
|
export declare function initClaudeParseState(): ClaudeParseState;
|
|
@@ -508,7 +521,16 @@ export declare function scanClaudeSession(filePath: string): Promise<ClaudeSessi
|
|
|
508
521
|
* exact even when the recent window is smaller than the true count.
|
|
509
522
|
*/
|
|
510
523
|
export interface ClaudeParserState {
|
|
511
|
-
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;
|
|
512
534
|
offset: number;
|
|
513
535
|
jsonlDroppingOversizedLine?: boolean;
|
|
514
536
|
timestamp?: string;
|