@phnx-labs/agents-cli 1.22.119 → 1.22.121
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 +236 -0
- package/README.md +19 -2
- package/dist/bootstrap.d.ts +0 -15
- package/dist/bootstrap.js +10 -409
- package/dist/cli/command-registry.d.ts +0 -58
- package/dist/cli/command-registry.js +2 -74
- package/dist/commands/accounts.d.ts +0 -17
- package/dist/commands/accounts.js +1 -52
- package/dist/commands/alias.d.ts +0 -12
- package/dist/commands/alias.js +0 -6
- package/dist/commands/apply.d.ts +0 -15
- package/dist/commands/apply.js +0 -63
- package/dist/commands/audit.d.ts +0 -12
- package/dist/commands/audit.js +0 -13
- package/dist/commands/auth.js +4 -12
- package/dist/commands/beta.d.ts +0 -6
- package/dist/commands/beta.js +2 -6
- package/dist/commands/browser-sessions-picker.d.ts +0 -8
- package/dist/commands/browser-sessions-picker.js +0 -26
- package/dist/commands/browser.d.ts +0 -57
- package/dist/commands/browser.js +0 -80
- package/dist/commands/budget.d.ts +0 -13
- package/dist/commands/budget.js +0 -2
- package/dist/commands/cli.d.ts +0 -12
- package/dist/commands/cli.js +0 -4
- package/dist/commands/cloud.d.ts +0 -9
- package/dist/commands/cloud.js +0 -43
- package/dist/commands/commands.d.ts +0 -9
- package/dist/commands/commands.js +0 -18
- package/dist/commands/computer-sessions-picker.d.ts +0 -11
- package/dist/commands/computer-sessions-picker.js +0 -29
- package/dist/commands/computer.d.ts +0 -62
- package/dist/commands/computer.js +3 -114
- package/dist/commands/config.d.ts +0 -14
- package/dist/commands/config.js +21 -60
- package/dist/commands/cost.d.ts +0 -11
- package/dist/commands/cost.js +1 -17
- package/dist/commands/daemon-test-harness.d.ts +0 -32
- package/dist/commands/daemon-test-harness.js +1 -36
- package/dist/commands/daemon.d.ts +0 -46
- package/dist/commands/daemon.js +8 -255
- package/dist/commands/detach-core.d.ts +0 -28
- package/dist/commands/detach-core.js +0 -30
- package/dist/commands/detach.d.ts +1 -12
- package/dist/commands/detach.js +16 -52
- package/dist/commands/device-config-test-harness.js +0 -12
- package/dist/commands/doctor.d.ts +0 -103
- package/dist/commands/doctor.js +3 -279
- package/dist/commands/events.d.ts +0 -29
- package/dist/commands/events.js +1 -55
- package/dist/commands/exec.d.ts +0 -163
- package/dist/commands/exec.js +15 -964
- package/dist/commands/factory.d.ts +0 -6
- package/dist/commands/factory.js +0 -2
- package/dist/commands/feed-watch.js +0 -20
- package/dist/commands/feed.d.ts +0 -38
- package/dist/commands/feed.js +1 -151
- package/dist/commands/feedback.d.ts +0 -5
- package/dist/commands/feedback.js +0 -5
- package/dist/commands/fleet-capture.d.ts +0 -12
- package/dist/commands/fleet-capture.js +1 -39
- package/dist/commands/focus.d.ts +6 -94
- package/dist/commands/focus.js +13 -183
- package/dist/commands/fork.d.ts +0 -21
- package/dist/commands/fork.js +0 -45
- package/dist/commands/funnel.d.ts +0 -3
- package/dist/commands/go.d.ts +2 -71
- package/dist/commands/go.js +5 -99
- package/dist/commands/harness-hooks.d.ts +0 -41
- package/dist/commands/harness-hooks.js +0 -48
- package/dist/commands/harness-wizard.d.ts +0 -137
- package/dist/commands/harness-wizard.js +0 -109
- package/dist/commands/harness.d.ts +0 -63
- package/dist/commands/harness.js +0 -103
- package/dist/commands/hooks.d.ts +0 -9
- package/dist/commands/hooks.js +0 -26
- package/dist/commands/humans.d.ts +0 -8
- package/dist/commands/humans.js +0 -8
- package/dist/commands/import.d.ts +0 -22
- package/dist/commands/import.js +0 -79
- package/dist/commands/insights.d.ts +0 -37
- package/dist/commands/insights.js +3 -115
- package/dist/commands/inspect.d.ts +0 -117
- package/dist/commands/inspect.js +2 -448
- package/dist/commands/lease.d.ts +0 -28
- package/dist/commands/lease.js +0 -43
- package/dist/commands/logs.d.ts +0 -23
- package/dist/commands/logs.js +0 -30
- package/dist/commands/mailboxes.d.ts +0 -18
- package/dist/commands/mailboxes.js +0 -29
- package/dist/commands/mcp.d.ts +0 -9
- package/dist/commands/mcp.js +0 -53
- package/dist/commands/memory.d.ts +0 -7
- package/dist/commands/memory.js +0 -7
- package/dist/commands/menubar.d.ts +0 -8
- package/dist/commands/menubar.js +0 -24
- package/dist/commands/message.d.ts +0 -23
- package/dist/commands/message.js +0 -50
- package/dist/commands/mine.d.ts +0 -22
- package/dist/commands/mine.js +0 -21
- package/dist/commands/models.d.ts +0 -8
- package/dist/commands/models.js +0 -33
- package/dist/commands/modes.d.ts +0 -10
- package/dist/commands/modes.js +0 -13
- package/dist/commands/monitors.d.ts +0 -9
- package/dist/commands/monitors.js +3 -160
- package/dist/commands/open.d.ts +0 -26
- package/dist/commands/open.js +0 -10
- package/dist/commands/output.d.ts +0 -18
- package/dist/commands/output.js +1 -34
- package/dist/commands/packages-materialize.d.ts +0 -11
- package/dist/commands/packages-materialize.js +0 -3
- package/dist/commands/packages.d.ts +0 -10
- package/dist/commands/packages.js +0 -50
- package/dist/commands/perf.d.ts +0 -41
- package/dist/commands/perf.js +0 -55
- package/dist/commands/permissions.d.ts +0 -9
- package/dist/commands/permissions.js +0 -43
- package/dist/commands/plugins.d.ts +0 -14
- package/dist/commands/plugins.js +2 -55
- package/dist/commands/profiles.d.ts +0 -25
- package/dist/commands/profiles.js +0 -27
- package/dist/commands/projects-todo.d.ts +2 -0
- package/dist/commands/projects-todo.js +132 -0
- package/dist/commands/projects.d.ts +0 -85
- package/dist/commands/projects.js +344 -291
- package/dist/commands/prune.js +0 -30
- package/dist/commands/ps-roster.d.ts +92 -0
- package/dist/commands/ps-roster.js +744 -0
- package/dist/commands/ps.d.ts +6 -0
- package/dist/commands/ps.js +98 -0
- package/dist/commands/refresh-rules.d.ts +0 -13
- package/dist/commands/refresh-rules.js +0 -14
- package/dist/commands/reminders.d.ts +0 -7
- package/dist/commands/repo.d.ts +0 -89
- package/dist/commands/repo.js +7 -205
- package/dist/commands/resource-view.d.ts +0 -33
- package/dist/commands/resource-view.js +0 -49
- package/dist/commands/resume.d.ts +1 -19
- package/dist/commands/resume.js +0 -50
- package/dist/commands/route.d.ts +0 -10
- package/dist/commands/route.js +0 -19
- package/dist/commands/routines.d.ts +0 -42
- package/dist/commands/routines.js +5 -290
- package/dist/commands/routines.test-fixture.d.ts +0 -34
- package/dist/commands/routines.test-fixture.js +4 -100
- package/dist/commands/rules.d.ts +0 -9
- package/dist/commands/rules.js +1 -6
- package/dist/commands/run-account-picker.d.ts +0 -59
- package/dist/commands/run-account-picker.js +0 -71
- package/dist/commands/run-broadcast.d.ts +0 -9
- package/dist/commands/run-broadcast.js +0 -7
- package/dist/commands/run-cloud.d.ts +0 -15
- package/dist/commands/run-cloud.js +0 -37
- package/dist/commands/run-device-picker.d.ts +0 -25
- package/dist/commands/run-device-picker.js +1 -53
- package/dist/commands/secrets-passthrough.d.ts +0 -31
- package/dist/commands/secrets-passthrough.js +0 -10
- package/dist/commands/send.d.ts +0 -20
- package/dist/commands/send.js +14 -18
- package/dist/commands/sessions-backfill.d.ts +0 -7
- package/dist/commands/sessions-backfill.js +1 -16
- package/dist/commands/sessions-backup-setup.js +0 -11
- package/dist/commands/sessions-bookmark.d.ts +0 -8
- package/dist/commands/sessions-bookmark.js +0 -28
- package/dist/commands/sessions-browser.d.ts +2 -117
- package/dist/commands/sessions-browser.js +5 -243
- package/dist/commands/sessions-export.d.ts +1 -26
- package/dist/commands/sessions-export.js +3 -81
- package/dist/commands/sessions-import.d.ts +0 -14
- package/dist/commands/sessions-import.js +0 -59
- package/dist/commands/sessions-inject.d.ts +0 -60
- package/dist/commands/sessions-inject.js +3 -96
- package/dist/commands/sessions-migrate.d.ts +2 -37
- package/dist/commands/sessions-migrate.js +7 -159
- package/dist/commands/sessions-optimize.d.ts +0 -12
- package/dist/commands/sessions-picker-factory.d.ts +0 -7
- package/dist/commands/sessions-picker-factory.js +0 -8
- package/dist/commands/sessions-picker.d.ts +4 -103
- package/dist/commands/sessions-picker.js +13 -352
- package/dist/commands/sessions-render.d.ts +1 -2
- package/dist/commands/sessions-render.js +3 -7
- package/dist/commands/sessions-resume.d.ts +1 -24
- package/dist/commands/sessions-resume.js +2 -65
- package/dist/commands/sessions-share.d.ts +1 -9
- package/dist/commands/sessions-share.js +1 -45
- package/dist/commands/sessions-stats.d.ts +0 -24
- package/dist/commands/sessions-stats.js +0 -49
- package/dist/commands/sessions-stop.d.ts +1 -20
- package/dist/commands/sessions-stop.js +4 -9
- package/dist/commands/sessions-tail.d.ts +1 -19
- package/dist/commands/sessions-tail.js +3 -36
- package/dist/commands/sessions-trace.d.ts +5 -36
- package/dist/commands/sessions-trace.js +7 -82
- package/dist/commands/sessions.d.ts +1 -666
- package/dist/commands/sessions.js +30 -2279
- package/dist/commands/sessions.test-fixture.d.ts +0 -9
- package/dist/commands/sessions.test-fixture.js +8 -23
- package/dist/commands/setup-accounts.d.ts +0 -6
- package/dist/commands/setup-browser.d.ts +0 -23
- package/dist/commands/setup-browser.js +0 -28
- package/dist/commands/setup-computer.d.ts +0 -16
- package/dist/commands/setup-computer.js +0 -28
- package/dist/commands/setup-fleet.d.ts +0 -8
- package/dist/commands/setup-fleet.js +0 -11
- package/dist/commands/setup-mine.d.ts +0 -14
- package/dist/commands/setup-mine.js +0 -25
- package/dist/commands/setup-preferences.d.ts +0 -47
- package/dist/commands/setup-preferences.js +0 -53
- package/dist/commands/setup-secrets.d.ts +2 -24
- package/dist/commands/setup-secrets.js +0 -24
- package/dist/commands/setup-term.d.ts +0 -21
- package/dist/commands/setup-term.js +0 -21
- package/dist/commands/setup-terminal.d.ts +0 -1
- package/dist/commands/setup-terminal.js +0 -1
- package/dist/commands/setup-tools.d.ts +2 -0
- package/dist/commands/setup-tools.js +73 -0
- package/dist/commands/setup.d.ts +0 -20
- package/dist/commands/setup.js +4 -57
- package/dist/commands/skills.d.ts +0 -9
- package/dist/commands/skills.js +1 -22
- package/dist/commands/snapshot.d.ts +0 -9
- package/dist/commands/snapshot.js +0 -10
- package/dist/commands/ssh.d.ts +0 -64
- package/dist/commands/ssh.js +9 -420
- package/dist/commands/status.d.ts +0 -12
- package/dist/commands/status.js +0 -26
- package/dist/commands/subagents.d.ts +0 -8
- package/dist/commands/subagents.js +0 -31
- package/dist/commands/sync.d.ts +0 -37
- package/dist/commands/sync.js +0 -256
- package/dist/commands/teams-picker.d.ts +0 -3
- package/dist/commands/teams-picker.js +0 -34
- package/dist/commands/teams.d.ts +1 -76
- package/dist/commands/teams.js +9 -477
- package/dist/commands/tmux.d.ts +0 -25
- package/dist/commands/tmux.js +0 -46
- package/dist/commands/traces.js +0 -19
- package/dist/commands/trash.d.ts +0 -17
- package/dist/commands/trash.js +1 -11
- package/dist/commands/uninstall.d.ts +0 -9
- package/dist/commands/uninstall.js +0 -6
- package/dist/commands/update.d.ts +0 -22
- package/dist/commands/update.js +0 -44
- package/dist/commands/upgrade.d.ts +0 -1
- package/dist/commands/upgrade.js +0 -1
- package/dist/commands/utils.d.ts +0 -85
- package/dist/commands/utils.js +1 -95
- package/dist/commands/versions.d.ts +0 -16
- package/dist/commands/versions.js +0 -134
- package/dist/commands/view.d.ts +0 -90
- package/dist/commands/view.js +4 -343
- package/dist/commands/watchdog.d.ts +0 -19
- package/dist/commands/watchdog.js +3 -43
- package/dist/commands/webhook.d.ts +0 -8
- package/dist/commands/webhook.js +0 -14
- package/dist/commands/workflows.d.ts +0 -8
- package/dist/commands/workflows.js +0 -6
- package/dist/index.d.ts +0 -25
- package/dist/index.js +5 -119
- package/dist/lib/account-capabilities.d.ts +0 -34
- package/dist/lib/account-capabilities.js +0 -32
- package/dist/lib/account-catalog.d.ts +0 -128
- package/dist/lib/account-catalog.js +8 -154
- package/dist/lib/account-provider-registry.d.ts +0 -14
- package/dist/lib/account-provider-registry.js +0 -8
- package/dist/lib/account-registry.d.ts +0 -74
- package/dist/lib/account-registry.js +14 -189
- package/dist/lib/account-schema.d.ts +0 -19
- package/dist/lib/account-schema.js +0 -43
- package/dist/lib/accounting/account-launch.d.ts +0 -15
- package/dist/lib/accounting/account-launch.js +1 -3
- package/dist/lib/accounting/account-pool-collect.d.ts +0 -26
- package/dist/lib/accounting/account-pool-collect.js +0 -42
- package/dist/lib/accounting/account-pool.d.ts +0 -41
- package/dist/lib/accounting/account-pool.js +0 -17
- package/dist/lib/accounting/capacity.d.ts +0 -32
- package/dist/lib/accounting/capacity.js +0 -32
- package/dist/lib/accounting/rotate.d.ts +0 -518
- package/dist/lib/accounting/rotate.js +4 -598
- package/dist/lib/accounting/usage-ingest.d.ts +0 -1
- package/dist/lib/accounting/usage-ingest.js +0 -44
- package/dist/lib/accounting/usage-sync.d.ts +0 -60
- package/dist/lib/accounting/usage-sync.js +4 -51
- package/dist/lib/accounting/usage.d.ts +0 -632
- package/dist/lib/accounting/usage.js +14 -1055
- package/dist/lib/accounts/add.d.ts +0 -60
- package/dist/lib/accounts/add.js +0 -127
- package/dist/lib/accounts/auth-operation-lock.d.ts +0 -1
- package/dist/lib/accounts/auth-operation-lock.js +0 -4
- package/dist/lib/accounts/migrate.d.ts +0 -9
- package/dist/lib/accounts/migrate.js +3 -42
- package/dist/lib/accounts/slots.d.ts +0 -25
- package/dist/lib/accounts/slots.js +1 -49
- package/dist/lib/acp/client.d.ts +0 -16
- package/dist/lib/acp/client.js +0 -29
- package/dist/lib/acp/harnesses.d.ts +0 -15
- package/dist/lib/acp/harnesses.js +0 -16
- package/dist/lib/acp/run.d.ts +0 -8
- package/dist/lib/acp/run.js +0 -8
- package/dist/lib/actor.d.ts +1 -71
- package/dist/lib/actor.js +1 -99
- package/dist/lib/add-dir.d.ts +0 -46
- package/dist/lib/add-dir.js +1 -70
- package/dist/lib/address.d.ts +0 -17
- package/dist/lib/address.js +0 -15
- package/dist/lib/agent-cli-commands.d.ts +0 -10
- package/dist/lib/agent-cli-commands.js +0 -10
- package/dist/lib/agent-modes.d.ts +0 -29
- package/dist/lib/agent-modes.js +0 -31
- package/dist/lib/agent-spec/agents.d.ts +0 -426
- package/dist/lib/agent-spec/agents.js +37 -924
- package/dist/lib/agent-spec/index.d.ts +0 -5
- package/dist/lib/agent-spec/index.js +0 -14
- package/dist/lib/agent-spec/materialize.d.ts +0 -10
- package/dist/lib/agent-spec/materialize.js +2 -139
- package/dist/lib/agent-spec/package-resolve.d.ts +0 -8
- package/dist/lib/agent-spec/package-resolve.js +1 -44
- package/dist/lib/agent-spec/package-schema.d.ts +0 -2
- package/dist/lib/agent-spec/package-schema.js +0 -11
- package/dist/lib/agent-spec/package-types.d.ts +0 -27
- package/dist/lib/agent-spec/package-types.js +0 -1
- package/dist/lib/agent-spec/primitives.d.ts +0 -18
- package/dist/lib/agent-spec/primitives.js +0 -27
- package/dist/lib/agent-spec/provider.js +0 -2
- package/dist/lib/agent-spec/resolve.d.ts +0 -23
- package/dist/lib/agent-spec/resolve.js +0 -41
- package/dist/lib/agent-spec/types.d.ts +0 -34
- package/dist/lib/agent-spec/types.js +0 -6
- package/dist/lib/agents.d.ts +0 -1
- package/dist/lib/agents.js +0 -1
- package/dist/lib/analytics/mix-commands.d.ts +0 -23
- package/dist/lib/analytics/mix-commands.js +0 -26
- package/dist/lib/analytics/recipes.d.ts +0 -1
- package/dist/lib/analytics/recipes.js +5 -10
- package/dist/lib/analytics/usage-db.js +4 -6
- package/dist/lib/answer-router.d.ts +0 -61
- package/dist/lib/answer-router.js +0 -57
- package/dist/lib/app-bundle-install.d.ts +0 -13
- package/dist/lib/app-bundle-install.js +0 -50
- package/dist/lib/artifact-actions.d.ts +0 -15
- package/dist/lib/artifact-actions.js +0 -18
- package/dist/lib/artifacts-client.d.ts +0 -10
- package/dist/lib/artifacts-client.js +0 -17
- package/dist/lib/ask-classifier.d.ts +0 -38
- package/dist/lib/ask-classifier.js +0 -27
- package/dist/lib/audit/log.d.ts +0 -47
- package/dist/lib/audit/log.js +1 -59
- package/dist/lib/auth-health.d.ts +0 -213
- package/dist/lib/auth-health.js +16 -266
- package/dist/lib/auth-mint.d.ts +0 -83
- package/dist/lib/auth-mint.js +8 -85
- package/dist/lib/auto-pull-worker.d.ts +0 -10
- package/dist/lib/auto-pull-worker.js +3 -48
- package/dist/lib/auto-pull.d.ts +0 -64
- package/dist/lib/auto-pull.js +0 -79
- package/dist/lib/beta.d.ts +0 -9
- package/dist/lib/beta.js +0 -9
- package/dist/lib/binary-shadow.d.ts +0 -22
- package/dist/lib/binary-shadow.js +4 -34
- package/dist/lib/boot-profile.d.ts +0 -10
- package/dist/lib/boot-profile.js +1 -31
- package/dist/lib/brand.d.ts +0 -20
- package/dist/lib/brand.js +0 -50
- package/dist/lib/browser/context.d.ts +0 -54
- package/dist/lib/browser/context.js +0 -49
- package/dist/lib/browser/paths.d.ts +0 -21
- package/dist/lib/browser/paths.js +0 -26
- package/dist/lib/browser/record.d.ts +0 -34
- package/dist/lib/browser/record.js +0 -38
- package/dist/lib/browser/sessions-list.d.ts +1 -79
- package/dist/lib/browser/sessions-list.js +2 -98
- package/dist/lib/browser-client.d.ts +0 -112
- package/dist/lib/browser-client.js +1 -90
- package/dist/lib/budget/config.d.ts +0 -6
- package/dist/lib/budget/config.js +0 -29
- package/dist/lib/budget/enforce.d.ts +0 -50
- package/dist/lib/budget/enforce.js +0 -27
- package/dist/lib/budget/ledger.d.ts +0 -23
- package/dist/lib/budget/ledger.js +0 -28
- package/dist/lib/budget/live-cloud.d.ts +0 -26
- package/dist/lib/budget/live-cloud.js +0 -12
- package/dist/lib/budget/live-team.d.ts +0 -17
- package/dist/lib/budget/live-team.js +1 -35
- package/dist/lib/budget/preflight.d.ts +0 -51
- package/dist/lib/budget/preflight.js +0 -36
- package/dist/lib/byok-usage.d.ts +0 -20
- package/dist/lib/byok-usage.js +1 -24
- package/dist/lib/capabilities.d.ts +0 -24
- package/dist/lib/capabilities.js +0 -29
- package/dist/lib/catchup.d.ts +0 -87
- package/dist/lib/catchup.js +1 -81
- package/dist/lib/channels/owner-forward.d.ts +0 -70
- package/dist/lib/channels/owner-forward.js +1 -43
- package/dist/lib/channels/owner-sink.js +0 -21
- package/dist/lib/channels/providers/desktop.d.ts +0 -37
- package/dist/lib/channels/providers/desktop.js +0 -72
- package/dist/lib/channels/providers/index.d.ts +0 -1
- package/dist/lib/channels/providers/index.js +2 -6
- package/dist/lib/channels/providers/mailbox.js +0 -7
- package/dist/lib/channels/providers/openclaw-telegram.js +0 -8
- package/dist/lib/channels/providers/rush.d.ts +0 -2
- package/dist/lib/channels/providers/rush.js +0 -26
- package/dist/lib/channels/providers/session.d.ts +2 -0
- package/dist/lib/channels/providers/session.js +26 -0
- package/dist/lib/channels/registry.d.ts +0 -27
- package/dist/lib/channels/registry.js +0 -10
- package/dist/lib/channels/resolve.d.ts +0 -19
- package/dist/lib/channels/resolve.js +0 -2
- package/dist/lib/channels/send.d.ts +0 -50
- package/dist/lib/channels/send.js +0 -25
- package/dist/lib/checkpoint.d.ts +0 -36
- package/dist/lib/checkpoint.js +0 -28
- package/dist/lib/claude-account-token.d.ts +0 -98
- package/dist/lib/claude-account-token.js +7 -169
- package/dist/lib/claude-statusline.d.ts +0 -41
- package/dist/lib/claude-statusline.js +0 -68
- package/dist/lib/cli-entry.d.ts +0 -26
- package/dist/lib/cli-entry.js +1 -61
- package/dist/lib/cli-resources.d.ts +31 -89
- package/dist/lib/cli-resources.js +116 -152
- package/dist/lib/cloud/antigravity.d.ts +0 -29
- package/dist/lib/cloud/antigravity.js +0 -36
- package/dist/lib/cloud/codex.d.ts +0 -8
- package/dist/lib/cloud/codex.js +0 -41
- package/dist/lib/cloud/cursor.d.ts +0 -5
- package/dist/lib/cloud/cursor.js +2 -7
- package/dist/lib/cloud/dispatch.d.ts +0 -13
- package/dist/lib/cloud/dispatch.js +1 -48
- package/dist/lib/cloud/factory.d.ts +0 -52
- package/dist/lib/cloud/factory.js +3 -67
- package/dist/lib/cloud/host.d.ts +0 -39
- package/dist/lib/cloud/host.js +5 -48
- package/dist/lib/cloud/registry.d.ts +0 -25
- package/dist/lib/cloud/registry.js +0 -29
- package/dist/lib/cloud/rush.d.ts +0 -47
- package/dist/lib/cloud/rush.js +1 -82
- package/dist/lib/cloud/session-index.d.ts +0 -27
- package/dist/lib/cloud/session-index.js +1 -32
- package/dist/lib/cloud/store.d.ts +0 -18
- package/dist/lib/cloud/store.js +0 -28
- package/dist/lib/cloud/stream.d.ts +0 -14
- package/dist/lib/cloud/stream.js +0 -25
- package/dist/lib/cloud/types.d.ts +0 -168
- package/dist/lib/cloud/types.js +0 -46
- package/dist/lib/cloudflare/creds.d.ts +0 -2
- package/dist/lib/cloudflare/creds.js +0 -14
- package/dist/lib/cloudflare/provision.d.ts +0 -8
- package/dist/lib/cloudflare/provision.js +1 -19
- package/dist/lib/codex-home.d.ts +0 -60
- package/dist/lib/codex-home.js +2 -72
- package/dist/lib/codex-policy.d.ts +0 -10
- package/dist/lib/codex-policy.js +0 -13
- package/dist/lib/command-skills.d.ts +0 -10
- package/dist/lib/command-skills.js +0 -10
- package/dist/lib/commands.d.ts +0 -74
- package/dist/lib/commands.js +0 -112
- package/dist/lib/comms-render.d.ts +0 -5
- package/dist/lib/comms-render.js +0 -5
- package/dist/lib/computer/context.d.ts +0 -23
- package/dist/lib/computer/context.js +0 -14
- package/dist/lib/computer/policy.d.ts +0 -20
- package/dist/lib/computer/policy.js +0 -53
- package/dist/lib/computer/record.d.ts +0 -33
- package/dist/lib/computer/record.js +1 -48
- package/dist/lib/computer/sessions-list.d.ts +1 -128
- package/dist/lib/computer/sessions-list.js +6 -165
- package/dist/lib/computer-client.d.ts +0 -106
- package/dist/lib/computer-client.js +5 -86
- package/dist/lib/concurrency.d.ts +0 -14
- package/dist/lib/concurrency.js +0 -3
- package/dist/lib/config-drift.d.ts +0 -31
- package/dist/lib/config-drift.js +0 -32
- package/dist/lib/config-keys.d.ts +1 -65
- package/dist/lib/config-keys.js +6 -52
- package/dist/lib/config-transfer.d.ts +0 -14
- package/dist/lib/config-transfer.js +0 -26
- package/dist/lib/convert.d.ts +0 -17
- package/dist/lib/convert.js +0 -18
- package/dist/lib/crabbox/cli.d.ts +0 -156
- package/dist/lib/crabbox/cli.js +1 -188
- package/dist/lib/crabbox/config.d.ts +0 -18
- package/dist/lib/crabbox/config.js +1 -19
- package/dist/lib/crabbox/lease.d.ts +0 -93
- package/dist/lib/crabbox/lease.js +4 -148
- package/dist/lib/crabbox/progress.d.ts +0 -56
- package/dist/lib/crabbox/progress.js +2 -49
- package/dist/lib/crabbox/runtimes.d.ts +0 -94
- package/dist/lib/crabbox/runtimes.js +1 -111
- package/dist/lib/crabbox/setup-copy.d.ts +0 -64
- package/dist/lib/crabbox/setup-copy.js +1 -53
- package/dist/lib/daemon/account-state-daemon-service.d.ts +0 -48
- package/dist/lib/daemon/account-state-daemon-service.js +2 -63
- package/dist/lib/daemon/attention-notify-service.d.ts +0 -12
- package/dist/lib/daemon/attention-notify-service.js +1 -24
- package/dist/lib/daemon/auth-sync-service.d.ts +0 -28
- package/dist/lib/daemon/auth-sync-service.js +0 -56
- package/dist/lib/daemon/catchup-service.d.ts +0 -34
- package/dist/lib/daemon/catchup-service.js +0 -30
- package/dist/lib/daemon/daemon.d.ts +0 -374
- package/dist/lib/daemon/daemon.js +44 -1049
- package/dist/lib/daemon/daemon.test-fixture.d.ts +0 -19
- package/dist/lib/daemon/daemon.test-fixture.js +1 -24
- package/dist/lib/daemon/device-probe-service.d.ts +0 -16
- package/dist/lib/daemon/device-probe-service.js +0 -20
- package/dist/lib/daemon/feed-stream-service.d.ts +0 -15
- package/dist/lib/daemon/feed-stream-service.js +0 -20
- package/dist/lib/daemon/harness-update-service.d.ts +2 -61
- package/dist/lib/daemon/harness-update-service.js +6 -86
- package/dist/lib/daemon/heartbeat-service.d.ts +0 -1
- package/dist/lib/daemon/heartbeat-service.js +0 -1
- package/dist/lib/daemon/leaked-daemons.d.ts +0 -46
- package/dist/lib/daemon/leaked-daemons.js +2 -78
- package/dist/lib/daemon/monitor-engine-service.d.ts +0 -13
- package/dist/lib/daemon/monitor-engine-service.js +3 -15
- package/dist/lib/daemon/runner.d.ts +0 -194
- package/dist/lib/daemon/runner.js +39 -760
- package/dist/lib/daemon/self-heal-service.d.ts +18 -17
- package/dist/lib/daemon/self-heal-service.js +83 -37
- package/dist/lib/daemon/self-update-service.d.ts +1 -131
- package/dist/lib/daemon/self-update-service.js +26 -189
- package/dist/lib/daemon/service.d.ts +0 -73
- package/dist/lib/daemon/service.js +0 -34
- package/dist/lib/daemon/session-index-service.d.ts +0 -12
- package/dist/lib/daemon/session-index-service.js +0 -23
- package/dist/lib/daemon/session-state-service.d.ts +0 -8
- package/dist/lib/daemon/session-state-service.js +0 -8
- package/dist/lib/daemon/session-summarizer-service.d.ts +0 -14
- package/dist/lib/daemon/session-summarizer-service.js +1 -19
- package/dist/lib/daemon/session-title-service.d.ts +0 -25
- package/dist/lib/daemon/session-title-service.js +0 -32
- package/dist/lib/daemon/state-dir-check-service.d.ts +0 -24
- package/dist/lib/daemon/state-dir-check-service.js +0 -29
- package/dist/lib/daemon/supervisor.d.ts +0 -112
- package/dist/lib/daemon/supervisor.js +4 -147
- package/dist/lib/daemon/testdata/process-view-daemon.js +0 -2
- package/dist/lib/daemon/testdata/process-view-start.js +0 -5
- package/dist/lib/daemon/tmux-reap-service.d.ts +0 -1
- package/dist/lib/daemon/tmux-reap-service.js +0 -1
- package/dist/lib/daemon/usage-sync-service.d.ts +0 -23
- package/dist/lib/daemon/usage-sync-service.js +0 -26
- package/dist/lib/daemon/watchdog-service.d.ts +0 -9
- package/dist/lib/daemon/watchdog-service.js +0 -13
- package/dist/lib/daemon/webhook-receiver-service.d.ts +0 -1
- package/dist/lib/daemon/webhook-receiver-service.js +0 -1
- package/dist/lib/daemon-health.d.ts +0 -55
- package/dist/lib/daemon-health.js +4 -77
- package/dist/lib/daemon-services.d.ts +0 -36
- package/dist/lib/daemon-services.js +0 -45
- package/dist/lib/daemon-ticks.d.ts +0 -126
- package/dist/lib/daemon-ticks.js +1 -156
- package/dist/lib/daemon-webhooks.d.ts +0 -57
- package/dist/lib/daemon-webhooks.js +2 -54
- package/dist/lib/deeplink/register.d.ts +0 -15
- package/dist/lib/deeplink/register.js +0 -60
- package/dist/lib/deeplink/url.d.ts +0 -24
- package/dist/lib/deeplink/url.js +1 -33
- package/dist/lib/device-config.d.ts +1 -192
- package/dist/lib/device-config.js +86 -240
- package/dist/lib/devices/config-migration.d.ts +0 -43
- package/dist/lib/devices/config-migration.js +7 -127
- package/dist/lib/devices/connect.d.ts +0 -168
- package/dist/lib/devices/connect.js +6 -254
- package/dist/lib/devices/device-docs.d.ts +0 -24
- package/dist/lib/devices/device-docs.js +0 -46
- package/dist/lib/devices/discovery-policy.d.ts +0 -30
- package/dist/lib/devices/discovery-policy.js +3 -34
- package/dist/lib/devices/doctor-findings.d.ts +0 -109
- package/dist/lib/devices/doctor-findings.js +30 -326
- package/dist/lib/devices/doctor-overview-cache.d.ts +0 -33
- package/dist/lib/devices/doctor-overview-cache.js +0 -81
- package/dist/lib/devices/fleet-divergence.d.ts +0 -89
- package/dist/lib/devices/fleet-divergence.js +1 -60
- package/dist/lib/devices/fleet-inventory.d.ts +0 -27
- package/dist/lib/devices/fleet-inventory.js +0 -38
- package/dist/lib/devices/fleet.d.ts +0 -95
- package/dist/lib/devices/fleet.js +0 -79
- package/dist/lib/devices/harness-inventory.d.ts +0 -75
- package/dist/lib/devices/harness-inventory.js +0 -101
- package/dist/lib/devices/health-report.d.ts +0 -37
- package/dist/lib/devices/health-report.js +0 -73
- package/dist/lib/devices/health.d.ts +0 -71
- package/dist/lib/devices/health.js +2 -82
- package/dist/lib/devices/interactive-host.d.ts +0 -10
- package/dist/lib/devices/interactive-host.js +0 -34
- package/dist/lib/devices/known-hosts.d.ts +0 -45
- package/dist/lib/devices/known-hosts.js +0 -77
- package/dist/lib/devices/pending.d.ts +0 -31
- package/dist/lib/devices/pending.js +7 -67
- package/dist/lib/devices/pool.d.ts +0 -36
- package/dist/lib/devices/pool.js +0 -30
- package/dist/lib/devices/reachability.d.ts +0 -25
- package/dist/lib/devices/reachability.js +0 -14
- package/dist/lib/devices/registry.d.ts +0 -141
- package/dist/lib/devices/registry.js +4 -151
- package/dist/lib/devices/resolve-profile.d.ts +0 -18
- package/dist/lib/devices/resolve-profile.js +0 -18
- package/dist/lib/devices/resolve-target.d.ts +0 -20
- package/dist/lib/devices/resolve-target.js +1 -51
- package/dist/lib/devices/rollout-verify.d.ts +1 -81
- package/dist/lib/devices/rollout-verify.js +0 -77
- package/dist/lib/devices/self-host.d.ts +0 -7
- package/dist/lib/devices/self-host.js +1 -29
- package/dist/lib/devices/ssh-config.d.ts +0 -18
- package/dist/lib/devices/ssh-config.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +0 -48
- package/dist/lib/devices/stats-cache.js +0 -80
- package/dist/lib/devices/sync.d.ts +0 -97
- package/dist/lib/devices/sync.js +0 -88
- package/dist/lib/devices/tailscale.d.ts +0 -18
- package/dist/lib/devices/tailscale.js +1 -38
- package/dist/lib/devices/terminfo.d.ts +0 -27
- package/dist/lib/devices/terminfo.js +2 -64
- package/dist/lib/devices/worker-pick.d.ts +0 -15
- package/dist/lib/devices/worker-pick.js +0 -48
- package/dist/lib/diff-text.d.ts +0 -18
- package/dist/lib/diff-text.js +0 -15
- package/dist/lib/doctor-diff.d.ts +0 -79
- package/dist/lib/doctor-diff.js +2 -181
- package/dist/lib/drift-sync.d.ts +0 -29
- package/dist/lib/drift-sync.js +0 -40
- package/dist/lib/drift.d.ts +0 -43
- package/dist/lib/drift.js +0 -31
- package/dist/lib/event-families.d.ts +0 -17
- package/dist/lib/event-families.js +0 -33
- package/dist/lib/event-provenance.d.ts +0 -4
- package/dist/lib/event-provenance.js +0 -5
- package/dist/lib/event-stream.d.ts +0 -29
- package/dist/lib/event-stream.js +0 -36
- package/dist/lib/events-ingest.d.ts +0 -27
- package/dist/lib/events-ingest.js +0 -54
- package/dist/lib/exec-account-home.d.ts +0 -24
- package/dist/lib/exec-account-home.js +0 -41
- package/dist/lib/exec-bounded.d.ts +1 -37
- package/dist/lib/exec-bounded.js +10 -46
- package/dist/lib/exec.d.ts +3 -583
- package/dist/lib/exec.js +18 -1025
- package/dist/lib/factory/snapshot.js +1 -15
- package/dist/lib/feed/activity-stream.d.ts +0 -27
- package/dist/lib/feed/activity-stream.js +4 -80
- package/dist/lib/feed/activity.d.ts +1 -243
- package/dist/lib/feed/activity.js +8 -273
- package/dist/lib/feed/answer.d.ts +0 -103
- package/dist/lib/feed/answer.js +0 -267
- package/dist/lib/feed/attention.d.ts +0 -56
- package/dist/lib/feed/attention.js +0 -161
- package/dist/lib/feed/envelope.d.ts +2 -17
- package/dist/lib/feed/envelope.js +0 -14
- package/dist/lib/feed/events.d.ts +0 -118
- package/dist/lib/feed/events.js +13 -213
- package/dist/lib/feed/feed.d.ts +1 -250
- package/dist/lib/feed/feed.js +24 -334
- package/dist/lib/feed/hub-server.d.ts +0 -100
- package/dist/lib/feed/hub-server.js +1 -191
- package/dist/lib/feed/hub.d.ts +0 -53
- package/dist/lib/feed/hub.js +0 -89
- package/dist/lib/feed/pr-status.d.ts +1 -19
- package/dist/lib/feed/pr-status.js +0 -18
- package/dist/lib/feed/tool-activity.d.ts +0 -59
- package/dist/lib/feed/tool-activity.js +1 -119
- package/dist/lib/feed/tools.d.ts +0 -100
- package/dist/lib/feed/tools.js +0 -89
- package/dist/lib/feed/watch.d.ts +0 -33
- package/dist/lib/feed/watch.js +2 -94
- package/dist/lib/feed-broadcast.d.ts +0 -148
- package/dist/lib/feed-broadcast.js +1 -283
- package/dist/lib/feed-outcome.d.ts +0 -50
- package/dist/lib/feed-outcome.js +1 -67
- package/dist/lib/feed-policy.d.ts +0 -6
- package/dist/lib/feed-policy.js +1 -27
- package/dist/lib/feed-post.d.ts +0 -61
- package/dist/lib/feed-post.js +4 -61
- package/dist/lib/feed-ranking.d.ts +1 -1
- package/dist/lib/fleet/apply.d.ts +0 -64
- package/dist/lib/fleet/apply.js +0 -125
- package/dist/lib/fleet/auth-sync.d.ts +0 -48
- package/dist/lib/fleet/auth-sync.js +4 -49
- package/dist/lib/fleet/capture.d.ts +0 -26
- package/dist/lib/fleet/capture.js +1 -36
- package/dist/lib/fleet/manifest.d.ts +0 -38
- package/dist/lib/fleet/manifest.js +0 -40
- package/dist/lib/fleet/types.d.ts +1 -138
- package/dist/lib/fleet/types.js +0 -12
- package/dist/lib/fleet-cache.d.ts +0 -12
- package/dist/lib/fleet-cache.js +0 -24
- package/dist/lib/fleet-shared-state.d.ts +6 -59
- package/dist/lib/fleet-shared-state.js +5 -51
- package/dist/lib/fleet-status.d.ts +0 -27
- package/dist/lib/fleet-status.js +0 -48
- package/dist/lib/format.d.ts +0 -74
- package/dist/lib/format.js +3 -77
- package/dist/lib/friction-heuristics.d.ts +0 -20
- package/dist/lib/friction-heuristics.js +0 -8
- package/dist/lib/fs-atomic.d.ts +0 -51
- package/dist/lib/fs-atomic.js +8 -66
- package/dist/lib/fs-walk.d.ts +0 -13
- package/dist/lib/fs-walk.js +0 -22
- package/dist/lib/fuzzy.d.ts +0 -29
- package/dist/lib/fuzzy.js +0 -31
- package/dist/lib/gemini-settings.js +0 -12
- package/dist/lib/git.d.ts +0 -366
- package/dist/lib/git.js +7 -574
- package/dist/lib/github/ci-failure.d.ts +30 -0
- package/dist/lib/github/ci-failure.js +95 -0
- package/dist/lib/github/gh-overload.d.ts +0 -38
- package/dist/lib/github/gh-overload.js +3 -55
- package/dist/lib/github/pr-mergeable.d.ts +3 -34
- package/dist/lib/github/pr-mergeable.js +2 -42
- package/dist/lib/github/pr-verdict.d.ts +0 -44
- package/dist/lib/github/pr-verdict.js +0 -36
- package/dist/lib/github/project-prs.d.ts +135 -105
- package/dist/lib/github/project-prs.js +530 -170
- package/dist/lib/github/release-drift.d.ts +45 -0
- package/dist/lib/github/release-drift.js +91 -0
- package/dist/lib/github/rest.d.ts +0 -50
- package/dist/lib/github/rest.js +0 -49
- package/dist/lib/github/viewer.d.ts +27 -0
- package/dist/lib/github/viewer.js +101 -0
- package/dist/lib/goose-commands.d.ts +0 -29
- package/dist/lib/goose-commands.js +3 -48
- package/dist/lib/harness/adapter.d.ts +0 -151
- package/dist/lib/harness/adapter.js +0 -41
- package/dist/lib/harness/adapters/claude.d.ts +0 -8
- package/dist/lib/harness/adapters/claude.js +0 -89
- package/dist/lib/harness/adapters/codex.js +0 -18
- package/dist/lib/harness/adapters/copilot.js +0 -3
- package/dist/lib/harness/adapters/cursor.js +0 -16
- package/dist/lib/harness/adapters/droid.js +0 -3
- package/dist/lib/harness/adapters/kimi.js +0 -19
- package/dist/lib/harness/adapters/muse.js +0 -9
- package/dist/lib/harness/adapters/opencode.js +0 -2
- package/dist/lib/harness/exec-config-version.d.ts +0 -2
- package/dist/lib/harness/exec-config-version.js +0 -13
- package/dist/lib/harness/index.js +0 -10
- package/dist/lib/harness-auth-capabilities.d.ts +0 -26
- package/dist/lib/harness-auth-capabilities.js +0 -18
- package/dist/lib/harness-connection-test.d.ts +0 -27
- package/dist/lib/harness-connection-test.js +2 -20
- package/dist/lib/heal.d.ts +0 -45
- package/dist/lib/heal.js +2 -82
- package/dist/lib/help.d.ts +0 -32
- package/dist/lib/help.js +0 -41
- package/dist/lib/helper-download.d.ts +0 -85
- package/dist/lib/helper-download.js +0 -95
- package/dist/lib/helper-versions.d.ts +0 -51
- package/dist/lib/helper-versions.js +1 -45
- package/dist/lib/hooks/cache.d.ts +0 -40
- package/dist/lib/hooks/cache.js +8 -122
- package/dist/lib/hooks/install.d.ts +0 -253
- package/dist/lib/hooks/install.js +49 -822
- package/dist/lib/hooks/match.d.ts +0 -24
- package/dist/lib/hooks/match.js +0 -31
- package/dist/lib/hooks/profile.d.ts +0 -21
- package/dist/lib/hooks/profile.js +0 -28
- package/dist/lib/hosts/credential-transport.d.ts +0 -6
- package/dist/lib/hosts/credential-transport.js +0 -17
- package/dist/lib/hosts/credentials.d.ts +0 -35
- package/dist/lib/hosts/credentials.js +0 -38
- package/dist/lib/hosts/dispatch.d.ts +0 -140
- package/dist/lib/hosts/dispatch.js +0 -176
- package/dist/lib/hosts/logs.d.ts +0 -25
- package/dist/lib/hosts/logs.js +1 -51
- package/dist/lib/hosts/option.d.ts +0 -12
- package/dist/lib/hosts/option.js +0 -12
- package/dist/lib/hosts/passthrough.d.ts +0 -129
- package/dist/lib/hosts/passthrough.js +12 -249
- package/dist/lib/hosts/progress.d.ts +0 -90
- package/dist/lib/hosts/progress.js +5 -115
- package/dist/lib/hosts/providers/devices.d.ts +0 -19
- package/dist/lib/hosts/providers/devices.js +1 -34
- package/dist/lib/hosts/providers/local.d.ts +0 -14
- package/dist/lib/hosts/providers/local.js +0 -22
- package/dist/lib/hosts/ready.d.ts +0 -99
- package/dist/lib/hosts/ready.js +0 -101
- package/dist/lib/hosts/reconcile.d.ts +0 -38
- package/dist/lib/hosts/reconcile.js +1 -50
- package/dist/lib/hosts/reconnect.d.ts +0 -75
- package/dist/lib/hosts/reconnect.js +0 -98
- package/dist/lib/hosts/registry.d.ts +0 -74
- package/dist/lib/hosts/registry.js +0 -110
- package/dist/lib/hosts/remote-cmd.d.ts +1 -228
- package/dist/lib/hosts/remote-cmd.js +19 -294
- package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +0 -1
- package/dist/lib/hosts/remote-cmd.test-fixture.js +0 -12
- package/dist/lib/hosts/remote-os.d.ts +0 -19
- package/dist/lib/hosts/remote-os.js +0 -22
- package/dist/lib/hosts/remote-session-id.d.ts +0 -22
- package/dist/lib/hosts/remote-session-id.js +0 -24
- package/dist/lib/hosts/routing-flag.d.ts +0 -21
- package/dist/lib/hosts/routing-flag.js +0 -21
- package/dist/lib/hosts/run-target.d.ts +0 -53
- package/dist/lib/hosts/run-target.js +0 -56
- package/dist/lib/hosts/session-index.d.ts +1 -53
- package/dist/lib/hosts/session-index.js +2 -59
- package/dist/lib/hosts/session-marker.d.ts +0 -31
- package/dist/lib/hosts/session-marker.js +0 -32
- package/dist/lib/hosts/ssh-config.d.ts +0 -25
- package/dist/lib/hosts/ssh-config.js +1 -32
- package/dist/lib/hosts/tasks.d.ts +0 -54
- package/dist/lib/hosts/tasks.js +0 -35
- package/dist/lib/hosts/types.d.ts +0 -39
- package/dist/lib/hosts/types.js +0 -22
- package/dist/lib/humans.d.ts +0 -21
- package/dist/lib/humans.js +0 -30
- package/dist/lib/identity/client.d.ts +1 -35
- package/dist/lib/identity/client.js +3 -29
- package/dist/lib/identity/index.d.ts +3 -27
- package/dist/lib/identity/index.js +8 -24
- package/dist/lib/import.d.ts +0 -104
- package/dist/lib/import.js +1 -137
- package/dist/lib/installations/active-check.d.ts +0 -53
- package/dist/lib/installations/active-check.js +0 -49
- package/dist/lib/installations/index.d.ts +0 -9
- package/dist/lib/installations/index.js +0 -9
- package/dist/lib/installations/installation-lock.js +0 -8
- package/dist/lib/installations/launch-gate.d.ts +0 -56
- package/dist/lib/installations/launch-gate.js +0 -83
- package/dist/lib/installations/migrate.d.ts +0 -100
- package/dist/lib/installations/migrate.js +105 -572
- package/dist/lib/installations/native-command.d.ts +0 -1
- package/dist/lib/installations/native-command.js +0 -6
- package/dist/lib/installations/resolve.d.ts +0 -24
- package/dist/lib/installations/resolve.js +0 -24
- package/dist/lib/installations/shims.d.ts +0 -395
- package/dist/lib/installations/shims.js +30 -957
- package/dist/lib/installations/store.d.ts +0 -199
- package/dist/lib/installations/store.js +1 -297
- package/dist/lib/installations/strategies.d.ts +0 -44
- package/dist/lib/installations/strategies.js +0 -87
- package/dist/lib/installations/types.d.ts +1 -67
- package/dist/lib/installations/types.js +0 -6
- package/dist/lib/installations/update-cancellation.d.ts +0 -74
- package/dist/lib/installations/update-cancellation.js +0 -74
- package/dist/lib/installations/update-policy.d.ts +0 -57
- package/dist/lib/installations/update-policy.js +0 -62
- package/dist/lib/installations/update-runtime.d.ts +0 -88
- package/dist/lib/installations/update-runtime.js +1 -127
- package/dist/lib/installations/update.d.ts +0 -53
- package/dist/lib/installations/update.js +0 -95
- package/dist/lib/installations/versions.d.ts +0 -310
- package/dist/lib/installations/versions.js +14 -837
- package/dist/lib/isolation-boundary-report.d.ts +0 -7
- package/dist/lib/isolation-boundary-report.js +0 -7
- package/dist/lib/launch-identity.d.ts +0 -8
- package/dist/lib/launch-identity.js +0 -8
- package/dist/lib/linear-autoclose.d.ts +0 -25
- package/dist/lib/linear-autoclose.js +0 -19
- package/dist/lib/linear-cache.d.ts +0 -57
- package/dist/lib/linear-cache.js +1 -81
- package/dist/lib/linear-project-counts.d.ts +0 -83
- package/dist/lib/linear-project-counts.js +2 -64
- package/dist/lib/linear-projects.d.ts +0 -62
- package/dist/lib/linear-projects.js +0 -85
- package/dist/lib/linear-rate-limit.d.ts +0 -21
- package/dist/lib/linear-rate-limit.js +1 -93
- package/dist/lib/linux-userns.d.ts +1 -42
- package/dist/lib/linux-userns.js +0 -47
- package/dist/lib/lock-compromise.d.ts +0 -7
- package/dist/lib/lock-compromise.js +0 -7
- package/dist/lib/log-follow.d.ts +0 -2
- package/dist/lib/log-follow.js +5 -15
- package/dist/lib/loop.d.ts +0 -106
- package/dist/lib/loop.js +0 -135
- package/dist/lib/machine-id.d.ts +0 -19
- package/dist/lib/machine-id.js +0 -19
- package/dist/lib/mailbox-gc.d.ts +0 -7
- package/dist/lib/mailbox-gc.js +0 -25
- package/dist/lib/mailbox-target.d.ts +0 -31
- package/dist/lib/mailbox-target.js +0 -27
- package/dist/lib/mailbox.d.ts +0 -77
- package/dist/lib/mailbox.js +5 -105
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/manifest.js +0 -36
- package/dist/lib/markdown.d.ts +0 -3
- package/dist/lib/markdown.js +0 -9
- package/dist/lib/mcp-registry.d.ts +1 -50
- package/dist/lib/mcp-registry.js +0 -64
- package/dist/lib/mcp.d.ts +0 -115
- package/dist/lib/mcp.js +3 -199
- package/dist/lib/memory-cache.d.ts +0 -11
- package/dist/lib/memory-cache.js +0 -9
- package/dist/lib/memory.d.ts +0 -52
- package/dist/lib/memory.js +12 -74
- package/dist/lib/menubar/download-menubar.d.ts +0 -35
- package/dist/lib/menubar/download-menubar.js +0 -38
- package/dist/lib/menubar/install-menubar.d.ts +0 -405
- package/dist/lib/menubar/install-menubar.js +25 -667
- package/dist/lib/menubar/migrate-prefs.d.ts +0 -47
- package/dist/lib/menubar/migrate-prefs.js +1 -70
- package/dist/lib/menubar/notify-desktop.d.ts +0 -82
- package/dist/lib/menubar/notify-desktop.js +0 -68
- package/dist/lib/menubar/resolve-version.d.ts +0 -41
- package/dist/lib/menubar/resolve-version.js +2 -44
- package/dist/lib/menubar/snapshot.d.ts +13 -66
- package/dist/lib/menubar/snapshot.js +45 -39
- package/dist/lib/merged-resources.d.ts +0 -10
- package/dist/lib/merged-resources.js +0 -10
- package/dist/lib/migrate-fold.d.ts +0 -31
- package/dist/lib/migrate-fold.js +6 -38
- package/dist/lib/model-tier-overrides.d.ts +0 -30
- package/dist/lib/model-tier-overrides.js +0 -14
- package/dist/lib/model-tiers.d.ts +0 -43
- package/dist/lib/model-tiers.js +5 -78
- package/dist/lib/models.d.ts +0 -145
- package/dist/lib/models.js +3 -333
- package/dist/lib/monitors/config.d.ts +0 -187
- package/dist/lib/monitors/config.js +4 -101
- package/dist/lib/monitors/dispatch.d.ts +0 -22
- package/dist/lib/monitors/dispatch.js +0 -35
- package/dist/lib/monitors/engine.d.ts +1 -53
- package/dist/lib/monitors/engine.js +17 -106
- package/dist/lib/monitors/fingerprint.d.ts +0 -43
- package/dist/lib/monitors/fingerprint.js +0 -57
- package/dist/lib/monitors/pid-watch.d.ts +0 -31
- package/dist/lib/monitors/pid-watch.js +0 -31
- package/dist/lib/monitors/remote.d.ts +0 -52
- package/dist/lib/monitors/remote.js +0 -42
- package/dist/lib/monitors/sources/command.d.ts +2 -9
- package/dist/lib/monitors/sources/command.js +13 -34
- package/dist/lib/monitors/sources/device.d.ts +0 -10
- package/dist/lib/monitors/sources/device.js +0 -14
- package/dist/lib/monitors/sources/failure.d.ts +0 -27
- package/dist/lib/monitors/sources/failure.js +0 -27
- package/dist/lib/monitors/sources/file.d.ts +0 -10
- package/dist/lib/monitors/sources/file.js +0 -11
- package/dist/lib/monitors/sources/http.d.ts +0 -7
- package/dist/lib/monitors/sources/http.js +0 -9
- package/dist/lib/monitors/sources/index.d.ts +0 -9
- package/dist/lib/monitors/sources/index.js +0 -9
- package/dist/lib/monitors/sources/poll.d.ts +0 -8
- package/dist/lib/monitors/sources/poll.js +0 -8
- package/dist/lib/monitors/sources/types.d.ts +0 -18
- package/dist/lib/monitors/sources/types.js +0 -8
- package/dist/lib/monitors/sources/webhook.d.ts +0 -7
- package/dist/lib/monitors/sources/webhook.js +0 -7
- package/dist/lib/monitors/sources/ws.d.ts +0 -10
- package/dist/lib/monitors/sources/ws.js +3 -13
- package/dist/lib/monitors/state.d.ts +0 -132
- package/dist/lib/monitors/state.js +2 -86
- package/dist/lib/native-accounts.d.ts +0 -32
- package/dist/lib/native-accounts.js +0 -17
- package/dist/lib/net-close.d.ts +0 -9
- package/dist/lib/net-close.js +0 -5
- package/dist/lib/notify.d.ts +0 -47
- package/dist/lib/notify.js +0 -30
- package/dist/lib/onepassword.d.ts +0 -14
- package/dist/lib/onepassword.js +0 -12
- package/dist/lib/open-url.d.ts +0 -38
- package/dist/lib/open-url.js +1 -69
- package/dist/lib/openclaw-keychain.js +2 -7
- package/dist/lib/operator.d.ts +0 -9
- package/dist/lib/operator.js +0 -34
- package/dist/lib/origin-machine.d.ts +0 -16
- package/dist/lib/origin-machine.js +0 -16
- package/dist/lib/output/git-output.d.ts +0 -31
- package/dist/lib/output/git-output.js +4 -43
- package/dist/lib/overdue.d.ts +0 -35
- package/dist/lib/overdue.js +0 -60
- package/dist/lib/owner-message.d.ts +0 -20
- package/dist/lib/owner-message.js +1 -40
- package/dist/lib/packages/output-home.d.ts +0 -28
- package/dist/lib/packages/output-home.js +0 -114
- package/dist/lib/paths.d.ts +0 -26
- package/dist/lib/paths.js +1 -27
- package/dist/lib/percentile.d.ts +0 -11
- package/dist/lib/percentile.js +0 -11
- package/dist/lib/perf/db.d.ts +0 -16
- package/dist/lib/perf/db.js +3 -32
- package/dist/lib/perf/spool.d.ts +0 -12
- package/dist/lib/perf/spool.js +0 -13
- package/dist/lib/perf/types.d.ts +0 -26
- package/dist/lib/perf/types.js +0 -1
- package/dist/lib/permissions-registry.d.ts +0 -48
- package/dist/lib/permissions-registry.js +0 -108
- package/dist/lib/permissions.d.ts +0 -215
- package/dist/lib/permissions.js +4 -407
- package/dist/lib/picker.d.ts +0 -124
- package/dist/lib/picker.js +0 -140
- package/dist/lib/placement.d.ts +0 -57
- package/dist/lib/placement.js +0 -47
- package/dist/lib/platform/exec.d.ts +0 -73
- package/dist/lib/platform/exec.js +0 -80
- package/dist/lib/platform/index.d.ts +0 -14
- package/dist/lib/platform/index.js +0 -14
- package/dist/lib/platform/ipc.d.ts +0 -10
- package/dist/lib/platform/ipc.js +0 -13
- package/dist/lib/platform/links.d.ts +0 -14
- package/dist/lib/platform/links.js +0 -25
- package/dist/lib/platform/paths.d.ts +0 -48
- package/dist/lib/platform/paths.js +0 -52
- package/dist/lib/platform/posixpath.d.ts +0 -18
- package/dist/lib/platform/posixpath.js +0 -62
- package/dist/lib/platform/process.d.ts +0 -67
- package/dist/lib/platform/process.js +5 -99
- package/dist/lib/platform/winpath.d.ts +0 -50
- package/dist/lib/platform/winpath.js +0 -81
- package/dist/lib/plugins/plugin-marketplace.d.ts +0 -165
- package/dist/lib/plugins/plugin-marketplace.js +10 -238
- package/dist/lib/plugins/plugins.d.ts +0 -238
- package/dist/lib/plugins/plugins.js +20 -454
- package/dist/lib/plugins/skills.d.ts +0 -86
- package/dist/lib/plugins/skills.js +1 -139
- package/dist/lib/pr-land-detach.d.ts +0 -17
- package/dist/lib/pr-land-detach.js +0 -30
- package/dist/lib/pricing/cost.d.ts +0 -29
- package/dist/lib/pricing/cost.js +0 -38
- package/dist/lib/pricing/index.d.ts +0 -6
- package/dist/lib/pricing/index.js +0 -6
- package/dist/lib/pricing/table.d.ts +0 -8
- package/dist/lib/pricing/table.js +3 -56
- package/dist/lib/probe.d.ts +0 -5
- package/dist/lib/probe.js +0 -40
- package/dist/lib/profiles-presets.d.ts +0 -20
- package/dist/lib/profiles-presets.js +1 -36
- package/dist/lib/profiles.d.ts +0 -185
- package/dist/lib/profiles.js +1 -158
- package/dist/lib/project-doctor.d.ts +0 -29
- package/dist/lib/project-doctor.js +0 -26
- package/dist/lib/project-focus.d.ts +0 -41
- package/dist/lib/project-focus.js +0 -49
- package/dist/lib/project-import.d.ts +0 -41
- package/dist/lib/project-import.js +0 -36
- package/dist/lib/project-key.d.ts +0 -65
- package/dist/lib/project-key.js +0 -70
- package/dist/lib/project-launch.d.ts +0 -49
- package/dist/lib/project-launch.js +2 -91
- package/dist/lib/project-probe.d.ts +0 -86
- package/dist/lib/project-probe.js +1 -88
- package/dist/lib/project-pull.d.ts +0 -96
- package/dist/lib/project-pull.js +0 -122
- package/dist/lib/project-resources.d.ts +0 -44
- package/dist/lib/project-resources.js +7 -171
- package/dist/lib/project-root.d.ts +0 -95
- package/dist/lib/project-root.js +0 -101
- package/dist/lib/project-schedule.d.ts +0 -43
- package/dist/lib/project-schedule.js +0 -52
- package/dist/lib/project-status.d.ts +0 -108
- package/dist/lib/project-status.js +0 -117
- package/dist/lib/projects.d.ts +0 -197
- package/dist/lib/projects.js +1 -213
- package/dist/lib/pwsh.d.ts +0 -41
- package/dist/lib/pwsh.js +0 -51
- package/dist/lib/quick-todo.d.ts +84 -0
- package/dist/lib/quick-todo.js +243 -0
- package/dist/lib/reconcile-and-repair.d.ts +0 -78
- package/dist/lib/reconcile-and-repair.js +3 -66
- package/dist/lib/redact.d.ts +0 -29
- package/dist/lib/redact.js +0 -54
- package/dist/lib/refresh-coordinator.d.ts +0 -15
- package/dist/lib/refresh-coordinator.js +0 -10
- package/dist/lib/refresh.d.ts +0 -39
- package/dist/lib/refresh.js +0 -45
- package/dist/lib/registry.d.ts +0 -65
- package/dist/lib/registry.js +0 -127
- package/dist/lib/reminders.d.ts +0 -21
- package/dist/lib/reminders.js +0 -30
- package/dist/lib/remote-agents-json.d.ts +0 -44
- package/dist/lib/remote-agents-json.js +1 -44
- package/dist/lib/reserved-stores.d.ts +0 -28
- package/dist/lib/reserved-stores.js +2 -28
- package/dist/lib/resource-aliases.d.ts +0 -12
- package/dist/lib/resource-aliases.js +0 -12
- package/dist/lib/resource-content-diff.d.ts +0 -28
- package/dist/lib/resource-content-diff.js +7 -34
- package/dist/lib/resource-inventory.d.ts +0 -51
- package/dist/lib/resource-inventory.js +0 -45
- package/dist/lib/resource-patterns.d.ts +0 -32
- package/dist/lib/resource-patterns.js +0 -34
- package/dist/lib/resource-profiles.d.ts +0 -7
- package/dist/lib/resource-profiles.js +0 -11
- package/dist/lib/resources/types.d.ts +0 -50
- package/dist/lib/resources/types.js +0 -7
- package/dist/lib/resources/workflows.d.ts +0 -7
- package/dist/lib/resources/workflows.js +0 -13
- package/dist/lib/resources.d.ts +0 -65
- package/dist/lib/resources.js +0 -78
- package/dist/lib/routers.d.ts +0 -67
- package/dist/lib/routers.js +0 -60
- package/dist/lib/routine-activation.d.ts +0 -23
- package/dist/lib/routine-activation.js +0 -24
- package/dist/lib/routine-context.d.ts +1 -91
- package/dist/lib/routine-context.js +0 -107
- package/dist/lib/routine-notify-owner.d.ts +0 -79
- package/dist/lib/routine-notify-owner.js +1 -103
- package/dist/lib/routine-notify.d.ts +0 -70
- package/dist/lib/routine-notify.js +1 -75
- package/dist/lib/routine-process-cleanup.d.ts +0 -1
- package/dist/lib/routine-process-cleanup.js +1 -4
- package/dist/lib/routine-readiness.d.ts +0 -63
- package/dist/lib/routine-readiness.js +4 -86
- package/dist/lib/routines-format.d.ts +0 -40
- package/dist/lib/routines-format.js +0 -67
- package/dist/lib/routines-placement.d.ts +0 -33
- package/dist/lib/routines-placement.js +0 -40
- package/dist/lib/routines-project.d.ts +0 -57
- package/dist/lib/routines-project.js +6 -80
- package/dist/lib/rules/compile.d.ts +0 -66
- package/dist/lib/rules/compile.js +6 -118
- package/dist/lib/rules/compose.d.ts +0 -56
- package/dist/lib/rules/compose.js +2 -83
- package/dist/lib/rules/rules.d.ts +0 -23
- package/dist/lib/rules/rules.js +0 -35
- package/dist/lib/rules/run-sync.d.ts +0 -16
- package/dist/lib/rules/run-sync.js +1 -44
- package/dist/lib/run-config.d.ts +0 -7
- package/dist/lib/run-config.js +0 -8
- package/dist/lib/run-defaults.d.ts +0 -14
- package/dist/lib/run-defaults.js +0 -14
- package/dist/lib/run-notify.d.ts +0 -18
- package/dist/lib/run-notify.js +0 -36
- package/dist/lib/run-trace-sync.d.ts +0 -21
- package/dist/lib/run-trace-sync.js +2 -66
- package/dist/lib/rush-session.d.ts +0 -26
- package/dist/lib/rush-session.js +0 -28
- package/dist/lib/sandbox.d.ts +0 -45
- package/dist/lib/sandbox.js +5 -93
- package/dist/lib/scheduler.d.ts +0 -28
- package/dist/lib/scheduler.js +0 -45
- package/dist/lib/scheduling/routines.d.ts +0 -652
- package/dist/lib/scheduling/routines.js +8 -475
- package/dist/lib/secrets-cli.d.ts +3 -8
- package/dist/lib/secrets-cli.js +1 -19
- package/dist/lib/secrets-client.d.ts +0 -117
- package/dist/lib/secrets-client.js +4 -275
- package/dist/lib/secrets-policy.d.ts +0 -142
- package/dist/lib/secrets-policy.js +4 -237
- package/dist/lib/secrets-types.d.ts +0 -87
- package/dist/lib/secrets-types.js +0 -13
- package/dist/lib/self-heal/checks/hook-manifest.js +0 -25
- package/dist/lib/self-heal/checks/hook-runtime.js +0 -5
- package/dist/lib/self-heal/checks/install-staging.d.ts +0 -1
- package/dist/lib/self-heal/checks/install-staging.js +1 -28
- package/dist/lib/self-heal/checks/menubar-helper.js +0 -5
- package/dist/lib/self-heal/checks/path.js +0 -9
- package/dist/lib/self-heal/checks/resources.js +0 -5
- package/dist/lib/self-heal/checks/shadowing.js +2 -16
- package/dist/lib/self-heal/checks/shims.js +0 -14
- package/dist/lib/self-heal/child.d.ts +12 -0
- package/dist/lib/self-heal/child.js +23 -0
- package/dist/lib/self-heal/registry.d.ts +0 -9
- package/dist/lib/self-heal/registry.js +0 -16
- package/dist/lib/self-heal/types.d.ts +0 -12
- package/dist/lib/self-heal/types.js +0 -15
- package/dist/lib/self-update.d.ts +0 -312
- package/dist/lib/self-update.js +9 -360
- package/dist/lib/service-manifest.d.ts +0 -39
- package/dist/lib/service-manifest.js +0 -72
- package/dist/lib/session/active.d.ts +13 -869
- package/dist/lib/session/active.js +20 -976
- package/dist/lib/session/actor-sidecar.d.ts +1 -32
- package/dist/lib/session/actor-sidecar.js +1 -41
- package/dist/lib/session/bookmarks.d.ts +0 -33
- package/dist/lib/session/bookmarks.js +0 -37
- package/dist/lib/session/bundle.d.ts +0 -86
- package/dist/lib/session/bundle.js +0 -54
- package/dist/lib/session/claude-accounts.d.ts +0 -40
- package/dist/lib/session/claude-accounts.js +0 -49
- package/dist/lib/session/cloud.d.ts +1 -23
- package/dist/lib/session/cloud.js +0 -26
- package/dist/lib/session/db.d.ts +5 -658
- package/dist/lib/session/db.js +22 -1443
- package/dist/lib/session/detached.d.ts +0 -21
- package/dist/lib/session/detached.js +0 -29
- package/dist/lib/session/discover.d.ts +3 -420
- package/dist/lib/session/discover.js +30 -1123
- package/dist/lib/session/fork.d.ts +1 -41
- package/dist/lib/session/fork.js +0 -19
- package/dist/lib/session/ghostty-tabs.d.ts +0 -24
- package/dist/lib/session/ghostty-tabs.js +0 -37
- package/dist/lib/session/glance-files.d.ts +19 -0
- package/dist/lib/session/glance-files.js +258 -0
- package/dist/lib/session/hook-sessions.d.ts +0 -33
- package/dist/lib/session/hook-sessions.js +4 -99
- package/dist/lib/session/host-link.d.ts +1 -94
- package/dist/lib/session/host-link.js +1 -81
- package/dist/lib/session/inject-target.d.ts +8 -0
- package/dist/lib/session/inject-target.js +32 -0
- package/dist/lib/session/inject.d.ts +0 -15
- package/dist/lib/session/inject.js +0 -15
- package/dist/lib/session/live-metadata.d.ts +1 -34
- package/dist/lib/session/live-metadata.js +1 -73
- package/dist/lib/session/local-tmux-attach.d.ts +0 -37
- package/dist/lib/session/local-tmux-attach.js +1 -76
- package/dist/lib/session/migrate-targets.d.ts +0 -46
- package/dist/lib/session/migrate-targets.js +0 -24
- package/dist/lib/session/migrations.d.ts +0 -12
- package/dist/lib/session/migrations.js +1 -20
- package/dist/lib/session/mirror.d.ts +0 -32
- package/dist/lib/session/mirror.js +0 -77
- package/dist/lib/session/origin-machine.js +0 -3
- package/dist/lib/session/pid-registry.d.ts +0 -80
- package/dist/lib/session/pid-registry.js +2 -81
- package/dist/lib/session/presence.d.ts +0 -39
- package/dist/lib/session/presence.js +2 -52
- package/dist/lib/session/process-view.d.ts +0 -11
- package/dist/lib/session/process-view.js +0 -25
- package/dist/lib/session/projection.d.ts +0 -3
- package/dist/lib/session/projection.js +0 -10
- package/dist/lib/session/provenance.d.ts +1 -37
- package/dist/lib/session/provenance.js +0 -69
- package/dist/lib/session/recovery.d.ts +1 -40
- package/dist/lib/session/recovery.js +2 -80
- package/dist/lib/session/relative-time.js +0 -3
- package/dist/lib/session/remote/peer-stream.d.ts +0 -42
- package/dist/lib/session/remote/peer-stream.js +0 -64
- package/dist/lib/session/remote/remote-bundle.d.ts +0 -6
- package/dist/lib/session/remote/remote-bundle.js +0 -21
- package/dist/lib/session/remote/remote-list.d.ts +1 -110
- package/dist/lib/session/remote/remote-list.js +5 -139
- package/dist/lib/session/remote/remote.d.ts +0 -98
- package/dist/lib/session/remote/remote.js +3 -119
- package/dist/lib/session/remote/watch.d.ts +1 -34
- package/dist/lib/session/remote/watch.js +12 -72
- package/dist/lib/session/remote-active.d.ts +0 -35
- package/dist/lib/session/remote-active.js +0 -48
- package/dist/lib/session/remote-bundle.js +0 -2
- package/dist/lib/session/remote-list.js +0 -2
- package/dist/lib/session/remote-preview-cache.d.ts +0 -7
- package/dist/lib/session/remote-preview-cache.js +1 -76
- package/dist/lib/session/remote.js +0 -2
- package/dist/lib/session/resume-command.d.ts +0 -6
- package/dist/lib/session/resume-command.js +0 -6
- package/dist/lib/session/resume-owner.d.ts +1 -52
- package/dist/lib/session/resume-owner.js +0 -56
- package/dist/lib/session/run-names.d.ts +0 -24
- package/dist/lib/session/run-names.js +0 -26
- package/dist/lib/session/session-cache.d.ts +2 -146
- package/dist/lib/session/session-cache.js +26 -203
- package/dist/lib/session/short-id.js +0 -3
- package/dist/lib/session/sync/agents.d.ts +0 -68
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/sync/backend.d.ts +0 -39
- package/dist/lib/session/sync/backend.js +0 -45
- package/dist/lib/session/sync/config.d.ts +0 -27
- package/dist/lib/session/sync/config.js +0 -56
- package/dist/lib/session/sync/managed-config.d.ts +0 -17
- package/dist/lib/session/sync/managed-config.js +0 -17
- package/dist/lib/session/sync/managed-key.d.ts +0 -38
- package/dist/lib/session/sync/managed-key.js +1 -42
- package/dist/lib/session/sync/net-client.d.ts +0 -36
- package/dist/lib/session/sync/net-client.js +0 -30
- package/dist/lib/session/sync/provision.d.ts +0 -2
- package/dist/lib/session/sync/provision.js +0 -13
- package/dist/lib/session/sync/r2.d.ts +0 -20
- package/dist/lib/session/sync/r2.js +0 -20
- package/dist/lib/session/sync/transcript-crypto.d.ts +0 -37
- package/dist/lib/session/sync/transcript-crypto.js +5 -37
- package/dist/lib/session/sync/worker-template.d.ts +0 -5
- package/dist/lib/session/sync/worker-template.js +0 -40
- package/dist/lib/session/testdata/pid-registry-namespace.js +0 -2
- package/dist/lib/session/testdata/process-view-private.js +0 -1
- package/dist/lib/session/throughput.d.ts +0 -24
- package/dist/lib/session/throughput.js +1 -25
- package/dist/lib/session/timeline-pass.d.ts +1 -63
- package/dist/lib/session/timeline-pass.js +161 -148
- package/dist/lib/session/title.d.ts +0 -182
- package/dist/lib/session/title.js +0 -139
- package/dist/lib/session/tool-index.d.ts +2 -21
- package/dist/lib/session/tool-index.js +3 -75
- package/dist/lib/session/tool-store.d.ts +2 -48
- package/dist/lib/session/tool-store.js +1 -33
- package/dist/lib/session/viewing-in.d.ts +0 -67
- package/dist/lib/session/viewing-in.js +1 -71
- package/dist/lib/session/watch.js +0 -2
- package/dist/lib/session/width.js +0 -3
- package/dist/lib/sessions-client.d.ts +0 -46
- package/dist/lib/sessions-client.js +36 -113
- package/dist/lib/settings-manifest.d.ts +1 -17
- package/dist/lib/settings-manifest.js +4 -57
- package/dist/lib/setup-tool-install.d.ts +0 -1
- package/dist/lib/setup-tool-install.js +2 -7
- package/dist/lib/setup-tool-status.d.ts +0 -3
- package/dist/lib/setup-tool-status.js +4 -14
- package/dist/lib/sha256-asset.d.ts +0 -20
- package/dist/lib/sha256-asset.js +0 -20
- package/dist/lib/share-runtime.d.ts +0 -10
- package/dist/lib/share-runtime.js +1 -27
- package/dist/lib/shim-heal.d.ts +0 -15
- package/dist/lib/shim-heal.js +3 -29
- package/dist/lib/signin-badge.d.ts +0 -72
- package/dist/lib/signin-badge.js +0 -86
- package/dist/lib/sink-format.d.ts +0 -32
- package/dist/lib/sink-format.js +0 -14
- package/dist/lib/smart-launch.d.ts +0 -75
- package/dist/lib/smart-launch.js +1 -83
- package/dist/lib/snapshot.d.ts +0 -46
- package/dist/lib/snapshot.js +5 -31
- package/dist/lib/spinner.d.ts +0 -37
- package/dist/lib/spinner.js +0 -37
- package/dist/lib/sqlite.d.ts +0 -13
- package/dist/lib/sqlite.js +3 -71
- package/dist/lib/ssh-exec.d.ts +0 -209
- package/dist/lib/ssh-exec.js +11 -248
- package/dist/lib/ssh-tunnel.d.ts +0 -43
- package/dist/lib/ssh-tunnel.js +2 -48
- package/dist/lib/staleness/checkers/commands.d.ts +0 -4
- package/dist/lib/staleness/checkers/commands.js +0 -4
- package/dist/lib/staleness/checkers/hooks.d.ts +0 -10
- package/dist/lib/staleness/checkers/hooks.js +0 -15
- package/dist/lib/staleness/checkers/mcp.d.ts +0 -9
- package/dist/lib/staleness/checkers/mcp.js +0 -9
- package/dist/lib/staleness/checkers/permissions.d.ts +0 -12
- package/dist/lib/staleness/checkers/permissions.js +0 -13
- package/dist/lib/staleness/checkers/plugins.d.ts +0 -8
- package/dist/lib/staleness/checkers/plugins.js +0 -8
- package/dist/lib/staleness/checkers/rules.d.ts +0 -15
- package/dist/lib/staleness/checkers/rules.js +0 -26
- package/dist/lib/staleness/checkers/skills.d.ts +0 -4
- package/dist/lib/staleness/checkers/skills.js +0 -4
- package/dist/lib/staleness/checkers/subagents.d.ts +0 -9
- package/dist/lib/staleness/checkers/subagents.js +0 -9
- package/dist/lib/staleness/checkers/types.d.ts +0 -34
- package/dist/lib/staleness/checkers/types.js +0 -19
- package/dist/lib/staleness/checkers/workflows.d.ts +0 -7
- package/dist/lib/staleness/checkers/workflows.js +0 -7
- package/dist/lib/staleness/detectors/commands.js +0 -17
- package/dist/lib/staleness/detectors/hooks.js +1 -6
- package/dist/lib/staleness/detectors/mcp.js +0 -4
- package/dist/lib/staleness/detectors/permissions.js +10 -23
- package/dist/lib/staleness/detectors/rules.js +0 -6
- package/dist/lib/staleness/detectors/skills.d.ts +0 -1
- package/dist/lib/staleness/detectors/skills.js +1 -12
- package/dist/lib/staleness/detectors/types.d.ts +0 -10
- package/dist/lib/staleness/fingerprint.d.ts +0 -19
- package/dist/lib/staleness/fingerprint.js +0 -35
- package/dist/lib/staleness/index.d.ts +0 -28
- package/dist/lib/staleness/index.js +1 -45
- package/dist/lib/staleness/layers.d.ts +0 -23
- package/dist/lib/staleness/layers.js +0 -34
- package/dist/lib/staleness/prune.d.ts +0 -71
- package/dist/lib/staleness/prune.js +2 -27
- package/dist/lib/staleness/registry.d.ts +0 -10
- package/dist/lib/staleness/registry.js +3 -33
- package/dist/lib/staleness/types.d.ts +0 -36
- package/dist/lib/staleness/types.js +0 -5
- package/dist/lib/staleness/writers/commands.js +1 -48
- package/dist/lib/staleness/writers/hooks.js +1 -18
- package/dist/lib/staleness/writers/kinds.d.ts +0 -12
- package/dist/lib/staleness/writers/kinds.js +0 -1
- package/dist/lib/staleness/writers/lazy-map.d.ts +0 -11
- package/dist/lib/staleness/writers/mcp.js +0 -13
- package/dist/lib/staleness/writers/plugins.js +0 -4
- package/dist/lib/staleness/writers/rules.d.ts +0 -1
- package/dist/lib/staleness/writers/rules.js +1 -19
- package/dist/lib/staleness/writers/skills.js +3 -14
- package/dist/lib/staleness/writers/sources.d.ts +0 -11
- package/dist/lib/staleness/writers/sources.js +0 -32
- package/dist/lib/staleness/writers/subagents.js +0 -16
- package/dist/lib/staleness/writers/types.d.ts +0 -58
- package/dist/lib/staleness/writers/workflows.js +1 -1
- package/dist/lib/standalone-tools.d.ts +24 -0
- package/dist/lib/standalone-tools.js +58 -0
- package/dist/lib/star-nudge.d.ts +0 -32
- package/dist/lib/star-nudge.js +2 -36
- package/dist/lib/startup/command-registry.d.ts +0 -45
- package/dist/lib/startup/command-registry.js +1 -46
- package/dist/lib/startup/dev-build.d.ts +0 -9
- package/dist/lib/startup/dev-build.js +1 -42
- package/dist/lib/startup/root-command.d.ts +0 -19
- package/dist/lib/startup/root-command.js +0 -19
- package/dist/lib/startup/spellcheck.d.ts +0 -13
- package/dist/lib/startup/spellcheck.js +0 -13
- package/dist/lib/state.d.ts +0 -376
- package/dist/lib/state.js +19 -737
- package/dist/lib/storage/selection.d.ts +0 -40
- package/dist/lib/storage/selection.js +0 -29
- package/dist/lib/subagents-registry.d.ts +0 -56
- package/dist/lib/subagents-registry.js +0 -99
- package/dist/lib/subagents.d.ts +0 -128
- package/dist/lib/subagents.js +1 -158
- package/dist/lib/summarizer/config.d.ts +0 -37
- package/dist/lib/summarizer/config.js +0 -37
- package/dist/lib/summarizer/pass.d.ts +0 -20
- package/dist/lib/summarizer/pass.js +1 -34
- package/dist/lib/summarizer/summarize.d.ts +1 -49
- package/dist/lib/summarizer/summarize.js +0 -35
- package/dist/lib/sync-passphrase.d.ts +0 -16
- package/dist/lib/sync-passphrase.js +1 -18
- package/dist/lib/sync-status.d.ts +0 -72
- package/dist/lib/sync-status.js +0 -55
- package/dist/lib/sync-umbrella.d.ts +0 -54
- package/dist/lib/sync-umbrella.js +0 -48
- package/dist/lib/system-run-sync.d.ts +0 -5
- package/dist/lib/system-run-sync.js +1 -31
- package/dist/lib/teams/agents.d.ts +1 -444
- package/dist/lib/teams/agents.js +11 -871
- package/dist/lib/teams/api.d.ts +2 -52
- package/dist/lib/teams/api.js +4 -63
- package/dist/lib/teams/debug.d.ts +0 -6
- package/dist/lib/teams/debug.js +0 -6
- package/dist/lib/teams/delivery.d.ts +0 -41
- package/dist/lib/teams/delivery.js +0 -32
- package/dist/lib/teams/file_ops.d.ts +0 -11
- package/dist/lib/teams/file_ops.js +0 -11
- package/dist/lib/teams/forEach.d.ts +0 -76
- package/dist/lib/teams/forEach.js +1 -77
- package/dist/lib/teams/index.d.ts +1 -7
- package/dist/lib/teams/index.js +1 -7
- package/dist/lib/teams/parsers.d.ts +0 -4
- package/dist/lib/teams/parsers.js +0 -81
- package/dist/lib/teams/persistence.d.ts +0 -2
- package/dist/lib/teams/persistence.js +0 -12
- package/dist/lib/teams/placement-probe.d.ts +0 -12
- package/dist/lib/teams/placement-probe.js +1 -51
- package/dist/lib/teams/pr-watch.d.ts +0 -107
- package/dist/lib/teams/pr-watch.js +1 -107
- package/dist/lib/teams/registry.d.ts +0 -36
- package/dist/lib/teams/registry.js +0 -47
- package/dist/lib/teams/remoteWorktree.d.ts +0 -50
- package/dist/lib/teams/remoteWorktree.js +1 -96
- package/dist/lib/teams/scheduler.d.ts +0 -111
- package/dist/lib/teams/scheduler.js +2 -125
- package/dist/lib/teams/summarizer.d.ts +0 -22
- package/dist/lib/teams/summarizer.js +6 -50
- package/dist/lib/teams/supervisor.d.ts +0 -38
- package/dist/lib/teams/supervisor.js +0 -21
- package/dist/lib/teams/worktree.d.ts +0 -95
- package/dist/lib/teams/worktree.js +3 -110
- package/dist/lib/template.d.ts +0 -21
- package/dist/lib/template.js +0 -23
- package/dist/lib/term-client.d.ts +0 -26
- package/dist/lib/term-client.js +0 -48
- package/dist/lib/term-driver.d.ts +0 -5
- package/dist/lib/term-driver.js +0 -8
- package/dist/lib/terminal/backends/ghostty.d.ts +0 -2
- package/dist/lib/terminal/backends/ghostty.js +0 -12
- package/dist/lib/terminal/backends/index.d.ts +0 -17
- package/dist/lib/terminal/backends/index.js +0 -15
- package/dist/lib/terminal/backends/iterm.d.ts +0 -2
- package/dist/lib/terminal/backends/iterm.js +0 -10
- package/dist/lib/terminal/backends/terminal-app.d.ts +0 -10
- package/dist/lib/terminal/backends/terminal-app.js +0 -30
- package/dist/lib/terminal/backends/tmux.d.ts +0 -10
- package/dist/lib/terminal/backends/tmux.js +0 -2
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +0 -25
- package/dist/lib/terminal/backends/vscodium-agent.js +0 -36
- package/dist/lib/terminal/engine.d.ts +0 -17
- package/dist/lib/terminal/engine.js +0 -10
- package/dist/lib/terminal/index.d.ts +0 -8
- package/dist/lib/terminal/inject.d.ts +1 -173
- package/dist/lib/terminal/inject.js +0 -146
- package/dist/lib/terminal/policy.d.ts +0 -8
- package/dist/lib/terminal/policy.js +0 -1
- package/dist/lib/terminal/preferred.d.ts +0 -66
- package/dist/lib/terminal/preferred.js +0 -30
- package/dist/lib/terminal/quote.d.ts +0 -8
- package/dist/lib/terminal/quote.js +0 -8
- package/dist/lib/terminal/resolve.d.ts +0 -46
- package/dist/lib/terminal/resolve.js +0 -30
- package/dist/lib/terminal/run-surface.d.ts +0 -56
- package/dist/lib/terminal/run-surface.js +0 -46
- package/dist/lib/terminal/shell.d.ts +0 -3
- package/dist/lib/terminal/shell.js +0 -13
- package/dist/lib/terminal/transport.d.ts +0 -14
- package/dist/lib/terminal/transport.js +1 -30
- package/dist/lib/terminal/types.d.ts +0 -40
- package/dist/lib/terminal/types.js +0 -10
- package/dist/lib/text/relative-time.d.ts +0 -36
- package/dist/lib/text/relative-time.js +0 -43
- package/dist/lib/text/short-id.d.ts +0 -16
- package/dist/lib/text/short-id.js +0 -16
- package/dist/lib/text/width.d.ts +0 -24
- package/dist/lib/text/width.js +10 -52
- package/dist/lib/tmux/binary.d.ts +0 -36
- package/dist/lib/tmux/binary.js +0 -28
- package/dist/lib/tmux/index.d.ts +0 -5
- package/dist/lib/tmux/index.js +0 -5
- package/dist/lib/tmux/orphan-reap.d.ts +0 -253
- package/dist/lib/tmux/orphan-reap.js +3 -299
- package/dist/lib/tmux/paths.d.ts +0 -14
- package/dist/lib/tmux/paths.js +0 -14
- package/dist/lib/tmux/session.d.ts +0 -263
- package/dist/lib/tmux/session.js +6 -344
- package/dist/lib/traces/backend.d.ts +0 -20
- package/dist/lib/traces/backend.js +0 -23
- package/dist/lib/traces/classify.d.ts +0 -20
- package/dist/lib/traces/classify.js +0 -7
- package/dist/lib/traces/insights.d.ts +1 -84
- package/dist/lib/traces/insights.js +0 -126
- package/dist/lib/traces/phenotype.d.ts +0 -69
- package/dist/lib/traces/phenotype.js +0 -150
- package/dist/lib/traces/provision.d.ts +0 -1
- package/dist/lib/traces/provision.js +0 -5
- package/dist/lib/traces/schema2-build.d.ts +3 -60
- package/dist/lib/traces/schema2-build.js +8 -149
- package/dist/lib/traces/schema2-danger.d.ts +0 -30
- package/dist/lib/traces/schema2-danger.js +1 -63
- package/dist/lib/traces/schema2.d.ts +0 -11
- package/dist/lib/traces/segments.d.ts +0 -44
- package/dist/lib/traces/segments.js +0 -65
- package/dist/lib/traces/sync.d.ts +2 -239
- package/dist/lib/traces/sync.js +15 -237
- package/dist/lib/traces/worker-template.d.ts +0 -3
- package/dist/lib/traces/worker-template.js +0 -29
- package/dist/lib/triggers/handlers.d.ts +0 -82
- package/dist/lib/triggers/handlers.js +0 -75
- package/dist/lib/triggers/webhook.d.ts +0 -158
- package/dist/lib/triggers/webhook.js +0 -143
- package/dist/lib/types.d.ts +0 -736
- package/dist/lib/types.js +0 -50
- package/dist/lib/uninstall.d.ts +0 -17
- package/dist/lib/uninstall.js +0 -75
- package/dist/lib/usage-backoff.d.ts +0 -20
- package/dist/lib/usage-backoff.js +3 -60
- package/dist/lib/usage-refresh.d.ts +0 -154
- package/dist/lib/usage-refresh.js +8 -212
- package/dist/lib/verbs.d.ts +0 -12
- package/dist/lib/verbs.js +0 -12
- package/dist/lib/version-duplicates.d.ts +0 -6
- package/dist/lib/version-duplicates.js +0 -6
- package/dist/lib/version.d.ts +0 -55
- package/dist/lib/version.js +1 -58
- package/dist/lib/view-types.d.ts +0 -48
- package/dist/lib/warn-unpushed.d.ts +0 -27
- package/dist/lib/warn-unpushed.js +1 -45
- package/dist/lib/watchdog/history.d.ts +0 -1
- package/dist/lib/watchdog/history.js +0 -3
- package/dist/lib/watchdog/index.js +0 -2
- package/dist/lib/watchdog/log.d.ts +0 -19
- package/dist/lib/watchdog/log.js +0 -34
- package/dist/lib/watchdog/read.d.ts +0 -27
- package/dist/lib/watchdog/read.js +3 -58
- package/dist/lib/watchdog/rotate.d.ts +0 -139
- package/dist/lib/watchdog/rotate.js +2 -151
- package/dist/lib/watchdog/runner.d.ts +0 -125
- package/dist/lib/watchdog/runner.js +3 -223
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -2
- package/dist/lib/watchdog/watchdog-agent.d.ts +0 -31
- package/dist/lib/watchdog/watchdog-agent.js +0 -27
- package/dist/lib/watchdog/watchdog.d.ts +0 -7
- package/dist/lib/watchdog/watchdog.js +0 -10
- package/dist/lib/watchdog/watchdogTail.js +0 -7
- package/dist/lib/whats-new.d.ts +0 -10
- package/dist/lib/whats-new.js +0 -21
- package/dist/lib/workflows-registry.d.ts +0 -45
- package/dist/lib/workflows-registry.js +0 -60
- package/dist/lib/workflows.d.ts +0 -282
- package/dist/lib/workflows.js +3 -213
- package/dist/lib/worktree/held.d.ts +0 -89
- package/dist/lib/worktree/held.js +1 -103
- package/dist/lib/wrap.d.ts +0 -32
- package/dist/lib/wrap.js +0 -32
- package/dist/lib/yaml-io.d.ts +0 -45
- package/dist/lib/yaml-io.js +0 -47
- package/dist/session-tracker/dist/adapters/claude.js +0 -2
- package/dist/session-tracker/dist/hook.sh +4 -37
- package/dist/session-tracker/dist/index.d.ts +0 -7
- package/dist/session-tracker/dist/index.js +0 -5
- package/dist/session-tracker/dist/install-hook.d.ts +0 -5
- package/dist/session-tracker/dist/install-hook.js +1 -15
- package/dist/session-tracker/dist/prune-state.js +0 -4
- package/dist/session-tracker/dist/reader.js +0 -4
- package/dist/session-tracker/dist/state-file.d.ts +0 -3
- package/dist/session-tracker/dist/state-file.js +3 -8
- package/package.json +3 -4
- package/scripts/postinstall.js +8 -116
- package/dist/lib/session/artifacts.d.ts +0 -14
- package/dist/lib/session/artifacts.js +0 -101
- package/dist/lib/session/bash-command.d.ts +0 -63
- package/dist/lib/session/bash-command.js +0 -490
- package/dist/lib/session/digest.d.ts +0 -66
- package/dist/lib/session/digest.js +0 -328
- package/dist/lib/session/highlights.d.ts +0 -151
- package/dist/lib/session/highlights.js +0 -391
- package/dist/lib/session/insights.d.ts +0 -165
- package/dist/lib/session/insights.js +0 -563
- package/dist/lib/session/linear.d.ts +0 -19
- package/dist/lib/session/linear.js +0 -99
- package/dist/lib/session/parse.d.ts +0 -224
- package/dist/lib/session/parse.js +0 -2408
- package/dist/lib/session/prompt.d.ts +0 -213
- package/dist/lib/session/prompt.js +0 -601
- package/dist/lib/session/render.d.ts +0 -133
- package/dist/lib/session/render.js +0 -1047
- package/dist/lib/session/share-html.d.ts +0 -35
- package/dist/lib/session/share-html.js +0 -319
- package/dist/lib/session/shell-programs.d.ts +0 -33
- package/dist/lib/session/shell-programs.js +0 -380
- package/dist/lib/session/state.d.ts +0 -269
- package/dist/lib/session/state.js +0 -777
- package/dist/lib/session/stream-render.d.ts +0 -4
- package/dist/lib/session/stream-render.js +0 -131
- package/dist/lib/session/tail.d.ts +0 -88
- package/dist/lib/session/tail.js +0 -294
- package/dist/lib/session/team-filter.d.ts +0 -129
- package/dist/lib/session/team-filter.js +0 -269
- package/dist/lib/session/timeline.d.ts +0 -172
- package/dist/lib/session/timeline.js +0 -636
- package/dist/lib/session/tool-calls.d.ts +0 -132
- package/dist/lib/session/tool-calls.js +0 -674
- package/dist/lib/session/trajectory-compare.d.ts +0 -48
- package/dist/lib/session/trajectory-compare.js +0 -116
- package/dist/lib/session/trajectory-html.d.ts +0 -19
- package/dist/lib/session/trajectory-html.js +0 -771
- package/dist/lib/session/trajectory-lineage.d.ts +0 -93
- package/dist/lib/session/trajectory-lineage.js +0 -195
- package/dist/lib/session/trajectory-text.d.ts +0 -38
- package/dist/lib/session/trajectory-text.js +0 -284
- package/dist/lib/session/trajectory.d.ts +0 -145
- package/dist/lib/session/trajectory.js +0 -394
- package/dist/lib/session/types.d.ts +0 -627
- package/dist/lib/session/types.js +0 -43
package/dist/lib/session/db.js
CHANGED
|
@@ -1,74 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite-backed session index and full-text search.
|
|
3
|
-
*
|
|
4
|
-
* Stores session metadata and user-prompt text in a WAL-mode SQLite database
|
|
5
|
-
* at ~/.agents/.history/sessions/sessions.db. Provides incremental upsert, scan-stamp
|
|
6
|
-
* ledger (mtime/size tracking to skip unchanged files), FTS5 search with
|
|
7
|
-
* BM25 ranking, and label-first search for /rename'd sessions.
|
|
8
|
-
*/
|
|
9
1
|
import * as fs from 'fs';
|
|
10
2
|
import * as path from 'path';
|
|
11
3
|
import Database from '../sqlite.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
4
|
+
import { claudeSubagentFiles, resolvedSubAgentCount } from './glance-files.js';
|
|
5
|
+
import { parseSession, sessionFilePathContainer } from '@phnx-labs/sessions-cli/reader';
|
|
6
|
+
import { extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from '@phnx-labs/sessions-cli/reader';
|
|
14
7
|
import { getSessionsDir, getSessionsDbPath } from '../state.js';
|
|
15
8
|
import { query as queryEvents, queryToolUsageForSessions } from '../feed/events.js';
|
|
16
9
|
import { machineForSessionFile } from '../origin-machine.js';
|
|
17
10
|
import { loadSessionActorIndex, readSessionActorRecord } from './actor-sidecar.js';
|
|
18
|
-
import { scanEventToolCalls } from '
|
|
11
|
+
import { scanEventToolCalls } from '@phnx-labs/sessions-cli/reader';
|
|
19
12
|
import { persistToolCalls, planEventToolResume, purgeToolCalls, toolEvidenceSourcePath } from './tool-store.js';
|
|
20
13
|
import { buildClaudeAccountIndex, resolveClaudeAccount } from './claude-accounts.js';
|
|
21
|
-
import { extractBackgroundShells, extractSkills, extractSlashCommands, harnessTracksBackgroundShells, isSubAgentTool, } from '
|
|
14
|
+
import { extractBackgroundShells, extractSkills, extractSlashCommands, harnessTracksBackgroundShells, isSubAgentTool, } from '@phnx-labs/sessions-cli/reader';
|
|
22
15
|
import { resolveResource } from '../resources.js';
|
|
23
16
|
import { discoverPlugins } from '../plugins/plugins.js';
|
|
24
17
|
import { machineId } from '../machine-id.js';
|
|
25
|
-
import { firstUserMessageFromEvents, lastUserMessageFromEvents } from '
|
|
26
|
-
import { emptyTimelineState, TIMELINE_EXTRACTOR_VERSION } from '
|
|
18
|
+
import { firstUserMessageFromEvents, lastUserMessageFromEvents } from '@phnx-labs/sessions-cli/reader';
|
|
19
|
+
import { emptyTimelineState, TIMELINE_EXTRACTOR_VERSION } from '@phnx-labs/sessions-cli/reader';
|
|
27
20
|
const SESSIONS_DIR = getSessionsDir();
|
|
28
21
|
const DB_PATH = getSessionsDbPath();
|
|
29
|
-
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
30
|
-
* assert against the constant instead of hardcoding a number that every bump
|
|
31
|
-
* then has to chase (docs/sessions.md calls the constant the source of truth). */
|
|
32
22
|
export const SCHEMA_VERSION = 51;
|
|
33
|
-
/**
|
|
34
|
-
* Bump to force the content extractor (assistant-answer text, alongside the
|
|
35
|
-
* user-prompt text every harness already accumulates) to re-derive on every
|
|
36
|
-
* session's next scan. Unlike RESOURCE_INDEX_VERSION this is read by the
|
|
37
|
-
* change-detector itself (`filterChangedEntries`, discover.ts) via
|
|
38
|
-
* `scan_ledger.extractor_version` — a stored version below this one is treated
|
|
39
|
-
* as "changed" even when the file's (mtime, size) are unchanged, so bumping it
|
|
40
|
-
* here backfills every existing session's assistant text on its next scan
|
|
41
|
-
* without a `DELETE FROM scan_ledger` (which would also throw away the
|
|
42
|
-
* resumable parser_state/content_text for Claude/Codex).
|
|
43
|
-
*
|
|
44
|
-
* v3 (PHNX-3621) invalidates OpenCode rows that merged v2 stamped before its
|
|
45
|
-
* genuine first-user-turn extractor was added.
|
|
46
|
-
* v4 (PHNX-3621 leftover) invalidates Grok rows stamped before the bounded
|
|
47
|
-
* chat_history.jsonl prefix read filled firstUserMessage.
|
|
48
|
-
* v5 (PHNX-3939) restores first-meta Codex fork ownership, including cold files.
|
|
49
|
-
* v6 (PHNX-3999) invalidates rows whose stored `label` is harness scaffolding — a
|
|
50
|
-
* `<bash-input>` shell echo, a `<command-name>` wrapper, a bare `/clear` — which
|
|
51
|
-
* `cleanGeneratedSessionLabel` now rejects. The label is written at index time, so
|
|
52
|
-
* without this bump every already-indexed session keeps showing the junk title
|
|
53
|
-
* until its transcript happens to change.
|
|
54
|
-
*/
|
|
55
23
|
export const CONTENT_INDEX_VERSION = 6;
|
|
56
|
-
/**
|
|
57
|
-
* Bump to force `agents sessions backfill resources` to re-derive every
|
|
58
|
-
* session's skill/slash-command tallies on its next run (resource_scan_ledger
|
|
59
|
-
* rows with a lower version are treated as stale — the same mechanism
|
|
60
|
-
* TOOL_INDEX_VERSION gives the tool backfill).
|
|
61
|
-
*/
|
|
62
24
|
const RESOURCE_INDEX_VERSION = 1;
|
|
63
|
-
/**
|
|
64
|
-
* Canonicalize a file path for use as a scan_ledger key. The same physical
|
|
65
|
-
* session file is reachable via multiple aliases — `~/.claude/projects/x.jsonl`
|
|
66
|
-
* (when `~/.claude` is a symlink to a versioned home) and
|
|
67
|
-
* `~/.agents/versions/claude/<v>/home/.claude/projects/x.jsonl`. Keying the
|
|
68
|
-
* ledger by the raw path means switching between these aliases (e.g. via
|
|
69
|
-
* `agents use`) misses the cache and forces a full re-parse. Realpath collapses
|
|
70
|
-
* all aliases to one stable key.
|
|
71
|
-
*/
|
|
72
25
|
function canonicalLedgerKey(filePath) {
|
|
73
26
|
if (!filePath)
|
|
74
27
|
return filePath;
|
|
@@ -79,16 +32,7 @@ function canonicalLedgerKey(filePath) {
|
|
|
79
32
|
return filePath;
|
|
80
33
|
}
|
|
81
34
|
}
|
|
82
|
-
// BM25 column weights for session_text: label > topic > project > content >
|
|
83
|
-
// assistant. Higher weights make matches in that column rank higher.
|
|
84
|
-
// `assistant` (the agent's own answers) is weighted BELOW `content` (the
|
|
85
|
-
// user's prompts): a user's own words are a stronger signal of "this is the
|
|
86
|
-
// session I meant" than the agent echoing/paraphrasing them back, so an
|
|
87
|
-
// assistant-only match still surfaces but ranks behind an equivalent
|
|
88
|
-
// user-prompt match.
|
|
89
|
-
/** BM25 column weights for FTS5: label > topic > project > content > assistant. */
|
|
90
35
|
const BM25_WEIGHTS = [5.0, 2.0, 1.5, 1.0, 0.5];
|
|
91
|
-
/** DDL for the sessions database (tables, indexes, FTS5 virtual table). */
|
|
92
36
|
const SCHEMA = `
|
|
93
37
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
94
38
|
id TEXT PRIMARY KEY,
|
|
@@ -562,40 +506,18 @@ CREATE TABLE IF NOT EXISTS session_remote_preview_cache (
|
|
|
562
506
|
);
|
|
563
507
|
CREATE INDEX IF NOT EXISTS idx_remote_preview_cache_fetched ON session_remote_preview_cache(fetched_at DESC);
|
|
564
508
|
`;
|
|
565
|
-
/**
|
|
566
|
-
* Bumping this invalidates every cached facet row without touching the schema
|
|
567
|
-
* version, so a change to the extraction logic (a new metric, a corrected bucket)
|
|
568
|
-
* re-derives on the next `agents insights` instead of silently reporting stale
|
|
569
|
-
* numbers alongside fresh ones. Same role as RESOURCE_INDEX_VERSION.
|
|
570
|
-
*/
|
|
571
|
-
/** Bump when facet extraction changes so cached rows recompute (shell-command-by-binary v7). */
|
|
572
509
|
export const INSIGHTS_EXTRACTOR_VERSION = 7;
|
|
573
|
-
/** Bump when classifyTopic's output changes so cached topics recompute (human task taxonomy v2). */
|
|
574
510
|
export const SESSION_TOPIC_EXTRACTOR_VERSION = 2;
|
|
575
|
-
// Bumped to 2 (PHNX-2973): the digest now carries `changedFiles` (per-file
|
|
576
|
-
// paths). Bumping invalidates v1 cache rows so a fresh recompute populates the
|
|
577
|
-
// new field instead of serving a stale digest that predates it.
|
|
578
511
|
const PREVIEW_EXTRACTOR_VERSION = 3;
|
|
579
|
-
/** Bump when classifyPhenotype's output changes so cached phenotypes recompute (PHNX-3327 v1). */
|
|
580
512
|
export const SESSION_PHENOTYPE_EXTRACTOR_VERSION = 1;
|
|
581
|
-
/** Bump when the summarizer output shape changes so cached summaries recompute (PHNX-3939 v1). */
|
|
582
513
|
export const SESSION_SUMMARY_EXTRACTOR_VERSION = 1;
|
|
583
514
|
let dbInstance = null;
|
|
584
|
-
/**
|
|
585
|
-
* Apply schema migrations from `fromVersion` → SCHEMA_VERSION. The new
|
|
586
|
-
* `CREATE IF NOT EXISTS` at SCHEMA doesn't help when column sets or FTS
|
|
587
|
-
* column definitions change — those need explicit migration here.
|
|
588
|
-
*/
|
|
589
515
|
function migrateSchema(db, fromVersion) {
|
|
590
516
|
if (fromVersion < 2) {
|
|
591
|
-
// v1 → v2: add `label` column to sessions and switch session_text from
|
|
592
|
-
// single `content` column to multi-column (label, topic, project, content).
|
|
593
517
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
594
518
|
if (!cols.some(c => c.name === 'label')) {
|
|
595
519
|
db.exec(`ALTER TABLE sessions ADD COLUMN label TEXT`);
|
|
596
520
|
}
|
|
597
|
-
// FTS5 virtual tables can't be ALTERed — drop and recreate. Scan ledger
|
|
598
|
-
// is cleared so every file gets re-parsed on next run, repopulating FTS5.
|
|
599
521
|
db.exec(`
|
|
600
522
|
DROP TABLE IF EXISTS session_text;
|
|
601
523
|
CREATE VIRTUAL TABLE session_text USING fts5(
|
|
@@ -610,15 +532,9 @@ function migrateSchema(db, fromVersion) {
|
|
|
610
532
|
`);
|
|
611
533
|
}
|
|
612
534
|
if (fromVersion < 3) {
|
|
613
|
-
// v2 → v3: topic extraction now strips team-spawn wrapper prompts
|
|
614
|
-
// (HEADLESS PLAN MODE prefix + summary suffix). Force a rescan so cached
|
|
615
|
-
// topics like "You are running in HEADLESS PLAN MODE..." get re-extracted.
|
|
616
535
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
617
536
|
}
|
|
618
537
|
if (fromVersion < 4) {
|
|
619
|
-
// v3 → v4: team-origin is now captured structurally from the JSONL
|
|
620
|
-
// `entrypoint` field at scan time. Add the column and force a rescan so
|
|
621
|
-
// every existing Claude session gets its flag populated.
|
|
622
538
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
623
539
|
if (!cols.some(c => c.name === 'is_team_origin')) {
|
|
624
540
|
db.exec(`ALTER TABLE sessions ADD COLUMN is_team_origin INTEGER DEFAULT 0`);
|
|
@@ -626,16 +542,9 @@ function migrateSchema(db, fromVersion) {
|
|
|
626
542
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
627
543
|
}
|
|
628
544
|
if (fromVersion < 5) {
|
|
629
|
-
// v4 → v5: ledger is now keyed by realpath instead of the as-discovered
|
|
630
|
-
// path, so symlink/version-relative aliases for the same physical file
|
|
631
|
-
// collapse to one row. Old aliased rows are dropped — next scan will
|
|
632
|
-
// repopulate under canonical keys.
|
|
633
545
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
634
546
|
}
|
|
635
547
|
if (fromVersion < 6) {
|
|
636
|
-
// v5 → v6: cost ($) and wall-clock duration are now computed at scan time
|
|
637
|
-
// from raw per-model token usage. Add the columns and force a full rescan
|
|
638
|
-
// so every existing session gets its cost_usd / duration_ms populated.
|
|
639
548
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
640
549
|
if (!cols.some(c => c.name === 'cost_usd')) {
|
|
641
550
|
db.exec(`ALTER TABLE sessions ADD COLUMN cost_usd REAL`);
|
|
@@ -646,9 +555,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
646
555
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
647
556
|
}
|
|
648
557
|
if (fromVersion < 7) {
|
|
649
|
-
// v6 → v7: the session-state engine now persists durable signals (PR opened,
|
|
650
|
-
// worktree, tracker ticket) at scan time. Add the columns and force a full
|
|
651
|
-
// rescan so every existing session gets them populated.
|
|
652
558
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
653
559
|
if (!cols.some(c => c.name === 'pr_url'))
|
|
654
560
|
db.exec(`ALTER TABLE sessions ADD COLUMN pr_url TEXT`);
|
|
@@ -661,10 +567,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
661
567
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
662
568
|
}
|
|
663
569
|
if (fromVersion < 8) {
|
|
664
|
-
// v7 → v8: the listing now sorts and labels by last-activity (last message
|
|
665
|
-
// time) instead of creation time. Add the column, seed it to `timestamp` so
|
|
666
|
-
// no row sorts as NULL before the rescan lands, then force a full rescan so
|
|
667
|
-
// every session gets its true last_activity (from lastTsMs) repopulated.
|
|
668
570
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
669
571
|
if (!cols.some(c => c.name === 'last_activity'))
|
|
670
572
|
db.exec(`ALTER TABLE sessions ADD COLUMN last_activity TEXT`);
|
|
@@ -672,21 +574,11 @@ function migrateSchema(db, fromVersion) {
|
|
|
672
574
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
673
575
|
}
|
|
674
576
|
if (fromVersion < 9) {
|
|
675
|
-
// v8 → v9: `agents run --name <slug>` gives a run a durable launch handle,
|
|
676
|
-
// resolvable via `agents sessions <name>`. Additive column; NO rescan — the
|
|
677
|
-
// name is set at run time (host sidecar / run-name sidecar), not parsed from
|
|
678
|
-
// transcripts, so existing rows stay valid with a NULL name.
|
|
679
577
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
680
578
|
if (!cols.some(c => c.name === 'name'))
|
|
681
579
|
db.exec(`ALTER TABLE sessions ADD COLUMN name TEXT`);
|
|
682
580
|
}
|
|
683
581
|
if (fromVersion < 10) {
|
|
684
|
-
// v9 → v10: `name` and `label` unify into a single `label`. `--name` now
|
|
685
|
-
// SEEDS the label at launch (refined later by an agent-generated title)
|
|
686
|
-
// instead of living in a separate immutable `name` column. Fold any existing
|
|
687
|
-
// name into label where the label is empty, mirror it into the FTS row, then
|
|
688
|
-
// drop the redundant column. Seeds re-apply from the run-name sidecars every
|
|
689
|
-
// scan (seedLabelsFromNames), so no rescan is required.
|
|
690
582
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
691
583
|
if (cols.some(c => c.name === 'name')) {
|
|
692
584
|
db.exec(`UPDATE sessions SET label = name
|
|
@@ -697,29 +589,18 @@ function migrateSchema(db, fromVersion) {
|
|
|
697
589
|
}
|
|
698
590
|
}
|
|
699
591
|
if (fromVersion < 11) {
|
|
700
|
-
// v10 → v11: the Claude scanner now captures the ExitPlanMode plan markdown
|
|
701
|
-
// at scan time so `agents sessions --json` can surface it without forcing
|
|
702
|
-
// consumers (the Factory NEEDS-YOU panel) to re-read raw JSONL. Additive
|
|
703
|
-
// column; rescan to backfill.
|
|
704
592
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
705
593
|
if (!cols.some(c => c.name === 'plan'))
|
|
706
594
|
db.exec(`ALTER TABLE sessions ADD COLUMN plan TEXT`);
|
|
707
595
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
708
596
|
}
|
|
709
597
|
if (fromVersion < 12) {
|
|
710
|
-
// v11 → v12: `output_tokens` — the real generated-token count, kept separate
|
|
711
|
-
// from `token_count` (which sums cache-read/-write and so is dominated by
|
|
712
|
-
// cheap re-counted context). This is the honest "output" metric powering
|
|
713
|
-
// `agents insights output`. Additive column; rescan to backfill from transcripts.
|
|
714
598
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
715
599
|
if (!cols.some(c => c.name === 'output_tokens'))
|
|
716
600
|
db.exec(`ALTER TABLE sessions ADD COLUMN output_tokens INTEGER`);
|
|
717
601
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
718
602
|
}
|
|
719
603
|
if (fromVersion < 13) {
|
|
720
|
-
// v12 → v13: routine runs archive their sandboxed transcript into the run
|
|
721
|
-
// directory and get indexed as origin='routine', linked by routine_name and
|
|
722
|
-
// routine_run_id. Existing rows are normal CLI-origin sessions.
|
|
723
604
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
724
605
|
if (!cols.some(c => c.name === 'origin'))
|
|
725
606
|
db.exec(`ALTER TABLE sessions ADD COLUMN origin TEXT DEFAULT 'cli'`);
|
|
@@ -731,12 +612,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
731
612
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
732
613
|
}
|
|
733
614
|
if (fromVersion < 14) {
|
|
734
|
-
// v13 → v14: the discovery scan now short-circuits the readdir + per-file
|
|
735
|
-
// stat of leaf transcript dirs whose (mtime, entry_count) is unchanged,
|
|
736
|
-
// caching that snapshot in the new `dir_ledger` table. Create it, and clear
|
|
737
|
-
// scan_ledger so the first post-upgrade scan does a clean full walk — that
|
|
738
|
-
// walk seeds BOTH ledgers correctly, so a cold/empty dir_ledger degrades to
|
|
739
|
-
// today's full behavior.
|
|
740
615
|
db.exec(`
|
|
741
616
|
CREATE TABLE IF NOT EXISTS dir_ledger (
|
|
742
617
|
dir_path TEXT PRIMARY KEY,
|
|
@@ -748,11 +623,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
748
623
|
`);
|
|
749
624
|
}
|
|
750
625
|
if (fromVersion < 15) {
|
|
751
|
-
// v14 → v15: the Claude scan becomes resumable (B-1). scan_ledger gains a
|
|
752
|
-
// `parser_state` continuation blob (offset + accumulator snapshot) and a
|
|
753
|
-
// `content_text` cache of the accumulated user doc. Add both columns, then
|
|
754
|
-
// clear scan_ledger so the first post-upgrade scan does a clean full walk
|
|
755
|
-
// that reseeds the cursor from byte 0.
|
|
756
626
|
const cols = db.prepare(`PRAGMA table_info(scan_ledger)`).all();
|
|
757
627
|
if (!cols.some(c => c.name === 'parser_state'))
|
|
758
628
|
db.exec(`ALTER TABLE scan_ledger ADD COLUMN parser_state TEXT`);
|
|
@@ -761,20 +631,9 @@ function migrateSchema(db, fromVersion) {
|
|
|
761
631
|
db.exec(`DELETE FROM scan_ledger;`);
|
|
762
632
|
}
|
|
763
633
|
if (fromVersion < 16) {
|
|
764
|
-
// v15 → v16: repair rows poisoned by the empty-shortId bug (now fixed at the
|
|
765
|
-
// source in deriveShortId). A session id that was only a known prefix — a bare
|
|
766
|
-
// `session_` dir, an id of exactly `api-` or `ses_` — derived to '' via
|
|
767
|
-
// `id.replace(prefix, '').slice(0, 8)`, passed the `short_id TEXT NOT NULL`
|
|
768
|
-
// column (empty string is not NULL), yet matched nothing in the
|
|
769
|
-
// `short_id LIKE ?` picker lookups. The parser no longer produces '', but
|
|
770
|
-
// existing rows don't self-heal: an empty short_id is not re-parsed unless its
|
|
771
|
-
// file changes, and an orphaned row (file gone) never re-parses at all. Repair
|
|
772
|
-
// in place — `substr(id, 1, 8)` is non-empty because `id` is the non-empty
|
|
773
|
-
// primary key — so every corrupt row becomes addressable. No rescan needed.
|
|
774
634
|
db.exec(`UPDATE sessions SET short_id = substr(id, 1, 8) WHERE short_id IS NULL OR short_id = ''`);
|
|
775
635
|
}
|
|
776
636
|
if (fromVersion < 17) {
|
|
777
|
-
// v16 → v17 (main): todos / recent dirs / linear project metadata.
|
|
778
637
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
779
638
|
if (!cols.some(c => c.name === 'todos'))
|
|
780
639
|
db.exec(`ALTER TABLE sessions ADD COLUMN todos TEXT`);
|
|
@@ -787,7 +646,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
787
646
|
db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
|
|
788
647
|
}
|
|
789
648
|
if (fromVersion < 18) {
|
|
790
|
-
// v17 → v18: persist origin machine for smart-launch affinity GROUP BY machine.
|
|
791
649
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
792
650
|
if (!cols.some(c => c.name === 'machine')) {
|
|
793
651
|
db.exec(`ALTER TABLE sessions ADD COLUMN machine TEXT`);
|
|
@@ -800,18 +658,11 @@ function migrateSchema(db, fromVersion) {
|
|
|
800
658
|
.prepare(`SELECT id, agent, file_path FROM sessions WHERE machine IS NULL OR machine = ''`)
|
|
801
659
|
.all();
|
|
802
660
|
const upd = db.prepare(`UPDATE sessions SET machine = ? WHERE id = ?`);
|
|
803
|
-
// migrateSchema runs inside getDB's schema transaction, so these writes
|
|
804
|
-
// deliberately share that transaction instead of opening a nested one.
|
|
805
661
|
for (const row of rows) {
|
|
806
662
|
upd.run(machineForSessionFile(row.file_path, row.agent), row.id);
|
|
807
663
|
}
|
|
808
664
|
}
|
|
809
665
|
if (fromVersion < 19) {
|
|
810
|
-
// v18 → v19: actor provenance (RUSH-2018) — who initiated the session and
|
|
811
|
-
// the actor's kind. Populated at write time from the resolved actor (the
|
|
812
|
-
// pid-registry/spawn path), not derivable from the transcript, so there is
|
|
813
|
-
// no ledger wipe here: a rescan can't backfill these, and forcing a full
|
|
814
|
-
// re-parse would be pure churn. Existing rows stay NULL until re-created.
|
|
815
666
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
816
667
|
if (!cols.some(c => c.name === 'actor'))
|
|
817
668
|
db.exec(`ALTER TABLE sessions ADD COLUMN actor TEXT`);
|
|
@@ -819,42 +670,24 @@ function migrateSchema(db, fromVersion) {
|
|
|
819
670
|
db.exec(`ALTER TABLE sessions ADD COLUMN initiated_by TEXT`);
|
|
820
671
|
}
|
|
821
672
|
if (fromVersion < 20) {
|
|
822
|
-
// v19 → v20: persist the transcript's model for the static session list.
|
|
823
673
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
824
674
|
if (!cols.some(c => c.name === 'model'))
|
|
825
675
|
db.exec(`ALTER TABLE sessions ADD COLUMN model TEXT`);
|
|
826
676
|
db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
|
|
827
677
|
}
|
|
828
678
|
if (fromVersion < 21) {
|
|
829
|
-
// v20 → v21: persist the team a session SPAWNED (`agents teams create/add`).
|
|
830
|
-
// The value was already derived at scan time (discover.ts detectSpawnedTeam)
|
|
831
|
-
// and set on SessionMeta, but had no column — so it was dropped at the write
|
|
832
|
-
// and no consumer ever saw it. Wipe BOTH ledgers, not just scan_ledger: with
|
|
833
|
-
// dir_ledger intact, collectChangedFilesInLeafDirs treats every non-live-root
|
|
834
|
-
// dir as unchanged and derives its hot set from the scan stamps just deleted,
|
|
835
|
-
// so archived dirs would never be re-parsed and would stay NULL forever.
|
|
836
679
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
837
680
|
if (!cols.some(c => c.name === 'spawned_team'))
|
|
838
681
|
db.exec(`ALTER TABLE sessions ADD COLUMN spawned_team TEXT`);
|
|
839
682
|
db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
|
|
840
683
|
}
|
|
841
684
|
if (fromVersion < 22) {
|
|
842
|
-
// v21 → v22: persist the transcript's aggregate tool-call count.
|
|
843
685
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
844
686
|
if (!cols.some(c => c.name === 'tool_call_count'))
|
|
845
687
|
db.exec(`ALTER TABLE sessions ADD COLUMN tool_call_count INTEGER`);
|
|
846
688
|
db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
|
|
847
689
|
}
|
|
848
690
|
if (fromVersion < 23) {
|
|
849
|
-
// v22 → v23: persist usedBrowser/usedComputer (#11) so the sessions picker
|
|
850
|
-
// preview can trust a positive detection instead of re-deriving it from a
|
|
851
|
-
// transcript regex on every render. Computed from a sessionId-scoped read
|
|
852
|
-
// of the events log (see detectToolUsage), not from the parsed transcript,
|
|
853
|
-
// so no ledger wipe is needed here — a rescan re-derives them regardless.
|
|
854
|
-
//
|
|
855
|
-
// Deliberately NO DEFAULT (NULL on ALTER, for every pre-existing row):
|
|
856
|
-
// NULL means "not yet computed by this scanner" (a legacy row), distinct
|
|
857
|
-
// from a real, computed 0/false.
|
|
858
691
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
859
692
|
if (!cols.some(c => c.name === 'used_browser'))
|
|
860
693
|
db.exec(`ALTER TABLE sessions ADD COLUMN used_browser INTEGER`);
|
|
@@ -862,9 +695,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
862
695
|
db.exec(`ALTER TABLE sessions ADD COLUMN used_computer INTEGER`);
|
|
863
696
|
}
|
|
864
697
|
if (fromVersion < 24) {
|
|
865
|
-
// v23 → v24: session_resource_usage (#12) — skill/slash-command usage per
|
|
866
|
-
// session, joined against the currently-installed resource/plugin for
|
|
867
|
-
// provenance. No ledger wipe: writeResourceUsage() owns this table.
|
|
868
698
|
db.exec(`
|
|
869
699
|
CREATE TABLE IF NOT EXISTS session_resource_usage (
|
|
870
700
|
session_id TEXT NOT NULL,
|
|
@@ -882,9 +712,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
882
712
|
`);
|
|
883
713
|
}
|
|
884
714
|
if (fromVersion < 25) {
|
|
885
|
-
// v24 → v25: tool-call evidence uses an independent ledger. Do not clear
|
|
886
|
-
// scan_ledger or dir_ledger: normal session listing stays warm, while tool
|
|
887
|
-
// history is filled once on demand.
|
|
888
715
|
db.exec(`
|
|
889
716
|
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
890
717
|
call_key TEXT PRIMARY KEY,
|
|
@@ -933,8 +760,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
933
760
|
`);
|
|
934
761
|
}
|
|
935
762
|
if (fromVersion < 26) {
|
|
936
|
-
// v25 → v26: make append accounting O(changed calls), including empty
|
|
937
|
-
// deltas, instead of reading every historical evidence row per append.
|
|
938
763
|
const callCols = db.prepare(`PRAGMA table_info(tool_calls)`).all();
|
|
939
764
|
if (!callCols.some((column) => column.name === 'evidence_bytes')) {
|
|
940
765
|
db.exec(`ALTER TABLE tool_calls ADD COLUMN evidence_bytes INTEGER NOT NULL DEFAULT 0`);
|
|
@@ -943,15 +768,9 @@ function migrateSchema(db, fromVersion) {
|
|
|
943
768
|
if (!ledgerCols.some((column) => column.name === 'evidence_bytes')) {
|
|
944
769
|
db.exec(`ALTER TABLE tool_scan_ledger ADD COLUMN evidence_bytes INTEGER NOT NULL DEFAULT 0`);
|
|
945
770
|
}
|
|
946
|
-
// The first tool schema existed only in prerelease development builds. Force its tool
|
|
947
|
-
// evidence through one bounded rebuild rather than trusting zeroed totals.
|
|
948
771
|
db.exec(`DELETE FROM tool_scan_ledger`);
|
|
949
772
|
}
|
|
950
773
|
if (fromVersion < 27) {
|
|
951
|
-
// v26 → v27: the original unicode word tokenizer could not prefilter the
|
|
952
|
-
// substring semantics promised by input/output/error queries. Rebuild only
|
|
953
|
-
// the derived FTS table from already-redacted call rows; transcripts and
|
|
954
|
-
// both scan ledgers remain warm.
|
|
955
774
|
db.exec(`
|
|
956
775
|
DROP TABLE IF EXISTS tool_call_text;
|
|
957
776
|
CREATE VIRTUAL TABLE tool_call_text USING fts5(
|
|
@@ -968,9 +787,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
968
787
|
`);
|
|
969
788
|
}
|
|
970
789
|
if (fromVersion < 28) {
|
|
971
|
-
// v27 → v28: retain every static program occurrence instead of only the
|
|
972
|
-
// distinct program set. The source transcript is rebuilt only by the
|
|
973
|
-
// explicit tools backfill; normal session and directory ledgers stay warm.
|
|
974
790
|
db.exec(`
|
|
975
791
|
CREATE TABLE IF NOT EXISTS tool_program_occurrences (
|
|
976
792
|
call_key TEXT NOT NULL,
|
|
@@ -985,9 +801,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
985
801
|
`);
|
|
986
802
|
}
|
|
987
803
|
if (fromVersion < 29) {
|
|
988
|
-
// v28 → v29: coverage and query planning address the independent tool
|
|
989
|
-
// ledger by session id. Rebuild only this derived ledger so a tool query
|
|
990
|
-
// never has to resolve or stat transcript paths.
|
|
991
804
|
db.exec(`
|
|
992
805
|
DROP TABLE tool_scan_ledger;
|
|
993
806
|
CREATE TABLE tool_scan_ledger (
|
|
@@ -1003,9 +816,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
1003
816
|
`);
|
|
1004
817
|
}
|
|
1005
818
|
if (fromVersion < 30) {
|
|
1006
|
-
// v29 → v30: prerelease tool-index builds temporarily used schema versions
|
|
1007
|
-
// later owned by independent main migrations. Repair from the physical
|
|
1008
|
-
// schema because a v29 marker alone cannot prove these columns are present.
|
|
1009
819
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((column) => column.name));
|
|
1010
820
|
if (!cols.has('tool_call_count')) {
|
|
1011
821
|
db.exec(`
|
|
@@ -1020,10 +830,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
1020
830
|
db.exec(`ALTER TABLE sessions ADD COLUMN used_computer INTEGER`);
|
|
1021
831
|
}
|
|
1022
832
|
if (fromVersion < 31) {
|
|
1023
|
-
// v30 → v31: independent ledger for the explicit resource-usage backfill
|
|
1024
|
-
// (agents sessions backfill resources). Do NOT wipe scan_ledger — normal
|
|
1025
|
-
// session listing stays warm; historical resource rows are filled once on
|
|
1026
|
-
// demand, exactly like the tool-index ledger (v25).
|
|
1027
833
|
db.exec(`
|
|
1028
834
|
CREATE TABLE IF NOT EXISTS resource_scan_ledger (
|
|
1029
835
|
session_id TEXT PRIMARY KEY,
|
|
@@ -1037,25 +843,11 @@ function migrateSchema(db, fromVersion) {
|
|
|
1037
843
|
`);
|
|
1038
844
|
}
|
|
1039
845
|
if (fromVersion < 32) {
|
|
1040
|
-
// v31 → v32: persist the effective managed launch mode so resume can
|
|
1041
|
-
// restore the same permission boundary instead of falling back to a CLI
|
|
1042
|
-
// default that may be more or less permissive.
|
|
1043
846
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((column) => column.name));
|
|
1044
847
|
if (!cols.has('mode'))
|
|
1045
848
|
db.exec(`ALTER TABLE sessions ADD COLUMN mode TEXT`);
|
|
1046
849
|
}
|
|
1047
850
|
if (fromVersion < 33) {
|
|
1048
|
-
// v32 → v33: attribute each Claude session to the account that produced it.
|
|
1049
|
-
// Until now `account` held ONE email resolved process-globally and stamped on
|
|
1050
|
-
// every row of a scan, so a machine with several signed-in accounts reported all
|
|
1051
|
-
// of its history under whichever resolved first.
|
|
1052
|
-
//
|
|
1053
|
-
// Do NOT wipe scan_ledger. Attribution is a pure function of (file_path,
|
|
1054
|
-
// version) — both already stored — so existing rows are repaired in place with
|
|
1055
|
-
// no transcript re-parsed. Adding `DELETE FROM scan_ledger` here to match the
|
|
1056
|
-
// other migrations would force a full re-parse of every indexed transcript to
|
|
1057
|
-
// recompute something derivable from two columns. The v31 migration sets the
|
|
1058
|
-
// same precedent.
|
|
1059
851
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((column) => column.name));
|
|
1060
852
|
if (!cols.has('account_key'))
|
|
1061
853
|
db.exec(`ALTER TABLE sessions ADD COLUMN account_key TEXT`);
|
|
@@ -1065,26 +857,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
1065
857
|
backfillClaudeAccounts(db);
|
|
1066
858
|
}
|
|
1067
859
|
if (fromVersion < 34) {
|
|
1068
|
-
// v33 -> v34: claude-opus-5 and claude-sonnet-5 were missing from the pricing
|
|
1069
|
-
// table. `getModelPricing` matches on a dash-bounded prefix, so neither could fall
|
|
1070
|
-
// back to its Claude 4 entry: both resolved to null and every session using them
|
|
1071
|
-
// priced to $0 -- silently, because an unpriced model contributes nothing rather
|
|
1072
|
-
// than raising. On one real index that was 526 sessions, 478 of them the current
|
|
1073
|
-
// default model.
|
|
1074
|
-
//
|
|
1075
|
-
// Adding the prices alone fixes nothing already indexed: cost_usd is computed at
|
|
1076
|
-
// scan time, and the scanner skips any transcript whose (file_mtime_ms, file_size)
|
|
1077
|
-
// is unchanged. Nor can those rows be repaired in place -- the row stores
|
|
1078
|
-
// token_count and output_tokens, not the uncached-input / cache-read / cache-write
|
|
1079
|
-
// split the price table needs -- so the figure has to come from re-reading the
|
|
1080
|
-
// transcript.
|
|
1081
|
-
//
|
|
1082
|
-
// Flush ONLY the affected transcripts, not the whole ledger. A blanket
|
|
1083
|
-
// `DELETE FROM scan_ledger` (what v5 -> v6 did when cost was introduced) would
|
|
1084
|
-
// re-parse every session on the next scan and break the contract the other
|
|
1085
|
-
// migrations here are tested against: adding a column must not invalidate warm
|
|
1086
|
-
// session ledgers. Scoping it to the rows that actually mispriced keeps every
|
|
1087
|
-
// other ledger entry warm and still guarantees the numbers correct themselves.
|
|
1088
860
|
db.exec(`
|
|
1089
861
|
DELETE FROM scan_ledger
|
|
1090
862
|
WHERE file_path IN (
|
|
@@ -1096,35 +868,9 @@ function migrateSchema(db, fromVersion) {
|
|
|
1096
868
|
`);
|
|
1097
869
|
}
|
|
1098
870
|
if (fromVersion < 35) {
|
|
1099
|
-
// v34 -> v35: the default listing sort was `ORDER BY IFNULL(last_activity,
|
|
1100
|
-
// timestamp) DESC` — wrapping the column in IFNULL() makes SQLite unable to
|
|
1101
|
-
// satisfy it from idx_sessions_last_activity, so every list/resume query did
|
|
1102
|
-
// a full table sort instead of an index walk (RUSH-2211). Every upsert path
|
|
1103
|
-
// already writes a non-NULL last_activity (resolveLastActivity falls back to
|
|
1104
|
-
// `timestamp`, itself NOT NULL) — the only rows that can still be NULL here
|
|
1105
|
-
// are ones written before the v8 migration that somehow slipped the backfill,
|
|
1106
|
-
// or seeded directly by a test. Backfill them so the column is unconditionally
|
|
1107
|
-
// NOT NULL, then querySessions can sort on the bare column and use the index.
|
|
1108
871
|
db.exec(`UPDATE sessions SET last_activity = timestamp WHERE last_activity IS NULL`);
|
|
1109
872
|
}
|
|
1110
873
|
if (fromVersion < 36) {
|
|
1111
|
-
// v35 -> v36: make the tool index incremental, and stop paying a full FTS
|
|
1112
|
-
// scan per deleted call.
|
|
1113
|
-
//
|
|
1114
|
-
// (a) tool_scan_ledger gains a resume point (parser_state + parsed_offset).
|
|
1115
|
-
// Existing rows get NULLs, which read as "no resume point": the next
|
|
1116
|
-
// scan of each session re-reads it once from byte 0 and records a resume
|
|
1117
|
-
// point, so every scan after that is incremental. No ledger is wiped.
|
|
1118
|
-
//
|
|
1119
|
-
// (b) tool_call_text is rebuilt so its rowid mirrors tool_calls.rowid. The
|
|
1120
|
-
// old rows were inserted with FTS5-assigned rowids and are only
|
|
1121
|
-
// addressable by the UNINDEXED call_key, i.e. a full index scan per
|
|
1122
|
-
// delete. There is no ALTER for that, and the rowids cannot be repaired
|
|
1123
|
-
// in place, so the table is dropped and repopulated from tool_calls --
|
|
1124
|
-
// the same non-destructive derived-table rebuild v27 did (the source of
|
|
1125
|
-
// truth is tool_calls, which is untouched). The rebuild also lands the
|
|
1126
|
-
// content as one merged segment, which is the compaction
|
|
1127
|
-
// optimizeSessionSearchIndex would otherwise have to do afterwards.
|
|
1128
874
|
const ledgerCols = new Set(db.prepare(`PRAGMA table_info(tool_scan_ledger)`).all()
|
|
1129
875
|
.map((column) => column.name));
|
|
1130
876
|
if (!ledgerCols.has('parser_state'))
|
|
@@ -1147,14 +893,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
1147
893
|
`);
|
|
1148
894
|
}
|
|
1149
895
|
if (fromVersion < 37) {
|
|
1150
|
-
// v36 -> v37: persist the burn SPLIT (uncached input / cache-read /
|
|
1151
|
-
// cache-write) and a second "no-cache" cost per session, so `agents insights output`
|
|
1152
|
-
// can report the token split and a --pricing no-cache scenario (RUSH-2287).
|
|
1153
|
-
// These are new nullable columns — do NOT flush scan_ledger (adding a column
|
|
1154
|
-
// must keep warm session ledgers warm, the contract the v33->v34 note above
|
|
1155
|
-
// states). Pre-upgrade rows stay NULL for the split until their transcript is
|
|
1156
|
-
// re-scanned; `agents insights output` reports the split only where it is present, so
|
|
1157
|
-
// an absent split reads as "not available for this session", never as zero.
|
|
1158
896
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1159
897
|
if (!cols.has('input_tokens'))
|
|
1160
898
|
db.exec(`ALTER TABLE sessions ADD COLUMN input_tokens INTEGER`);
|
|
@@ -1166,23 +904,11 @@ function migrateSchema(db, fromVersion) {
|
|
|
1166
904
|
db.exec(`ALTER TABLE sessions ADD COLUMN cost_usd_nocache REAL`);
|
|
1167
905
|
}
|
|
1168
906
|
if (fromVersion < 38) {
|
|
1169
|
-
// v37 -> v38: archived_at (RUSH-2436). Makes the local DB authoritative for
|
|
1170
|
-
// content: a session whose transcript file is gone but whose user turns still
|
|
1171
|
-
// live in session_text is kept (flagged archived) instead of dropped from
|
|
1172
|
-
// listings. New nullable column, so no ledger flush — it is populated lazily
|
|
1173
|
-
// by querySessions the first time it confirms a scanned file is gone.
|
|
1174
907
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
1175
908
|
if (!cols.some((c) => c.name === 'archived_at'))
|
|
1176
909
|
db.exec(`ALTER TABLE sessions ADD COLUMN archived_at INTEGER`);
|
|
1177
910
|
}
|
|
1178
911
|
if (fromVersion < 39) {
|
|
1179
|
-
// v38 -> v39: durable tool-session metadata (RUSH-2549). Browser task identity
|
|
1180
|
-
// lived only in the daemon's tasks.json, which is rewritten from the live task
|
|
1181
|
-
// map -- so stopping a task erased the link to the agent session that drove it
|
|
1182
|
-
// and every finished task listed as "unlinked". Computer-use had the identity
|
|
1183
|
-
// right but wrote it to the 7-day event ledger, so its history expired instead.
|
|
1184
|
-
// Both now persist metadata here. Pure additions, so no ledger flush: nothing
|
|
1185
|
-
// already indexed is invalidated, and the tables populate going forward.
|
|
1186
912
|
db.exec(`
|
|
1187
913
|
CREATE TABLE IF NOT EXISTS browser_sessions (
|
|
1188
914
|
task TEXT NOT NULL,
|
|
@@ -1220,19 +946,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
1220
946
|
`);
|
|
1221
947
|
}
|
|
1222
948
|
if (fromVersion < 40) {
|
|
1223
|
-
// v39 -> v40: persist the fan-out a session left behind — sub-agents spawned
|
|
1224
|
-
// and shells backgrounded (RUSH-3091/3095). Both were recomputed per render
|
|
1225
|
-
// from the transcript, so a REMOTE or unindexed row (rendered from
|
|
1226
|
-
// SessionMeta alone by sessions-picker's formatMetaOnlyBody, which has no
|
|
1227
|
-
// events to derive from) showed neither. Columns are what make them
|
|
1228
|
-
// renderable there.
|
|
1229
|
-
//
|
|
1230
|
-
// New nullable columns, so no ledger flush — the v33->v34 contract that
|
|
1231
|
-
// adding a column keeps warm session ledgers warm. Pre-upgrade rows stay
|
|
1232
|
-
// NULL until re-scanned, and NULL is deliberately distinct from 0: NULL is
|
|
1233
|
-
// "not computed / harness cannot report it", 0 is "scanned, none found".
|
|
1234
|
-
// The renders omit the segment for both, so neither can be misread as a
|
|
1235
|
-
// positive "nothing is running" claim.
|
|
1236
949
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1237
950
|
if (!cols.has('sub_agent_count'))
|
|
1238
951
|
db.exec(`ALTER TABLE sessions ADD COLUMN sub_agent_count INTEGER`);
|
|
@@ -1241,32 +954,11 @@ function migrateSchema(db, fromVersion) {
|
|
|
1241
954
|
}
|
|
1242
955
|
}
|
|
1243
956
|
if (fromVersion < 41) {
|
|
1244
|
-
// v40 -> v41: persist the custom-harness / profile name a run was launched
|
|
1245
|
-
// as (PHNX-2935). Transcript discovery still keys `agent` on the HOST
|
|
1246
|
-
// (the file lives under the host's session dir), so without this column
|
|
1247
|
-
// `agents sessions` cannot tell `agents run deepseek` from a native claude
|
|
1248
|
-
// run. Additive, no ledger flush — the name is launch metadata joined from
|
|
1249
|
-
// the actor sidecar, not parsed from the transcript. Pre-upgrade rows stay
|
|
1250
|
-
// NULL (native / unknown) until a sidecar-backed rescan fills them.
|
|
1251
957
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1252
958
|
if (!cols.has('harness'))
|
|
1253
959
|
db.exec(`ALTER TABLE sessions ADD COLUMN harness TEXT`);
|
|
1254
960
|
}
|
|
1255
961
|
if (fromVersion < 42) {
|
|
1256
|
-
// v41 -> v42: index the agent's ANSWERS, not just the user's prompts.
|
|
1257
|
-
// session_text gains an `assistant` column (own FTS5 column, own lower
|
|
1258
|
-
// BM25 weight — see BM25_WEIGHTS) and scan_ledger gains `extractor_version`
|
|
1259
|
-
// so the change-detector can force a re-extract independent of
|
|
1260
|
-
// (mtime, size). FTS5 can't ALTER a virtual table's column set, but unlike
|
|
1261
|
-
// v1->v2 (which dropped the table and forced a blind full rescan of
|
|
1262
|
-
// everything), the existing label/topic/project/content in every row is
|
|
1263
|
-
// still exactly right — only `assistant` is missing. Rename the old table
|
|
1264
|
-
// out of the way, create the new 6-column one, and copy the old rows back
|
|
1265
|
-
// in (assistant defaults to '' until the extractor_version lever backfills
|
|
1266
|
-
// it on that row's next scan) — search over label/topic/project/content
|
|
1267
|
-
// never blacks out for the transient window it takes existing sessions to
|
|
1268
|
-
// get rescanned, which can be a long time for a session whose transcript
|
|
1269
|
-
// is otherwise cold.
|
|
1270
962
|
db.exec(`ALTER TABLE session_text RENAME TO session_text_v41`);
|
|
1271
963
|
db.exec(`
|
|
1272
964
|
CREATE VIRTUAL TABLE session_text USING fts5(
|
|
@@ -1288,52 +980,15 @@ function migrateSchema(db, fromVersion) {
|
|
|
1288
980
|
if (!ledgerCols.some(c => c.name === 'extractor_version')) {
|
|
1289
981
|
db.exec(`ALTER TABLE scan_ledger ADD COLUMN extractor_version INTEGER`);
|
|
1290
982
|
}
|
|
1291
|
-
// No `DELETE FROM scan_ledger` — the new column is NULL on every existing
|
|
1292
|
-
// row, which already never equals CONTENT_INDEX_VERSION, so every session
|
|
1293
|
-
// re-extracts on its next scan while parser_state/content_text (the
|
|
1294
|
-
// Claude/Codex resumable continuation) stay intact for rows that don't
|
|
1295
|
-
// need a full reparse for any OTHER reason.
|
|
1296
983
|
}
|
|
1297
984
|
if (fromVersion < 43) {
|
|
1298
|
-
// v42 -> v43: persist a per-tool-call END timestamp (PHNX-3437). The traces
|
|
1299
|
-
// insight engine could only book a failed call's wasted time from the
|
|
1300
|
-
// bounded gap to the NEXT call — so a call that BLOCKED for minutes and was
|
|
1301
|
-
// the last in its session (or was followed quickly by an unrelated call)
|
|
1302
|
-
// registered as ~0 waste. The call's result record already carried its own
|
|
1303
|
-
// timestamp at ingestion; this column persists it so `end_timestamp -
|
|
1304
|
-
// timestamp` (the call's own blocking duration) becomes the primary
|
|
1305
|
-
// attribution, with the gap heuristic kept as the fallback for NULL rows.
|
|
1306
|
-
//
|
|
1307
|
-
// Additive, nullable column — no ledger flush (the v33->v34 contract that
|
|
1308
|
-
// adding a column keeps warm session ledgers warm). Pre-upgrade rows stay
|
|
1309
|
-
// NULL until re-indexed, and `insights.ts` degrades a NULL end back to the
|
|
1310
|
-
// bounded-gap behavior, so nothing crashes or yields NaN. The paired
|
|
1311
|
-
// TOOL_INDEX_VERSION bump (7 -> 8) is what re-derives it on a re-index; the
|
|
1312
|
-
// tool index is deliberately independent of SCHEMA_VERSION and is never
|
|
1313
|
-
// force-rescanned by a migration (only by the explicit tool backfill or a
|
|
1314
|
-
// normal incremental append), so a bare ALTER here would otherwise leave
|
|
1315
|
-
// existing rows dark forever.
|
|
1316
985
|
const cols = new Set(db.prepare(`PRAGMA table_info(tool_calls)`).all().map((c) => c.name));
|
|
1317
986
|
if (!cols.has('end_timestamp'))
|
|
1318
987
|
db.exec(`ALTER TABLE tool_calls ADD COLUMN end_timestamp TEXT`);
|
|
1319
988
|
}
|
|
1320
989
|
if (fromVersion < 44) {
|
|
1321
|
-
// v43 -> v44: backfill duration_ms for sessions whose harness scan extractor
|
|
1322
|
-
// never derived it (PHNX-3457). rush/grok/kimi/cursor/muse/antigravity/hermes/
|
|
1323
|
-
// openclaw left duration_ms NULL — 52% of the corpus, 100% of rush — so the
|
|
1324
|
-
// console median was computed over only the ~48% that carried it, skewing it
|
|
1325
|
-
// short. Going forward resolveDurationMs() populates it at every upsert; this
|
|
1326
|
-
// repairs already-indexed rows in place from the timestamps they already store
|
|
1327
|
-
// (last_activity, itself resolved from the last-message time else file mtime,
|
|
1328
|
-
// minus the creation timestamp), so no transcript is re-parsed. julianday is
|
|
1329
|
-
// avoided because it does not accept a trailing 'Z'; the arithmetic is done in
|
|
1330
|
-
// JS with the exact Date.parse resolveDurationMs uses, keeping the backfill and
|
|
1331
|
-
// the live path consistent. Only rows with a positive span are touched; a NULL
|
|
1332
|
-
// that cannot be resolved stays NULL rather than becoming a fabricated 0.
|
|
1333
990
|
const nullDurationRows = db.prepare(`SELECT id, timestamp, last_activity FROM sessions
|
|
1334
991
|
WHERE duration_ms IS NULL AND last_activity IS NOT NULL`).all();
|
|
1335
|
-
// Runs inside migrateSchema's own transaction (db.ts:1468), so no nested
|
|
1336
|
-
// db.transaction() here — that would raise "transaction within a transaction".
|
|
1337
992
|
const update = db.prepare(`UPDATE sessions SET duration_ms = ? WHERE id = ?`);
|
|
1338
993
|
for (const row of nullDurationRows) {
|
|
1339
994
|
const startMs = Date.parse(row.timestamp);
|
|
@@ -1344,69 +999,33 @@ function migrateSchema(db, fromVersion) {
|
|
|
1344
999
|
}
|
|
1345
1000
|
}
|
|
1346
1001
|
if (fromVersion < 45) {
|
|
1347
|
-
// v44 -> v45: retain the full first genuine user turn separately from the
|
|
1348
|
-
// one-line topic. The CONTENT_INDEX_VERSION bump re-parses every transcript
|
|
1349
|
-
// through its normal incremental scan path so existing rows backfill without
|
|
1350
|
-
// a second consumer-side transcript reader.
|
|
1351
1002
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1352
1003
|
if (!cols.has('first_user_message'))
|
|
1353
1004
|
db.exec(`ALTER TABLE sessions ADD COLUMN first_user_message TEXT`);
|
|
1354
1005
|
}
|
|
1355
1006
|
if (fromVersion < 46) {
|
|
1356
|
-
// v45 -> v46: mirror provenance for fleet-synced peer session digests
|
|
1357
|
-
// (PHNX-3792). Additive columns, no ledger wipe: they are written only by the
|
|
1358
|
-
// session-mirror consume path (never a transcript scan), so re-parsing every
|
|
1359
|
-
// indexed transcript would be pure churn — a genuine local row keeps them NULL.
|
|
1360
1007
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1361
1008
|
if (!cols.has('mirror_synced_at'))
|
|
1362
1009
|
db.exec(`ALTER TABLE sessions ADD COLUMN mirror_synced_at INTEGER`);
|
|
1363
1010
|
if (!cols.has('mirror_source'))
|
|
1364
1011
|
db.exec(`ALTER TABLE sessions ADD COLUMN mirror_source TEXT`);
|
|
1365
|
-
// The idx_sessions_mirror_synced index is created unconditionally after this
|
|
1366
|
-
// block (fresh DBs skip migrations), alongside idx_sessions_last_activity.
|
|
1367
1012
|
}
|
|
1368
1013
|
if (fromVersion < 47) {
|
|
1369
|
-
// v46 -> v47: carry the actor's Phoenix id (PHNX-3798). Additive, write-once
|
|
1370
|
-
// launch metadata joined from the actor sidecar — never transcript-derived,
|
|
1371
|
-
// exactly like actor/initiated_by (v19) — so there is no ledger wipe: a rescan
|
|
1372
|
-
// can't backfill it and forcing a full re-parse would be pure churn. Existing
|
|
1373
|
-
// rows stay NULL until the sidecar-join fills them (the ON CONFLICT COALESCEs).
|
|
1374
1014
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1375
1015
|
if (!cols.has('phoenix_id'))
|
|
1376
1016
|
db.exec(`ALTER TABLE sessions ADD COLUMN phoenix_id TEXT`);
|
|
1377
1017
|
}
|
|
1378
1018
|
if (fromVersion < 48) {
|
|
1379
|
-
// v47 -> v48: retain the LATEST genuine user turn beside the first
|
|
1380
|
-
// (PHNX-3939). For a /continue, a redirect, or an interrupted-and-restated
|
|
1381
|
-
// session the operative request is the last thing the user said, and the row
|
|
1382
|
-
// title / sidebar Request card show that.
|
|
1383
|
-
//
|
|
1384
|
-
// Deliberately WITHOUT a CONTENT_INDEX_VERSION bump, unlike the v45
|
|
1385
|
-
// first_user_message column. That bump forces a re-parse of every indexed
|
|
1386
|
-
// transcript on the next scan; measured on this fleet it pinned the daemon
|
|
1387
|
-
// long enough to blow the session-index and session-state tick deadlines and
|
|
1388
|
-
// park both services. It also buys nothing here: the value can only CHANGE
|
|
1389
|
-
// when the transcript changes, so the ordinary incremental scan fills it in
|
|
1390
|
-
// for exactly the sessions where it matters, and the sidebar's request comes
|
|
1391
|
-
// from the daemon-folded `session_timelines` projection rather than this
|
|
1392
|
-
// column. An old row simply keeps NULL until its next write.
|
|
1393
1019
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1394
1020
|
if (!cols.has('last_user_message'))
|
|
1395
1021
|
db.exec(`ALTER TABLE sessions ADD COLUMN last_user_message TEXT`);
|
|
1396
1022
|
}
|
|
1397
1023
|
if (fromVersion < 49) {
|
|
1398
|
-
// v48 -> v49: account-first storage (PHNX-3940).
|
|
1399
1024
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map(c => c.name));
|
|
1400
1025
|
if (!cols.has('account_id'))
|
|
1401
1026
|
db.exec(`ALTER TABLE sessions ADD COLUMN account_id TEXT`);
|
|
1402
1027
|
}
|
|
1403
1028
|
if (fromVersion < 50) {
|
|
1404
|
-
// v49 -> v50: the daemon-generated session title (PHNX-3797). Additive
|
|
1405
|
-
// columns, no ledger wipe: nothing in a transcript produces them — the
|
|
1406
|
-
// session-title service writes them and the scanner's upsert never names
|
|
1407
|
-
// them — so re-parsing every indexed transcript would be pure churn. Rows
|
|
1408
|
-
// stay NULL until the titler reaches them, which the recap ladder handles by
|
|
1409
|
-
// falling back to the user's own first message.
|
|
1410
1029
|
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1411
1030
|
if (!cols.has('generated_title'))
|
|
1412
1031
|
db.exec(`ALTER TABLE sessions ADD COLUMN generated_title TEXT`);
|
|
@@ -1416,18 +1035,6 @@ function migrateSchema(db, fromVersion) {
|
|
|
1416
1035
|
db.exec(`ALTER TABLE sessions ADD COLUMN generated_title_at INTEGER`);
|
|
1417
1036
|
}
|
|
1418
1037
|
if (fromVersion < 51) {
|
|
1419
|
-
// v50 -> v51: session_text rows move to the rowid of the sessions row they
|
|
1420
|
-
// describe (PHNX-4154), the repair v36 made to tool_call_text. The only key
|
|
1421
|
-
// was the UNINDEXED session_id, so every delete and content read was a full
|
|
1422
|
-
// scan of the FTS content — 669 MB and 0.2-0.5 s per statement on a real
|
|
1423
|
-
// box — and the mirror ingest issues one per mirrored row (2,298 there)
|
|
1424
|
-
// inside a daemon tick: the event loop froze for minutes, every service
|
|
1425
|
-
// blew its deadline, and browser verbs timed out behind it.
|
|
1426
|
-
//
|
|
1427
|
-
// Same shape as v42: rename, recreate, copy back keyed by sessions.rowid,
|
|
1428
|
-
// drop — search never blacks out. OR REPLACE keeps the newest of any
|
|
1429
|
-
// duplicate session_id rows. A text row whose session is gone has no rowid
|
|
1430
|
-
// to sit at and is dropped; nothing could reach it anyway.
|
|
1431
1038
|
db.exec(`ALTER TABLE session_text RENAME TO session_text_v50`);
|
|
1432
1039
|
db.exec(`
|
|
1433
1040
|
CREATE VIRTUAL TABLE session_text USING fts5(
|
|
@@ -1449,17 +1056,7 @@ function migrateSchema(db, fromVersion) {
|
|
|
1449
1056
|
db.exec(`DROP TABLE session_text_v50`);
|
|
1450
1057
|
}
|
|
1451
1058
|
}
|
|
1452
|
-
/**
|
|
1453
|
-
* Stamp `account_key` / `account_org` / `account` on every Claude row from its
|
|
1454
|
-
* `file_path` and recorded `version`. Used by the v33 migration; idempotent, so it is
|
|
1455
|
-
* safe to re-run.
|
|
1456
|
-
*/
|
|
1457
1059
|
function backfillClaudeAccounts(db, scope = 'all') {
|
|
1458
|
-
// 'unresolved' exists so the getDB repair touches ONLY rows that are actually
|
|
1459
|
-
// broken. Re-resolving every Claude row on an unrelated trigger would silently
|
|
1460
|
-
// downgrade a correct row whose version home has since been uninstalled and its
|
|
1461
|
-
// trash snapshot pruned — the row would go from attributed to dark for no reason
|
|
1462
|
-
// the user caused. The migration wants 'all'; the repair does not.
|
|
1463
1060
|
const where = scope === 'all'
|
|
1464
1061
|
? `agent = 'claude'`
|
|
1465
1062
|
: `agent = 'claude' AND (account_key IS NULL
|
|
@@ -1468,44 +1065,23 @@ function backfillClaudeAccounts(db, scope = 'all') {
|
|
|
1468
1065
|
const rows = db.prepare(`SELECT id, file_path, version FROM sessions WHERE ${where}`).all();
|
|
1469
1066
|
if (rows.length === 0)
|
|
1470
1067
|
return;
|
|
1471
|
-
// `account` is overwritten, not COALESCEd. Every pre-v33 row carries the wrong
|
|
1472
|
-
// globally-resolved email; keeping it on a row we could not attribute would leave a
|
|
1473
|
-
// known-false address on display (commands/sessions.ts prints it, and its fuzzy
|
|
1474
|
-
// matcher scores on it) and would disagree with the scan path, which writes
|
|
1475
|
-
// `account = excluded.account` unconditionally. A dark row reads NULL.
|
|
1476
1068
|
const update = db.prepare(`UPDATE sessions SET account_key = ?, account_org = ?, account = ? WHERE id = ?`);
|
|
1477
1069
|
for (const row of rows) {
|
|
1478
1070
|
const bucket = resolveClaudeAccount(index, row.file_path ?? '', row.version, readSessionActorRecord(row.id)?.accountId);
|
|
1479
1071
|
update.run(bucket.key, bucket.orgName, bucket.email, row.id);
|
|
1480
1072
|
}
|
|
1481
1073
|
}
|
|
1482
|
-
/** Open (or return the cached) sessions database, applying migrations as needed. */
|
|
1483
1074
|
export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
1484
1075
|
if (dbInstance)
|
|
1485
1076
|
return dbInstance;
|
|
1486
1077
|
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
1487
1078
|
const db = new Database(DB_PATH);
|
|
1488
|
-
// Wait up to 30s instead of failing immediately on SQLITE_BUSY. Install the
|
|
1489
|
-
// handler before journal_mode: concurrent first opens can race for its schema
|
|
1490
|
-
// lock, before a later busy_timeout would have a chance to wait. Multiple
|
|
1491
|
-
// agents (CLIs, skills, hooks) open this DB concurrently. The first scan of
|
|
1492
|
-
// a new version home can take longer than 10s; concurrent callers need enough
|
|
1493
|
-
// headroom to wait. The ledger-recheck in upsertSessionsBatch makes
|
|
1494
|
-
// subsequent writers near-instant, so 30s is a rarely-reached safety net.
|
|
1495
1079
|
try {
|
|
1496
1080
|
db.pragma(`busy_timeout = ${Math.max(0, Math.trunc(initialBusyTimeoutMs))}`);
|
|
1497
1081
|
db.pragma('journal_mode = WAL');
|
|
1498
1082
|
db.pragma('synchronous = NORMAL');
|
|
1499
1083
|
db.pragma('temp_store = MEMORY');
|
|
1500
1084
|
db.exec(SCHEMA);
|
|
1501
|
-
// `session_remote_preview_cache` is a lazy cache table (like the others
|
|
1502
|
-
// below), independent of SCHEMA_VERSION — but it shipped once already
|
|
1503
|
-
// without `last_caller_revision` before this column was added, so a DB
|
|
1504
|
-
// that already ran that earlier version has the table WITHOUT the column,
|
|
1505
|
-
// and `CREATE TABLE IF NOT EXISTS` above is a no-op against it. Guard with
|
|
1506
|
-
// the same PRAGMA-table_info pattern the versioned `sessions` migrations
|
|
1507
|
-
// use, so an existing cache DB gains the column instead of every read/write
|
|
1508
|
-
// throwing "no such column".
|
|
1509
1085
|
const remotePreviewCacheCols = db.prepare(`PRAGMA table_info(session_remote_preview_cache)`).all();
|
|
1510
1086
|
if (!remotePreviewCacheCols.some(c => c.name === 'last_caller_revision')) {
|
|
1511
1087
|
db.exec(`ALTER TABLE session_remote_preview_cache ADD COLUMN last_caller_revision TEXT`);
|
|
@@ -1519,8 +1095,6 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
|
1519
1095
|
db.prepare(`INSERT OR IGNORE INTO meta(key, value) VALUES ('schema_version', ?)`).run(String(SCHEMA_VERSION));
|
|
1520
1096
|
}
|
|
1521
1097
|
else if (currentVersion < SCHEMA_VERSION) {
|
|
1522
|
-
// Re-read after BEGIN IMMEDIATE acquires the writer lock. A second process
|
|
1523
|
-
// may have completed the migration while this connection was waiting.
|
|
1524
1098
|
const migrate = db.transaction(() => {
|
|
1525
1099
|
const lockedVersion = readSchemaVersion();
|
|
1526
1100
|
if (lockedVersion === undefined || lockedVersion >= SCHEMA_VERSION)
|
|
@@ -1530,21 +1104,12 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
|
1530
1104
|
});
|
|
1531
1105
|
migrate();
|
|
1532
1106
|
}
|
|
1533
|
-
// Index last_activity only after the column is guaranteed to exist — fresh DBs
|
|
1534
|
-
// get it from CREATE TABLE above, existing pre-v8 DBs from the migration just
|
|
1535
|
-
// run. It must NOT live in SCHEMA (executed before migration) or an existing
|
|
1536
|
-
// DB would fail the index build on a column it doesn't have yet.
|
|
1537
1107
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_last_activity ON sessions(last_activity DESC)`);
|
|
1538
1108
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_origin ON sessions(origin)`);
|
|
1539
|
-
// Same fresh-vs-migrated rule: the column is guaranteed above (fresh from
|
|
1540
|
-
// CREATE TABLE, existing from migration v46), so index the mirror pruner's
|
|
1541
|
-
// scan column here rather than in SCHEMA (PHNX-3792).
|
|
1542
1109
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_mirror_synced ON sessions(mirror_synced_at)`);
|
|
1543
1110
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_routine_run_id ON sessions(routine_run_id)`);
|
|
1544
1111
|
const sessionColumns = db.prepare('PRAGMA table_info(sessions)').all();
|
|
1545
1112
|
if (['account_id', 'phoenix_id'].some(name => !sessionColumns.some(column => column.name === name))) {
|
|
1546
|
-
// Partial upgrades can stamp the current version before every column exists.
|
|
1547
|
-
// Recheck under the writer lock so concurrent openers cannot add it twice.
|
|
1548
1113
|
db.transaction(() => {
|
|
1549
1114
|
const columns = db.prepare('PRAGMA table_info(sessions)').all();
|
|
1550
1115
|
if (!columns.some(column => column.name === 'account_id'))
|
|
@@ -1553,18 +1118,7 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
|
1553
1118
|
db.exec('ALTER TABLE sessions ADD COLUMN phoenix_id TEXT');
|
|
1554
1119
|
})();
|
|
1555
1120
|
}
|
|
1556
|
-
// Account attribution repair. Two ways a Claude row ends up wrong even at v33:
|
|
1557
|
-
// an older CLI (whose INSERT does not name the column) writes NULL, and a DB
|
|
1558
|
-
// migrated by a build that predates the "clear the stale email on a dark row" fix
|
|
1559
|
-
// keeps a known-wrong address. The v33 migration cannot fix either — it never runs
|
|
1560
|
-
// again. Cheap guard first so the common case is one indexed lookup, then repair.
|
|
1561
|
-
// Same shape as the `machine` repair below, for the same reason.
|
|
1562
1121
|
{
|
|
1563
|
-
// Column guard FIRST, like the `machine` repair below. schema_version can be
|
|
1564
|
-
// stamped at SCHEMA_VERSION without the column existing — getDB writes the marker
|
|
1565
|
-
// for any DB whose meta has no row (a hand-built or partially-created index), and
|
|
1566
|
-
// migrateSchema never runs in that path. Querying account_key unguarded would
|
|
1567
|
-
// then throw "no such column" and take down every command that opens the index.
|
|
1568
1122
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
1569
1123
|
if (cols.some((c) => c.name === 'account_key') && cols.some((c) => c.name === 'account_org')) {
|
|
1570
1124
|
const needsRepair = db.prepare(`
|
|
@@ -1578,9 +1132,6 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
|
1578
1132
|
db.transaction(() => backfillClaudeAccounts(db, 'unresolved'))();
|
|
1579
1133
|
}
|
|
1580
1134
|
}
|
|
1581
|
-
// machine column + indexes: only after the column is guaranteed present.
|
|
1582
|
-
// Fresh SCHEMA (v17) includes the column; older DBs get it from migrate v17.
|
|
1583
|
-
// If a partial upgrade left schema_version ahead of the column, repair here.
|
|
1584
1135
|
{
|
|
1585
1136
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
1586
1137
|
if (!cols.some((c) => c.name === 'machine')) {
|
|
@@ -1599,23 +1150,12 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
|
1599
1150
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_machine_ts ON sessions(machine, timestamp DESC)`);
|
|
1600
1151
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_agent_ts ON sessions(agent, timestamp DESC)`);
|
|
1601
1152
|
}
|
|
1602
|
-
// harness column: only after the column is guaranteed present.
|
|
1603
|
-
// Fresh SCHEMA (v41) includes the column; older DBs get it from migrate v41.
|
|
1604
|
-
// schema_version can be stamped at SCHEMA_VERSION without the column existing
|
|
1605
|
-
// — getDB writes the marker for any DB whose meta has no row (a hand-built
|
|
1606
|
-
// or partially-created index), and migrateSchema never runs in that path
|
|
1607
|
-
// (`currentVersion === undefined`). If a partial upgrade left schema_version
|
|
1608
|
-
// ahead of the column, repair here so the next upsertSession INSERT naming
|
|
1609
|
-
// `harness` does not throw (PHNX-2935). Same shape as the `machine` repair
|
|
1610
|
-
// above, for the same reason.
|
|
1611
1153
|
{
|
|
1612
1154
|
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
1613
1155
|
if (!cols.some((c) => c.name === 'harness')) {
|
|
1614
1156
|
db.exec(`ALTER TABLE sessions ADD COLUMN harness TEXT`);
|
|
1615
1157
|
}
|
|
1616
1158
|
}
|
|
1617
|
-
// One-shot cleanup of the pre-SQLite JSONL indexes. Safe — nothing reads
|
|
1618
|
-
// them anymore. Guarded by a meta flag so we only try once.
|
|
1619
1159
|
const cleaned = db.prepare(`SELECT value FROM meta WHERE key = 'legacy_indexes_removed'`).get();
|
|
1620
1160
|
if (!cleaned) {
|
|
1621
1161
|
for (const p of [
|
|
@@ -1627,7 +1167,7 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
|
1627
1167
|
if (fs.existsSync(p))
|
|
1628
1168
|
fs.unlinkSync(p);
|
|
1629
1169
|
}
|
|
1630
|
-
catch {
|
|
1170
|
+
catch { }
|
|
1631
1171
|
}
|
|
1632
1172
|
db.prepare(`INSERT OR IGNORE INTO meta(key, value) VALUES ('legacy_indexes_removed', '1')`).run();
|
|
1633
1173
|
}
|
|
@@ -1640,7 +1180,6 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
|
|
|
1640
1180
|
throw error;
|
|
1641
1181
|
}
|
|
1642
1182
|
}
|
|
1643
|
-
/** Bound synchronous cache contention without changing other database callers. */
|
|
1644
1183
|
export function withSessionDBTimeout(timeoutMs, operation) {
|
|
1645
1184
|
const db = getDB(timeoutMs);
|
|
1646
1185
|
const previous = db.prepare('PRAGMA busy_timeout').get().timeout;
|
|
@@ -1652,35 +1191,16 @@ export function withSessionDBTimeout(timeoutMs, operation) {
|
|
|
1652
1191
|
db.pragma(`busy_timeout = ${previous}`);
|
|
1653
1192
|
}
|
|
1654
1193
|
}
|
|
1655
|
-
/** Close the cached database connection. */
|
|
1656
1194
|
export function closeDB() {
|
|
1657
1195
|
clearSessionExistenceCache();
|
|
1658
1196
|
if (dbInstance) {
|
|
1659
1197
|
dbInstance.close();
|
|
1660
1198
|
dbInstance = null;
|
|
1661
|
-
// Closing the connection finalizes every prepared statement it owns. Drop
|
|
1662
|
-
// the cached upsert/FTS statements too, so the next getDB() rebuilds them
|
|
1663
|
-
// against the fresh connection instead of re-running a finalized statement
|
|
1664
|
-
// (which throws "statement has been finalized" on the first upsert).
|
|
1665
1199
|
cachedStmts = {};
|
|
1666
1200
|
}
|
|
1667
1201
|
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Compact the session + tool-call full-text-search indexes.
|
|
1670
|
-
*
|
|
1671
|
-
* FTS5 appends a new segment on every insert and leaves a tombstone on every
|
|
1672
|
-
* delete. The scanner delete+inserts a session's docs on each rescan (and on
|
|
1673
|
-
* every extractor-version bump), and FTS5 never merges those segments on its
|
|
1674
|
-
* own — so `tool_call_text_data` / `session_text_data` accumulate hundreds of
|
|
1675
|
-
* thousands of unmerged segments, gigabytes of index for tens of MB of content,
|
|
1676
|
-
* and `agents sessions` queries slow to a crawl. The FTS5 `'optimize'` command
|
|
1677
|
-
* merges every segment into one and purges tombstones. Non-destructive: no
|
|
1678
|
-
* searchable content is lost. Reclaimed space becomes reusable free pages inside
|
|
1679
|
-
* the DB file; run VACUUM (with the daemon stopped) to return it to the OS.
|
|
1680
|
-
*/
|
|
1681
1202
|
export function optimizeSessionSearchIndex() {
|
|
1682
1203
|
const db = getDB();
|
|
1683
|
-
// Hardcoded literals — never interpolate caller input into an identifier.
|
|
1684
1204
|
const tables = ['tool_call_text', 'session_text'];
|
|
1685
1205
|
const segments = (table) => db.prepare(`SELECT count(*) AS n FROM ${table}_data`).get().n;
|
|
1686
1206
|
return tables.map((table) => {
|
|
@@ -1689,37 +1209,11 @@ export function optimizeSessionSearchIndex() {
|
|
|
1689
1209
|
return { table, segmentsBefore, segmentsAfter: segments(table) };
|
|
1690
1210
|
});
|
|
1691
1211
|
}
|
|
1692
|
-
/**
|
|
1693
|
-
* Segment count above which a scan pays for a slice of merge work. Below it the
|
|
1694
|
-
* index is small enough that querying it is not the bottleneck and merging is
|
|
1695
|
-
* pure overhead on every scan.
|
|
1696
|
-
*/
|
|
1697
1212
|
const FTS_MAINTENANCE_SEGMENT_THRESHOLD = 512;
|
|
1698
|
-
/**
|
|
1699
|
-
* Page budget for one incremental merge. FTS5's `'merge'` command does at most
|
|
1700
|
-
* this much work and returns — it is not `'optimize'`, which merges the whole
|
|
1701
|
-
* index in one unbounded pass. That bound is why this can run on the scan path:
|
|
1702
|
-
* the cost per scan is fixed, and repeated scans converge the index instead of
|
|
1703
|
-
* one scan stalling on a multi-gigabyte compaction.
|
|
1704
|
-
*/
|
|
1705
1213
|
const FTS_MAINTENANCE_MERGE_PAGES = 64;
|
|
1706
|
-
/**
|
|
1707
|
-
* Keep the FTS indexes from degrading on the normal scan path.
|
|
1708
|
-
*
|
|
1709
|
-
* `optimizeSessionSearchIndex` is the full, unbounded compaction behind
|
|
1710
|
-
* `agents sessions optimize`. Leaving it as the ONLY compaction meant the index
|
|
1711
|
-
* degraded until a human happened to run that command, which is how
|
|
1712
|
-
* `tool_call_text_data` reached gigabytes for tens of MB of content. This is the
|
|
1713
|
-
* automatic counterpart: bounded, threshold-gated, and safe to call after every
|
|
1714
|
-
* batch of writes. Non-destructive — merging never changes what is searchable.
|
|
1715
|
-
*
|
|
1716
|
-
* Returns one result per table it actually merged (empty when every table is
|
|
1717
|
-
* under the threshold, which is the common case on a warm index).
|
|
1718
|
-
*/
|
|
1719
1214
|
export function maintainSessionSearchIndex(db = getDB(), options = {}) {
|
|
1720
1215
|
const threshold = options.segmentThreshold ?? FTS_MAINTENANCE_SEGMENT_THRESHOLD;
|
|
1721
1216
|
const pages = options.mergePages ?? FTS_MAINTENANCE_MERGE_PAGES;
|
|
1722
|
-
// Hardcoded literals — never interpolate caller input into an identifier.
|
|
1723
1217
|
const tables = ['tool_call_text', 'session_text'];
|
|
1724
1218
|
const segments = (table) => db.prepare(`SELECT count(*) AS n FROM ${table}_data`).get().n;
|
|
1725
1219
|
const results = [];
|
|
@@ -1732,11 +1226,7 @@ export function maintainSessionSearchIndex(db = getDB(), options = {}) {
|
|
|
1732
1226
|
}
|
|
1733
1227
|
return results;
|
|
1734
1228
|
}
|
|
1735
|
-
|
|
1736
|
-
// Scan coordinator — prevents concurrent full scans across processes
|
|
1737
|
-
// ---------------------------------------------------------------------------
|
|
1738
|
-
/** How long a scan claim is trusted before it's considered stale (ms). */
|
|
1739
|
-
const SCAN_CLAIM_TTL_MS = 120_000; // 2 minutes
|
|
1229
|
+
const SCAN_CLAIM_TTL_MS = 120_000;
|
|
1740
1230
|
function isProcessAlive(pid) {
|
|
1741
1231
|
if (!pid || isNaN(pid))
|
|
1742
1232
|
return false;
|
|
@@ -1748,18 +1238,6 @@ function isProcessAlive(pid) {
|
|
|
1748
1238
|
return false;
|
|
1749
1239
|
}
|
|
1750
1240
|
}
|
|
1751
|
-
/**
|
|
1752
|
-
* Try to claim the right to run the incremental scan. Returns true if this
|
|
1753
|
-
* process should proceed with scanning, false if another live process is
|
|
1754
|
-
* already scanning (caller should skip the scan and serve from the DB).
|
|
1755
|
-
*
|
|
1756
|
-
* Uses the `meta` table so it survives crashes — dead PIDs are detected via
|
|
1757
|
-
* process.kill(pid, 0), stale entries via TTL. No external lock files needed.
|
|
1758
|
-
*
|
|
1759
|
-
* Wrapped in db.transaction() (BEGIN IMMEDIATE) so the read-then-write is
|
|
1760
|
-
* atomic and busy_timeout retries correctly — bare auto-commit DML in WAL
|
|
1761
|
-
* mode can return SQLITE_BUSY_SNAPSHOT which bypasses the busy handler.
|
|
1762
|
-
*/
|
|
1763
1241
|
export function tryClaimScan(pid) {
|
|
1764
1242
|
const db = getDB();
|
|
1765
1243
|
const txn = db.transaction(() => {
|
|
@@ -1772,9 +1250,8 @@ export function tryClaimScan(pid) {
|
|
|
1772
1250
|
const existingTs = parseInt(parts[1], 10);
|
|
1773
1251
|
const ageMs = Date.now() - existingTs;
|
|
1774
1252
|
if (isProcessAlive(existingPid) && ageMs < SCAN_CLAIM_TTL_MS) {
|
|
1775
|
-
return false;
|
|
1253
|
+
return false;
|
|
1776
1254
|
}
|
|
1777
|
-
// Dead PID or expired TTL — take over below
|
|
1778
1255
|
}
|
|
1779
1256
|
db.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('scan_in_progress', ?)`)
|
|
1780
1257
|
.run(`${pid}:${Date.now()}`);
|
|
@@ -1782,13 +1259,6 @@ export function tryClaimScan(pid) {
|
|
|
1782
1259
|
});
|
|
1783
1260
|
return txn();
|
|
1784
1261
|
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Read-only probe: is a scan in progress right now, held by a LIVE process
|
|
1787
|
-
* within the TTL? (RUSH-2682). Unlike {@link tryClaimScan} it never writes, so a
|
|
1788
|
-
* caller that lost the claim can wait for the in-flight scan to finish rather
|
|
1789
|
-
* than returning the pre-scan DB snapshot as if it were the answer. Returns
|
|
1790
|
-
* false for a dead-PID or expired claim — that scan is not actually running.
|
|
1791
|
-
*/
|
|
1792
1262
|
export function scanInProgressByLivePid() {
|
|
1793
1263
|
const db = getDB();
|
|
1794
1264
|
const existing = db
|
|
@@ -1801,10 +1271,6 @@ export function scanInProgressByLivePid() {
|
|
|
1801
1271
|
const ts = parseInt(parts[1], 10);
|
|
1802
1272
|
return isProcessAlive(pid) && Date.now() - ts < SCAN_CLAIM_TTL_MS;
|
|
1803
1273
|
}
|
|
1804
|
-
/**
|
|
1805
|
-
* Release the scan claim written by tryClaimScan. Only deletes the entry
|
|
1806
|
-
* if it still belongs to this process (guards against TTL takeovers).
|
|
1807
|
-
*/
|
|
1808
1274
|
export function releaseScan(pid) {
|
|
1809
1275
|
const db = getDB();
|
|
1810
1276
|
const txn = db.transaction(() => {
|
|
@@ -1820,14 +1286,9 @@ export function releaseScan(pid) {
|
|
|
1820
1286
|
});
|
|
1821
1287
|
txn();
|
|
1822
1288
|
}
|
|
1823
|
-
/** Return the absolute path to the sessions database file. */
|
|
1824
1289
|
export function getDBPath() {
|
|
1825
1290
|
return DB_PATH;
|
|
1826
1291
|
}
|
|
1827
|
-
/**
|
|
1828
|
-
* Look up the file stat stamp we stored the last time we scanned a given file path.
|
|
1829
|
-
* Callers compare this to the current fs.stat to decide whether to rescan.
|
|
1830
|
-
*/
|
|
1831
1292
|
export function getScanStampByPath(filePath) {
|
|
1832
1293
|
const db = getDB();
|
|
1833
1294
|
const row = db
|
|
@@ -1837,19 +1298,11 @@ export function getScanStampByPath(filePath) {
|
|
|
1837
1298
|
? { fileMtimeMs: row.file_mtime_ms, fileSize: row.file_size, scannedAt: row.scanned_at, extractorVersion: row.extractor_version }
|
|
1838
1299
|
: null;
|
|
1839
1300
|
}
|
|
1840
|
-
/**
|
|
1841
|
-
* Bulk-load the stamp ledger for a set of file paths in a single SQL query.
|
|
1842
|
-
* This is the fast path used by the incremental scanner — avoids N+1 queries.
|
|
1843
|
-
*/
|
|
1844
1301
|
export function getScanStampsForPaths(filePaths) {
|
|
1845
1302
|
const result = new Map();
|
|
1846
1303
|
if (filePaths.length === 0)
|
|
1847
1304
|
return result;
|
|
1848
1305
|
const db = getDB();
|
|
1849
|
-
// Multiple input paths can resolve to the same canonical key (e.g. the same
|
|
1850
|
-
// session JSONL reachable via `~/.claude/...` and `~/.agents/versions/...`).
|
|
1851
|
-
// We query DB by canonical key, then fan results back out to every original
|
|
1852
|
-
// alias so callers can `.get(filePath)` with the path they passed in.
|
|
1853
1306
|
const canonicalToOriginals = new Map();
|
|
1854
1307
|
for (const fp of filePaths) {
|
|
1855
1308
|
const canonical = canonicalLedgerKey(fp);
|
|
@@ -1860,7 +1313,6 @@ export function getScanStampsForPaths(filePaths) {
|
|
|
1860
1313
|
canonicalToOriginals.set(canonical, [fp]);
|
|
1861
1314
|
}
|
|
1862
1315
|
const canonicalKeys = [...canonicalToOriginals.keys()];
|
|
1863
|
-
// SQLite parameter limit is typically 999 / 32766 — chunk defensively.
|
|
1864
1316
|
const CHUNK = 500;
|
|
1865
1317
|
for (let i = 0; i < canonicalKeys.length; i += CHUNK) {
|
|
1866
1318
|
const chunk = canonicalKeys.slice(i, i + CHUNK);
|
|
@@ -1886,14 +1338,6 @@ export function getScanStampsForPaths(filePaths) {
|
|
|
1886
1338
|
}
|
|
1887
1339
|
return result;
|
|
1888
1340
|
}
|
|
1889
|
-
/**
|
|
1890
|
-
* Bulk-load the resumable-parse continuation (parser_state + content_text) plus
|
|
1891
|
-
* the stamp for a set of file paths in a single chunked query. Mirrors
|
|
1892
|
-
* {@link getScanStampsForPaths}: keys by canonical path and fans results back to
|
|
1893
|
-
* every original alias so callers can `.get(filePath)` with the path they passed.
|
|
1894
|
-
* The Claude incremental scan uses this to fetch each changed file's prior
|
|
1895
|
-
* continuation without an N+1 of {@link getScanStampByPath}.
|
|
1896
|
-
*/
|
|
1897
1341
|
export function getParserStatesForPaths(filePaths) {
|
|
1898
1342
|
const result = new Map();
|
|
1899
1343
|
if (filePaths.length === 0)
|
|
@@ -1936,10 +1380,6 @@ export function getParserStatesForPaths(filePaths) {
|
|
|
1936
1380
|
}
|
|
1937
1381
|
return result;
|
|
1938
1382
|
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Record scan stamps for files we've looked at. Covers both files that produced
|
|
1941
|
-
* a session and files we looked at but chose not to index (e.g. malformed).
|
|
1942
|
-
*/
|
|
1943
1383
|
export function recordScans(entries) {
|
|
1944
1384
|
if (entries.length === 0)
|
|
1945
1385
|
return;
|
|
@@ -1956,23 +1396,11 @@ export function recordScans(entries) {
|
|
|
1956
1396
|
const now = Date.now();
|
|
1957
1397
|
const txn = db.transaction((items) => {
|
|
1958
1398
|
for (const { filePath, scan } of items) {
|
|
1959
|
-
// Stamped at the CURRENT content extractor version even for a file that
|
|
1960
|
-
// yielded no session (malformed / no id): we ran today's extractor over
|
|
1961
|
-
// it and it produced nothing, so it is current, not stale — otherwise a
|
|
1962
|
-
// permanently-unparseable file would re-trigger "changed" on every scan
|
|
1963
|
-
// forever once CONTENT_INDEX_VERSION is bumped.
|
|
1964
1399
|
stmt.run(canonicalLedgerKey(filePath), scan.fileMtimeMs, scan.fileSize, now, CONTENT_INDEX_VERSION);
|
|
1965
1400
|
}
|
|
1966
1401
|
});
|
|
1967
1402
|
txn(entries);
|
|
1968
1403
|
}
|
|
1969
|
-
/**
|
|
1970
|
-
* Bulk-load the dir ledger for a set of leaf directories in a single SQL query.
|
|
1971
|
-
* Mirrors {@link getScanStampsForPaths}: keys by canonical path (so a dir
|
|
1972
|
-
* reachable via a symlinked version home and its realpath collapse to one row)
|
|
1973
|
-
* and fans the result back out to every original alias so callers can
|
|
1974
|
-
* `.get(dirPath)` with the path they passed in.
|
|
1975
|
-
*/
|
|
1976
1404
|
export function getDirLedgerForPaths(dirs) {
|
|
1977
1405
|
const result = new Map();
|
|
1978
1406
|
if (dirs.length === 0)
|
|
@@ -2008,11 +1436,6 @@ export function getDirLedgerForPaths(dirs) {
|
|
|
2008
1436
|
}
|
|
2009
1437
|
return result;
|
|
2010
1438
|
}
|
|
2011
|
-
/**
|
|
2012
|
-
* Upsert dir-scan stamps. Recorded after a full readdir of a leaf transcript
|
|
2013
|
-
* dir so the next scan can skip that dir when its (mtime, entry_count) is
|
|
2014
|
-
* unchanged. Mirrors {@link recordScans}.
|
|
2015
|
-
*/
|
|
2016
1439
|
export function recordDirScans(entries) {
|
|
2017
1440
|
if (entries.length === 0)
|
|
2018
1441
|
return;
|
|
@@ -2167,19 +1590,6 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
2167
1590
|
ELSE sessions.mirror_source
|
|
2168
1591
|
END
|
|
2169
1592
|
`);
|
|
2170
|
-
/**
|
|
2171
|
-
* Did this session emit at least one browser/computer-automation event?
|
|
2172
|
-
* A scoped, sessionId-filtered read of the events log — NOT a re-scan of the
|
|
2173
|
-
* (potentially huge) transcript — since browser.navigate / browser.screenshot
|
|
2174
|
-
* / computer.action are recorded there via events.ts's emit(), keyed by the
|
|
2175
|
-
* same session id that is this row's `meta.id` (the provenance floor stamps
|
|
2176
|
-
* AGENT_SESSION_ID/AGENTS_SESSION_ID on every such event at emit time).
|
|
2177
|
-
*
|
|
2178
|
-
* Called independently of {@link enrichCachedSessionMeta} (which SKIPS
|
|
2179
|
-
* claude/codex entries in the batch path because their caller already parsed
|
|
2180
|
-
* the transcript for todos/recentDirectoriesTouched) — this never touches the
|
|
2181
|
-
* transcript, so it must run for every agent, every time.
|
|
2182
|
-
*/
|
|
2183
1593
|
function detectToolUsage(sessionId) {
|
|
2184
1594
|
const usedBrowser = queryEvents({ sessionId, eventTypes: ['browser.navigate', 'browser.screenshot'], limit: 1 }).length > 0;
|
|
2185
1595
|
const usedComputer = queryEvents({ sessionId, eventTypes: ['computer.action'], limit: 1 }).length > 0;
|
|
@@ -2190,17 +1600,6 @@ const insertResourceUsageStmt = (db) => db.prepare(`
|
|
|
2190
1600
|
INSERT INTO session_resource_usage (session_id, kind, name, plugin, source, repo_root, snapshot_sha, count)
|
|
2191
1601
|
VALUES (@session_id, @kind, @name, @plugin, @source, @repo_root, @snapshot_sha, @count)
|
|
2192
1602
|
`);
|
|
2193
|
-
/**
|
|
2194
|
-
* Resolve a skill/slash-command's provenance for `session_resource_usage`
|
|
2195
|
-
* (#12). A flat (non-namespaced) resource resolves via resolveResource()'s
|
|
2196
|
-
* project/user/system/extra-repo scan. A namespaced one (`plugin:name`, e.g.
|
|
2197
|
-
* `rush:design` — the shape both a plugin skill's `args.skill` and a plugin
|
|
2198
|
-
* command's SessionEvent.slashCommand carry) is plugin-owned and lives under
|
|
2199
|
-
* the plugin's own directory, invisible to resolveResource()'s flat scan —
|
|
2200
|
-
* resolved instead against the already-discovered plugin list. Neither found
|
|
2201
|
-
* (renamed or uninstalled since the session ran) returns all-undefined
|
|
2202
|
-
* rather than a stale guess.
|
|
2203
|
-
*/
|
|
2204
1603
|
function resolveResourceProvenance(kind, name, cwd, plugins) {
|
|
2205
1604
|
const listOf = (p) => (kind === 'skills' ? p.skills : p.commands);
|
|
2206
1605
|
const colonIdx = name.indexOf(':');
|
|
@@ -2220,28 +1619,6 @@ function resolveResourceProvenance(kind, name, cwd, plugins) {
|
|
|
2220
1619
|
return {};
|
|
2221
1620
|
return { plugin: plugin.name, source: plugin.marketplace, repoRoot: plugin.repoRoot, snapshotSha: plugin.snapshotSha };
|
|
2222
1621
|
}
|
|
2223
|
-
/**
|
|
2224
|
-
* Persist already-computed skill/slash-command tallies for a session (#12)
|
|
2225
|
-
* into `session_resource_usage`, replacing any prior rows for it (a rescan's
|
|
2226
|
-
* usage supersedes the old — same replace-on-upsert shape as the FTS text
|
|
2227
|
-
* below). `discoverPlugins()` (real I/O: manifest + directory reads) only
|
|
2228
|
-
* runs when there is actually a skill/command to resolve, so a session with
|
|
2229
|
-
* neither pays nothing beyond the DELETE.
|
|
2230
|
-
*
|
|
2231
|
-
* Split from {@link writeResourceUsage} so a caller that already has the
|
|
2232
|
-
* tallies (claude/codex's incremental accumulator — see
|
|
2233
|
-
* ClaudeParseState.skillEvents/slashCommandEvents, threaded onto
|
|
2234
|
-
* SessionMeta.skillsUsed/slashCommandsUsed) can write without re-parsing the
|
|
2235
|
-
* transcript to re-derive them.
|
|
2236
|
-
*
|
|
2237
|
-
* Deliberately NOT wrapped in its own `db.transaction()`: better-sqlite3
|
|
2238
|
-
* (this repo's wrapper included) does not support nested transactions, and
|
|
2239
|
-
* `upsertSessionsBatch` calls this from INSIDE its own outer transaction. A
|
|
2240
|
-
* standalone caller (enrichCachedSessionMeta, outside any transaction) still
|
|
2241
|
-
* gets each statement committed individually — a crash between the DELETE
|
|
2242
|
-
* and an INSERT self-heals on the next rescan, the same risk profile as any
|
|
2243
|
-
* other un-batched write in this file.
|
|
2244
|
-
*/
|
|
2245
1622
|
function writeResourceUsageFromTallies(sessionId, skills, commands, cwd) {
|
|
2246
1623
|
const db = getDB();
|
|
2247
1624
|
const del = deleteResourceUsageStmt(db);
|
|
@@ -2270,27 +1647,10 @@ function writeResourceUsageFromTallies(sessionId, skills, commands, cwd) {
|
|
|
2270
1647
|
ins.run(bind);
|
|
2271
1648
|
}
|
|
2272
1649
|
}
|
|
2273
|
-
/**
|
|
2274
|
-
* Persist skill/slash-command usage for a session (#12) by deriving the
|
|
2275
|
-
* tallies from a full parsed transcript. Used by {@link enrichCachedSessionMeta}
|
|
2276
|
-
* (every `upsertSession()` call, and `upsertSessionsBatch` for every harness
|
|
2277
|
-
* EXCEPT claude/codex, which pre-compute skillsUsed/slashCommandsUsed via
|
|
2278
|
-
* their incremental accumulator instead — see writeResourceUsageFromTallies
|
|
2279
|
-
* and the call site in upsertSessionsBatch).
|
|
2280
|
-
*/
|
|
2281
1650
|
function writeResourceUsage(sessionId, events, cwd) {
|
|
2282
1651
|
writeResourceUsageFromTallies(sessionId, extractSkills(events), extractSlashCommands(events), cwd);
|
|
2283
1652
|
}
|
|
2284
|
-
|
|
2285
|
-
* Fan-out the session left behind, from an ALREADY-parsed transcript (never a
|
|
2286
|
-
* re-parse — every caller here has `events` in hand).
|
|
2287
|
-
*
|
|
2288
|
-
* `backgroundShellCount` is `undefined`, not 0, for a harness that cannot report
|
|
2289
|
-
* backgrounded shells. That distinction is the whole point: 0 asserts "none were
|
|
2290
|
-
* started", while undefined says "this harness does not record it" — and the
|
|
2291
|
-
* renders omit the segment for both, so neither can read as "nothing is running".
|
|
2292
|
-
*/
|
|
2293
|
-
function fanOutCounts(events, agent) {
|
|
1653
|
+
function fanOutCounts(events, agent, sessionFile) {
|
|
2294
1654
|
let subAgentCount = 0;
|
|
2295
1655
|
for (const e of events) {
|
|
2296
1656
|
if (e.type !== 'tool_use' || e._local)
|
|
@@ -2298,14 +1658,14 @@ function fanOutCounts(events, agent) {
|
|
|
2298
1658
|
if (isSubAgentTool(e.tool || '', e.command || ''))
|
|
2299
1659
|
subAgentCount++;
|
|
2300
1660
|
}
|
|
1661
|
+
const children = agent === 'claude' && sessionFile ? claudeSubagentFiles(sessionFile) : undefined;
|
|
2301
1662
|
return {
|
|
2302
|
-
subAgentCount,
|
|
1663
|
+
subAgentCount: resolvedSubAgentCount(children, subAgentCount),
|
|
2303
1664
|
backgroundShellCount: harnessTracksBackgroundShells(agent)
|
|
2304
1665
|
? extractBackgroundShells(events).length
|
|
2305
1666
|
: undefined,
|
|
2306
1667
|
};
|
|
2307
1668
|
}
|
|
2308
|
-
/** Fold transcript-derived metadata that every parser can supply uniformly. */
|
|
2309
1669
|
function enrichMetaFromEvents(meta, events) {
|
|
2310
1670
|
return {
|
|
2311
1671
|
...meta,
|
|
@@ -2313,7 +1673,7 @@ function enrichMetaFromEvents(meta, events) {
|
|
|
2313
1673
|
lastUserMessage: meta.lastUserMessage ?? lastUserMessageFromEvents(events),
|
|
2314
1674
|
todos: extractTodoProgressFromEvents(events),
|
|
2315
1675
|
recentDirectoriesTouched: extractRecentDirectoriesTouched(events, meta.cwd),
|
|
2316
|
-
...fanOutCounts(events, meta.agent),
|
|
1676
|
+
...fanOutCounts(events, meta.agent, meta.filePath),
|
|
2317
1677
|
};
|
|
2318
1678
|
}
|
|
2319
1679
|
function enrichCachedSessionMeta(meta) {
|
|
@@ -2325,22 +1685,12 @@ function enrichCachedSessionMeta(meta) {
|
|
|
2325
1685
|
return enrichMetaFromEvents(meta, events);
|
|
2326
1686
|
}
|
|
2327
1687
|
catch {
|
|
2328
|
-
// Synthetic/cloud rows can intentionally name a transcript that is not local.
|
|
2329
1688
|
return meta;
|
|
2330
1689
|
}
|
|
2331
1690
|
}
|
|
2332
|
-
/**
|
|
2333
|
-
* The one way to address a session's FTS row: its rowid is the sessions rowid
|
|
2334
|
-
* (schema comment on session_text). A `session_id = ?` predicate scans the whole
|
|
2335
|
-
* index instead — the PHNX-4154 daemon freeze.
|
|
2336
|
-
*/
|
|
2337
1691
|
const SESSION_TEXT_ROWID = `(SELECT rowid FROM sessions WHERE id = ?)`;
|
|
2338
1692
|
const deleteTextStmt = (db) => db.prepare(`DELETE FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`);
|
|
2339
|
-
// Binds the session id twice: once for the rowid lookup, once for the column.
|
|
2340
1693
|
const insertTextStmt = (db) => db.prepare(`INSERT INTO session_text (rowid, session_id, label, topic, project, content, assistant) VALUES (${SESSION_TEXT_ROWID}, ?, ?, ?, ?, ?, ?)`);
|
|
2341
|
-
// Read back the label the upsert actually stored (which may be the preserved
|
|
2342
|
-
// one, not the incoming blank) so the FTS label column stays consistent with
|
|
2343
|
-
// sessions.label after a bare rescan.
|
|
2344
1694
|
const readLabelStmt = (db) => db.prepare(`SELECT label FROM sessions WHERE id = ?`);
|
|
2345
1695
|
let cachedStmts = {};
|
|
2346
1696
|
function stmts(db) {
|
|
@@ -2354,33 +1704,17 @@ function stmts(db) {
|
|
|
2354
1704
|
}
|
|
2355
1705
|
return cachedStmts;
|
|
2356
1706
|
}
|
|
2357
|
-
/**
|
|
2358
|
-
* Return the label stored for a session, as text for the FTS index (never NULL).
|
|
2359
|
-
* Called inside the upsert transaction, AFTER the row upsert, so it reflects the
|
|
2360
|
-
* preserve-non-empty-label rule in the ON CONFLICT clause rather than the raw
|
|
2361
|
-
* incoming label.
|
|
2362
|
-
*/
|
|
2363
1707
|
function storedFtsLabel(readLabel, id) {
|
|
2364
1708
|
const row = readLabel.get(id);
|
|
2365
1709
|
return row?.label ?? '';
|
|
2366
1710
|
}
|
|
2367
|
-
/** Resolve origin machine for a row: prefer caller-stamped meta, else path. */
|
|
2368
1711
|
function resolveMachine(meta) {
|
|
2369
1712
|
if (meta.machine && meta.machine.trim())
|
|
2370
1713
|
return meta.machine.trim();
|
|
2371
1714
|
return machineForSessionFile(meta.filePath, meta.agent);
|
|
2372
1715
|
}
|
|
2373
|
-
/**
|
|
2374
|
-
* Upsert a session row and replace its FTS5 content in a single transaction.
|
|
2375
|
-
* `content` is the tokenizable user-prompt text; pass '' to leave the row unsearchable.
|
|
2376
|
-
*/
|
|
2377
1716
|
export function upsertSession(meta, content, scan, assistantContent = '') {
|
|
2378
1717
|
meta = enrichCachedSessionMeta(meta);
|
|
2379
|
-
// Join the durable sessionId -> actor sidecar (RUSH-2019) when the caller
|
|
2380
|
-
// didn't already carry an actor, so a scanned transcript still attributes to a
|
|
2381
|
-
// person. The ON CONFLICT COALESCEs actor/initiated_by, so this fills a fresh
|
|
2382
|
-
// row AND backfills one indexed null-first (before its sidecar existed), while a
|
|
2383
|
-
// rescan carrying no actor still keeps the stored owner.
|
|
2384
1718
|
const actorRec = meta.actor && meta.accountId ? undefined : readSessionActorRecord(meta.id);
|
|
2385
1719
|
const toolUsage = detectToolUsage(meta.id);
|
|
2386
1720
|
const db = getDB();
|
|
@@ -2393,10 +1727,6 @@ export function upsertSession(meta, content, scan, assistantContent = '') {
|
|
|
2393
1727
|
origin: meta.origin ?? 'cli',
|
|
2394
1728
|
routine_name: meta.routineName ?? null,
|
|
2395
1729
|
routine_run_id: meta.routineRunId ?? null,
|
|
2396
|
-
// Backfill the origin version from the launch-time sidecar when the transcript
|
|
2397
|
-
// scan couldn't derive one (codex's `.codex-homes/<version>/` home) — the same
|
|
2398
|
-
// write-once join as mode/harness, so native resume stops falling back to
|
|
2399
|
-
// `/continue` for a missing recorded version (PHNX-3626).
|
|
2400
1730
|
version: meta.version ?? actorRec?.version ?? null,
|
|
2401
1731
|
account: meta.account ?? null,
|
|
2402
1732
|
account_key: meta.accountKey ?? null,
|
|
@@ -2450,17 +1780,10 @@ export function upsertSession(meta, content, scan, assistantContent = '') {
|
|
|
2450
1780
|
const txn = db.transaction(() => {
|
|
2451
1781
|
upsert.run(row);
|
|
2452
1782
|
delText.run(meta.id);
|
|
2453
|
-
insText.run(meta.id, meta.id,
|
|
2454
|
-
// Use the label the upsert actually stored (preserve-non-empty rule),
|
|
2455
|
-
// not the raw incoming one, so FTS label ranking survives a bare rescan.
|
|
2456
|
-
storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
|
|
1783
|
+
insText.run(meta.id, meta.id, storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
|
|
2457
1784
|
});
|
|
2458
1785
|
txn();
|
|
2459
1786
|
}
|
|
2460
|
-
/** A verified Codex rollout has one native owner. An older last-meta scan may
|
|
2461
|
-
* have indexed its child bytes under the parent id. Reconcile only paths that
|
|
2462
|
-
* were successfully written, after the whole batch, so a real parent rollout
|
|
2463
|
-
* repaired in the same transaction keeps its own row regardless of scan order. */
|
|
2464
1787
|
function reconcileCodexFileOwners(db, metas) {
|
|
2465
1788
|
const owners = new Map(metas.filter(meta => meta.agent === 'codex' && meta.filePath).map(meta => [meta.filePath, meta.id]));
|
|
2466
1789
|
const paths = [...owners.keys()];
|
|
@@ -2471,39 +1794,22 @@ function reconcileCodexFileOwners(db, metas) {
|
|
|
2471
1794
|
for (const row of rows) {
|
|
2472
1795
|
if (owners.get(row.file_path) === row.id)
|
|
2473
1796
|
continue;
|
|
2474
|
-
// The text row is keyed by the sessions rowid, so it goes before the row.
|
|
2475
1797
|
db.prepare(`DELETE FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`).run(row.id);
|
|
2476
1798
|
db.prepare('DELETE FROM sessions WHERE id = ?').run(row.id);
|
|
2477
1799
|
for (const table of ['session_preview_cache', 'session_summaries', 'session_insights', 'session_topics', 'session_phenotypes', 'session_resource_usage', 'resource_scan_ledger']) {
|
|
2478
1800
|
db.prepare(`DELETE FROM ${table} WHERE session_id = ?`).run(row.id);
|
|
2479
1801
|
}
|
|
2480
|
-
// The tool ledger has a unique file path too; release the wrong binding
|
|
2481
|
-
// before the child evidence is persisted below. Transcript files stay put.
|
|
2482
1802
|
purgeToolCalls(db, row.id);
|
|
2483
1803
|
}
|
|
2484
1804
|
}
|
|
2485
1805
|
}
|
|
2486
|
-
/** Batch-upsert sessions with their FTS5 content and scan stamps in a single transaction. */
|
|
2487
1806
|
export function upsertSessionsBatch(entries) {
|
|
2488
1807
|
if (entries.length === 0)
|
|
2489
1808
|
return;
|
|
2490
1809
|
const db = getDB();
|
|
2491
1810
|
const { upsert, delText, insText, readLabel } = stmts(db);
|
|
2492
1811
|
const now = Date.now();
|
|
2493
|
-
// One directory read for the whole batch: join the durable sessionId -> actor
|
|
2494
|
-
// sidecar (RUSH-2019) so scanned transcripts attribute to a person. Only used
|
|
2495
|
-
// for entries whose meta carries no actor; the ON CONFLICT COALESCEs the column,
|
|
2496
|
-
// so this fills fresh rows AND backfills null-first ones, never clobbering a
|
|
2497
|
-
// stored owner on rescan.
|
|
2498
1812
|
const actorIndex = loadSessionActorIndex();
|
|
2499
|
-
// Persist the Claude resumable-parse continuation (parser_state + content_text)
|
|
2500
|
-
// alongside the stamp, plus the CURRENT content extractor version — a caller
|
|
2501
|
-
// that reached this batch write ran today's extractor over the file, so the
|
|
2502
|
-
// ledger row is current regardless of which branch (full/incremental)
|
|
2503
|
-
// produced it. On a full/incremental Claude parse the caller passes the
|
|
2504
|
-
// serialized newState + accumulated user doc so the NEXT scan can resume from
|
|
2505
|
-
// the persisted offset (B-2). Other scanners pass neither, leaving both columns
|
|
2506
|
-
// NULL exactly as before — their ledger rows are unaffected.
|
|
2507
1813
|
const ledger = db.prepare(`
|
|
2508
1814
|
INSERT INTO scan_ledger (file_path, file_mtime_ms, file_size, scanned_at, parser_state, content_text, extractor_version)
|
|
2509
1815
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
@@ -2515,43 +1821,19 @@ export function upsertSessionsBatch(entries) {
|
|
|
2515
1821
|
content_text = excluded.content_text,
|
|
2516
1822
|
extractor_version = excluded.extractor_version
|
|
2517
1823
|
`);
|
|
2518
|
-
// Build a lookup from canonical file path → entry, used inside the write
|
|
2519
|
-
// transaction to re-check the ledger AFTER acquiring the lock. When a
|
|
2520
|
-
// concurrent process already committed the same files between our
|
|
2521
|
-
// filterChangedFiles call and now, the ledger will have matching (mtime, size)
|
|
2522
|
-
// rows — we skip those entries, making the second writer's transaction a
|
|
2523
|
-
// near-instant no-op rather than redundant work.
|
|
2524
1824
|
const byPath = new Map(entries
|
|
2525
1825
|
.filter(e => e.scan && e.meta.filePath)
|
|
2526
1826
|
.map(e => [canonicalLedgerKey(e.meta.filePath), e]));
|
|
2527
1827
|
const enrichedEntries = entries.map(entry => {
|
|
2528
1828
|
if (entry.meta.agent === 'claude' || entry.meta.agent === 'codex' || !entry.meta.filePath) {
|
|
2529
|
-
// claude/codex keep their resumable-parse optimization (no transcript read
|
|
2530
|
-
// here). When their scanner already handed us normalized events, the counts
|
|
2531
|
-
// are free — take them. Otherwise leave them undefined, which persists NULL
|
|
2532
|
-
// and means "not computed yet", and the next scan that carries events fills
|
|
2533
|
-
// it. Never collapse that to 0: see fanOutCounts.
|
|
2534
1829
|
return entry.events
|
|
2535
1830
|
? { ...entry, meta: enrichMetaFromEvents(entry.meta, entry.events) }
|
|
2536
1831
|
: entry;
|
|
2537
1832
|
}
|
|
2538
|
-
// Harnesses whose scanners produce no events AND whose parseSession reads a
|
|
2539
|
-
// potentially large flat transcript file (not a compact SQLite DB). Calling
|
|
2540
|
-
// parseSession on the warm tick for an active large session wedges the Node
|
|
2541
|
-
// event loop for seconds, making browser IPC miss its connection window
|
|
2542
|
-
// (PHNX-3411). Defer their tool-call indexing to runDeferredToolIndex, which
|
|
2543
|
-
// uses ensureToolIndex with tool_scan_ledger stamps and byte/file budget caps.
|
|
2544
|
-
// NOT opencode — parseOpenCode issues a targeted SQLite query, so it is fast
|
|
2545
|
-
// even for large sessions and its results populate recentDirectoriesTouched.
|
|
2546
1833
|
const LARGE_TRANSCRIPT_AGENTS = new Set(['kimi', 'grok']);
|
|
2547
1834
|
if (!entry.events && LARGE_TRANSCRIPT_AGENTS.has(entry.meta.agent)) {
|
|
2548
1835
|
return entry;
|
|
2549
1836
|
}
|
|
2550
|
-
// Enrich the entry. When the scanner already provided events, use them
|
|
2551
|
-
// directly (no transcript re-read). When it didn't — e.g. OpenCode whose
|
|
2552
|
-
// scanner produces only metadata but whose parseOpenCode is a fast SQLite
|
|
2553
|
-
// query — fall back to parseSession. Agents that would call an expensive
|
|
2554
|
-
// flat-file parse already returned above.
|
|
2555
1837
|
try {
|
|
2556
1838
|
const toolSourcePath = toolEvidenceSourcePath(entry.meta.filePath, entry.meta.agent);
|
|
2557
1839
|
const toolScan = toolSourcePath === entry.meta.filePath
|
|
@@ -2562,17 +1844,6 @@ export function upsertSessionsBatch(entries) {
|
|
|
2562
1844
|
})();
|
|
2563
1845
|
const events = entry.events ?? parseSession(entry.meta.filePath, entry.meta.agent);
|
|
2564
1846
|
writeResourceUsage(entry.meta.id, events, entry.meta.cwd);
|
|
2565
|
-
// Resume the tool index from the last scan of this append-only stream when
|
|
2566
|
-
// it is safe to (PHNX-3411). A live session's transcript grows every tick,
|
|
2567
|
-
// so a full re-derive re-sanitizes its ENTIRE tool history each time — the
|
|
2568
|
-
// synchronous O(session) work that wedged the daemon event loop for the
|
|
2569
|
-
// 11 non-streaming harnesses. `planEventToolResume` returns the prior
|
|
2570
|
-
// snapshot only when the file is still an append of what was scanned
|
|
2571
|
-
// before; otherwise `prior` is null and this is a full replace from
|
|
2572
|
-
// event 0 (identical index, just re-derived).
|
|
2573
|
-
// No tool stamp (a scanner that carried no scan record) means the resume
|
|
2574
|
-
// point cannot be size-guarded, so full-scan rather than trust a stale
|
|
2575
|
-
// offset. persistToolCalls below also skips a resume-less write.
|
|
2576
1847
|
const prior = toolScan
|
|
2577
1848
|
? planEventToolResume(db, entry.meta.id, toolSourcePath, toolScan, events.length)
|
|
2578
1849
|
: null;
|
|
@@ -2580,13 +1851,9 @@ export function upsertSessionsBatch(entries) {
|
|
|
2580
1851
|
return {
|
|
2581
1852
|
...entry,
|
|
2582
1853
|
meta: enrichMetaFromEvents(entry.meta, events),
|
|
2583
|
-
// The CHANGED calls only. On a resume these are the newly appended tail
|
|
2584
|
-
// (append-safe upsert); on a full scan they are the whole history.
|
|
2585
1854
|
toolCalls: scanned.calls,
|
|
2586
1855
|
toolScan,
|
|
2587
1856
|
toolIndexMode: (prior ? 'append' : 'replace'),
|
|
2588
|
-
// Persist where the NEXT scan resumes: the collector snapshot + how many
|
|
2589
|
-
// events this scan folded.
|
|
2590
1857
|
toolResume: { parserState: JSON.stringify(scanned.snapshot), parsedOffset: scanned.eventCount },
|
|
2591
1858
|
};
|
|
2592
1859
|
}
|
|
@@ -2595,16 +1862,9 @@ export function upsertSessionsBatch(entries) {
|
|
|
2595
1862
|
}
|
|
2596
1863
|
});
|
|
2597
1864
|
const writtenEntries = [];
|
|
2598
|
-
// Pre-compute browser/computer usage for all sessions outside the write
|
|
2599
|
-
// transaction. detectToolUsage scans all event log files (O(files) I/O
|
|
2600
|
-
// per call) and holding the SQLite write lock during that scan is what
|
|
2601
|
-
// causes the "DB locked" errors (RUSH-2006). One pass for the whole batch
|
|
2602
|
-
// costs O(files) total instead of O(N × files) inside the lock.
|
|
2603
1865
|
const toolUsageBySession = queryToolUsageForSessions(new Set(enrichedEntries.map(e => e.meta.id)));
|
|
2604
1866
|
const txn = db.transaction((items) => {
|
|
2605
|
-
|
|
2606
|
-
// by a concurrent process since our pre-scan is visible here.
|
|
2607
|
-
const CHUNK = 500; // stay under SQLite's 999-variable limit
|
|
1867
|
+
const CHUNK = 500;
|
|
2608
1868
|
const alreadyIndexed = new Set();
|
|
2609
1869
|
const paths = [...byPath.keys()];
|
|
2610
1870
|
for (let i = 0; i < paths.length; i += CHUNK) {
|
|
@@ -2615,12 +1875,6 @@ export function upsertSessionsBatch(entries) {
|
|
|
2615
1875
|
.all(...chunk);
|
|
2616
1876
|
for (const row of rows) {
|
|
2617
1877
|
const entry = byPath.get(row.file_path);
|
|
2618
|
-
// A concurrent writer's row only makes THIS entry redundant when it is
|
|
2619
|
-
// current at CONTENT_INDEX_VERSION too — otherwise a (mtime, size) match
|
|
2620
|
-
// alone would make the version lever a no-op: the very reason this batch
|
|
2621
|
-
// was scheduled (a stale extractor_version) would be silently skipped as
|
|
2622
|
-
// "someone else already indexed it", when what they indexed predates the
|
|
2623
|
-
// current extractor.
|
|
2624
1878
|
if (entry &&
|
|
2625
1879
|
row.file_mtime_ms === entry.scan.fileMtimeMs &&
|
|
2626
1880
|
row.file_size === entry.scan.fileSize &&
|
|
@@ -2633,28 +1887,11 @@ export function upsertSessionsBatch(entries) {
|
|
|
2633
1887
|
const { meta, content, assistantContent, scan, parserState, contentText } = entry;
|
|
2634
1888
|
if (alreadyIndexed.has(meta.id))
|
|
2635
1889
|
continue;
|
|
2636
|
-
// Per-row guard: one malformed session (e.g. a required field that resolves to
|
|
2637
|
-
// NULL) must not abort the whole batch and take down the entire `agents sessions`
|
|
2638
|
-
// listing. A constraint error uses SQLite's ABORT resolution — it reverts only the
|
|
2639
|
-
// failing statement, not the transaction — and the db.transaction wrapper only rolls
|
|
2640
|
-
// back when the error escapes `fn`, so catching + skipping here leaves the txn valid
|
|
2641
|
-
// and committable. We deliberately do NOT stamp the ledger for a skipped row, so the
|
|
2642
|
-
// next scan re-tries it (self-healing once the underlying parser is fixed).
|
|
2643
1890
|
const toolUsage = toolUsageBySession.get(meta.id) ?? { usedBrowser: false, usedComputer: false };
|
|
2644
|
-
// claude/codex skip enrichCachedSessionMeta above (preserving their
|
|
2645
|
-
// resumable-parse optimization) — write their pre-computed
|
|
2646
|
-
// skillsUsed/slashCommandsUsed (folded incrementally by discover.ts's
|
|
2647
|
-
// accumulator) here instead. Every other harness already got this
|
|
2648
|
-
// write from enrichCachedSessionMeta() in the .map() above.
|
|
2649
1891
|
if (meta.agent === 'claude' || meta.agent === 'codex') {
|
|
2650
1892
|
writeResourceUsageFromTallies(meta.id, meta.skillsUsed ?? [], meta.slashCommandsUsed ?? [], meta.cwd);
|
|
2651
1893
|
}
|
|
2652
1894
|
try {
|
|
2653
|
-
// Typed, not a bare literal: bun binds named parameters in strict mode, where a
|
|
2654
|
-
// MISSING key throws (node binds NULL instead). A silently dropped key therefore
|
|
2655
|
-
// breaks only the shipped binary's runtime, and the per-row catch below swallows
|
|
2656
|
-
// it — the exact shape of the bug that shipped account_key unbound. Annotating
|
|
2657
|
-
// against SessionRow makes tsc reject the next omission.
|
|
2658
1895
|
const row = {
|
|
2659
1896
|
id: meta.id,
|
|
2660
1897
|
short_id: meta.shortId,
|
|
@@ -2663,8 +1900,6 @@ export function upsertSessionsBatch(entries) {
|
|
|
2663
1900
|
origin: meta.origin ?? 'cli',
|
|
2664
1901
|
routine_name: meta.routineName ?? null,
|
|
2665
1902
|
routine_run_id: meta.routineRunId ?? null,
|
|
2666
|
-
// Backfill the origin version from the launch-time sidecar when the scan
|
|
2667
|
-
// couldn't derive one — see the single-row upsert above (PHNX-3626).
|
|
2668
1903
|
version: meta.version ?? actorIndex.get(meta.id)?.version ?? null,
|
|
2669
1904
|
account: meta.account ?? null,
|
|
2670
1905
|
account_key: meta.accountKey ?? null,
|
|
@@ -2717,10 +1952,7 @@ export function upsertSessionsBatch(entries) {
|
|
|
2717
1952
|
};
|
|
2718
1953
|
upsert.run(row);
|
|
2719
1954
|
delText.run(meta.id);
|
|
2720
|
-
insText.run(meta.id, meta.id,
|
|
2721
|
-
// Mirror upsertSession: index the label the upsert actually stored
|
|
2722
|
-
// (preserve-non-empty rule), not the raw incoming one.
|
|
2723
|
-
storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
|
|
1955
|
+
insText.run(meta.id, meta.id, storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
|
|
2724
1956
|
if (scan && meta.filePath) {
|
|
2725
1957
|
ledger.run(canonicalLedgerKey(meta.filePath), scan.fileMtimeMs, scan.fileSize, now, parserState ?? null, contentText ?? null, CONTENT_INDEX_VERSION);
|
|
2726
1958
|
}
|
|
@@ -2735,38 +1967,21 @@ export function upsertSessionsBatch(entries) {
|
|
|
2735
1967
|
reconcileCodexFileOwners(db, writtenEntries.map(entry => entry.meta));
|
|
2736
1968
|
});
|
|
2737
1969
|
txn(enrichedEntries);
|
|
2738
|
-
// Tool evidence shares the transcript parse above but owns an independent
|
|
2739
|
-
// transaction/ledger. If this write fails, the normal session row remains
|
|
2740
|
-
// valid and ensureToolIndex retries from the missing tool ledger later.
|
|
2741
1970
|
for (const entry of writtenEntries) {
|
|
2742
1971
|
const toolScan = entry.toolScan ?? entry.scan;
|
|
2743
1972
|
if (!toolScan || !entry.toolCalls)
|
|
2744
1973
|
continue;
|
|
2745
1974
|
try {
|
|
2746
|
-
// `resume` is set only by the full-file harness path above; claude/codex
|
|
2747
|
-
// pass none, so their tool ledger keeps carrying no event-offset resume
|
|
2748
|
-
// point (their resume rides the content-scan ledger instead) — unchanged.
|
|
2749
1975
|
persistToolCalls(db, entry.meta, entry.toolCalls, toolScan, {
|
|
2750
1976
|
mode: entry.toolIndexMode ?? 'replace',
|
|
2751
1977
|
resume: entry.toolResume,
|
|
2752
1978
|
});
|
|
2753
1979
|
}
|
|
2754
1980
|
catch {
|
|
2755
|
-
// Boundary is intentionally retryable via tool_scan_ledger.
|
|
2756
1981
|
}
|
|
2757
1982
|
}
|
|
2758
|
-
// Every batch appends FTS segments (session_text always, tool_call_text for the
|
|
2759
|
-
// harnesses indexed above). Pay a bounded slice of the merge here so the
|
|
2760
|
-
// scan path keeps its own index healthy instead of leaving all compaction to
|
|
2761
|
-
// the manual `agents sessions optimize` (RUSH-2208). Threshold-gated, so a
|
|
2762
|
-
// small index costs two counts and nothing else.
|
|
2763
1983
|
maintainSessionSearchIndex(db);
|
|
2764
1984
|
}
|
|
2765
|
-
/**
|
|
2766
|
-
* Sync labels for a set of sessions. For each id in the map, if the stored
|
|
2767
|
-
* label differs, update both `sessions.label` and the FTS5 label column.
|
|
2768
|
-
* Leaves FTS5 content/topic/project untouched — cheap to call every run.
|
|
2769
|
-
*/
|
|
2770
1985
|
export function syncLabels(labelMap) {
|
|
2771
1986
|
if (labelMap.size === 0)
|
|
2772
1987
|
return 0;
|
|
@@ -2782,8 +1997,6 @@ export function syncLabels(labelMap) {
|
|
|
2782
1997
|
.all(...chunk);
|
|
2783
1998
|
for (const row of rows) {
|
|
2784
1999
|
const live = labelMap.get(row.id)?.trim() || null;
|
|
2785
|
-
// A missing/empty live label means "no refinement yet", not "erase the
|
|
2786
|
-
// generated title or launch handle already stored for this session".
|
|
2787
2000
|
if (live && live !== (row.label ?? '')) {
|
|
2788
2001
|
updates.push({ id: row.id, label: live });
|
|
2789
2002
|
}
|
|
@@ -2802,23 +2015,6 @@ export function syncLabels(labelMap) {
|
|
|
2802
2015
|
txn(updates);
|
|
2803
2016
|
return updates.length;
|
|
2804
2017
|
}
|
|
2805
|
-
/**
|
|
2806
|
-
* Seed session labels from `agents run --name` handles, keyed by session id.
|
|
2807
|
-
*
|
|
2808
|
-
* `--name` is the universal launch-time way to set a session's label — the same
|
|
2809
|
-
* field an agent later refines with a generated title (`syncLabels`) or the user
|
|
2810
|
-
* with `/rename`. The seed's source of truth lives outside the transcript (host
|
|
2811
|
-
* task sidecars, run-name sidecars written at launch), so it is re-applied by id
|
|
2812
|
-
* every scan rather than parsed per-file. It only fills a label that is still
|
|
2813
|
-
* EMPTY — an agent-generated title always wins over the seed, so a Claude run's
|
|
2814
|
-
* `--name` shows until Claude titles it, and a non-Claude run keeps its `--name`
|
|
2815
|
-
* as the label. Writes both `sessions.label` and the FTS5 label column so a
|
|
2816
|
-
* seeded name is fuzzy-searchable. Cheap to call every run; returns rows updated.
|
|
2817
|
-
*
|
|
2818
|
-
* Ordering matters: this runs AFTER the per-agent scans (which apply
|
|
2819
|
-
* agent-generated titles via {@link syncLabels}), so it never overwrites a real
|
|
2820
|
-
* title — it only backfills the gap the seed was meant to cover.
|
|
2821
|
-
*/
|
|
2822
2018
|
export function seedLabelsFromNames(nameMap) {
|
|
2823
2019
|
if (nameMap.size === 0)
|
|
2824
2020
|
return 0;
|
|
@@ -2834,7 +2030,6 @@ export function seedLabelsFromNames(nameMap) {
|
|
|
2834
2030
|
.all(...chunk);
|
|
2835
2031
|
for (const row of rows) {
|
|
2836
2032
|
const seed = nameMap.get(row.id);
|
|
2837
|
-
// Only fill an empty label; a real agent title (non-empty) always wins.
|
|
2838
2033
|
if (seed && !(row.label ?? '').trim()) {
|
|
2839
2034
|
updates.push({ id: row.id, label: seed });
|
|
2840
2035
|
}
|
|
@@ -2853,15 +2048,6 @@ export function seedLabelsFromNames(nameMap) {
|
|
|
2853
2048
|
txn(updates);
|
|
2854
2049
|
return updates.length;
|
|
2855
2050
|
}
|
|
2856
|
-
/**
|
|
2857
|
-
* Sync topics (session titles) for a set of sessions, keyed by id. For agents
|
|
2858
|
-
* whose human-readable title lives in a side index that updates independently
|
|
2859
|
-
* of the transcript (Codex `session_index.jsonl`), the per-file scan can't see
|
|
2860
|
-
* a title that lands later. This applies those titles by id, updating both
|
|
2861
|
-
* `sessions.topic` and the FTS5 topic column. Only ever sets a non-empty title
|
|
2862
|
-
* and only when it differs from the stored value — cheap to call every run.
|
|
2863
|
-
* Returns the number of rows updated.
|
|
2864
|
-
*/
|
|
2865
2051
|
export function syncTopics(topicMap) {
|
|
2866
2052
|
if (topicMap.size === 0)
|
|
2867
2053
|
return 0;
|
|
@@ -2895,7 +2081,6 @@ export function syncTopics(topicMap) {
|
|
|
2895
2081
|
txn(updates);
|
|
2896
2082
|
return updates.length;
|
|
2897
2083
|
}
|
|
2898
|
-
/** Convert a raw database row into a SessionMeta object. */
|
|
2899
2084
|
function rowToMeta(row) {
|
|
2900
2085
|
return {
|
|
2901
2086
|
id: row.id,
|
|
@@ -2948,18 +2133,10 @@ function rowToMeta(row) {
|
|
|
2948
2133
|
linearProjectUrl: row.linear_project_url ?? undefined,
|
|
2949
2134
|
machine: row.machine ?? undefined,
|
|
2950
2135
|
actor: row.actor ?? undefined,
|
|
2951
|
-
// Narrow the free-text column to the known kinds; an unexpected value maps
|
|
2952
|
-
// to undefined rather than being asserted as a valid kind.
|
|
2953
2136
|
initiatedBy: row.initiated_by === 'human' || row.initiated_by === 'agent' ? row.initiated_by : undefined,
|
|
2954
2137
|
phoenixId: row.phoenix_id ?? undefined,
|
|
2955
|
-
// NULL = never computed by this scanner (legacy row) — leave undefined so
|
|
2956
|
-
// the sessions picker knows to fall back to the transcript-regex detection
|
|
2957
|
-
// instead of trusting a false "never used browser/computer".
|
|
2958
2138
|
usedBrowser: row.used_browser === null ? undefined : row.used_browser === 1,
|
|
2959
2139
|
usedComputer: row.used_computer === null ? undefined : row.used_computer === 1,
|
|
2960
|
-
// A stamped archived_at means the transcript file is gone but the session's
|
|
2961
|
-
// user turns still live in session_text — the row is served from the DB and
|
|
2962
|
-
// flagged, never dropped (RUSH-2436). NULL leaves both undefined (live row).
|
|
2963
2140
|
archivedAt: row.archived_at ?? undefined,
|
|
2964
2141
|
archived: row.archived_at != null ? true : undefined,
|
|
2965
2142
|
mirrorSyncedAt: row.mirror_synced_at ?? undefined,
|
|
@@ -2979,14 +2156,6 @@ function parseJsonColumn(value) {
|
|
|
2979
2156
|
return undefined;
|
|
2980
2157
|
}
|
|
2981
2158
|
}
|
|
2982
|
-
/**
|
|
2983
|
-
* The recency signal used to sort and label the listing: last-message time when
|
|
2984
|
-
* a parser computed it (`meta.lastActivity` from `lastTsMs`), else the file's
|
|
2985
|
-
* mtime (its last write), else creation time. Guarded on `filePath` so synthetic
|
|
2986
|
-
* / cloud rows (no local file) fall to their creation timestamp rather than a
|
|
2987
|
-
* bogus scan-time mtime. Always an ISO string, so it sorts lexicographically
|
|
2988
|
-
* against `timestamp` and feeds `formatRelativeTime` unchanged.
|
|
2989
|
-
*/
|
|
2990
2159
|
function resolveLastActivity(meta, scan) {
|
|
2991
2160
|
if (meta.lastActivity)
|
|
2992
2161
|
return meta.lastActivity;
|
|
@@ -2994,26 +2163,6 @@ function resolveLastActivity(meta, scan) {
|
|
|
2994
2163
|
return new Date(scan.fileMtimeMs).toISOString();
|
|
2995
2164
|
return meta.timestamp;
|
|
2996
2165
|
}
|
|
2997
|
-
/**
|
|
2998
|
-
* The persisted wall-clock span for a session (PHNX-3457).
|
|
2999
|
-
*
|
|
3000
|
-
* `durationMs` is canonically `lastTs − firstTs`. Some harness scan extractors
|
|
3001
|
-
* derive it themselves from per-event timestamps (claude/codex/droid/gemini/
|
|
3002
|
-
* opencode set `meta.durationMs`); the rest (rush/grok/kimi/cursor/muse/
|
|
3003
|
-
* antigravity/hermes/openclaw) never did, so `duration_ms` landed NULL for them —
|
|
3004
|
-
* 52% of the corpus, including 100% of the dominant `rush` usage — and the
|
|
3005
|
-
* console median was computed over only the ~48% that happened to carry it,
|
|
3006
|
-
* skewing it short and misleading.
|
|
3007
|
-
*
|
|
3008
|
-
* This closes the gap at the single write boundary every harness funnels
|
|
3009
|
-
* through, so parity is automatic rather than per-extractor: when the extractor
|
|
3010
|
-
* already computed a precise span, keep it; otherwise derive it from the same
|
|
3011
|
-
* `timestamp` (creation) and `resolveLastActivity` (last event, itself resolved
|
|
3012
|
-
* from the harness's last-message time, else file mtime) that the row already
|
|
3013
|
-
* stores. Returns null only when no positive span can be established (a single
|
|
3014
|
-
* timestamped event, or a clock that runs backwards), which reads as NULL rather
|
|
3015
|
-
* than a fabricated 0.
|
|
3016
|
-
*/
|
|
3017
2166
|
function resolveDurationMs(meta, scan) {
|
|
3018
2167
|
if (meta.durationMs != null)
|
|
3019
2168
|
return meta.durationMs;
|
|
@@ -3029,17 +2178,10 @@ export function isSessionActivityFresh(row, maxAgeMs, nowMs) {
|
|
|
3029
2178
|
const activityMs = Number.isFinite(parsedActivityMs) ? parsedActivityMs : row.file_mtime_ms ?? undefined;
|
|
3030
2179
|
return activityMs != null && nowMs - activityMs <= maxAgeMs;
|
|
3031
2180
|
}
|
|
3032
|
-
/** Persist a lazily resolved Linear project without reparsing the transcript. */
|
|
3033
2181
|
export function cacheLinearProject(sessionId, project, projectUrl) {
|
|
3034
2182
|
getDB().prepare(`UPDATE sessions SET linear_project = ?, linear_project_url = ? WHERE id = ?`)
|
|
3035
2183
|
.run(project, projectUrl, sessionId);
|
|
3036
2184
|
}
|
|
3037
|
-
/**
|
|
3038
|
-
* Newest indexed session file for an agent working in `cwd`. Lets the live
|
|
3039
|
-
* `--active` scanner locate a Codex transcript (whose files are date-partitioned,
|
|
3040
|
-
* not cwd-keyed like Claude's) by reusing the index. Returns undefined if the
|
|
3041
|
-
* session hasn't been scanned yet — the caller degrades to no live state.
|
|
3042
|
-
*/
|
|
3043
2185
|
export function latestSessionFileForCwd(agent, cwd, options) {
|
|
3044
2186
|
if (!cwd)
|
|
3045
2187
|
return undefined;
|
|
@@ -3047,7 +2189,7 @@ export function latestSessionFileForCwd(agent, cwd, options) {
|
|
|
3047
2189
|
try {
|
|
3048
2190
|
normalized = fs.realpathSync(cwd);
|
|
3049
2191
|
}
|
|
3050
|
-
catch {
|
|
2192
|
+
catch { }
|
|
3051
2193
|
const db = getDB();
|
|
3052
2194
|
const row = db
|
|
3053
2195
|
.prepare(`SELECT file_path, last_activity, timestamp, file_mtime_ms
|
|
@@ -3064,7 +2206,6 @@ export function latestSessionFileForCwd(agent, cwd, options) {
|
|
|
3064
2206
|
}
|
|
3065
2207
|
return row.file_path;
|
|
3066
2208
|
}
|
|
3067
|
-
/** Build a parameterized WHERE clause from query options. */
|
|
3068
2209
|
function buildSessionWhere(options) {
|
|
3069
2210
|
const where = [];
|
|
3070
2211
|
const params = [];
|
|
@@ -3089,12 +2230,6 @@ function buildSessionWhere(options) {
|
|
|
3089
2230
|
params.push(options.cwd);
|
|
3090
2231
|
}
|
|
3091
2232
|
if (options.cwdPrefix) {
|
|
3092
|
-
// A LOCAL stored cwd uses the host path separator (normalizeCwd runs
|
|
3093
|
-
// path.normalize on it), so the subdir wildcard must too — a hardcoded '/'
|
|
3094
|
-
// never matches a Windows `C:\a\b` subpath and the listing comes back empty.
|
|
3095
|
-
// A cwd recorded on another machine keeps its own separators, so this
|
|
3096
|
-
// wildcard does not match foreign subpaths; both sides go through
|
|
3097
|
-
// normalizeCwd, so the exact `cwd = ?` comparison still holds for them.
|
|
3098
2233
|
where.push('(cwd = ? OR cwd LIKE ?)');
|
|
3099
2234
|
params.push(options.cwdPrefix, options.cwdPrefix + path.sep + '%');
|
|
3100
2235
|
}
|
|
@@ -3106,10 +2241,6 @@ function buildSessionWhere(options) {
|
|
|
3106
2241
|
where.push('machine = ? COLLATE NOCASE');
|
|
3107
2242
|
params.push(options.machine);
|
|
3108
2243
|
}
|
|
3109
|
-
// id lookup. SQLite's LIKE is case-insensitive for ASCII, so a lowercased
|
|
3110
|
-
// pattern matches mixed-case ids; the `=` exact compare adds COLLATE NOCASE
|
|
3111
|
-
// for the same reason. short_id carries its own index (idx_sessions_short_id);
|
|
3112
|
-
// id is the PRIMARY KEY.
|
|
3113
2244
|
if (options.idExact) {
|
|
3114
2245
|
where.push('(id = ? COLLATE NOCASE OR short_id = ? COLLATE NOCASE OR routine_run_id = ? COLLATE NOCASE)');
|
|
3115
2246
|
params.push(options.idExact, options.idExact, options.idExact);
|
|
@@ -3119,7 +2250,6 @@ function buildSessionWhere(options) {
|
|
|
3119
2250
|
params.push(`${options.idPrefix}%`, `${options.idPrefix}%`, `${options.idPrefix}%`);
|
|
3120
2251
|
}
|
|
3121
2252
|
if (typeof options.sinceMs === 'number') {
|
|
3122
|
-
// Compare as strings; ISO 8601 timestamps sort lexicographically.
|
|
3123
2253
|
where.push('timestamp >= ?');
|
|
3124
2254
|
params.push(new Date(options.sinceMs).toISOString());
|
|
3125
2255
|
}
|
|
@@ -3133,10 +2263,6 @@ function buildSessionWhere(options) {
|
|
|
3133
2263
|
if (options.onlyTeamOrigin) {
|
|
3134
2264
|
where.push('IFNULL(is_team_origin, 0) = 1');
|
|
3135
2265
|
}
|
|
3136
|
-
// #12: join against session_resource_usage. A subquery IN, not a real JOIN
|
|
3137
|
-
// on the base SELECT, keeps `SELECT * FROM sessions` untouched for every
|
|
3138
|
-
// other caller of buildSessionWhere() (countSessions, the usage rollup, …)
|
|
3139
|
-
// that never wants a skill/plugin filter.
|
|
3140
2266
|
if (options.skill) {
|
|
3141
2267
|
where.push(`id IN (
|
|
3142
2268
|
SELECT session_id FROM session_resource_usage
|
|
@@ -3154,23 +2280,14 @@ function buildSessionWhere(options) {
|
|
|
3154
2280
|
const directoryMembershipCache = new Map();
|
|
3155
2281
|
let directoryMembershipSweepCount = 0;
|
|
3156
2282
|
const DIRECTORY_MTIME_SETTLE_MS = 2_000;
|
|
3157
|
-
/** Process-local diagnostics for the real-filesystem existence-cache tests. */
|
|
3158
2283
|
export function getSessionExistenceCacheStats() {
|
|
3159
2284
|
return { sweeps: directoryMembershipSweepCount };
|
|
3160
2285
|
}
|
|
3161
|
-
/** Clear process-local directory membership state when the session DB closes. */
|
|
3162
2286
|
function clearSessionExistenceCache() {
|
|
3163
2287
|
directoryMembershipCache.clear();
|
|
3164
2288
|
directoryMembershipSweepCount = 0;
|
|
3165
2289
|
}
|
|
3166
2290
|
function findMissingFilePaths(filePaths) {
|
|
3167
|
-
// Existence is decided on the CONTAINER file, never the raw stored path. A
|
|
3168
|
-
// composite `file_path` (`<container>#<id>`, e.g. OpenCode's `opencode.db#ses_…`)
|
|
3169
|
-
// names a row INSIDE a shared file — its basename is never a directory entry,
|
|
3170
|
-
// so a dirname/basename membership check on the composite string classified
|
|
3171
|
-
// every such row as deleted and pruned it (RUSH-2357). Group the original
|
|
3172
|
-
// paths under the container we actually stat, and map the verdict back so the
|
|
3173
|
-
// returned set still holds the original `file_path` strings the caller keys on.
|
|
3174
2291
|
const byDir = new Map();
|
|
3175
2292
|
for (const p of filePaths) {
|
|
3176
2293
|
const container = sessionFilePathContainer(p);
|
|
@@ -3212,9 +2329,6 @@ function findMissingFilePaths(filePaths) {
|
|
|
3212
2329
|
}
|
|
3213
2330
|
catch {
|
|
3214
2331
|
directoryMembershipCache.delete(dir);
|
|
3215
|
-
// Directory itself is gone (or unreadable) — every file in it is missing.
|
|
3216
|
-
// Also covers the race where readdir loses to a concurrent delete: fall
|
|
3217
|
-
// back to a direct stat rather than assuming existence.
|
|
3218
2332
|
for (const [base, originals] of bases) {
|
|
3219
2333
|
const filePath = path.join(dir, base);
|
|
3220
2334
|
if (!fs.existsSync(filePath))
|
|
@@ -3229,25 +2343,12 @@ function findMissingFilePaths(filePaths) {
|
|
|
3229
2343
|
}
|
|
3230
2344
|
return missing;
|
|
3231
2345
|
}
|
|
3232
|
-
/** Query sessions from the database, applying filters and ordering by last-activity descending (default). */
|
|
3233
2346
|
export function querySessions(options = {}) {
|
|
3234
2347
|
const db = getDB();
|
|
3235
2348
|
const { clause, params } = buildSessionWhere(options);
|
|
3236
|
-
// When a LIMIT is in play, we still need to filter stale rows AFTER the query,
|
|
3237
|
-
// so over-fetch a small buffer. Without this, a page of 50 rows where the first
|
|
3238
|
-
// 5 are stale would return only 45 to the caller even when there are more.
|
|
3239
2349
|
const limitClause = options.limit
|
|
3240
2350
|
? `LIMIT ${Math.max(1, Math.floor(options.limit)) + 16}`
|
|
3241
2351
|
: '';
|
|
3242
|
-
// NULLs last so unpriced / duration-less rows never crowd out real data when
|
|
3243
|
-
// sorting by cost or duration. timestamp is never null (NOT NULL column).
|
|
3244
|
-
// Default sort is the bare `last_activity` column, not `IFNULL(last_activity,
|
|
3245
|
-
// timestamp)` — the v35 migration backfills every row so last_activity is
|
|
3246
|
-
// never NULL, and every upsert path (resolveLastActivity) keeps it that way
|
|
3247
|
-
// going forward. Wrapping the column in IFNULL() defeats
|
|
3248
|
-
// idx_sessions_last_activity (SQLite can't use an index on an expression that
|
|
3249
|
-
// isn't the bare column); the bare column lets the planner walk the index
|
|
3250
|
-
// instead of sorting the whole result set (RUSH-2211).
|
|
3251
2352
|
const orderClause = options.sortBy === 'cost'
|
|
3252
2353
|
? 'ORDER BY cost_usd IS NULL, cost_usd DESC, timestamp DESC'
|
|
3253
2354
|
: options.sortBy === 'duration'
|
|
@@ -3259,19 +2360,6 @@ export function querySessions(options = {}) {
|
|
|
3259
2360
|
const trimmed = options.limit ? rows.slice(0, options.limit) : rows;
|
|
3260
2361
|
return trimmed.map(rowToMeta);
|
|
3261
2362
|
}
|
|
3262
|
-
// A row whose transcript file is gone from disk is one of two things, and the
|
|
3263
|
-
// local DB is now authoritative for telling them apart (RUSH-2436):
|
|
3264
|
-
// - ARCHIVED — its user turns still live in session_text. The session is
|
|
3265
|
-
// real; the file was just removed (agents remove trash, a manual rm, a
|
|
3266
|
-
// .history rotation, a box reimage). Keep it, stamped `archived`, so it
|
|
3267
|
-
// still lists and renders from the DB instead of silently vanishing.
|
|
3268
|
-
// - PHANTOM — a stale/moved file_path with NO cached content (#136), e.g. a
|
|
3269
|
-
// path a scan forgot to rewrite. There is nothing durable to serve, so it
|
|
3270
|
-
// stays suppressed exactly as before.
|
|
3271
|
-
// We NO LONGER purge tool-call evidence here: merely listing a file-gone
|
|
3272
|
-
// session used to DELETE its redacted tool calls (purgeToolCalls), destroying
|
|
3273
|
-
// durable data on a read. Synthetic rows (OpenClaw channels/cron — see
|
|
3274
|
-
// scanOpenClawIncremental) carry an empty file_path and are exempt.
|
|
3275
2363
|
const missingPaths = findMissingFilePaths(rows.map(r => r.file_path).filter((p) => !!p));
|
|
3276
2364
|
const missing = rows.filter(r => r.file_path && missingPaths.has(r.file_path));
|
|
3277
2365
|
const missingIds = new Set(missing.map(r => r.id));
|
|
@@ -3284,8 +2372,6 @@ export function querySessions(options = {}) {
|
|
|
3284
2372
|
for (const row of missing) {
|
|
3285
2373
|
const content = readContent.get(row.id)?.content;
|
|
3286
2374
|
if (content && content.trim() !== '') {
|
|
3287
|
-
// Genuine archived session: stamp archived_at the first time we confirm
|
|
3288
|
-
// the file is gone, and reflect it on the in-memory row we return.
|
|
3289
2375
|
if (row.archived_at == null) {
|
|
3290
2376
|
markArchived.run(now, row.id);
|
|
3291
2377
|
row.archived_at = now;
|
|
@@ -3298,10 +2384,6 @@ export function querySessions(options = {}) {
|
|
|
3298
2384
|
});
|
|
3299
2385
|
classify();
|
|
3300
2386
|
}
|
|
3301
|
-
// Un-archive a row whose file came back (a recoverable-trash restore, a re-sync):
|
|
3302
|
-
// it is present on disk now, so it must not keep reporting `archived` to a
|
|
3303
|
-
// machine consumer reading the --json listing. Rare, so the write only fires when
|
|
3304
|
-
// such a row actually exists (RUSH-2436).
|
|
3305
2387
|
const resurrected = rows.filter(r => r.archived_at != null && !missingIds.has(r.id));
|
|
3306
2388
|
if (resurrected.length > 0) {
|
|
3307
2389
|
const clearArchived = db.prepare(`UPDATE sessions SET archived_at = NULL WHERE id = ?`);
|
|
@@ -3317,15 +2399,6 @@ export function querySessions(options = {}) {
|
|
|
3317
2399
|
const trimmed = options.limit ? live.slice(0, options.limit) : live;
|
|
3318
2400
|
return trimmed.map(rowToMeta);
|
|
3319
2401
|
}
|
|
3320
|
-
/**
|
|
3321
|
-
* Cheap query for the daemon's deferred tool-index pass (PHNX-3411).
|
|
3322
|
-
*
|
|
3323
|
-
* Returns the most-recently-active sessions whose parseSession reads a large
|
|
3324
|
-
* flat transcript (kimi: wire.jsonl, grok: chat_history.jsonl). Their scanners
|
|
3325
|
-
* produce no events, so upsertSessionsBatch skips them in the warm tick to
|
|
3326
|
-
* avoid wedging the event loop. ensureToolIndex uses tool_scan_ledger stamps
|
|
3327
|
-
* to skip already-current rows and applies byte/file budget caps.
|
|
3328
|
-
*/
|
|
3329
2402
|
export function querySessionsForDeferredToolIndex(limit) {
|
|
3330
2403
|
const db = getDB();
|
|
3331
2404
|
const rows = db.prepare(`
|
|
@@ -3337,7 +2410,6 @@ export function querySessionsForDeferredToolIndex(limit) {
|
|
|
3337
2410
|
`).all(limit);
|
|
3338
2411
|
return rows.map(rowToMeta);
|
|
3339
2412
|
}
|
|
3340
|
-
/** Count sessions matching the given filter options. */
|
|
3341
2413
|
export function countSessions(options = {}) {
|
|
3342
2414
|
const db = getDB();
|
|
3343
2415
|
const { clause, params } = buildSessionWhere(options);
|
|
@@ -3345,21 +2417,12 @@ export function countSessions(options = {}) {
|
|
|
3345
2417
|
const row = db.prepare(sql).get(...params);
|
|
3346
2418
|
return row ? row.n : 0;
|
|
3347
2419
|
}
|
|
3348
|
-
/** What to group a usage rollup by. */
|
|
3349
|
-
/**
|
|
3350
|
-
* Read cached facets for the given sessions, dropping any row that is stale.
|
|
3351
|
-
*
|
|
3352
|
-
* Staleness is decided in SQL against the session's own `file_mtime_ms` / `file_size`,
|
|
3353
|
-
* the same pair the scanner maintains — so the cache cannot disagree with the index,
|
|
3354
|
-
* `IS` rather than `=` so a source with no statable file — NULL on both sides — is a
|
|
3355
|
-
* cache HIT rather than a permanent miss that re-parses it on every run.
|
|
3356
|
-
*/
|
|
3357
2420
|
export function readSessionInsights(ids) {
|
|
3358
2421
|
const db = getDB();
|
|
3359
2422
|
const out = new Map();
|
|
3360
2423
|
if (ids.length === 0)
|
|
3361
2424
|
return out;
|
|
3362
|
-
const CHUNK = 400;
|
|
2425
|
+
const CHUNK = 400;
|
|
3363
2426
|
for (let i = 0; i < ids.length; i += CHUNK) {
|
|
3364
2427
|
const chunk = ids.slice(i, i + CHUNK);
|
|
3365
2428
|
const phs = chunk.map(() => '?').join(',');
|
|
@@ -3377,22 +2440,11 @@ export function readSessionInsights(ids) {
|
|
|
3377
2440
|
out.set(row.id, JSON.parse(row.facets));
|
|
3378
2441
|
}
|
|
3379
2442
|
catch {
|
|
3380
|
-
// A corrupt cache row is not a reason to fail the report; recompute it.
|
|
3381
2443
|
}
|
|
3382
2444
|
}
|
|
3383
2445
|
}
|
|
3384
2446
|
return out;
|
|
3385
2447
|
}
|
|
3386
|
-
/**
|
|
3387
|
-
* Persist freshly computed facets against the stamp of the bytes actually parsed.
|
|
3388
|
-
*
|
|
3389
|
-
* The caller passes the stat it observed when it read the file. Re-reading the stamp
|
|
3390
|
-
* from the sessions table inside this INSERT would race: a concurrent rescan between
|
|
3391
|
-
* the parse and the write (the cold path flushes in batches, so the window is minutes
|
|
3392
|
-
* wide, and this module treats concurrent access as a design assumption) stamps NEW
|
|
3393
|
-
* bytes onto OLD facets — a permanent false cache hit until the file changes again.
|
|
3394
|
-
* tool-index.ts sets the precedent: stat at parse time, carry the stamp into the write.
|
|
3395
|
-
*/
|
|
3396
2448
|
export function writeSessionInsights(entries) {
|
|
3397
2449
|
if (entries.length === 0)
|
|
3398
2450
|
return;
|
|
@@ -3415,11 +2467,9 @@ export function writeSessionInsights(entries) {
|
|
|
3415
2467
|
}
|
|
3416
2468
|
})();
|
|
3417
2469
|
}
|
|
3418
|
-
/** Drop every cached facet row. Backs `agents insights --refresh`. */
|
|
3419
2470
|
export function clearSessionInsights() {
|
|
3420
2471
|
getDB().exec(`DELETE FROM session_insights`);
|
|
3421
2472
|
}
|
|
3422
|
-
/** Read cached trace topics only when their transcript byte stamps still match. */
|
|
3423
2473
|
export function readSessionTopics(ids) {
|
|
3424
2474
|
const db = getDB();
|
|
3425
2475
|
const out = new Map();
|
|
@@ -3443,13 +2493,11 @@ export function readSessionTopics(ids) {
|
|
|
3443
2493
|
out.set(row.id, JSON.parse(row.topicJson));
|
|
3444
2494
|
}
|
|
3445
2495
|
catch {
|
|
3446
|
-
// Invalid derived cache data is a miss and self-heals on the next write.
|
|
3447
2496
|
}
|
|
3448
2497
|
}
|
|
3449
2498
|
}
|
|
3450
2499
|
return out;
|
|
3451
2500
|
}
|
|
3452
|
-
/** Persist trace topics against the exact transcript bytes used to classify them. */
|
|
3453
2501
|
export function writeSessionTopics(entries) {
|
|
3454
2502
|
if (entries.length === 0)
|
|
3455
2503
|
return;
|
|
@@ -3472,7 +2520,6 @@ export function writeSessionTopics(entries) {
|
|
|
3472
2520
|
}
|
|
3473
2521
|
})();
|
|
3474
2522
|
}
|
|
3475
|
-
/** Read cached failure phenotypes only when their transcript byte stamps still match. */
|
|
3476
2523
|
export function readSessionPhenotypes(ids) {
|
|
3477
2524
|
const db = getDB();
|
|
3478
2525
|
const out = new Map();
|
|
@@ -3496,13 +2543,11 @@ export function readSessionPhenotypes(ids) {
|
|
|
3496
2543
|
out.set(row.id, JSON.parse(row.phenotypeJson));
|
|
3497
2544
|
}
|
|
3498
2545
|
catch {
|
|
3499
|
-
// Invalid derived cache data is a miss and self-heals on the next write.
|
|
3500
2546
|
}
|
|
3501
2547
|
}
|
|
3502
2548
|
}
|
|
3503
2549
|
return out;
|
|
3504
2550
|
}
|
|
3505
|
-
/** Persist failure phenotypes against the exact transcript bytes used to classify them. */
|
|
3506
2551
|
export function writeSessionPhenotypes(entries) {
|
|
3507
2552
|
if (entries.length === 0)
|
|
3508
2553
|
return;
|
|
@@ -3525,7 +2570,6 @@ export function writeSessionPhenotypes(entries) {
|
|
|
3525
2570
|
}
|
|
3526
2571
|
})();
|
|
3527
2572
|
}
|
|
3528
|
-
/** Read one derived preview only when it matches the transcript bytes on disk. */
|
|
3529
2573
|
export function readSessionPreviewCache(id, sourceStamp) {
|
|
3530
2574
|
const row = getDB().prepare(`
|
|
3531
2575
|
SELECT pc.preview_json AS previewJson
|
|
@@ -3544,7 +2588,6 @@ export function readSessionPreviewCache(id, sourceStamp) {
|
|
|
3544
2588
|
return undefined;
|
|
3545
2589
|
}
|
|
3546
2590
|
}
|
|
3547
|
-
/** Persist normalized preview data against the exact transcript bytes parsed. */
|
|
3548
2591
|
export function writeSessionPreviewCache(entry) {
|
|
3549
2592
|
getDB().prepare(`
|
|
3550
2593
|
INSERT INTO session_preview_cache
|
|
@@ -3558,38 +2601,10 @@ export function writeSessionPreviewCache(entry) {
|
|
|
3558
2601
|
preview_json = excluded.preview_json
|
|
3559
2602
|
`).run(entry.id, entry.fileMtimeMs, entry.fileSize, PREVIEW_EXTRACTOR_VERSION, Date.now(), JSON.stringify(entry.preview));
|
|
3560
2603
|
}
|
|
3561
|
-
/** Bump when the cached remote preview envelope shape changes so cached rows recompute (PHNX-3999 v1). */
|
|
3562
2604
|
export const REMOTE_PREVIEW_SCHEMA_VERSION = 1;
|
|
3563
|
-
/** Cap on distinct (device, sessionId) rows this cache holds — a requester box
|
|
3564
|
-
* previews many peers' sessions over a long uptime, so this bounds growth the
|
|
3565
|
-
* same way the fleet session mirror bounds itself to 200 rows (db.ts, PHNX-3792):
|
|
3566
|
-
* oldest-by-last-fetch is evicted first. Row count alone is not a real bound —
|
|
3567
|
-
* every field the cached envelope carries is already bounded (digest fields,
|
|
3568
|
-
* `detail.messages` <= 8 x 4000 chars, `detail.timeline` <= 8 steps, etc.), but
|
|
3569
|
-
* 500 rows x an unbounded per-row size is still unbounded storage, so this is
|
|
3570
|
-
* paired with a total-byte budget below. */
|
|
3571
2605
|
const REMOTE_PREVIEW_CACHE_MAX_ROWS = 500;
|
|
3572
|
-
/**
|
|
3573
|
-
* Total on-disk budget for `envelope_json` across every cached row, enforced
|
|
3574
|
-
* ALONGSIDE the row cap (whichever evicts more aggressively wins). At the
|
|
3575
|
-
* 500-row cap this is a ~32 KiB/row average, which comfortably fits the
|
|
3576
|
-
* bounded envelope shape above — a single row is refused entry above
|
|
3577
|
-
* {@link REMOTE_PREVIEW_ENVELOPE_MAX_BYTES} rather than being allowed to
|
|
3578
|
-
* consume the whole budget by itself.
|
|
3579
|
-
*/
|
|
3580
2606
|
const REMOTE_PREVIEW_CACHE_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
|
|
3581
|
-
/**
|
|
3582
|
-
* Per-envelope cache-write cap. The bounded fields above make a well-formed
|
|
3583
|
-
* envelope small; this exists to refuse an outlier (a version-skewed peer, or
|
|
3584
|
-
* a future field that forgets to bound itself) rather than let ONE session
|
|
3585
|
-
* blow the total budget. A refused write is NOT an error — the caller still
|
|
3586
|
-
* gets the live envelope for this call, it simply is not persisted, so the
|
|
3587
|
-
* next call re-fetches instead of silently caching a giant blob.
|
|
3588
|
-
*/
|
|
3589
2607
|
export const REMOTE_PREVIEW_ENVELOPE_MAX_BYTES = 512 * 1024;
|
|
3590
|
-
/** Read the durable cached remote preview row for one (device, sessionId) pair,
|
|
3591
|
-
* regardless of whether it currently holds a successful envelope. Undefined
|
|
3592
|
-
* means this box has never attempted (or recorded) a fetch for that pair. */
|
|
3593
2608
|
export function readRemotePreviewCache(device, sessionId) {
|
|
3594
2609
|
const row = getDB().prepare(`
|
|
3595
2610
|
SELECT fetched_at AS fetchedAt, ok, envelope_json AS envelopeJson, failure_reason AS failureReason,
|
|
@@ -3606,7 +2621,6 @@ export function readRemotePreviewCache(device, sessionId) {
|
|
|
3606
2621
|
envelope = JSON.parse(row.envelopeJson);
|
|
3607
2622
|
}
|
|
3608
2623
|
catch {
|
|
3609
|
-
// Corrupt cache row: treat as no payload rather than throwing.
|
|
3610
2624
|
}
|
|
3611
2625
|
}
|
|
3612
2626
|
return {
|
|
@@ -3619,14 +2633,6 @@ export function readRemotePreviewCache(device, sessionId) {
|
|
|
3619
2633
|
lastCallerRevision: row.lastCallerRevision ?? undefined,
|
|
3620
2634
|
};
|
|
3621
2635
|
}
|
|
3622
|
-
/**
|
|
3623
|
-
* Record the caller's own `--revision` cursor for one (device, sessionId)
|
|
3624
|
-
* pair, independent of any fetch. A no-op if no row exists yet (nothing to
|
|
3625
|
-
* compare against on a session this box has never fetched). This is what lets
|
|
3626
|
-
* "same revision as last time" serve the durable cache with zero SSH
|
|
3627
|
-
* indefinitely — the comparison is against THIS value, never against the
|
|
3628
|
-
* envelope's own `details.sourceRevision` (which may be a different format).
|
|
3629
|
-
*/
|
|
3630
2636
|
export function writeRemotePreviewCallerRevision(device, sessionId, revision) {
|
|
3631
2637
|
getDB().prepare(`
|
|
3632
2638
|
UPDATE session_remote_preview_cache
|
|
@@ -3642,8 +2648,6 @@ function pruneRemotePreviewCache(maxRows = REMOTE_PREVIEW_CACHE_MAX_ROWS) {
|
|
|
3642
2648
|
SELECT rowid FROM session_remote_preview_cache ORDER BY fetched_at DESC LIMIT ?
|
|
3643
2649
|
)
|
|
3644
2650
|
`).run(maxRows);
|
|
3645
|
-
// Total-byte budget, independent of row count: walk newest-first, keep
|
|
3646
|
-
// rows until the running total would exceed the budget, drop the rest.
|
|
3647
2651
|
const rows = db.prepare(`
|
|
3648
2652
|
SELECT rowid AS rowid,
|
|
3649
2653
|
envelope_bytes + length(CAST(COALESCE(failure_reason, '') AS BLOB))
|
|
@@ -3663,13 +2667,6 @@ function pruneRemotePreviewCache(maxRows = REMOTE_PREVIEW_CACHE_MAX_ROWS) {
|
|
|
3663
2667
|
db.prepare(`DELETE FROM session_remote_preview_cache WHERE rowid IN (${placeholders})`).run(...evict);
|
|
3664
2668
|
}
|
|
3665
2669
|
}
|
|
3666
|
-
/**
|
|
3667
|
-
* Record a successful remote fetch: replaces the payload and resets backoff.
|
|
3668
|
-
* An envelope over {@link REMOTE_PREVIEW_ENVELOPE_MAX_BYTES} is refused —
|
|
3669
|
-
* this is a write-path bound, not a request failure: the caller already has
|
|
3670
|
-
* the live envelope for this call from the fetch that produced it, this only
|
|
3671
|
-
* decides whether it is worth persisting.
|
|
3672
|
-
*/
|
|
3673
2670
|
export function writeRemotePreviewCacheSuccess(device, sessionId, envelope, fetchedAt = Date.now(), revision) {
|
|
3674
2671
|
const envelopeJson = JSON.stringify(envelope);
|
|
3675
2672
|
const envelopeBytes = Buffer.byteLength(envelopeJson, 'utf8');
|
|
@@ -3695,23 +2692,12 @@ export function writeRemotePreviewCacheSuccess(device, sessionId, envelope, fetc
|
|
|
3695
2692
|
});
|
|
3696
2693
|
write();
|
|
3697
2694
|
}
|
|
3698
|
-
/** Cap on a stored `failure_reason` string. Some reasons interpolate
|
|
3699
|
-
* peer-controlled content (`validateEnvelope`'s mismatched-id message
|
|
3700
|
-
* embeds the peer's own claimed session id) — bound it so a misbehaving or
|
|
3701
|
-
* malicious peer can't inflate a cache row's stored text unboundedly. */
|
|
3702
2695
|
const REMOTE_PREVIEW_FAILURE_REASON_MAX_CHARS = 300;
|
|
3703
2696
|
function boundFailureReason(reason) {
|
|
3704
2697
|
return reason.length > REMOTE_PREVIEW_FAILURE_REASON_MAX_CHARS
|
|
3705
2698
|
? reason.slice(0, REMOTE_PREVIEW_FAILURE_REASON_MAX_CHARS) + '…'
|
|
3706
2699
|
: reason;
|
|
3707
2700
|
}
|
|
3708
|
-
/**
|
|
3709
|
-
* Record a failed remote fetch attempt with exponential backoff, WITHOUT
|
|
3710
|
-
* discarding a prior good envelope — a session that answered once and is now
|
|
3711
|
-
* offline still degrades to that last-good payload (read back via
|
|
3712
|
-
* {@link readRemotePreviewCache}'s `ok`/`envelope`), annotated stale by the
|
|
3713
|
-
* caller, rather than losing it the moment one attempt fails.
|
|
3714
|
-
*/
|
|
3715
2701
|
export function writeRemotePreviewCacheFailure(device, sessionId, reason, backoffMs, now = Date.now()) {
|
|
3716
2702
|
const boundedReason = boundFailureReason(reason);
|
|
3717
2703
|
const db = getDB();
|
|
@@ -3734,36 +2720,14 @@ export function writeRemotePreviewCacheFailure(device, sessionId, reason, backof
|
|
|
3734
2720
|
consecutive_failures = excluded.consecutive_failures,
|
|
3735
2721
|
next_attempt_at = excluded.next_attempt_at
|
|
3736
2722
|
`).run(device, sessionId, REMOTE_PREVIEW_SCHEMA_VERSION, existing?.fetchedAt ?? now, keepOk, envelopeJson, envelopeBytes, boundedReason, consecutiveFailures, nextAttemptAt);
|
|
3737
|
-
// A failure-only row still occupies a slot in the row/byte-bounded cache —
|
|
3738
|
-
// it must be pruned like any other write, or a persistently-offline peer's
|
|
3739
|
-
// failure rows accumulate without bound (they were previously exempt from
|
|
3740
|
-
// this call entirely).
|
|
3741
2723
|
pruneRemotePreviewCache();
|
|
3742
2724
|
});
|
|
3743
2725
|
write();
|
|
3744
2726
|
}
|
|
3745
|
-
/**
|
|
3746
|
-
* The session's durable user-turn text, as stored in the `session_text` FTS
|
|
3747
|
-
* `content` column at scan time (all harnesses, keyed by session_id). This is
|
|
3748
|
-
* the content that survives the transcript file being deleted (RUSH-2436): the
|
|
3749
|
-
* render path reads it so a file-gone session still shows its user prompts, and
|
|
3750
|
-
* querySessions uses its presence to tell a genuine archived session (has
|
|
3751
|
-
* content) from a phantom (a stale/moved file_path with none). Returns undefined
|
|
3752
|
-
* when no row exists; an empty string when a row exists but carried no content.
|
|
3753
|
-
*/
|
|
3754
2727
|
export function readSessionContent(id) {
|
|
3755
2728
|
const row = getDB().prepare(`SELECT content FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`).get(id);
|
|
3756
2729
|
return row?.content;
|
|
3757
2730
|
}
|
|
3758
|
-
/**
|
|
3759
|
-
* Read the last-computed preview digest for a session by id, validated against
|
|
3760
|
-
* the session row's OWN stored file stamp rather than a live `fs.stat` — the
|
|
3761
|
-
* transcript file is gone, so there is nothing on disk to stat, but the preview
|
|
3762
|
-
* cache was written with the same `file_mtime_ms`/`file_size` the sessions row
|
|
3763
|
-
* still records, so the join re-establishes that stamp. Backs the file-gone
|
|
3764
|
-
* render/preview path (RUSH-2436). Returns undefined when no cached digest
|
|
3765
|
-
* survives for this session.
|
|
3766
|
-
*/
|
|
3767
2731
|
export function readArchivedSessionPreview(id) {
|
|
3768
2732
|
const row = getDB().prepare(`
|
|
3769
2733
|
SELECT pc.preview_json AS previewJson
|
|
@@ -3783,11 +2747,6 @@ export function readArchivedSessionPreview(id) {
|
|
|
3783
2747
|
return undefined;
|
|
3784
2748
|
}
|
|
3785
2749
|
}
|
|
3786
|
-
/**
|
|
3787
|
-
* Read one summary only when it matches the transcript bytes on disk (the
|
|
3788
|
-
* reuse-or-recompute stamp the SessionSummarizerService gates on). Mirrors
|
|
3789
|
-
* {@link readSessionPreviewCache}.
|
|
3790
|
-
*/
|
|
3791
2750
|
export function readSessionSummary(id, sourceStamp) {
|
|
3792
2751
|
const row = getDB().prepare(`
|
|
3793
2752
|
SELECT summary_json AS summaryJson
|
|
@@ -3806,14 +2765,6 @@ export function readSessionSummary(id, sourceStamp) {
|
|
|
3806
2765
|
return undefined;
|
|
3807
2766
|
}
|
|
3808
2767
|
}
|
|
3809
|
-
/**
|
|
3810
|
-
* Read the latest stored summary by session id, regardless of the transcript
|
|
3811
|
-
* stamp — the low-cost merge read used by the display path (a live row through
|
|
3812
|
-
* {@link applyImmutableMemo} or a history/mirror row) to surface the
|
|
3813
|
-
* last-computed summary without a model call. A slightly-stale summary is the
|
|
3814
|
-
* best available until the service recomputes; the SERVICE uses the stamped
|
|
3815
|
-
* {@link readSessionSummary} to decide whether to recompute.
|
|
3816
|
-
*/
|
|
3817
2768
|
export function readSessionSummaryAny(id) {
|
|
3818
2769
|
const row = getDB().prepare(`
|
|
3819
2770
|
SELECT summary_json AS summaryJson
|
|
@@ -3829,7 +2780,6 @@ export function readSessionSummaryAny(id) {
|
|
|
3829
2780
|
return undefined;
|
|
3830
2781
|
}
|
|
3831
2782
|
}
|
|
3832
|
-
/** Persist a computed summary against the exact transcript bytes it was derived from. */
|
|
3833
2783
|
export function writeSessionSummary(entry) {
|
|
3834
2784
|
getDB().prepare(`
|
|
3835
2785
|
INSERT INTO session_summaries
|
|
@@ -3851,11 +2801,6 @@ function parseTimelineProjection(json) {
|
|
|
3851
2801
|
return undefined;
|
|
3852
2802
|
}
|
|
3853
2803
|
}
|
|
3854
|
-
/**
|
|
3855
|
-
* Read the projection AND the resume state for one session, whatever the
|
|
3856
|
-
* transcript stamp — the daemon pass's read, which needs the state to continue
|
|
3857
|
-
* from and re-stats the file itself.
|
|
3858
|
-
*/
|
|
3859
2804
|
export function readSessionTimelineEntry(id) {
|
|
3860
2805
|
const row = getDB().prepare(`
|
|
3861
2806
|
SELECT projection_json AS projectionJson, state_json AS stateJson, computed_at AS computedAt
|
|
@@ -3874,11 +2819,6 @@ export function readSessionTimelineEntry(id) {
|
|
|
3874
2819
|
return undefined;
|
|
3875
2820
|
}
|
|
3876
2821
|
}
|
|
3877
|
-
/**
|
|
3878
|
-
* Read only the bounded projection for one session — the display merge, on the
|
|
3879
|
-
* read path. Deliberately does NOT parse the resume state: a live row needs the
|
|
3880
|
-
* 8 steps and the request, never the fold's bookkeeping.
|
|
3881
|
-
*/
|
|
3882
2822
|
export function readSessionTimelineAny(id, stamp) {
|
|
3883
2823
|
const row = getDB().prepare(`
|
|
3884
2824
|
SELECT projection_json AS projectionJson
|
|
@@ -3890,7 +2830,6 @@ export function readSessionTimelineAny(id, stamp) {
|
|
|
3890
2830
|
return undefined;
|
|
3891
2831
|
return parseTimelineProjection(row.projectionJson);
|
|
3892
2832
|
}
|
|
3893
|
-
/** Persist a folded timeline against the exact transcript bytes it was folded to. */
|
|
3894
2833
|
export function writeSessionTimeline(entry) {
|
|
3895
2834
|
const { state, ...projection } = entry.timeline;
|
|
3896
2835
|
getDB().prepare(`
|
|
@@ -3906,13 +2845,6 @@ export function writeSessionTimeline(entry) {
|
|
|
3906
2845
|
state_json = excluded.state_json
|
|
3907
2846
|
`).run(entry.id, entry.fileMtimeMs, entry.fileSize, TIMELINE_EXTRACTOR_VERSION, entry.computedAtMs ?? Date.now(), JSON.stringify(projection), JSON.stringify(state));
|
|
3908
2847
|
}
|
|
3909
|
-
/**
|
|
3910
|
-
* The most-recently-active sessions whose transcript is genuinely local to this
|
|
3911
|
-
* box — a real `file_path`, not a peer mirror — for publishing to the fleet
|
|
3912
|
-
* session mirror (PHNX-3792). Bounded and team-origin-excluded so the published
|
|
3913
|
-
* payload stays the size a picker would show. Never returns a row this box is
|
|
3914
|
-
* itself mirroring from a peer (`mirror_synced_at IS NULL`).
|
|
3915
|
-
*/
|
|
3916
2848
|
export function queryLocalOriginSessionsForMirror(self, limit) {
|
|
3917
2849
|
const rows = getDB().prepare(`
|
|
3918
2850
|
SELECT id, short_id, agent, version, machine, cwd, topic, first_user_message,
|
|
@@ -3931,23 +2863,10 @@ export function queryLocalOriginSessionsForMirror(self, limit) {
|
|
|
3931
2863
|
cwd: r.cwd, topic: r.topic, firstUserMessage: r.first_user_message, label: r.label,
|
|
3932
2864
|
generatedTitle: r.generated_title,
|
|
3933
2865
|
lastActivity: r.last_activity, timestamp: r.timestamp, ticketId: r.ticket_id, prUrl: r.pr_url,
|
|
3934
|
-
// Ride the daemon-computed summary alongside the digest so a peer renders it
|
|
3935
|
-
// without a transcript (PHNX-3939); only a summarized session carries one.
|
|
3936
2866
|
summary: readSessionSummaryAny(r.id) ?? null,
|
|
3937
2867
|
timeline: readSessionTimelineAny(r.id) ?? null,
|
|
3938
2868
|
}));
|
|
3939
2869
|
}
|
|
3940
|
-
/**
|
|
3941
|
-
* Write one peer session's digest into the local `sessions` index as a mirror
|
|
3942
|
-
* row (PHNX-3792), so the picker/list/focus render its topic/preview inline
|
|
3943
|
-
* with no per-row SSH. The upsert is GUARDED: it never overwrites a genuine
|
|
3944
|
-
* local transcript row (`file_path` non-empty and not itself a mirror), so a
|
|
3945
|
-
* mirror can only create a new peer-only row or enrich an existing empty-file
|
|
3946
|
-
* host-dispatch stub / older mirror. Returns true when a row was written (the
|
|
3947
|
-
* caller then persists the matching preview digest); false when a real row was
|
|
3948
|
-
* protected. `machine` carries the publisher's recorded execution host so the
|
|
3949
|
-
* row collapses with the live fan-out row under the same `machine:id` key.
|
|
3950
|
-
*/
|
|
3951
2870
|
export function upsertMirrorSession(row, source, syncedAt) {
|
|
3952
2871
|
const db = getDB();
|
|
3953
2872
|
const lastActivity = row.lastActivity ?? row.timestamp;
|
|
@@ -4009,24 +2928,14 @@ export function upsertMirrorSession(row, source, syncedAt) {
|
|
|
4009
2928
|
});
|
|
4010
2929
|
if (result.changes === 0)
|
|
4011
2930
|
return false;
|
|
4012
|
-
// Keep the mirror row searchable by topic + first user turn, like a local row.
|
|
4013
2931
|
db.prepare(`DELETE FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`).run(row.id);
|
|
4014
2932
|
db.prepare(`
|
|
4015
2933
|
INSERT INTO session_text (rowid, session_id, label, topic, project, content, assistant)
|
|
4016
2934
|
VALUES (${SESSION_TEXT_ROWID}, ?, ?, ?, '', ?, '')
|
|
4017
2935
|
`).run(row.id, row.id, row.label ?? '', row.topic ?? '', row.firstUser ?? '');
|
|
4018
|
-
// Carry the peer's daemon-computed summary into the same session_summaries
|
|
4019
|
-
// cache the local merge reads (PHNX-3939), so a peer session renders its
|
|
4020
|
-
// goal/checkpoints/checklist inline with no transcript. Stamp is null: the
|
|
4021
|
-
// display merge reads by id (readSessionSummaryAny), and this box's own
|
|
4022
|
-
// summarizer service only ever recomputes LOCAL live sessions, never a peer's.
|
|
4023
2936
|
if (row.summary) {
|
|
4024
2937
|
writeSessionSummary({ id: row.id, fileMtimeMs: null, fileSize: null, summary: row.summary });
|
|
4025
2938
|
}
|
|
4026
|
-
// Same for the peer's folded timeline (PHNX-3939): the projection is what a row
|
|
4027
|
-
// renders, and the resume state is deliberately EMPTY — this box never folds a
|
|
4028
|
-
// peer's transcript (it does not have one), so storing a foreign byte offset
|
|
4029
|
-
// would be a resume point into a file that is not here.
|
|
4030
2939
|
if (row.timeline) {
|
|
4031
2940
|
writeSessionTimeline({
|
|
4032
2941
|
id: row.id,
|
|
@@ -4037,19 +2946,6 @@ export function upsertMirrorSession(row, source, syncedAt) {
|
|
|
4037
2946
|
}
|
|
4038
2947
|
return true;
|
|
4039
2948
|
}
|
|
4040
|
-
/**
|
|
4041
|
-
* The most-recently-active LOCAL sessions the titler may consider (PHNX-3797).
|
|
4042
|
-
*
|
|
4043
|
-
* Deliberately not "every untitled row": the caller decides which of these still
|
|
4044
|
-
* needs work by comparing each row's stored `generatedTitleKey` against the key
|
|
4045
|
-
* of its current user text, so a first-user-message change re-titles without a
|
|
4046
|
-
* second stored copy of that text. Rows excluded here can never be titled:
|
|
4047
|
-
* - a peer MIRROR row — its owning box generates its title and publishes it;
|
|
4048
|
-
* - a row with no user text at all — there is nothing user-anchored to title;
|
|
4049
|
-
* - a row that already carries a `/rename`-style `label`, which outranks a
|
|
4050
|
-
* generated title in the ladder, so generating one would buy nothing.
|
|
4051
|
-
* `sinceMs` bounds the work to sessions recent enough to still be shown.
|
|
4052
|
-
*/
|
|
4053
2949
|
export function querySessionTitleCandidates(limit, opts = {}) {
|
|
4054
2950
|
const clauses = [
|
|
4055
2951
|
`mirror_synced_at IS NULL`,
|
|
@@ -4061,8 +2957,6 @@ export function querySessionTitleCandidates(limit, opts = {}) {
|
|
|
4061
2957
|
params.push(opts.id, opts.id);
|
|
4062
2958
|
}
|
|
4063
2959
|
else {
|
|
4064
|
-
// An explicitly requested session is titled even when it carries a label or
|
|
4065
|
-
// is older than the window; the periodic sweep honours both bounds.
|
|
4066
2960
|
clauses.push(`(label IS NULL OR trim(label) = '')`);
|
|
4067
2961
|
if (opts.sinceMs != null) {
|
|
4068
2962
|
clauses.push(`COALESCE(last_activity, timestamp) >= ?`);
|
|
@@ -4091,13 +2985,6 @@ export function querySessionTitleCandidates(limit, opts = {}) {
|
|
|
4091
2985
|
generatedTitleKey: r.generated_title_key,
|
|
4092
2986
|
}));
|
|
4093
2987
|
}
|
|
4094
|
-
/**
|
|
4095
|
-
* Persist one generated title against the source key it was derived from
|
|
4096
|
-
* (PHNX-3797). `sourceKey` is what makes generation once-per-session: the next
|
|
4097
|
-
* sweep sees a stored key equal to the row's current user text and skips it,
|
|
4098
|
-
* and regenerates only when that text changed. Never touches a mirror row —
|
|
4099
|
-
* the publishing box owns its own sessions' titles.
|
|
4100
|
-
*/
|
|
4101
2988
|
export function setSessionGeneratedTitle(id, title, sourceKey, now = Date.now()) {
|
|
4102
2989
|
const result = getDB().prepare(`
|
|
4103
2990
|
UPDATE sessions
|
|
@@ -4106,12 +2993,6 @@ export function setSessionGeneratedTitle(id, title, sourceKey, now = Date.now())
|
|
|
4106
2993
|
`).run(title, sourceKey, now, id);
|
|
4107
2994
|
return result.changes > 0;
|
|
4108
2995
|
}
|
|
4109
|
-
/**
|
|
4110
|
-
* Drop peer mirror rows (and their cached digests) whose last sync predates the
|
|
4111
|
-
* cutoff — the size ceiling / staleness pruner for the fleet session mirror
|
|
4112
|
-
* (PHNX-3792). Only ever touches mirror rows (`mirror_synced_at IS NOT NULL`),
|
|
4113
|
-
* never a genuine local or host-dispatch row. Returns the number of rows pruned.
|
|
4114
|
-
*/
|
|
4115
2996
|
export function pruneMirrorSessions(cutoffMs) {
|
|
4116
2997
|
const db = getDB();
|
|
4117
2998
|
const stale = db.prepare(`SELECT id FROM sessions WHERE mirror_synced_at IS NOT NULL AND mirror_synced_at < ?`).all(cutoffMs);
|
|
@@ -4124,7 +3005,6 @@ export function pruneMirrorSessions(cutoffMs) {
|
|
|
4124
3005
|
const delTimeline = db.prepare(`DELETE FROM session_timelines WHERE session_id = ?`);
|
|
4125
3006
|
const txn = db.transaction(() => {
|
|
4126
3007
|
for (const { id } of stale) {
|
|
4127
|
-
// Text first: its rowid lookup needs the sessions row still present.
|
|
4128
3008
|
delText.run(id);
|
|
4129
3009
|
delRow.run(id);
|
|
4130
3010
|
delPreview.run(id);
|
|
@@ -4135,7 +3015,6 @@ export function pruneMirrorSessions(cutoffMs) {
|
|
|
4135
3015
|
txn();
|
|
4136
3016
|
return stale.length;
|
|
4137
3017
|
}
|
|
4138
|
-
/** Plugin provenance already indexed for resources used by one session. */
|
|
4139
3018
|
export function getSessionPlugins(id) {
|
|
4140
3019
|
const rows = getDB().prepare(`
|
|
4141
3020
|
SELECT DISTINCT plugin
|
|
@@ -4150,7 +3029,6 @@ export function queryAffinityRollup(options) {
|
|
|
4150
3029
|
const where = [];
|
|
4151
3030
|
const params = [];
|
|
4152
3031
|
const sinceMs = options.sinceMs ?? (Date.now() - 14 * 24 * 60 * 60 * 1000);
|
|
4153
|
-
// ISO timestamps sort lexicographically; compare as string prefix of datetime.
|
|
4154
3032
|
where.push(`timestamp >= ?`);
|
|
4155
3033
|
params.push(new Date(sinceMs).toISOString());
|
|
4156
3034
|
if (options.onlyCli !== false) {
|
|
@@ -4196,13 +3074,6 @@ export function queryAffinityRollup(options) {
|
|
|
4196
3074
|
`;
|
|
4197
3075
|
return db.prepare(sql).all(...params);
|
|
4198
3076
|
}
|
|
4199
|
-
/**
|
|
4200
|
-
* Aggregate cost / duration / tokens across sessions, grouped by agent,
|
|
4201
|
-
* project, shortened model id, account, or calendar day. Honors the same
|
|
4202
|
-
* filter shape as querySessions (agent, since/until, team-origin) so
|
|
4203
|
-
* `agents insights cost --since 7d --by day` lines up with what
|
|
4204
|
-
* `agents sessions` would list. Ordered by cost desc.
|
|
4205
|
-
*/
|
|
4206
3077
|
export function queryUsageRollup(options) {
|
|
4207
3078
|
const db = getDB();
|
|
4208
3079
|
const { clause, params } = buildSessionWhere(options);
|
|
@@ -4211,8 +3082,6 @@ export function queryUsageRollup(options) {
|
|
|
4211
3082
|
: options.groupBy === 'project'
|
|
4212
3083
|
? `IFNULL(NULLIF(project, ''), '(no project)')`
|
|
4213
3084
|
: options.groupBy === 'model'
|
|
4214
|
-
// Match shortenModel(): remove Claude's redundant harness prefix and
|
|
4215
|
-
// an optional eight-digit release-date suffix before grouping.
|
|
4216
3085
|
? `IFNULL(NULLIF(CASE
|
|
4217
3086
|
WHEN substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, -9, 1) = '-'
|
|
4218
3087
|
AND length(substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, -8)) = 8
|
|
@@ -4222,19 +3091,12 @@ export function queryUsageRollup(options) {
|
|
|
4222
3091
|
ELSE CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END
|
|
4223
3092
|
END, ''), '(unknown)')`
|
|
4224
3093
|
: options.groupBy === 'account'
|
|
4225
|
-
// A NULL account_key means this harness has no account attribution yet —
|
|
4226
|
-
// the mechanism is Claude-only today (see lib/session/claude-accounts.ts).
|
|
4227
|
-
// Bucket per agent so the rows are named honestly instead of being called
|
|
4228
|
-
// "not indexed", which they are not, and instead of joining a real account.
|
|
4229
3094
|
? `IFNULL(NULLIF(account_key, ''), 'unattributed:' || agent)`
|
|
4230
|
-
// ISO timestamps are lexicographically date-sortable; the date is the
|
|
4231
|
-
// first 10 chars (YYYY-MM-DD).
|
|
4232
3095
|
: `substr(timestamp, 1, 10)`;
|
|
4233
3096
|
const sql = `
|
|
4234
3097
|
SELECT
|
|
4235
3098
|
${keyExpr} AS key,
|
|
4236
3099
|
${options.groupBy === 'account'
|
|
4237
|
-
// One label per account_key by construction, so MAX just picks it out.
|
|
4238
3100
|
? `MAX(CASE WHEN account_org IS NOT NULL AND account IS NOT NULL
|
|
4239
3101
|
THEN account_org || ' <' || account || '>' END) AS label,`
|
|
4240
3102
|
: ''}
|
|
@@ -4257,34 +3119,9 @@ export function queryUsageRollup(options) {
|
|
|
4257
3119
|
`;
|
|
4258
3120
|
return db.prepare(sql).all(...params);
|
|
4259
3121
|
}
|
|
4260
|
-
/**
|
|
4261
|
-
* Roll up skill / slash-command usage from session_resource_usage, joined to
|
|
4262
|
-
* `sessions` for attribution so the same filter shape as querySessions
|
|
4263
|
-
* (agent / project / since / machine) narrows WHICH sessions count. Grouped by
|
|
4264
|
-
* resource identity (kind + name + plugin + source) and ordered by invocation
|
|
4265
|
-
* volume — the read side of "which skills/commands do I actually use, and which
|
|
4266
|
-
* are dead weight". `order: 'bottom'` ranks least-used first (the one-time
|
|
4267
|
-
* skills); `limit` caps the returned rows.
|
|
4268
|
-
*
|
|
4269
|
-
* The signal only captures EXPLICIT invocations (slash commands and `Skill`
|
|
4270
|
-
* tool calls). An auto-triggered skill (loaded by description match) emits no
|
|
4271
|
-
* event, so it reads as zero here — a 0 means "never explicitly invoked", not
|
|
4272
|
-
* "never loaded". Skill invocations are recorded for Claude and Kimi (the
|
|
4273
|
-
* `Skill`-tool harnesses); slash-commands are Claude-only.
|
|
4274
|
-
*
|
|
4275
|
-
* `kind` / `pluginFilter` filter the RESOURCE rows directly (r.kind / r.plugin),
|
|
4276
|
-
* distinct from QueryOptions.skill / QueryOptions.plugin, which filter SESSIONS
|
|
4277
|
-
* — deliberately not routed through buildSessionWhere so `--plugin rush` shows
|
|
4278
|
-
* only rush's resources rather than every resource used by a rush-touching
|
|
4279
|
-
* session.
|
|
4280
|
-
*/
|
|
4281
3122
|
export function queryResourceUsageStats(options) {
|
|
4282
3123
|
const db = getDB();
|
|
4283
3124
|
const { clause, params } = buildSessionWhere(options);
|
|
4284
|
-
// buildSessionWhere emits bare column names (agent, timestamp, machine, …) and
|
|
4285
|
-
// `id IN (…)` subqueries; in this join they resolve unambiguously to `sessions`
|
|
4286
|
-
// (session_resource_usage carries none of those columns, and its key is
|
|
4287
|
-
// session_id, not id). Strip the leading WHERE so resource predicates append.
|
|
4288
3125
|
const base = clause.replace(/^WHERE\s+/, '');
|
|
4289
3126
|
const preds = base ? [base] : [];
|
|
4290
3127
|
const allParams = [...params];
|
|
@@ -4299,15 +3136,6 @@ export function queryResourceUsageStats(options) {
|
|
|
4299
3136
|
const whereClause = preds.length ? `WHERE ${preds.join(' AND ')}` : '';
|
|
4300
3137
|
const direction = options.order === 'bottom' ? 'ASC' : 'DESC';
|
|
4301
3138
|
const limitClause = options.limit ? `LIMIT ${Math.max(1, Math.floor(options.limit))}` : '';
|
|
4302
|
-
// Group by resource IDENTITY = (kind, name) only, per SES-IF-4b. name already
|
|
4303
|
-
// embeds the `plugin:short` prefix for a plugin resource, so (kind, name) is
|
|
4304
|
-
// the true identity. plugin and source are PROVENANCE, not identity, and drift
|
|
4305
|
-
// per session — the same `rush:design` resolves plugin='rush' in a session
|
|
4306
|
-
// whose cwd discovered the plugin and plugin=NULL in one that didn't, and the
|
|
4307
|
-
// layer a flat resource resolved to varies (user vs system). Grouping on either
|
|
4308
|
-
// would split one resource into fractional rows. Aggregate both with MAX so the
|
|
4309
|
-
// row carries a representative (non-NULL when any session resolved it) label
|
|
4310
|
-
// while the counts stay whole.
|
|
4311
3139
|
const sql = `
|
|
4312
3140
|
SELECT
|
|
4313
3141
|
r.kind AS kind,
|
|
@@ -4325,36 +3153,9 @@ export function queryResourceUsageStats(options) {
|
|
|
4325
3153
|
`;
|
|
4326
3154
|
return db.prepare(sql).all(...allParams);
|
|
4327
3155
|
}
|
|
4328
|
-
/**
|
|
4329
|
-
* Coverage of the resource-usage signal, as three honest facts:
|
|
4330
|
-
*
|
|
4331
|
-
* - `scanned` — sessions the resource extractor has actually processed, i.e.
|
|
4332
|
-
* those carrying a `resource_scan_ledger` row at the current
|
|
4333
|
-
* `RESOURCE_INDEX_VERSION`. This is the true "has the historical backfill run"
|
|
4334
|
-
* signal: the ledger is stamped for EVERY scanned session, including ones that
|
|
4335
|
-
* invoked nothing (`resource_count = 0`), so `scanned/total` rises to ~1 after
|
|
4336
|
-
* `agents sessions backfill resources` regardless of how sparse explicit
|
|
4337
|
-
* invocations are.
|
|
4338
|
-
* - `covered` — distinct sessions that carry AT LEAST ONE row in
|
|
4339
|
-
* `session_resource_usage`, i.e. that actually recorded an explicit invocation.
|
|
4340
|
-
* This is an ABSOLUTE signal count, not a coverage ratio: it stays small even
|
|
4341
|
-
* at full scan coverage because most sessions invoke no skill/command, and a
|
|
4342
|
-
* non-recording harness contributes none by construction.
|
|
4343
|
-
* - `total` — sessions indexed.
|
|
4344
|
-
*
|
|
4345
|
-
* The two were previously conflated: `covered/total` was framed as coverage and
|
|
4346
|
-
* read ~1.2% even after a full backfill (most sessions genuinely invoke nothing),
|
|
4347
|
-
* so the "run the backfill" hint never cleared. Keying the hint on `scanned/total`
|
|
4348
|
-
* fixes that — see `commands/sessions-stats.ts` (PHNX-2301).
|
|
4349
|
-
*/
|
|
4350
3156
|
export function resourceUsageCoverage() {
|
|
4351
3157
|
const db = getDB();
|
|
4352
3158
|
const covered = db.prepare(`SELECT COUNT(DISTINCT session_id) AS n FROM session_resource_usage`).get().n;
|
|
4353
|
-
// JOIN sessions so a ledger row for a since-vanished transcript (dropped from
|
|
4354
|
-
// `sessions` but not yet from the ledger) can't inflate scan coverage past the
|
|
4355
|
-
// indexed set. Only rows at the current extractor version count as scanned —
|
|
4356
|
-
// a stale-version row is re-derived on the next backfill, so it is not yet
|
|
4357
|
-
// "covered" for this extractor.
|
|
4358
3159
|
const scanned = db.prepare(`
|
|
4359
3160
|
SELECT COUNT(*) AS n
|
|
4360
3161
|
FROM resource_scan_ledger l
|
|
@@ -4364,7 +3165,6 @@ export function resourceUsageCoverage() {
|
|
|
4364
3165
|
const total = db.prepare(`SELECT COUNT(*) AS n FROM sessions`).get().n;
|
|
4365
3166
|
return { covered, scanned, total };
|
|
4366
3167
|
}
|
|
4367
|
-
/** Has this session's resource usage been derived at the current extractor version for this exact file? */
|
|
4368
3168
|
function needsResourceIndex(db, sessionId, stamp) {
|
|
4369
3169
|
const row = db
|
|
4370
3170
|
.prepare(`SELECT file_mtime_ms, file_size, extractor_version FROM resource_scan_ledger WHERE session_id = ?`)
|
|
@@ -4374,7 +3174,6 @@ function needsResourceIndex(db, sessionId, stamp) {
|
|
|
4374
3174
|
|| row.file_size !== stamp.fileSize
|
|
4375
3175
|
|| row.extractor_version !== RESOURCE_INDEX_VERSION;
|
|
4376
3176
|
}
|
|
4377
|
-
/** Record that a session's resource usage is current at RESOURCE_INDEX_VERSION for this file stamp. */
|
|
4378
3177
|
function stampResourceLedger(db, sessionId, filePath, stamp, resourceCount) {
|
|
4379
3178
|
db.prepare(`
|
|
4380
3179
|
INSERT INTO resource_scan_ledger
|
|
@@ -4389,25 +3188,8 @@ function stampResourceLedger(db, sessionId, filePath, stamp, resourceCount) {
|
|
|
4389
3188
|
resource_count = excluded.resource_count
|
|
4390
3189
|
`).run(sessionId, filePath, stamp.fileMtimeMs, stamp.fileSize, RESOURCE_INDEX_VERSION, Date.now(), resourceCount);
|
|
4391
3190
|
}
|
|
4392
|
-
/**
|
|
4393
|
-
* One-shot historical backfill of session_resource_usage (#12). The normal
|
|
4394
|
-
* incremental scan writes resource usage only for sessions whose transcript it
|
|
4395
|
-
* (re)parses; a session indexed before this feature shipped keeps a fresh
|
|
4396
|
-
* scan_ledger row and is never re-derived, so its skill/slash-command tallies
|
|
4397
|
-
* were never recorded. This walks the session index, re-parses each transcript
|
|
4398
|
-
* FROM BYTE 0 (parseSession fully materializes — no resumable cursor, so
|
|
4399
|
-
* claude/codex re-derive their tallies from scratch), writes the usage, and
|
|
4400
|
-
* stamps resource_scan_ledger so reruns skip completed transcripts — the same
|
|
4401
|
-
* independent-ledger shape `agents sessions backfill tools` uses.
|
|
4402
|
-
*
|
|
4403
|
-
* Harness-agnostic: parseSession + extractSkills/extractSlashCommands cover
|
|
4404
|
-
* every agent uniformly, so there is no per-harness branch to keep in parity.
|
|
4405
|
-
* Synthetic rows without a transcript (OpenClaw channels/cron) are skipped.
|
|
4406
|
-
*/
|
|
4407
3191
|
export function backfillResourceUsage(filter = {}, onProgress) {
|
|
4408
3192
|
const db = getDB();
|
|
4409
|
-
// No LIMIT: the backfill covers the whole matching history. skipExistenceCheck
|
|
4410
|
-
// stays off so vanished transcripts are dropped, matching querySessions.
|
|
4411
3193
|
const sessions = querySessions({ ...filter, limit: undefined });
|
|
4412
3194
|
const result = { scanned: 0, updated: 0, skipped: 0, failed: 0, resourceRows: 0 };
|
|
4413
3195
|
let done = 0;
|
|
@@ -4420,8 +3202,6 @@ export function backfillResourceUsage(filter = {}, onProgress) {
|
|
|
4420
3202
|
result.scanned++;
|
|
4421
3203
|
let stamp;
|
|
4422
3204
|
try {
|
|
4423
|
-
// Composite rows (`<container>#<id>`) stat their container file — the
|
|
4424
|
-
// per-session bytes/mtime stamp is derived during discovery, not here.
|
|
4425
3205
|
const st = fs.statSync(sessionFilePathContainer(meta.filePath));
|
|
4426
3206
|
stamp = { fileMtimeMs: st.mtimeMs, fileSize: st.size };
|
|
4427
3207
|
}
|
|
@@ -4439,14 +3219,6 @@ export function backfillResourceUsage(filter = {}, onProgress) {
|
|
|
4439
3219
|
}
|
|
4440
3220
|
try {
|
|
4441
3221
|
const events = parseSession(meta.filePath, meta.agent);
|
|
4442
|
-
// Fail loud on a bad read. writeResourceUsage DELETEs the session's rows
|
|
4443
|
-
// before it re-inserts, so a transcript that parses to ZERO events (a
|
|
4444
|
-
// truncated / mid-sync / corrupt file — parseSession returns [] instead of
|
|
4445
|
-
// throwing) would silently wipe real historical usage and then stamp the
|
|
4446
|
-
// ledger "current", masking the loss. A completed historical transcript
|
|
4447
|
-
// always has turns, so an empty parse is never a legitimate "this session
|
|
4448
|
-
// used nothing" — it is an unreadable file. Skip it (count as failed, leave
|
|
4449
|
-
// the ledger unstamped) so a later good read retries.
|
|
4450
3222
|
if (events.length === 0) {
|
|
4451
3223
|
result.failed++;
|
|
4452
3224
|
done++;
|
|
@@ -4467,14 +3239,6 @@ export function backfillResourceUsage(filter = {}, onProgress) {
|
|
|
4467
3239
|
}
|
|
4468
3240
|
return result;
|
|
4469
3241
|
}
|
|
4470
|
-
/**
|
|
4471
|
-
* Map every team name to the session that ran `agents teams create/add` for it.
|
|
4472
|
-
*
|
|
4473
|
-
* One scan over the rows that carry a `spawned_team`, rather than a query per
|
|
4474
|
-
* team — `agents teams list` needs the whole map at once, and the column has no
|
|
4475
|
-
* index. When two sessions spawned the same team name (a team re-created after a
|
|
4476
|
-
* disband), the most recent wins, which is the one whose work the name refers to.
|
|
4477
|
-
*/
|
|
4478
3242
|
export function teamSpawners() {
|
|
4479
3243
|
const db = getDB();
|
|
4480
3244
|
const rows = db
|
|
@@ -4488,26 +3252,13 @@ export function teamSpawners() {
|
|
|
4488
3252
|
}
|
|
4489
3253
|
return out;
|
|
4490
3254
|
}
|
|
4491
|
-
/**
|
|
4492
|
-
* Return the N most expensive sessions (cost_usd DESC, NULLs excluded),
|
|
4493
|
-
* honoring the same filter shape as querySessions. Drops rows whose JSONL
|
|
4494
|
-
* vanished, mirroring querySessions' liveness filter.
|
|
4495
|
-
*/
|
|
4496
3255
|
export function topSessionsByCost(n, options = {}) {
|
|
4497
3256
|
const db = getDB();
|
|
4498
3257
|
const { clause, params } = buildSessionWhere(options);
|
|
4499
3258
|
const whereCost = clause ? `${clause} AND cost_usd IS NOT NULL` : 'WHERE cost_usd IS NOT NULL';
|
|
4500
3259
|
const limit = Math.max(1, Math.floor(n));
|
|
4501
|
-
// Over-fetch a small buffer to survive the on-disk liveness filter below.
|
|
4502
3260
|
const sql = `SELECT * FROM sessions ${whereCost} ORDER BY cost_usd DESC, timestamp DESC LIMIT ${limit + 16}`;
|
|
4503
3261
|
const rows = db.prepare(sql).all(...params);
|
|
4504
|
-
// Keep a row whose file is present OR whose transcript is already archived
|
|
4505
|
-
// (file gone but user turns still in session_text) — an expensive, real
|
|
4506
|
-
// session must not drop out of the cost rollup just because its file was
|
|
4507
|
-
// removed (RUSH-2436). A file-gone row with no durable content (a phantom) is
|
|
4508
|
-
// still excluded. archived_at is the persisted signal; stamp it here too (once)
|
|
4509
|
-
// so a session first surfaced through the cost rollup reports `archived`
|
|
4510
|
-
// consistently with the listing path.
|
|
4511
3262
|
const readContent = db.prepare(`SELECT content FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`);
|
|
4512
3263
|
const markArchived = db.prepare(`UPDATE sessions SET archived_at = ? WHERE id = ? AND archived_at IS NULL`);
|
|
4513
3264
|
const now = Date.now();
|
|
@@ -4539,10 +3290,6 @@ export function topSessionsByCost(n, options = {}) {
|
|
|
4539
3290
|
durationMs: r.duration_ms ?? 0,
|
|
4540
3291
|
}));
|
|
4541
3292
|
}
|
|
4542
|
-
/**
|
|
4543
|
-
* Read machine attribution in batches without materializing full sessions.
|
|
4544
|
-
* Failure is best-effort so the live view can still render local attribution.
|
|
4545
|
-
*/
|
|
4546
3293
|
export function findSessionMachinesByIds(ids) {
|
|
4547
3294
|
const out = new Map();
|
|
4548
3295
|
const uniq = [...new Set(ids.filter(Boolean))];
|
|
@@ -4550,7 +3297,7 @@ export function findSessionMachinesByIds(ids) {
|
|
|
4550
3297
|
return out;
|
|
4551
3298
|
try {
|
|
4552
3299
|
const db = getDB();
|
|
4553
|
-
const CHUNK = 500;
|
|
3300
|
+
const CHUNK = 500;
|
|
4554
3301
|
for (let i = 0; i < uniq.length; i += CHUNK) {
|
|
4555
3302
|
const batch = uniq.slice(i, i + CHUNK);
|
|
4556
3303
|
const placeholders = batch.map(() => '?').join(',');
|
|
@@ -4563,7 +3310,6 @@ export function findSessionMachinesByIds(ids) {
|
|
|
4563
3310
|
}
|
|
4564
3311
|
}
|
|
4565
3312
|
catch {
|
|
4566
|
-
/* index read is best-effort — an unavailable DB leaves rows un-attributed */
|
|
4567
3313
|
}
|
|
4568
3314
|
return out;
|
|
4569
3315
|
}
|
|
@@ -4572,7 +3318,6 @@ export function getSessionById(id) {
|
|
|
4572
3318
|
const row = db.prepare(`SELECT * FROM sessions WHERE id = ?`).get(id);
|
|
4573
3319
|
return row ? rowToMeta(row) : null;
|
|
4574
3320
|
}
|
|
4575
|
-
/** Exact ids win over prefixes; the normal existence check preserves archived content but excludes phantoms. */
|
|
4576
3321
|
export function findSessionsById(idQuery, scope = {}) {
|
|
4577
3322
|
const q = idQuery.trim();
|
|
4578
3323
|
if (!q)
|
|
@@ -4582,35 +3327,25 @@ export function findSessionsById(idQuery, scope = {}) {
|
|
|
4582
3327
|
return exact;
|
|
4583
3328
|
return querySessions({ ...scope, idPrefix: q });
|
|
4584
3329
|
}
|
|
4585
|
-
/**
|
|
4586
|
-
* Upgrade a session-id crumb to the full indexed id. The 8-char hex `short_id`
|
|
4587
|
-
* a "Sent from …" footer shows would 404 as a `…/console/sessions/<id>` URL, so
|
|
4588
|
-
* an owner ping resolves it through the index first (PHNX-3698). A value that is
|
|
4589
|
-
* already a full id (or any non-crumb shape, e.g. a native `ses_…` id), or a
|
|
4590
|
-
* crumb the index cannot resolve, is returned unchanged — the caller keeps
|
|
4591
|
-
* exactly what it had rather than getting a fabricated id.
|
|
4592
|
-
*/
|
|
4593
3330
|
export function resolveFullSessionId(idOrCrumb) {
|
|
4594
3331
|
const id = idOrCrumb?.trim();
|
|
4595
3332
|
if (!id)
|
|
4596
3333
|
return undefined;
|
|
4597
3334
|
if (!/^[0-9a-f]{8}$/i.test(id))
|
|
4598
|
-
return id;
|
|
3335
|
+
return id;
|
|
4599
3336
|
const hit = findSessionsByShortIds([id]).get(id.toLowerCase());
|
|
4600
3337
|
return hit?.id ?? id;
|
|
4601
3338
|
}
|
|
4602
|
-
/** Batch-resolve pane short ids; on collision the most recently active session wins. */
|
|
4603
3339
|
export function findSessionsByShortIds(shortIds) {
|
|
4604
3340
|
const out = new Map();
|
|
4605
3341
|
const uniq = [...new Set(shortIds.map((s) => s.trim().toLowerCase()).filter(Boolean))];
|
|
4606
3342
|
if (uniq.length === 0)
|
|
4607
3343
|
return out;
|
|
4608
3344
|
const db = getDB();
|
|
4609
|
-
const CHUNK = 500;
|
|
3345
|
+
const CHUNK = 500;
|
|
4610
3346
|
for (let i = 0; i < uniq.length; i += CHUNK) {
|
|
4611
3347
|
const batch = uniq.slice(i, i + CHUNK);
|
|
4612
3348
|
const placeholders = batch.map(() => '?').join(',');
|
|
4613
|
-
// timestamp ASC so a later (newer) row overwrites an earlier one per short_id.
|
|
4614
3349
|
const rows = db
|
|
4615
3350
|
.prepare(`SELECT * FROM sessions WHERE short_id IN (${placeholders}) ORDER BY timestamp ASC`)
|
|
4616
3351
|
.all(...batch);
|
|
@@ -4622,11 +3357,6 @@ export function findSessionsByShortIds(shortIds) {
|
|
|
4622
3357
|
}
|
|
4623
3358
|
return out;
|
|
4624
3359
|
}
|
|
4625
|
-
/**
|
|
4626
|
-
* Escape a raw user query into a safe FTS5 MATCH expression.
|
|
4627
|
-
* Splits on non-word characters, keeps tokens >= 2 chars, and OR-joins
|
|
4628
|
-
* them with a prefix wildcard so partial typing ('rush dep') matches.
|
|
4629
|
-
*/
|
|
4630
3360
|
export function buildFtsQuery(input) {
|
|
4631
3361
|
const terms = input.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length >= 2);
|
|
4632
3362
|
if (terms.length === 0)
|
|
@@ -4634,34 +3364,12 @@ export function buildFtsQuery(input) {
|
|
|
4634
3364
|
const expr = terms.map(t => `${t}*`).join(' OR ');
|
|
4635
3365
|
return { expr, terms };
|
|
4636
3366
|
}
|
|
4637
|
-
/**
|
|
4638
|
-
* Build a `label:(...)` FTS5 column-filter MATCH expression for the label
|
|
4639
|
-
* tier. Unlike `buildFtsQuery` (2-char floor, tuned for full-content search),
|
|
4640
|
-
* this allows 1-char terms: label search is the interactive type-ahead path —
|
|
4641
|
-
* the query grows one keystroke at a time, so a single character has to be
|
|
4642
|
-
* indexable too. Terms are filtered to `[a-z0-9]` before being embedded in the
|
|
4643
|
-
* expression string, so there's no FTS5 syntax injection from user input.
|
|
4644
|
-
*/
|
|
4645
3367
|
function buildLabelFtsQuery(input) {
|
|
4646
3368
|
const terms = input.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length >= 1);
|
|
4647
3369
|
if (terms.length === 0)
|
|
4648
3370
|
return '';
|
|
4649
3371
|
return `label:(${terms.map(t => `${t}*`).join(' OR ')})`;
|
|
4650
3372
|
}
|
|
4651
|
-
/**
|
|
4652
|
-
* Label-first search. Sessions whose custom label substring-matches the query
|
|
4653
|
-
* always rank ahead of FTS5 hits — this gives predictable behavior when a user
|
|
4654
|
-
* types the exact name they gave a session via /rename.
|
|
4655
|
-
*
|
|
4656
|
-
* Tiers (highest → lowest):
|
|
4657
|
-
* 1. Exact label match (case-insensitive): score 1_000_000
|
|
4658
|
-
* 2. Label prefix match: score 900_000
|
|
4659
|
-
* 3. Label contains query: score 800_000
|
|
4660
|
-
* 4. FTS5 BM25 hits: score 1..1000 (scaled)
|
|
4661
|
-
*
|
|
4662
|
-
* Note: FTS5's bm25() returns negative numbers; we flip the sign for tier 4
|
|
4663
|
-
* so "higher = better" is consistent across all tiers.
|
|
4664
|
-
*/
|
|
4665
3373
|
export function ftsSearch(input, limit = 200) {
|
|
4666
3374
|
const db = getDB();
|
|
4667
3375
|
const trimmed = input.trim();
|
|
@@ -4671,28 +3379,6 @@ export function ftsSearch(input, limit = 200) {
|
|
|
4671
3379
|
const lower = trimmed.toLowerCase();
|
|
4672
3380
|
const seen = new Set();
|
|
4673
3381
|
const hits = [];
|
|
4674
|
-
// Tier 1-3: handle-based matches, ordered by exactness. A session's handle is
|
|
4675
|
-
// its `label` — set by an agent title / `/rename`, or seeded at launch from
|
|
4676
|
-
// `agents run --name`. Typing it resolves the session ahead of any FTS content
|
|
4677
|
-
// hit.
|
|
4678
|
-
//
|
|
4679
|
-
// Candidates come from the FTS5 `label` column, not a raw `LOWER(label) LIKE
|
|
4680
|
-
// '%q%'` scan of `sessions`: a leading wildcard can't use any index, so on a
|
|
4681
|
-
// large session table that was a full-table scan on every keystroke of
|
|
4682
|
-
// interactive search (RUSH-2211). `session_text.label` is kept 1:1 with
|
|
4683
|
-
// `sessions.label` by every upsert path (storedFtsLabel), so this is the
|
|
4684
|
-
// same data, indexed. Token-prefix matching seeks the FTS index instead of
|
|
4685
|
-
// scanning every row, at the cost of only matching at token boundaries — a
|
|
4686
|
-
// substring inside a single token (e.g. "ckf" inside "quickfix") no longer
|
|
4687
|
-
// matches, since FTS5 only indexes prefixes of whole tokens, not arbitrary
|
|
4688
|
-
// interior slices. (A slice spanning a token boundary, like "ix-b" inside
|
|
4689
|
-
// "fix-bug", still matches: "ix-b" tokenizes to "ix" + "b", and "b" is a
|
|
4690
|
-
// valid prefix of the "bug" token.) That's the accepted trade-off for an
|
|
4691
|
-
// indexable interactive path; the exact/prefix/contains scoring below still
|
|
4692
|
-
// runs in JS over the FTS candidate set, so ranking among real matches is
|
|
4693
|
-
// unchanged. Only a query with no indexable token (rare — e.g.
|
|
4694
|
-
// punctuation-only input) falls back to the direct scan rather than
|
|
4695
|
-
// silently dropping the tier.
|
|
4696
3382
|
const labelMatchExpr = buildLabelFtsQuery(input);
|
|
4697
3383
|
const labelRows = labelMatchExpr
|
|
4698
3384
|
? db.prepare(`
|
|
@@ -4705,7 +3391,6 @@ export function ftsSearch(input, limit = 200) {
|
|
|
4705
3391
|
`).all(`%${lower}%`);
|
|
4706
3392
|
let hasExactLabelMatch = false;
|
|
4707
3393
|
for (const row of labelRows) {
|
|
4708
|
-
// Score the label by match quality (exact > prefix > contains).
|
|
4709
3394
|
let score = 0;
|
|
4710
3395
|
const handle = row.label;
|
|
4711
3396
|
if (handle) {
|
|
@@ -4725,22 +3410,13 @@ export function ftsSearch(input, limit = 200) {
|
|
|
4725
3410
|
}
|
|
4726
3411
|
if (score === 0)
|
|
4727
3412
|
continue;
|
|
4728
|
-
// matchedTerms is empty for handle hits — the picker can render the handle
|
|
4729
|
-
// itself as the highlight, no badge needed.
|
|
4730
3413
|
hits.push({ sessionId: row.id, score, matchedTerms: [] });
|
|
4731
3414
|
seen.add(row.id);
|
|
4732
3415
|
}
|
|
4733
|
-
// If the query exactly names a labeled session, don't dilute the result
|
|
4734
|
-
// with FTS5 content hits — the user typed a specific thing, show just it.
|
|
4735
3416
|
if (hasExactLabelMatch) {
|
|
4736
3417
|
hits.sort((a, b) => b.score - a.score);
|
|
4737
3418
|
return hits.slice(0, limit);
|
|
4738
3419
|
}
|
|
4739
|
-
// Tier 4: FTS5 content match, skipping anything already surfaced via label.
|
|
4740
|
-
// `snippet(session_text, -1, ...)` lets FTS5 pick the best-matching column
|
|
4741
|
-
// itself (label/topic/project/content/assistant) rather than us guessing —
|
|
4742
|
-
// a query that only matched in `assistant` (an agent-only answer) still gets
|
|
4743
|
-
// an excerpt from the right column instead of an empty content snippet.
|
|
4744
3420
|
if (expr) {
|
|
4745
3421
|
try {
|
|
4746
3422
|
const rows = db
|
|
@@ -4766,24 +3442,11 @@ export function ftsSearch(input, limit = 200) {
|
|
|
4766
3442
|
}
|
|
4767
3443
|
}
|
|
4768
3444
|
catch {
|
|
4769
|
-
/* invalid MATCH expression — tier 4 just yields nothing */
|
|
4770
3445
|
}
|
|
4771
3446
|
}
|
|
4772
3447
|
hits.sort((a, b) => b.score - a.score);
|
|
4773
3448
|
return hits.slice(0, limit);
|
|
4774
3449
|
}
|
|
4775
|
-
/**
|
|
4776
|
-
* Rewrite file_path for sessions whose path starts with any of `moves.from`,
|
|
4777
|
-
* replacing that prefix with `moves.to` and clearing the matching scan_ledger
|
|
4778
|
-
* entries so the next scan re-indexes from the new location.
|
|
4779
|
-
*
|
|
4780
|
-
* One transaction for the whole batch (PHNX-3940 T7): a migration that moves
|
|
4781
|
-
* several homes into slots and several version dirs into trash must not leave
|
|
4782
|
-
* the index half-rewritten. Longest `from` wins when prefixes nest, and each
|
|
4783
|
-
* session row is rewritten at most once.
|
|
4784
|
-
*
|
|
4785
|
-
* Returns the number of session rows updated.
|
|
4786
|
-
*/
|
|
4787
3450
|
export function reindexMovedSessionPaths(moves) {
|
|
4788
3451
|
const usable = moves.filter((m) => m.from && m.from !== m.to);
|
|
4789
3452
|
if (usable.length === 0)
|
|
@@ -4810,19 +3473,9 @@ export function reindexMovedSessionPaths(moves) {
|
|
|
4810
3473
|
return n;
|
|
4811
3474
|
})();
|
|
4812
3475
|
}
|
|
4813
|
-
/**
|
|
4814
|
-
* Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
|
|
4815
|
-
* it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
|
|
4816
|
-
* entries so they are re-indexed from the new location on the next scan.
|
|
4817
|
-
*
|
|
4818
|
-
* Used by removeVersion after soft-deleting a version directory to trash, so
|
|
4819
|
-
* that session reads (transcript view, /continue) still work from the trash path.
|
|
4820
|
-
* Returns the number of session rows updated.
|
|
4821
|
-
*/
|
|
4822
3476
|
export function updateSessionFilePaths(oldPrefix, newPrefix) {
|
|
4823
3477
|
return reindexMovedSessionPaths([{ from: oldPrefix, to: newPrefix }]);
|
|
4824
3478
|
}
|
|
4825
|
-
/** Count indexed sessions whose transcript path sits under `prefix`. */
|
|
4826
3479
|
export function countSessionsWithFilePrefix(prefix) {
|
|
4827
3480
|
if (!prefix)
|
|
4828
3481
|
return 0;
|
|
@@ -4831,24 +3484,6 @@ export function countSessionsWithFilePrefix(prefix) {
|
|
|
4831
3484
|
.get(prefix + '%');
|
|
4832
3485
|
return row.c;
|
|
4833
3486
|
}
|
|
4834
|
-
/**
|
|
4835
|
-
* Upsert one browser task's durable metadata.
|
|
4836
|
-
*
|
|
4837
|
-
* Called at task START. This executes INSIDE the browser daemon (daemon.ts
|
|
4838
|
-
* constructs BrowserService; ipc.ts dispatches `start` to it), which makes the
|
|
4839
|
-
* shared daemon a writer of this DB — but the identity it writes is RESOLVED IN
|
|
4840
|
-
* THE CALLING CLI PROCESS and forwarded over IPC, never resolved here. That
|
|
4841
|
-
* distinction is the whole fix: resolving it daemon-side would attribute every
|
|
4842
|
-
* task to the daemon's own actor (the RUSH-2020 bug).
|
|
4843
|
-
*
|
|
4844
|
-
* `agents browser stop` deliberately does NOT delete this row: the whole point
|
|
4845
|
-
* is that the link outlives the task.
|
|
4846
|
-
*
|
|
4847
|
-
* Identity fields are only ever widened, never blanked. A later capture-count
|
|
4848
|
-
* update carries no session id, and `COALESCE(excluded.…, browser_sessions.…)`
|
|
4849
|
-
* keeps the one recorded at start rather than overwriting it with NULL --
|
|
4850
|
-
* otherwise the second write would undo exactly what the first was for.
|
|
4851
|
-
*/
|
|
4852
3487
|
export function recordBrowserSession(record) {
|
|
4853
3488
|
const db = getDB();
|
|
4854
3489
|
const now = Date.now();
|
|
@@ -4872,13 +3507,6 @@ export function recordBrowserSession(record) {
|
|
|
4872
3507
|
captures_remote = COALESCE(excluded.captures_remote, browser_sessions.captures_remote)
|
|
4873
3508
|
`).run(record.task, record.profile, record.sessionId ?? null, record.launchId ?? null, record.actor ?? null, record.machine ?? machineId(), record.startedAt ?? now, record.lastActivity ?? now, record.counts?.screenshot ?? 0, record.counts?.pdf ?? 0, record.counts?.recording ?? 0, record.counts?.download ?? 0, record.captureDir ?? null, record.capturesRemote ?? null);
|
|
4874
3509
|
}
|
|
4875
|
-
/**
|
|
4876
|
-
* Upsert one computer-use invocation's durable metadata.
|
|
4877
|
-
*
|
|
4878
|
-
* `action_count` accumulates: a `computer run` loop emits many actions under one
|
|
4879
|
-
* invocation id, and each arrives as its own call, so the count is incremented
|
|
4880
|
-
* rather than replaced. Identity is widened-only, for the same reason as above.
|
|
4881
|
-
*/
|
|
4882
3510
|
export function recordComputerSession(record) {
|
|
4883
3511
|
const db = getDB();
|
|
4884
3512
|
const now = Date.now();
|
|
@@ -4916,17 +3544,6 @@ function toStoredBrowserSession(row) {
|
|
|
4916
3544
|
capturesRemote: row.captures_remote ?? undefined,
|
|
4917
3545
|
};
|
|
4918
3546
|
}
|
|
4919
|
-
/**
|
|
4920
|
-
* Stored browser tasks, newest first; optionally scoped to one profile.
|
|
4921
|
-
*
|
|
4922
|
-
* A PROFILE-SCOPED read is deliberately unbounded. These rows are the identity
|
|
4923
|
-
* for capture dirs that still exist on disk, and dropping the oldest ones would
|
|
4924
|
-
* silently regress exactly those tasks to `unlinked` — the symptom this whole
|
|
4925
|
-
* change removes, reappearing at a higher threshold. The row count is already
|
|
4926
|
-
* bounded by the tasks that profile has ever run, and each row is a short
|
|
4927
|
-
* metadata record. Only the unscoped read (every profile, used for overviews)
|
|
4928
|
-
* takes a ceiling, since that one has no natural bound.
|
|
4929
|
-
*/
|
|
4930
3547
|
export function listBrowserSessionRecords(profile, opts = {}) {
|
|
4931
3548
|
const db = getDB();
|
|
4932
3549
|
const rows = (profile
|
|
@@ -4935,7 +3552,6 @@ export function listBrowserSessionRecords(profile, opts = {}) {
|
|
|
4935
3552
|
.all(opts.limit ?? TOOL_SESSION_LIST_LIMIT));
|
|
4936
3553
|
return rows.map(toStoredBrowserSession);
|
|
4937
3554
|
}
|
|
4938
|
-
/** One stored browser task by its (profile, task) key, or null. */
|
|
4939
3555
|
export function getBrowserSessionRecord(profile, task) {
|
|
4940
3556
|
const db = getDB();
|
|
4941
3557
|
const row = db
|
|
@@ -4943,35 +3559,11 @@ export function getBrowserSessionRecord(profile, task) {
|
|
|
4943
3559
|
.get(profile, task);
|
|
4944
3560
|
return row ? toStoredBrowserSession(row) : null;
|
|
4945
3561
|
}
|
|
4946
|
-
/**
|
|
4947
|
-
* Retention for the tool-session tables.
|
|
4948
|
-
*
|
|
4949
|
-
* These are the durable answer to a ledger that prunes at 7 days, so they are
|
|
4950
|
-
* deliberately long-lived — but "durable" is not "unbounded". `computer_sessions`
|
|
4951
|
-
* takes one row per `agents computer` CLI PROCESS (`COMPUTER_INVOCATION_ID` is
|
|
4952
|
-
* minted per process), and an agent driving a desktop invokes that hundreds of
|
|
4953
|
-
* times a day, so an unbounded table would grow without limit and be read in
|
|
4954
|
-
* full on every listing.
|
|
4955
|
-
*/
|
|
4956
3562
|
const TOOL_SESSION_MAX_AGE_DAYS = 365;
|
|
4957
|
-
/** Default ceiling on rows one listing will read. */
|
|
4958
3563
|
const TOOL_SESSION_LIST_LIMIT = 2000;
|
|
4959
|
-
/**
|
|
4960
|
-
* Drop tool-session rows past {@link TOOL_SESSION_MAX_AGE_DAYS}.
|
|
4961
|
-
*
|
|
4962
|
-
* Called from the listing path, never from the write hot path: an
|
|
4963
|
-
* `agents computer` action must not pay for a table sweep (see the fail-soft
|
|
4964
|
-
* note on `recordComputerSession`'s caller). Returns rows deleted.
|
|
4965
|
-
*/
|
|
4966
3564
|
export function pruneToolSessions(maxAgeDays = TOOL_SESSION_MAX_AGE_DAYS) {
|
|
4967
3565
|
const db = getDB();
|
|
4968
3566
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
4969
|
-
// Cheap indexed guard first, so the common case is one lookup rather than a
|
|
4970
|
-
// write transaction (the same shape as the account repair below). Every
|
|
4971
|
-
// listing calls this, and on any table younger than the retention window
|
|
4972
|
-
// there is nothing to delete — but a bare DELETE still opens a write
|
|
4973
|
-
// transaction, which under `busy_timeout = 30000` can make a read-only
|
|
4974
|
-
// listing wait on the session indexer for no reason at all.
|
|
4975
3567
|
const stale = db.prepare(`
|
|
4976
3568
|
SELECT 1 FROM computer_sessions WHERE started_at < ?
|
|
4977
3569
|
UNION ALL
|
|
@@ -4984,22 +3576,9 @@ export function pruneToolSessions(maxAgeDays = TOOL_SESSION_MAX_AGE_DAYS) {
|
|
|
4984
3576
|
const browser = db.prepare(`DELETE FROM browser_sessions WHERE started_at < ?`).run(cutoff);
|
|
4985
3577
|
return Number(computer.changes ?? 0) + Number(browser.changes ?? 0);
|
|
4986
3578
|
}
|
|
4987
|
-
/**
|
|
4988
|
-
* Stored computer-use invocations, newest first, bounded.
|
|
4989
|
-
*
|
|
4990
|
-
* The limit is a real ceiling, not a nicety: `--json` serializes whatever this
|
|
4991
|
-
* returns, so an unbounded read would dump the entire table on every call.
|
|
4992
|
-
*/
|
|
4993
3579
|
export function listComputerSessionRecords(opts = {}) {
|
|
4994
3580
|
const db = getDB();
|
|
4995
3581
|
const limit = opts.limit ?? TOOL_SESSION_LIST_LIMIT;
|
|
4996
|
-
// `startedBeforeMs` selects the COMPLEMENT of the caller's other source
|
|
4997
|
-
// rather than the newest N. The caller that recovers pruned runs already
|
|
4998
|
-
// holds every recent invocation from the event ledger and discards any row
|
|
4999
|
-
// it has seen — so a bare newest-N read hands it 2000 rows it is guaranteed
|
|
5000
|
-
// to throw away, and returns nothing at all once the table passes that size.
|
|
5001
|
-
// Bounding by "older than the ledger reaches" truncates the TAIL of
|
|
5002
|
-
// recoverable history instead of deleting all of it.
|
|
5003
3582
|
const rows = (opts.startedBeforeMs === undefined
|
|
5004
3583
|
? db
|
|
5005
3584
|
.prepare(`SELECT * FROM computer_sessions ORDER BY started_at DESC LIMIT ?`)
|