@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
|
@@ -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';
|
|
@@ -1215,7 +1215,9 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1215
1215
|
accountKey: acct.key,
|
|
1216
1216
|
accountOrg: acct.orgName ?? undefined,
|
|
1217
1217
|
topic: scan.topic,
|
|
1218
|
-
|
|
1218
|
+
// The live sessions metadata file contains only user `/rename` values;
|
|
1219
|
+
// prefer it when present, then fall back to the title parsed from JSONL.
|
|
1220
|
+
label: label || scan.label,
|
|
1219
1221
|
messageCount: scan.messageCount,
|
|
1220
1222
|
toolCallCount: scan.toolCallCount,
|
|
1221
1223
|
tokenCount: scan.tokenCount,
|
|
@@ -1237,6 +1239,8 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1237
1239
|
todos: scan.todos,
|
|
1238
1240
|
recentDirectoriesTouched: scan.recentDirectoriesTouched,
|
|
1239
1241
|
skillsUsed: scan.skillsUsed,
|
|
1242
|
+
subAgentCount: scan.subAgentCount,
|
|
1243
|
+
backgroundShellCount: scan.backgroundShellCount,
|
|
1240
1244
|
slashCommandsUsed: scan.slashCommandsUsed,
|
|
1241
1245
|
};
|
|
1242
1246
|
}
|
|
@@ -1253,7 +1257,9 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1253
1257
|
accountKey: acct.key,
|
|
1254
1258
|
accountOrg: acct.orgName ?? undefined,
|
|
1255
1259
|
model: scan.model,
|
|
1256
|
-
|
|
1260
|
+
// Keep parsed ai-title/custom-title values even when the transcript has
|
|
1261
|
+
// no timestamp and therefore takes this stat-backed fallback path.
|
|
1262
|
+
label: label || scan.label,
|
|
1257
1263
|
messageCount: scan.messageCount,
|
|
1258
1264
|
toolCallCount: scan.toolCallCount,
|
|
1259
1265
|
tokenCount: scan.tokenCount,
|
|
@@ -1276,6 +1282,8 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1276
1282
|
todos: scan.todos,
|
|
1277
1283
|
recentDirectoriesTouched: scan.recentDirectoriesTouched,
|
|
1278
1284
|
skillsUsed: scan.skillsUsed,
|
|
1285
|
+
subAgentCount: scan.subAgentCount,
|
|
1286
|
+
backgroundShellCount: scan.backgroundShellCount,
|
|
1279
1287
|
slashCommandsUsed: scan.slashCommandsUsed,
|
|
1280
1288
|
};
|
|
1281
1289
|
}
|
|
@@ -2992,6 +3000,8 @@ function extractDroidMessageText(content) {
|
|
|
2992
3000
|
/** Zero-value accumulator for a fresh (from-byte-0) Claude parse. */
|
|
2993
3001
|
export function initClaudeParseState() {
|
|
2994
3002
|
return {
|
|
3003
|
+
subAgents: 0,
|
|
3004
|
+
backgroundShells: 0,
|
|
2995
3005
|
timestamp: undefined,
|
|
2996
3006
|
cwd: undefined,
|
|
2997
3007
|
gitBranch: undefined,
|
|
@@ -3040,6 +3050,14 @@ function foldDerivedToolState(state, event) {
|
|
|
3040
3050
|
// from a full re-parse (see session/db.ts's writeResourceUsage doc comment).
|
|
3041
3051
|
if (isSkillInvocation(event))
|
|
3042
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
|
+
}
|
|
3043
3061
|
if (event.tool === 'SlashCommand') {
|
|
3044
3062
|
const slashCommand = extractSlashCommandFromToolInput(event.args);
|
|
3045
3063
|
if (slashCommand)
|
|
@@ -3247,9 +3265,18 @@ export function finalizeClaudeScan(state) {
|
|
|
3247
3265
|
const durationMs = state.firstTsMs !== undefined && state.lastTsMs !== undefined && state.lastTsMs > state.firstTsMs
|
|
3248
3266
|
? state.lastTsMs - state.firstTsMs
|
|
3249
3267
|
: undefined;
|
|
3250
|
-
//
|
|
3251
|
-
//
|
|
3252
|
-
|
|
3268
|
+
// A topic is the first meaningful prompt. Harness-owned names travel in the
|
|
3269
|
+
// separate label field so consumers can replace an early topic once Claude's
|
|
3270
|
+
// generated title (or a later `/rename`) arrives.
|
|
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);
|
|
3253
3280
|
const worktree = detectWorktree(state.cwd, state.gitBranch);
|
|
3254
3281
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3255
3282
|
return {
|
|
@@ -3258,7 +3285,8 @@ export function finalizeClaudeScan(state) {
|
|
|
3258
3285
|
gitBranch: state.gitBranch,
|
|
3259
3286
|
version: state.version,
|
|
3260
3287
|
model: state.model,
|
|
3261
|
-
topic:
|
|
3288
|
+
topic: state.topic,
|
|
3289
|
+
label,
|
|
3262
3290
|
entrypoint: state.entrypoint,
|
|
3263
3291
|
messageCount: state.messageCount,
|
|
3264
3292
|
toolCallCount: state.toolCallCount,
|
|
@@ -3283,6 +3311,8 @@ export function finalizeClaudeScan(state) {
|
|
|
3283
3311
|
recentDirectoriesTouched: state.recentDirectoriesTouched.length ? state.recentDirectoriesTouched : undefined,
|
|
3284
3312
|
skillsUsed: state.skillEvents.length ? extractSkills(state.skillEvents) : undefined,
|
|
3285
3313
|
slashCommandsUsed: state.slashCommandEvents.length ? extractSlashCommands(state.slashCommandEvents) : undefined,
|
|
3314
|
+
subAgentCount: state.subAgents,
|
|
3315
|
+
backgroundShellCount: state.backgroundShells,
|
|
3286
3316
|
};
|
|
3287
3317
|
}
|
|
3288
3318
|
/** Stream a Claude JSONL file and extract scan-level metadata (timestamp, cwd, topic, tokens). */
|
|
@@ -3324,7 +3354,9 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3324
3354
|
: allIds;
|
|
3325
3355
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3326
3356
|
return {
|
|
3327
|
-
v:
|
|
3357
|
+
v: 4,
|
|
3358
|
+
subAgents: state.subAgents,
|
|
3359
|
+
backgroundShells: state.backgroundShells,
|
|
3328
3360
|
offset,
|
|
3329
3361
|
jsonlDroppingOversizedLine: jsonlDroppingOversizedLine || undefined,
|
|
3330
3362
|
timestamp: state.timestamp,
|
|
@@ -3396,6 +3428,8 @@ export function hydrateClaudeParseState(prior) {
|
|
|
3396
3428
|
seen.add(`pad:${pad++}`);
|
|
3397
3429
|
}
|
|
3398
3430
|
return {
|
|
3431
|
+
subAgents: prior.subAgents,
|
|
3432
|
+
backgroundShells: prior.backgroundShells,
|
|
3399
3433
|
timestamp: prior.timestamp,
|
|
3400
3434
|
cwd: prior.cwd,
|
|
3401
3435
|
gitBranch: prior.gitBranch,
|
|
@@ -3563,7 +3597,7 @@ function parsePriorClaudeState(row) {
|
|
|
3563
3597
|
return null;
|
|
3564
3598
|
try {
|
|
3565
3599
|
const parsed = JSON.parse(row.parserState);
|
|
3566
|
-
if (parsed?.v !==
|
|
3600
|
+
if (parsed?.v !== 4 || typeof parsed.offset !== 'number' || parsed.toolCalls?.v !== 1)
|
|
3567
3601
|
return null;
|
|
3568
3602
|
return parsed;
|
|
3569
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
|
}
|
|
@@ -11,7 +11,7 @@ import { sanitizeForTerminal } from '../redact.js';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import Database from '../sqlite.js';
|
|
13
13
|
import { isSyntheticUserMessage, extractSlashCommandName, extractSlashCommandFromToolInput } from './prompt.js';
|
|
14
|
-
import { structuredToolResult } from './tool-calls.js';
|
|
14
|
+
import { structuredToolResult, commandsFromCodexExec } from './tool-calls.js';
|
|
15
15
|
/**
|
|
16
16
|
* Largest session file we will load into memory. Above this we throw a clean
|
|
17
17
|
* error instead of OOMing or hitting V8's ERR_STRING_TOO_LONG. Aligns with
|
|
@@ -270,6 +270,15 @@ export function summarizeToolUse(tool, args) {
|
|
|
270
270
|
// Codex tools
|
|
271
271
|
case 'exec_command':
|
|
272
272
|
return `Bash: ${truncate(String(args.command || args.cmd || '').replace(/\n/g, ' ').trim(), 120)}`;
|
|
273
|
+
// Newer Codex JS-cell shell wrapper: prefer the unwrapped command (set at parse
|
|
274
|
+
// time), else the extracted command, else the raw cell code.
|
|
275
|
+
case 'exec': {
|
|
276
|
+
const cmd = args.command || codexExecCommand(String(args.input || ''));
|
|
277
|
+
if (cmd)
|
|
278
|
+
return `Bash: ${truncate(String(cmd).replace(/\n/g, ' ').trim(), 120)}`;
|
|
279
|
+
const code = String(args.input || '').replace(/\n/g, ' ').trim();
|
|
280
|
+
return code ? `exec: ${truncate(code, 100)}` : 'exec';
|
|
281
|
+
}
|
|
273
282
|
case 'read_file':
|
|
274
283
|
return `Read ${shortenPath(args.file_path || args.path || '')}`;
|
|
275
284
|
case 'write_file':
|
|
@@ -573,6 +582,23 @@ export function applyPatchTargetPaths(input) {
|
|
|
573
582
|
function applyPatchTargetPath(input) {
|
|
574
583
|
return applyPatchTargetPaths(input)[0];
|
|
575
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Newer Codex (gpt-5.6-sol / codex >=~0.145) runs every shell command inside a JS
|
|
587
|
+
* cell: a `custom_tool_call` named `exec` whose `input` is code like
|
|
588
|
+
* `const r = await tools.exec_command({cmd:"git status", "workdir":"…"});`. The real
|
|
589
|
+
* shell command(s) are buried in those `cmd:"…"` literals. Reuse the canonical
|
|
590
|
+
* acorn-based extractor (`commandsFromCodexExec` in `tool-calls.ts`, already used by
|
|
591
|
+
* the tool-call index): it walks the whole AST — so a cell that runs several via
|
|
592
|
+
* `Promise.all([tools.exec_command(...), tools.exec_command(...)])` yields every
|
|
593
|
+
* command, not just the first — and matches only real `tools.exec_command` calls,
|
|
594
|
+
* so a `cmd:"…"` string mentioned in a comment or docstring is never mistaken for an
|
|
595
|
+
* invocation. Returns `undefined` for a non-shell cell (`tools.view_image(...)`, a
|
|
596
|
+
* raw JS computation) so it stays labeled by its code, not faked into a shell step.
|
|
597
|
+
*/
|
|
598
|
+
function codexExecCommand(input) {
|
|
599
|
+
const commands = commandsFromCodexExec(input);
|
|
600
|
+
return commands.length > 0 ? commands.join('\n') : undefined;
|
|
601
|
+
}
|
|
576
602
|
/**
|
|
577
603
|
* Parse Codex JSONL *content* (already read into a string) into normalized
|
|
578
604
|
* events. Split from `parseCodex` so the tail reader can parse just the last
|
|
@@ -706,6 +732,9 @@ export function parseCodexContent(content) {
|
|
|
706
732
|
const rawName = payload.name || 'unknown';
|
|
707
733
|
const input = typeof payload.input === 'string' ? payload.input : '';
|
|
708
734
|
const isApplyPatch = rawName === 'apply_patch';
|
|
735
|
+
// Newer Codex wraps shell in a JS `exec` cell — unwrap the real command so
|
|
736
|
+
// the step reads by its program instead of a bare "exec" (see extractor).
|
|
737
|
+
const execCommand = rawName === 'exec' ? codexExecCommand(input) : undefined;
|
|
709
738
|
// Multi-file patches: one tool_use per file so artifact discovery sees
|
|
710
739
|
// every path (RUSH-1410). Single-file / non-patch keep one event.
|
|
711
740
|
const patchPaths = isApplyPatch ? applyPatchTargetPaths(input) : [];
|
|
@@ -715,6 +744,8 @@ export function parseCodexContent(content) {
|
|
|
715
744
|
const args = { input: truncatedInput };
|
|
716
745
|
if (patchPath)
|
|
717
746
|
args.file_path = patchPath;
|
|
747
|
+
if (execCommand)
|
|
748
|
+
args.command = execCommand;
|
|
718
749
|
const callId = payload.call_id || payload.id;
|
|
719
750
|
if (callId)
|
|
720
751
|
callMap.set(callId, { name: tool, args });
|
|
@@ -725,6 +756,7 @@ export function parseCodexContent(content) {
|
|
|
725
756
|
tool,
|
|
726
757
|
callId,
|
|
727
758
|
args,
|
|
759
|
+
command: execCommand,
|
|
728
760
|
path: patchPath,
|
|
729
761
|
});
|
|
730
762
|
};
|
|
@@ -34,5 +34,40 @@ export declare function extractSlashCommandFromToolInput(toolInput: Record<strin
|
|
|
34
34
|
export declare const HEADLESS_PLAN_MODE_PREFIX = "You are running in HEADLESS PLAN MODE.";
|
|
35
35
|
/** Strip framework noise from a raw session prompt, returning only meaningful user text. */
|
|
36
36
|
export declare function cleanSessionPrompt(raw: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* How a session's first user turn reads once its framework noise is stripped —
|
|
39
|
+
* so a UI row can show `[image]` / `$ cmd` / `/skill` instead of a screenshot
|
|
40
|
+
* path, a pasted shell line, or a skill's install directory.
|
|
41
|
+
*/
|
|
42
|
+
export type UserPromptKind = 'text' | 'image' | 'command' | 'skill';
|
|
43
|
+
export interface ClassifiedPrompt {
|
|
44
|
+
/**
|
|
45
|
+
* The one-line, display-ready form of the prompt — never a bare path. NOT
|
|
46
|
+
* length-capped: the recap card shows it in full; row consumers cap it
|
|
47
|
+
* themselves (`deriveSessionRecap`).
|
|
48
|
+
*/
|
|
49
|
+
clean: string;
|
|
50
|
+
kind: UserPromptKind;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Classify a raw first user turn into a display-ready `{ clean, kind }`, so a
|
|
54
|
+
* Fleet/session row shows the user's actual intent rather than path noise:
|
|
55
|
+
*
|
|
56
|
+
* - `skill` — a `/skill` invocation's injected "Base directory for this
|
|
57
|
+
* skill: …" line collapses to `/<name>`.
|
|
58
|
+
* - `command` — a `$ `-prefixed shell paste keeps only the first command.
|
|
59
|
+
* - `image` — a screenshot/image path standing alone, or an image
|
|
60
|
+
* attachment with no real accompanying text, becomes `[image]`.
|
|
61
|
+
* - `text` — everything else: wrapper tags stripped (via
|
|
62
|
+
* {@link cleanSessionPrompt}) and any inline image path replaced
|
|
63
|
+
* with `[image]` so a mixed prompt still drops the path.
|
|
64
|
+
*
|
|
65
|
+
* Pure and cross-harness (text markers, no per-agent branch). `hasImageAttachment`
|
|
66
|
+
* lets a caller that only has an already-extracted topic line still detect an
|
|
67
|
+
* image-only turn from the row's attachment list.
|
|
68
|
+
*/
|
|
69
|
+
export declare function classifyUserPrompt(raw: string | undefined, opts?: {
|
|
70
|
+
hasImageAttachment?: boolean;
|
|
71
|
+
}): ClassifiedPrompt;
|
|
37
72
|
/** Extract a one-line topic from a raw user message, or undefined if the message is pure noise. */
|
|
38
73
|
export declare function extractSessionTopic(raw: string): string | undefined;
|
|
@@ -145,6 +145,62 @@ export function cleanSessionPrompt(raw) {
|
|
|
145
145
|
.filter(line => !NOISE_LINE_PATTERNS.some(pattern => pattern.test(line)));
|
|
146
146
|
return meaningful.join('\n').trim();
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* A skill invocation injects a system line naming the skill's install directory:
|
|
150
|
+
* "Base directory for this skill: /home/.../skills/<name>"
|
|
151
|
+
* which collapses to "/<name>" so a row shows the skill, not the path. Anchored
|
|
152
|
+
* to that exact injected line — a bare `.../skills/<name>` mention in ordinary
|
|
153
|
+
* prose is NOT a skill invocation and must keep its real text.
|
|
154
|
+
*/
|
|
155
|
+
const SKILL_BASEDIR_RE = /Base directory for this skill:\s*\S*[\/\\]skills[\/\\]([\w.-]+)/i;
|
|
156
|
+
/**
|
|
157
|
+
* An explicit shell-prompt paste (`$ cmd`). Only the command survives. Deliberately
|
|
158
|
+
* `$`-only: a leading `>` is a markdown blockquote far more often than a shell
|
|
159
|
+
* prompt, so matching it misread quoted prose as a command.
|
|
160
|
+
*/
|
|
161
|
+
const COMMAND_PREFIX_RE = /^\$\s+(\S.*)$/;
|
|
162
|
+
/**
|
|
163
|
+
* A filesystem path (absolute or `~`-rooted) ending in an image extension —
|
|
164
|
+
* matched even across the spaces a CleanShot filename carries. Used both to
|
|
165
|
+
* detect an image-only prompt and to drop the path from a mixed one.
|
|
166
|
+
*/
|
|
167
|
+
const IMAGE_PATH_RE = /[\/~][^\n]*?\.(?:png|jpe?g|gif|webp|heic|bmp|svg)\b/i;
|
|
168
|
+
/**
|
|
169
|
+
* Classify a raw first user turn into a display-ready `{ clean, kind }`, so a
|
|
170
|
+
* Fleet/session row shows the user's actual intent rather than path noise:
|
|
171
|
+
*
|
|
172
|
+
* - `skill` — a `/skill` invocation's injected "Base directory for this
|
|
173
|
+
* skill: …" line collapses to `/<name>`.
|
|
174
|
+
* - `command` — a `$ `-prefixed shell paste keeps only the first command.
|
|
175
|
+
* - `image` — a screenshot/image path standing alone, or an image
|
|
176
|
+
* attachment with no real accompanying text, becomes `[image]`.
|
|
177
|
+
* - `text` — everything else: wrapper tags stripped (via
|
|
178
|
+
* {@link cleanSessionPrompt}) and any inline image path replaced
|
|
179
|
+
* with `[image]` so a mixed prompt still drops the path.
|
|
180
|
+
*
|
|
181
|
+
* Pure and cross-harness (text markers, no per-agent branch). `hasImageAttachment`
|
|
182
|
+
* lets a caller that only has an already-extracted topic line still detect an
|
|
183
|
+
* image-only turn from the row's attachment list.
|
|
184
|
+
*/
|
|
185
|
+
export function classifyUserPrompt(raw, opts = {}) {
|
|
186
|
+
const text = (raw ?? '').replace(/\r/g, '').trim();
|
|
187
|
+
const skill = text.match(SKILL_BASEDIR_RE);
|
|
188
|
+
if (skill)
|
|
189
|
+
return { clean: `/${skill[1]}`, kind: 'skill' };
|
|
190
|
+
const firstLine = text.split('\n').map(line => line.trim()).find(Boolean) ?? '';
|
|
191
|
+
const cmd = firstLine.match(COMMAND_PREFIX_RE);
|
|
192
|
+
if (cmd)
|
|
193
|
+
return { clean: `$ ${cmd[1]}`.trim(), kind: 'command' };
|
|
194
|
+
const cleaned = cleanSessionPrompt(text);
|
|
195
|
+
const cleanedFirst = cleaned.split('\n').map(line => line.trim()).find(Boolean) ?? '';
|
|
196
|
+
const imgMatch = firstLine.match(IMAGE_PATH_RE);
|
|
197
|
+
const imageOnly = !!imgMatch && firstLine.replace(imgMatch[0], '').replace(/[^\w]/g, '').length <= 8;
|
|
198
|
+
if (imageOnly || (opts.hasImageAttachment && !cleanedFirst)) {
|
|
199
|
+
return { clean: '[image]', kind: 'image' };
|
|
200
|
+
}
|
|
201
|
+
const display = (cleanedFirst || firstLine).replace(IMAGE_PATH_RE, '[image]').trim();
|
|
202
|
+
return { clean: display, kind: 'text' };
|
|
203
|
+
}
|
|
148
204
|
/** Extract a one-line topic from a raw user message, or undefined if the message is pure noise. */
|
|
149
205
|
export function extractSessionTopic(raw) {
|
|
150
206
|
if (!raw.trim())
|
|
@@ -141,8 +141,17 @@ export declare function fetchPeerPreviewDigest(sessionId: string, machine: strin
|
|
|
141
141
|
* that would inherit it for its whole life — `agents browser start` inside it
|
|
142
142
|
* would then be refused as a cross-machine drive. A one-shot `--device` command
|
|
143
143
|
* can carry the marker; a session cannot.
|
|
144
|
+
*
|
|
145
|
+
* `opts.sessionId` (with `tty`) prints the session id and resume command when
|
|
146
|
+
* the SSH hop ends, so OpenSSH's `Shared connection … closed.` is not the last
|
|
147
|
+
* thing on the local shell (RUSH-3227). Omit it for one-shot non-TTY renders.
|
|
144
148
|
*/
|
|
149
|
+
export declare function peerHopCloseNotice(opts: {
|
|
150
|
+
tty?: boolean;
|
|
151
|
+
sessionId?: string;
|
|
152
|
+
}, machine: string, code: number | null): string | undefined;
|
|
145
153
|
export declare function runOnPeer(args: string[], machine: string, opts?: {
|
|
146
154
|
tty?: boolean;
|
|
147
155
|
env?: Record<string, string>;
|
|
156
|
+
sessionId?: string;
|
|
148
157
|
}): Promise<'ok' | 'no-target'>;
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { spawn } from 'child_process';
|
|
17
17
|
import chalk from 'chalk';
|
|
18
|
-
import { SSH_OPTS, controlOpts, assertValidSshTarget, shellQuote, REMOTE_STDOUT_MAX_BYTES, RemoteUtf8Accumulator, } from '../../ssh-exec.js';
|
|
18
|
+
import { SSH_OPTS, controlOpts, assertValidSshTarget, shellQuote, SSH_CONN_FAILURE_CODE, REMOTE_STDOUT_MAX_BYTES, RemoteUtf8Accumulator, } from '../../ssh-exec.js';
|
|
19
|
+
import { connectionEndedNotice } from '../../hosts/reconnect.js';
|
|
19
20
|
import { sshTargetFor } from '../../devices/connect.js';
|
|
20
21
|
import { resolveExplicitTargetSet } from '../../devices/resolve-target.js';
|
|
21
22
|
import { loadDevices, isDialableDevice } from '../../devices/registry.js';
|
|
@@ -577,7 +578,16 @@ export async function fetchPeerPreviewDigest(sessionId, machine, timeoutMs = PEE
|
|
|
577
578
|
* that would inherit it for its whole life — `agents browser start` inside it
|
|
578
579
|
* would then be refused as a cross-machine drive. A one-shot `--device` command
|
|
579
580
|
* can carry the marker; a session cannot.
|
|
581
|
+
*
|
|
582
|
+
* `opts.sessionId` (with `tty`) prints the session id and resume command when
|
|
583
|
+
* the SSH hop ends, so OpenSSH's `Shared connection … closed.` is not the last
|
|
584
|
+
* thing on the local shell (RUSH-3227). Omit it for one-shot non-TTY renders.
|
|
580
585
|
*/
|
|
586
|
+
export function peerHopCloseNotice(opts, machine, code) {
|
|
587
|
+
if (!opts.tty || !opts.sessionId)
|
|
588
|
+
return undefined;
|
|
589
|
+
return connectionEndedNotice({ kind: 'session', id: opts.sessionId }, machine, { dropped: code === SSH_CONN_FAILURE_CODE });
|
|
590
|
+
}
|
|
581
591
|
export async function runOnPeer(args, machine, opts = {}) {
|
|
582
592
|
const peer = await resolvePeerTarget(machine);
|
|
583
593
|
if (!peer)
|
|
@@ -602,6 +612,13 @@ export async function runOnPeer(args, machine, opts = {}) {
|
|
|
602
612
|
process.stderr.write(chalk.red(`Failed to reach ${machine}: ${err?.message ?? 'ssh failed to launch'}\n`));
|
|
603
613
|
resolve('ok');
|
|
604
614
|
});
|
|
605
|
-
child.on('close', () =>
|
|
615
|
+
child.on('close', (code) => {
|
|
616
|
+
// Interactive TTY hop: OpenSSH prints "Shared connection … closed." and
|
|
617
|
+
// nothing else. Leave the session id on the local shell (RUSH-3227).
|
|
618
|
+
const notice = peerHopCloseNotice(opts, machine, code);
|
|
619
|
+
if (notice)
|
|
620
|
+
process.stderr.write(notice);
|
|
621
|
+
resolve('ok');
|
|
622
|
+
});
|
|
606
623
|
});
|
|
607
624
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ActiveSession } from '../active.js';
|
|
2
2
|
import { readActiveSessionsCache } from '../session-cache.js';
|
|
3
3
|
export declare const SESSION_WATCH_VERSION: 1;
|
|
4
4
|
export declare const SESSION_WATCH_HEARTBEAT_MS = 15000;
|
|
@@ -8,7 +8,8 @@ import { deviceIdentityArgs, sshTargetFor } from '../../devices/connect.js';
|
|
|
8
8
|
import { machineId, normalizeHost } from '../../machine-id.js';
|
|
9
9
|
import { SSH_OPTS, controlOpts, shellQuote } from '../../ssh-exec.js';
|
|
10
10
|
import { buildWindowsAgentsCommand, remoteShellFor } from '../../hosts/remote-cmd.js';
|
|
11
|
-
import {
|
|
11
|
+
import { isReapableOrphan } from '../active.js';
|
|
12
|
+
import { activeSessionsJournalPath, activeSessionJournalIdentity, readActiveSessionsCache, noteActiveSessionsJournalReader, } from '../session-cache.js';
|
|
12
13
|
export const SESSION_WATCH_VERSION = 1;
|
|
13
14
|
export const SESSION_WATCH_HEARTBEAT_MS = 15_000;
|
|
14
15
|
/** Stable, opaque identity for one row within one device scope. */
|
|
@@ -18,7 +19,11 @@ export function sessionWatchRowKey(scope, row) {
|
|
|
18
19
|
}
|
|
19
20
|
export function toSessionWatchRow(scope, row) {
|
|
20
21
|
const rowKey = sessionWatchRowKey(scope, row);
|
|
21
|
-
|
|
22
|
+
// A dead, days-stale crash-orphan is folded OUT of the reconnectable set — it
|
|
23
|
+
// is not resumable, so the "Needs reconnecting" list stops ballooning with
|
|
24
|
+
// leaked --device tunnel sessions (RUSH-3011 / issue #3b). A live or
|
|
25
|
+
// recently-exited session stays resumable.
|
|
26
|
+
const resumable = Boolean(row.sessionId) && !isReapableOrphan(row);
|
|
22
27
|
const viewingIn = row.viewingIn
|
|
23
28
|
? [row.viewingIn.app, row.viewingIn.tab ? `tab ${row.viewingIn.tab}` : undefined].filter(Boolean).join(' ')
|
|
24
29
|
: null;
|
|
@@ -111,9 +116,15 @@ export async function watchLocalSessions(options) {
|
|
|
111
116
|
catch { /* first publication */ }
|
|
112
117
|
const initial = readCache('local');
|
|
113
118
|
options.emit(state.reset(options.scope, initial?.sessions ?? []));
|
|
119
|
+
// Known gap (out of scope for RUSH-2484): a present cache alone marks
|
|
120
|
+
// 'available' with no staleness/age check, so a reconnect can briefly render
|
|
121
|
+
// a stale snapshot as live before the out-of-band gather this file triggers
|
|
122
|
+
// (see watchActiveSessionsReaderPresence in session-cache.ts) replaces it.
|
|
114
123
|
options.emit(state.scope(options.scope, initial ? 'available' : 'unavailable', initial ? undefined : 'awaiting publisher'));
|
|
115
124
|
if (options.signal.aborted)
|
|
116
125
|
return;
|
|
126
|
+
// Signal the daemon that a consumer is live so it does not skip the gather.
|
|
127
|
+
noteActiveSessionsJournalReader();
|
|
117
128
|
await new Promise((resolve) => {
|
|
118
129
|
let partial = '';
|
|
119
130
|
let reading = false;
|
|
@@ -162,7 +173,10 @@ export async function watchLocalSessions(options) {
|
|
|
162
173
|
fs.mkdirSync(path.dirname(journal), { recursive: true });
|
|
163
174
|
const journalListener = () => readAppended();
|
|
164
175
|
fs.watchFile(journal, { interval: options.journalPollMs ?? 250 }, journalListener);
|
|
165
|
-
const heartbeatTimer = setInterval(() =>
|
|
176
|
+
const heartbeatTimer = setInterval(() => {
|
|
177
|
+
noteActiveSessionsJournalReader();
|
|
178
|
+
options.emit(state.heartbeat(options.scope));
|
|
179
|
+
}, heartbeatMs);
|
|
166
180
|
const stop = () => { fs.unwatchFile(journal, journalListener); clearInterval(heartbeatTimer); resolve(); };
|
|
167
181
|
options.signal.addEventListener('abort', stop, { once: true });
|
|
168
182
|
// Close the offset/read/watch handoff: a publisher may append after the
|