@phnx-labs/agents-cli 1.22.120 → 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 +227 -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 +17 -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
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session discovery across Claude, Codex, Gemini, OpenCode, and OpenClaw.
|
|
3
|
-
*
|
|
4
|
-
* Performs incremental scans: each agent's session files are stat'd and compared
|
|
5
|
-
* to a scan-stamp ledger in SQLite. Only files whose mtime or size changed since
|
|
6
|
-
* the last run are re-parsed. All metadata is upserted into the sessions DB so
|
|
7
|
-
* subsequent queries are served entirely from the cache.
|
|
8
|
-
*/
|
|
9
1
|
import * as fs from 'fs';
|
|
10
2
|
import * as path from 'path';
|
|
11
3
|
import * as os from 'os';
|
|
@@ -22,13 +14,14 @@ import { walkForFilesWithStat } from '../fs-walk.js';
|
|
|
22
14
|
import { hasCommand } from '../cli-resources.js';
|
|
23
15
|
import { execFileShellSpec } from '../platform/exec.js';
|
|
24
16
|
import { getConfigSymlinkVersion } from '../installations/shims.js';
|
|
25
|
-
import { SESSION_AGENTS } from '
|
|
17
|
+
import { SESSION_AGENTS } from '@phnx-labs/sessions-cli/reader';
|
|
26
18
|
import { deriveShortId } from './short-id.js';
|
|
27
19
|
import { buildClaudeAccountIndex, resolveClaudeAccount } from './claude-accounts.js';
|
|
28
|
-
import { cleanFirstUserMessage, extractSessionTopic, extractSlashCommandName, extractSlashCommandFromToolInput, cleanGeneratedSessionLabel } from '
|
|
29
|
-
import { isBackgroundShellStart, isSkillInvocation, extractSkills, extractSlashCommands, isSubAgentTool } from '
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
20
|
+
import { cleanFirstUserMessage, extractSessionTopic, extractSlashCommandName, extractSlashCommandFromToolInput, cleanGeneratedSessionLabel } from '@phnx-labs/sessions-cli/reader';
|
|
21
|
+
import { isBackgroundShellStart, isSkillInvocation, extractSkills, extractSlashCommands, isSubAgentTool } from '@phnx-labs/sessions-cli/reader';
|
|
22
|
+
import { claudeSubagentFiles, resolvedSubAgentCount } from './glance-files.js';
|
|
23
|
+
import { parseAntigravity, parseCursor, splitSessionFilePath } from '@phnx-labs/sessions-cli/reader';
|
|
24
|
+
import { extractPrUrl, detectWorktree, detectTicket, isPrCreateCommand, detectSpawnedTeam, isTicketCreateTool, extractCreatedTicket, extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from '@phnx-labs/sessions-cli/reader';
|
|
32
25
|
import { costOfUsage, costOfUsageNoCache } from '../pricing/index.js';
|
|
33
26
|
import { isSelfHost } from '../devices/self-host.js';
|
|
34
27
|
import { readSessionActorRecord } from './actor-sidecar.js';
|
|
@@ -38,31 +31,16 @@ import { mapBounded } from '../concurrency.js';
|
|
|
38
31
|
import { getDB, getScanStampByPath, getScanStampsForPaths, getParserStatesForPaths, getDirLedgerForPaths, recordDirScans, recordScans, syncLabels, seedLabelsFromNames, syncTopics, upsertSessionsBatch, querySessions, countSessions, ftsSearch, getSessionById, tryClaimScan, releaseScan, scanInProgressByLivePid, cacheLinearProject, CONTENT_INDEX_VERSION, } from './db.js';
|
|
39
32
|
import { buildRunNameMap } from './run-names.js';
|
|
40
33
|
import { resolveLinearApiKey } from '../linear-cache.js';
|
|
41
|
-
import { ToolCallCollector, collectClaudeToolCalls, collectCodexToolCalls, } from '
|
|
34
|
+
import { ToolCallCollector, collectClaudeToolCalls, collectCodexToolCalls, } from '@phnx-labs/sessions-cli/reader';
|
|
42
35
|
import { purgeMissingToolCallsInDirectory } from './tool-store.js';
|
|
43
36
|
const HOME = os.homedir();
|
|
44
|
-
// Versions can live under either repo: the user repo (current canonical
|
|
45
|
-
// location, ~/.agents/.history/versions/) or the system repo (legacy / npm-shipped,
|
|
46
|
-
// ~/.agents-system/versions/). Both must be scanned — sessions written by
|
|
47
|
-
// any installed version end up in that version's projects/ dir, and the user
|
|
48
|
-
// can be running one repo's version while another repo holds older versions
|
|
49
|
-
// whose JSONLs the user still wants to search.
|
|
50
37
|
const VERSIONS_ROOTS = [getHistoryDir(), getAgentsDir()];
|
|
51
38
|
const RUSH_SESSIONS_DIR = path.join(HOME, '.rush', 'sessions');
|
|
52
39
|
const HERMES_SESSIONS_DIR = path.join(HOME, '.hermes', 'sessions');
|
|
53
|
-
/** Muse Code sessions: ~/.local/share/muse/sessions/YYYY/MM/DD/<uuid>/session.jsonl */
|
|
54
40
|
const MUSE_SESSIONS_DIR = path.join(HOME, '.local', 'share', 'muse', 'sessions');
|
|
55
|
-
/** How long OpenClaw channel/cron snapshots stay valid before we re-shell-out. */
|
|
56
41
|
const OPENCLAW_TTL_MS = 60_000;
|
|
57
42
|
const ACTIVE_APPEND_RESCAN_DEBOUNCE_MS = 5_000;
|
|
58
|
-
/** One JSONL record may not force an unbounded string allocation. */
|
|
59
43
|
const SESSION_JSONL_LINE_MAX_BYTES = 1024 * 1024;
|
|
60
|
-
/**
|
|
61
|
-
* Stream one appended JSONL range, applying only newline-terminated records.
|
|
62
|
-
* Memory is bounded to one record. An ordinary unterminated tail remains for the
|
|
63
|
-
* next scan. Once a record exceeds the cap, its consumed offset and a one-bit
|
|
64
|
-
* dropping state advance together so later scans never reread the growing tail.
|
|
65
|
-
*/
|
|
66
44
|
async function applyJsonlAppend(filePath, fromOffset, wasDroppingOversizedLine, apply) {
|
|
67
45
|
let pending = Buffer.alloc(0);
|
|
68
46
|
let droppingOversizedLine = wasDroppingOversizedLine;
|
|
@@ -101,7 +79,6 @@ async function applyJsonlAppend(filePath, fromOffset, wasDroppingOversizedLine,
|
|
|
101
79
|
apply(JSON.parse(line));
|
|
102
80
|
}
|
|
103
81
|
catch {
|
|
104
|
-
// One malformed line never aborts the session scan.
|
|
105
82
|
}
|
|
106
83
|
}
|
|
107
84
|
}
|
|
@@ -122,56 +99,30 @@ async function applyJsonlAppend(filePath, fromOffset, wasDroppingOversizedLine,
|
|
|
122
99
|
}
|
|
123
100
|
return { consumedBytes, droppingOversizedLine, skippedOversizedLine };
|
|
124
101
|
}
|
|
125
|
-
/**
|
|
126
|
-
* How recently a file must have been scanned to be treated as "hot" — a
|
|
127
|
-
* candidate for an in-place append even when its parent dir's mtime hasn't
|
|
128
|
-
* moved. A dir-ledger match lets us skip the per-file stat of everything in a
|
|
129
|
-
* leaf dir EXCEPT its hot set; a file is hot if it lives under the agent's live
|
|
130
|
-
* `~/.<agent>` root (the only tree an agent appends to live) or was scanned
|
|
131
|
-
* within this window. 10 minutes comfortably covers a session that paused
|
|
132
|
-
* between `agents sessions` calls but is still being written to.
|
|
133
|
-
*/
|
|
134
102
|
const HOT_FILE_WINDOW_MS = 600_000;
|
|
135
|
-
/** Emergency kill-switch for the directory-ledger optimization. */
|
|
136
103
|
function dirLedgerDisabled() {
|
|
137
104
|
const v = process.env.AGENTS_SESSIONS_NO_DIR_LEDGER;
|
|
138
105
|
return v === '1' || v === 'true';
|
|
139
106
|
}
|
|
140
107
|
let cachedOpenClawWorkspaces = null;
|
|
141
|
-
/** Max time a `waitForScan` repair waits for a concurrent scan to settle. */
|
|
142
108
|
const WAIT_FOR_SCAN_TIMEOUT_MS = 2_000;
|
|
143
109
|
const WAIT_FOR_SCAN_POLL_MS = 100;
|
|
144
110
|
function sleep(ms) {
|
|
145
111
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
146
112
|
}
|
|
147
113
|
const cachedAgentVersions = new Map();
|
|
148
|
-
/**
|
|
149
|
-
* Discover sessions. Scans only files whose (mtime, size) have changed since
|
|
150
|
-
* the last run; everything else is served from the SQLite cache.
|
|
151
|
-
*
|
|
152
|
-
* Only one process runs the incremental scan at a time. When many agents boot
|
|
153
|
-
* simultaneously (e.g. after a restart), the first to claim the scan slot does
|
|
154
|
-
* the work; the rest skip parsing entirely and serve from the DB. The claim is
|
|
155
|
-
* stored in the `meta` table — crash-safe via dead-PID detection and a 2-min
|
|
156
|
-
* TTL, no external lock files needed.
|
|
157
|
-
*/
|
|
158
114
|
export async function discoverSessions(options) {
|
|
159
115
|
const { claimed } = await scanSessionsIncremental({
|
|
160
116
|
agent: options?.agent,
|
|
161
117
|
onProgress: options?.onProgress,
|
|
162
118
|
});
|
|
163
119
|
if (!claimed && options?.waitForScan) {
|
|
164
|
-
// Lost the single-flight claim to another live process (a foreground
|
|
165
|
-
// `agents sessions*` or the daemon's index warm). Rather than return the
|
|
166
|
-
// pre-scan snapshot that just missed, wait (bounded) for that scan to finish
|
|
167
|
-
// so the read below reflects what it wrote (RUSH-2682 cold-miss repair).
|
|
168
120
|
await waitForScanToSettle();
|
|
169
121
|
}
|
|
170
122
|
return queryIndexedSessions(options, {
|
|
171
123
|
skipExistenceCheck: options?.skipExistenceCheck ?? false,
|
|
172
124
|
});
|
|
173
125
|
}
|
|
174
|
-
/** Read only matching Claude/Codex transcripts on a cold ID lookup; indexing stays with the daemon. */
|
|
175
126
|
export async function findLocalSessionTranscripts(selector, agent) {
|
|
176
127
|
const matches = new Map();
|
|
177
128
|
const seen = new Set();
|
|
@@ -209,7 +160,6 @@ export async function findLocalSessionTranscripts(selector, agent) {
|
|
|
209
160
|
}
|
|
210
161
|
return [...matches.values()];
|
|
211
162
|
}
|
|
212
|
-
/** Repair local metadata before deciding that a conversation has no context. */
|
|
213
163
|
export async function hydrateSessionTranscript(session) {
|
|
214
164
|
const accountId = session.accountId ?? readSessionActorRecord(session.id)?.accountId;
|
|
215
165
|
if (accountId && !session.accountId)
|
|
@@ -219,7 +169,7 @@ export async function hydrateSessionTranscript(session) {
|
|
|
219
169
|
if (file && fs.statSync(file).isFile() && fs.statSync(file).size > 0)
|
|
220
170
|
return session;
|
|
221
171
|
}
|
|
222
|
-
catch {
|
|
172
|
+
catch { }
|
|
223
173
|
if (session.machine && !isSelfHost(session.machine))
|
|
224
174
|
return session;
|
|
225
175
|
if (session.agent === 'claude' || session.agent === 'codex') {
|
|
@@ -237,33 +187,20 @@ export async function hydrateSessionTranscript(session) {
|
|
|
237
187
|
const indexed = getSessionById(session.id);
|
|
238
188
|
return indexed ? { ...session, ...indexed, accountId: session.accountId ?? indexed.accountId } : session;
|
|
239
189
|
}
|
|
240
|
-
/** Separate write half so daemon warming does not pay for listing or external enrichment. */
|
|
241
190
|
export async function scanSessionsIncremental(options) {
|
|
242
|
-
// Touch the DB so the schema is ready and connection is cached for this run.
|
|
243
191
|
getDB();
|
|
244
192
|
const agents = options?.agent ? [options.agent] : SESSION_AGENTS;
|
|
245
193
|
const onProgress = options?.onProgress;
|
|
246
194
|
if (!tryClaimScan(process.pid))
|
|
247
195
|
return { claimed: false, scanned: 0 };
|
|
248
|
-
// `parsed` climbs monotonically within one scanner run, so the last value per
|
|
249
|
-
// (phase, agent) is that scanner's total. Phase-keyed because the dotfile scan
|
|
250
|
-
// and the routine-archive scan both report under the same agent id.
|
|
251
196
|
const parsedByPhaseAgent = new Map();
|
|
252
197
|
const track = (phase) => (p) => {
|
|
253
198
|
parsedByPhaseAgent.set(`${phase}\0${p.agent}`, p.parsed);
|
|
254
199
|
onProgress?.(p);
|
|
255
200
|
};
|
|
256
201
|
try {
|
|
257
|
-
// Bounded + staggered instead of a single Promise.all: scanning every
|
|
258
|
-
// agent's dotfile dir (~/.claude, ~/.codex, ~/.gemini, …) simultaneously
|
|
259
|
-
// reads to behavioral EDR (CrowdStrike Falcon) as a ransomware-style bulk
|
|
260
|
-
// file-enumeration sweep. Same dirs, same results — just not all at once.
|
|
261
202
|
await scanAgentsBounded(agents, agent => dispatchAgentScan(agent, track('dotfiles')));
|
|
262
203
|
await scanAgentsBounded(agents, agent => scanRoutineArchivesIncremental(agent, track('routines')));
|
|
263
|
-
// Seed labels from `agents run --name` handles onto the freshly-scanned
|
|
264
|
-
// rows by id. Runs AFTER the per-agent scans (which applied agent-generated
|
|
265
|
-
// titles via syncLabels), so a real title always wins and the seed only
|
|
266
|
-
// backfills sessions that would otherwise be unnamed.
|
|
267
204
|
seedLabelsFromNames(buildRunNameMap());
|
|
268
205
|
}
|
|
269
206
|
finally {
|
|
@@ -274,7 +211,6 @@ export async function scanSessionsIncremental(options) {
|
|
|
274
211
|
scanned += n;
|
|
275
212
|
return { claimed: true, scanned };
|
|
276
213
|
}
|
|
277
|
-
/** Bounded wait so a wedged scan cannot hang a foreground cold-miss repair. */
|
|
278
214
|
export async function waitForScanToSettle(timeoutMs = WAIT_FOR_SCAN_TIMEOUT_MS, pollMs = WAIT_FOR_SCAN_POLL_MS) {
|
|
279
215
|
const deadline = Date.now() + timeoutMs;
|
|
280
216
|
while (scanInProgressByLivePid()) {
|
|
@@ -284,7 +220,6 @@ export async function waitForScanToSettle(timeoutMs = WAIT_FOR_SCAN_TIMEOUT_MS,
|
|
|
284
220
|
}
|
|
285
221
|
return true;
|
|
286
222
|
}
|
|
287
|
-
/** Read the current SQLite snapshot without scanning or parsing transcript files. */
|
|
288
223
|
export async function queryIndexedSessions(options, indexedOptions = {}) {
|
|
289
224
|
getDB();
|
|
290
225
|
const agents = options?.agent ? [options.agent] : SESSION_AGENTS;
|
|
@@ -292,24 +227,11 @@ export async function queryIndexedSessions(options, indexedOptions = {}) {
|
|
|
292
227
|
if (indexedOptions.resolveLinear !== false)
|
|
293
228
|
await resolveLinearProjects(sessions);
|
|
294
229
|
for (const s of sessions) {
|
|
295
|
-
// A non-empty transcript path is authoritative for origin (a live-home file
|
|
296
|
-
// is this box; `backups/<agent>/<machine>/…` names that peer), so derive
|
|
297
|
-
// there. An EMPTY file_path carries no such signal — `machineForSessionFile`
|
|
298
|
-
// falls back to THIS box — so keep the machine the row was recorded with
|
|
299
|
-
// instead: a host dispatch stamps the EXECUTION host on its empty-file index
|
|
300
|
-
// row (`registerHostSession`), and re-deriving re-attributed the dispatcher's
|
|
301
|
-
// own pool row to itself, splitting it from the executing peer's fan-out row
|
|
302
|
-
// and making resume-by-id read as "ambiguous (2 sessions)" (RUSH-2486 /
|
|
303
|
-
// RUSH-2479 criterion 2). A recorded-less empty-file row still falls back.
|
|
304
230
|
if (s.filePath || !s.machine?.trim())
|
|
305
231
|
s.machine = machineForSessionFile(s.filePath, s.agent);
|
|
306
232
|
}
|
|
307
233
|
return scopeToManaged(sessions, agents, options);
|
|
308
234
|
}
|
|
309
|
-
/**
|
|
310
|
-
* Resolve locally without scanning or fleet I/O, keeping concurrent crash recovery lock-light.
|
|
311
|
-
* The existence check preserves archived content while rejecting transcriptless phantoms.
|
|
312
|
-
*/
|
|
313
235
|
export async function resolveIndexedSessionById(idQuery) {
|
|
314
236
|
const q = idQuery.trim();
|
|
315
237
|
if (!q)
|
|
@@ -357,16 +279,6 @@ async function resolveLinearProjects(sessions) {
|
|
|
357
279
|
cacheLinearProject(session.id, project.name, project.url);
|
|
358
280
|
}));
|
|
359
281
|
}
|
|
360
|
-
/**
|
|
361
|
-
* Drop unmanaged rows for agents that HAVE managed versions.
|
|
362
|
-
*
|
|
363
|
-
* Scoping happens here, at query time, rather than by narrowing the scan: the index
|
|
364
|
-
* stays complete, so `--unmanaged` needs no re-scan and every other consumer of the
|
|
365
|
-
* DB (watchdog, the Factory watcher, `--roots`) is unaffected.
|
|
366
|
-
*
|
|
367
|
-
* An agent with no managed versions is left alone entirely — someone who has never
|
|
368
|
-
* run `agents add` sees exactly what they saw before.
|
|
369
|
-
*/
|
|
370
282
|
export function scopeToManaged(sessions, agents, options) {
|
|
371
283
|
if (options?.includeUnmanaged)
|
|
372
284
|
return sessions;
|
|
@@ -378,12 +290,6 @@ export function scopeToManaged(sessions, agents, options) {
|
|
|
378
290
|
options?.onHiddenUnmanaged?.(hidden);
|
|
379
291
|
return kept;
|
|
380
292
|
}
|
|
381
|
-
/**
|
|
382
|
-
* True once agents-cli manages ANY agent version. Until then it manages nothing, so
|
|
383
|
-
* scoping to "managed only" would leave the listing empty for a user who has never
|
|
384
|
-
* run `agents add` — the browser is most of the tool's value before you install
|
|
385
|
-
* anything through it.
|
|
386
|
-
*/
|
|
387
293
|
function anyManagedVersions() {
|
|
388
294
|
for (const root of VERSIONS_ROOTS) {
|
|
389
295
|
const base = path.join(root, 'versions');
|
|
@@ -401,26 +307,19 @@ function anyManagedVersions() {
|
|
|
401
307
|
if (fs.readdirSync(path.join(base, a.name), { withFileTypes: true }).some((e) => e.isDirectory()))
|
|
402
308
|
return true;
|
|
403
309
|
}
|
|
404
|
-
catch {
|
|
310
|
+
catch { }
|
|
405
311
|
}
|
|
406
312
|
}
|
|
407
313
|
return false;
|
|
408
314
|
}
|
|
409
|
-
/**
|
|
410
|
-
* How many agents' dotfile dirs we scan at once, and the minimum spacing between
|
|
411
|
-
* successive scan starts. A small bound + stagger turns a simultaneous bulk
|
|
412
|
-
* multi-dotfile sweep (a behavioral-EDR file-enumeration trigger) into a trickle.
|
|
413
|
-
*/
|
|
414
315
|
export const DOTFILE_SCAN_CONCURRENCY = 2;
|
|
415
316
|
const DOTFILE_SCAN_STAGGER_MS = 15;
|
|
416
|
-
/** Run each agent's incremental scan, bounded + staggered. Order is irrelevant (each scan writes its own rows). */
|
|
417
317
|
export function scanAgentsBounded(items, run) {
|
|
418
318
|
return mapBounded(items, run, {
|
|
419
319
|
concurrency: DOTFILE_SCAN_CONCURRENCY,
|
|
420
320
|
staggerMs: DOTFILE_SCAN_STAGGER_MS,
|
|
421
321
|
});
|
|
422
322
|
}
|
|
423
|
-
/** Dispatch a single agent's incremental dotfile scan. */
|
|
424
323
|
function dispatchAgentScan(agent, onProgress) {
|
|
425
324
|
switch (agent) {
|
|
426
325
|
case 'claude': return scanClaudeIncremental(onProgress);
|
|
@@ -438,61 +337,18 @@ function dispatchAgentScan(agent, onProgress) {
|
|
|
438
337
|
default: return Promise.resolve();
|
|
439
338
|
}
|
|
440
339
|
}
|
|
441
|
-
/**
|
|
442
|
-
* The machine a discovered session originated on. Cross-machine sync mirrors a
|
|
443
|
-
* remote transcript to backups/<agent>/<machine>/<subdir>/… (see mirrorPath in
|
|
444
|
-
* sync/agents.ts); every other transcript is a live-home file on this box. So:
|
|
445
|
-
* when the path sits under the agent's backups root, the first segment below it
|
|
446
|
-
* is the origin machine id; otherwise it's the local machine.
|
|
447
|
-
*/
|
|
448
|
-
/**
|
|
449
|
-
* True when this transcript belongs to a version agents-cli manages — i.e. it lives
|
|
450
|
-
* under a version home (or a backup mirror of one) rather than in the user's own
|
|
451
|
-
* `~/.<agent>`.
|
|
452
|
-
*
|
|
453
|
-
* `agents sessions` scans both, which is right for indexing: the DB stays a complete
|
|
454
|
-
* picture and `--unmanaged` can surface everything without a re-scan. But listing
|
|
455
|
-
* *by default* is a different question. Once you have managed versions, an unmanaged
|
|
456
|
-
* install's history is not really agents-cli's to show — most visibly after
|
|
457
|
-
* `agents add --isolated`, where the whole point was to keep the two apart.
|
|
458
|
-
*/
|
|
459
340
|
export function isManagedSessionFile(filePath) {
|
|
460
|
-
// Synthetic rows (OpenClaw workspace sessions, cloud/remote entries) have no local
|
|
461
|
-
// transcript to classify. They are produced BY agents-cli rather than read out of
|
|
462
|
-
// someone's dotfile dir, so scoping must not silently swallow them.
|
|
463
341
|
if (!filePath || !path.isAbsolute(filePath))
|
|
464
342
|
return true;
|
|
465
|
-
// A composite file_path (`<container>#<id>`) names a row inside a single shared
|
|
466
|
-
// DB the scanner reads from one fixed location (OpenCode's `opencode.db`) — that
|
|
467
|
-
// store is never a per-install dotfile under a version home, so the managed-vs-
|
|
468
|
-
// unmanaged split does not apply: there is exactly one store, not a "your own"
|
|
469
|
-
// copy to hide. Classifying it as unmanaged hid every OpenCode row from default
|
|
470
|
-
// listings once any agent was managed (RUSH-2357). Keyed off the composite FORM,
|
|
471
|
-
// so any future single-DB harness inherits this.
|
|
472
343
|
if (splitSessionFilePath(filePath).fragment !== undefined)
|
|
473
344
|
return true;
|
|
474
345
|
const roots = [
|
|
475
346
|
...VERSIONS_ROOTS.map((root) => path.join(root, 'versions')),
|
|
476
347
|
path.join(getHistoryDir(), 'backups'),
|
|
477
|
-
// Codex's managed home is not always under versions/. On macOS the versioned path
|
|
478
|
-
// overflows SUN_LEN for codex's control socket, so the shim relocates it to
|
|
479
|
-
// `<agentsUserDir>/.codex-homes/<key>/` (lib/codex-home.ts) — keyed by version OR
|
|
480
|
-
// by account short key (`a-<accountId prefix>`, lib/codex-home.ts `codexShortKey`).
|
|
481
348
|
path.join(getUserAgentsDir(), '.codex-homes'),
|
|
482
|
-
// Account slots (PHNX-3940): a named account's HOME-shaped dir under
|
|
483
|
-
// `<historyDir>/accounts/<agent>/<accountId>/` (lib/accounts/slots.ts
|
|
484
|
-
// `slotDir`), sharing the one managed install rather than owning a version
|
|
485
|
-
// home of its own. Without this root, every account-slot transcript read as
|
|
486
|
-
// unmanaged the moment any version was managed, hiding a fully registered,
|
|
487
|
-
// runnable account's entire history from the default listing.
|
|
488
349
|
path.join(getHistoryDir(), 'accounts'),
|
|
489
|
-
// Routine archives are agents-cli's OWN run output — managed by definition.
|
|
490
350
|
getRunsDir(),
|
|
491
351
|
];
|
|
492
|
-
// Compare realpaths as well as the literal roots. A transcript's stored path is
|
|
493
|
-
// resolved, so on macOS (`/var` -> `/private/var`) a temp-dir HOME yields
|
|
494
|
-
// `/private/var/...` for the file and `/var/...` for the root, and a plain prefix
|
|
495
|
-
// test silently classifies every managed session as the user's own.
|
|
496
352
|
const real = safeRealpathSync(filePath) || filePath;
|
|
497
353
|
return roots.some((root) => {
|
|
498
354
|
if (filePath.startsWith(root + path.sep))
|
|
@@ -501,16 +357,10 @@ export function isManagedSessionFile(filePath) {
|
|
|
501
357
|
return !!realRoot && real.startsWith(realRoot + path.sep);
|
|
502
358
|
});
|
|
503
359
|
}
|
|
504
|
-
/**
|
|
505
|
-
* Count sessions in scope without running an incremental scan. Assumes the DB
|
|
506
|
-
* is already fresh (typically true because `discoverSessions` ran first this
|
|
507
|
-
* turn). Uses the exact same filter shape as the discover query.
|
|
508
|
-
*/
|
|
509
360
|
export function countSessionsInScope(options) {
|
|
510
361
|
const agents = options.agent ? [options.agent] : SESSION_AGENTS;
|
|
511
362
|
return countSessions(buildQueryOptions(options, agents, { includeLimit: false }));
|
|
512
363
|
}
|
|
513
|
-
/** Translate DiscoverOptions into the QueryOptions shape expected by the DB layer. */
|
|
514
364
|
function buildQueryOptions(options, agents, opts) {
|
|
515
365
|
const projectQuery = options?.project?.trim();
|
|
516
366
|
const sinceMs = options?.since ? parseTimeFilter(options.since) : undefined;
|
|
@@ -521,8 +371,6 @@ function buildQueryOptions(options, agents, opts) {
|
|
|
521
371
|
cwdPrefixFilter = normalizeCwd(options.cwdPrefix);
|
|
522
372
|
}
|
|
523
373
|
else if (!options?.all && !projectQuery && options?.agent !== 'rush' && options?.agent !== 'hermes') {
|
|
524
|
-
// Rush and Hermes sessions are cloud/gateway-bound and have no cwd — skip
|
|
525
|
-
// cwd filtering when the user explicitly asked for them.
|
|
526
374
|
cwdFilter = normalizeCwd(options?.cwd || process.cwd());
|
|
527
375
|
}
|
|
528
376
|
return {
|
|
@@ -546,109 +394,36 @@ function buildQueryOptions(options, agents, opts) {
|
|
|
546
394
|
idPrefix: options?.idPrefix,
|
|
547
395
|
};
|
|
548
396
|
}
|
|
549
|
-
/**
|
|
550
|
-
* Canonicalize a working directory path (follows symlinks when it is local).
|
|
551
|
-
*
|
|
552
|
-
* Most callers pass a cwd RECORDED in a transcript, which may name a directory
|
|
553
|
-
* on another machine — a POSIX path read on a Windows host, say. `path.resolve()`
|
|
554
|
-
* rebases such a path onto the current drive (`/Users/me` -> `D:\Users\me`),
|
|
555
|
-
* inventing a location that never existed. So an absolute path is normalized but
|
|
556
|
-
* never rebased; only a genuinely relative one resolves against the process cwd.
|
|
557
|
-
*
|
|
558
|
-
* `path.normalize()` still runs on every branch: it collapses `.`, `..`, and
|
|
559
|
-
* duplicate separators, and folds separators on Windows. Both sides of the cwd
|
|
560
|
-
* filter in `db.ts` (`cwd = ?` and `cwd LIKE ? || path.sep || '%'`) come through
|
|
561
|
-
* here, so dropping that would leave a trailing slash or a `..` segment in one
|
|
562
|
-
* side and match nothing.
|
|
563
|
-
*
|
|
564
|
-
* Realpath is attempted only for a path that is absolute in THIS platform's
|
|
565
|
-
* terms. A POSIX-rooted path on Windows is drive-relative to `fs.realpathSync`,
|
|
566
|
-
* which would resolve `/Users/me` against the current drive and reintroduce the
|
|
567
|
-
* graft for any path that happens to exist locally.
|
|
568
|
-
*/
|
|
569
397
|
export function _normalizeCwdForTest(cwd) {
|
|
570
398
|
return normalizeCwd(cwd);
|
|
571
399
|
}
|
|
572
400
|
function normalizeCwd(cwd) {
|
|
573
401
|
if (!cwd)
|
|
574
402
|
return '';
|
|
575
|
-
// A POSIX-rooted path on Windows belongs to another machine. Normalize it with
|
|
576
|
-
// POSIX rules so its separators survive — path.win32.normalize would fold them
|
|
577
|
-
// to backslashes, mangling the very path we are trying to preserve — and never
|
|
578
|
-
// realpath it, since fs.realpathSync would resolve it against the current drive.
|
|
579
403
|
if (process.platform === 'win32' && /^\//.test(cwd) && !/^[a-zA-Z]:/.test(cwd)) {
|
|
580
404
|
return stripTrailingSep(path.posix.normalize(cwd));
|
|
581
405
|
}
|
|
582
|
-
// The mirror case (RUSH-2358): a Windows-rooted path (`C:\...`, `C:/...`, or a
|
|
583
|
-
// UNC `\\server\share\...`) read on POSIX belongs to another machine too, but
|
|
584
|
-
// `path.isAbsolute()` here uses POSIX rules and doesn't recognize a drive
|
|
585
|
-
// letter — without this branch such a cwd falls into the `path.resolve()` arm
|
|
586
|
-
// below and gets silently prefixed with THIS process's own cwd, corrupting the
|
|
587
|
-
// path (and, via WORKTREE_RE, can misattribute the worktree slug to whatever
|
|
588
|
-
// worktree the reading process happens to be running in). Normalize with
|
|
589
|
-
// win32 rules so separators survive; never realpath it, for the same
|
|
590
|
-
// cross-drive reason as the mirror branch above.
|
|
591
406
|
if (process.platform !== 'win32' && /^([a-zA-Z]:[\\/]|\\\\)/.test(cwd)) {
|
|
592
407
|
return stripTrailingSep(path.win32.normalize(cwd));
|
|
593
408
|
}
|
|
594
409
|
const normalized = path.isAbsolute(cwd) ? stripTrailingSep(path.normalize(cwd)) : path.resolve(cwd);
|
|
595
410
|
return safeRealpathSync(normalized) || normalized;
|
|
596
411
|
}
|
|
597
|
-
/** Drop a trailing separator so `cwd = ?` and the `cwd LIKE ? + sep` subdir
|
|
598
|
-
* wildcard agree; a root path (`/`, `C:\`) keeps its separator. */
|
|
599
412
|
function stripTrailingSep(p) {
|
|
600
413
|
const stripped = p.replace(/[\\/]+$/, '');
|
|
601
414
|
return stripped.length > 0 && !/^[a-zA-Z]:$/.test(stripped) ? stripped : p;
|
|
602
415
|
}
|
|
603
|
-
/** Canonical 8-4-4-4-12 hex UUID (covers both v4 and the v7 ids newer harnesses mint). */
|
|
604
416
|
const UUID_36 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
605
|
-
/** kimi and rush mint `session_` + a UUID. */
|
|
606
417
|
const SESSION_UUID_PREFIX = /^session_/;
|
|
607
|
-
/** opencode mints `ses_` + a 26-char ULID — NOT a UUID, so it needs its own shape. */
|
|
608
418
|
const SES_ULID = /^ses_[0-9a-z]{26}$/;
|
|
609
|
-
/**
|
|
610
|
-
* Whether a query is a session id in full, rather than an id prefix or a search
|
|
611
|
-
* phrase.
|
|
612
|
-
*
|
|
613
|
-
* Callers use this to decide that an id lookup is the ONLY admissible
|
|
614
|
-
* interpretation: a complete id is unique, so when it misses there is nothing
|
|
615
|
-
* left to widen to. Without the check, `sessions <uuid>` fell through to the
|
|
616
|
-
* FTS content search, which tokenizes the UUID and matches every transcript that
|
|
617
|
-
* merely mentions it — surfacing unrelated sessions as if they were id matches.
|
|
618
|
-
*
|
|
619
|
-
* The accepted shapes are the ones the index actually holds, measured over a
|
|
620
|
-
* 12,507-row index: a bare UUID (11,116 rows), `session_` + UUID (1,360 — kimi
|
|
621
|
-
* and rush), and `ses_` + ULID (15 — opencode). Deliberately NOT covered, so a
|
|
622
|
-
* miss keeps today's search behavior rather than gaining a wrong error: routine
|
|
623
|
-
* run ids (ISO timestamps, matched via `routineRunId` below) and cloud execution
|
|
624
|
-
* ids, whose charset is too permissive to distinguish from a search phrase.
|
|
625
|
-
*/
|
|
626
419
|
export function isCompleteSessionId(query) {
|
|
627
420
|
const q = query.trim().toLowerCase();
|
|
628
421
|
return UUID_36.test(q.replace(SESSION_UUID_PREFIX, '')) || SES_ULID.test(q);
|
|
629
422
|
}
|
|
630
|
-
/**
|
|
631
|
-
* Whether a query should be treated as a session id rather than a search phrase
|
|
632
|
-
* — the one canonical id-shaped test, shared by every session-id resolver.
|
|
633
|
-
*
|
|
634
|
-
* True for a complete id (`isCompleteSessionId`) AND for a bare hex short-id or
|
|
635
|
-
* prefix (`d3470b57`), which the complete-id check rejects. Any id-shaped query
|
|
636
|
-
* resolves by id ONLY (exact -> prefix -> `findSessionsById` index) and must
|
|
637
|
-
* never fall back to fuzzy content search: a short id like `d3470b57` otherwise
|
|
638
|
-
* surfaces every transcript that merely MENTIONS the string (a resume prompt
|
|
639
|
-
* echoes the parent id into the body of many later sessions). The hex test
|
|
640
|
-
* catches the bare short-id/prefix; `isCompleteSessionId` additionally catches
|
|
641
|
-
* the prefixed whole ids (`session_…`, `ses_…`) that the hex test rejects.
|
|
642
|
-
*/
|
|
643
423
|
export function looksLikeSessionId(query) {
|
|
644
424
|
const trimmed = query.trim();
|
|
645
425
|
return /^[0-9a-f-]{6,}$/i.test(trimmed) || isCompleteSessionId(trimmed);
|
|
646
426
|
}
|
|
647
|
-
/**
|
|
648
|
-
* Resolve a session by full or short ID. Accepts a pre-loaded session list
|
|
649
|
-
* (fast path from discoverSessions) and falls back to a DB lookup for the
|
|
650
|
-
* "I only know the id" case.
|
|
651
|
-
*/
|
|
652
427
|
export function resolveSessionById(sessions, idQuery) {
|
|
653
428
|
const query = idQuery.toLowerCase();
|
|
654
429
|
const exact = sessions.filter(s => s.id.toLowerCase() === query ||
|
|
@@ -660,20 +435,6 @@ export function resolveSessionById(sessions, idQuery) {
|
|
|
660
435
|
s.shortId.toLowerCase().startsWith(query) ||
|
|
661
436
|
s.routineRunId?.toLowerCase().startsWith(query));
|
|
662
437
|
}
|
|
663
|
-
// ---------------------------------------------------------------------------
|
|
664
|
-
// Content-index search (FTS5-backed)
|
|
665
|
-
// ---------------------------------------------------------------------------
|
|
666
|
-
/**
|
|
667
|
-
* Run an FTS5 search over the DB and union hits with the given session list.
|
|
668
|
-
*
|
|
669
|
-
* The listing pool is a minority of the index — cwd-scoped, default-capped at
|
|
670
|
-
* 50, and skipping whole classes of indexed transcript — so intersecting FTS
|
|
671
|
-
* hits with it dropped grep-visible sessions that the index already found
|
|
672
|
-
* (PHNX-2767: `agents sessions "tmux pane"` returned 0 while the project
|
|
673
|
-
* transcripts matched). Hits already in the pool keep the caller's SessionMeta;
|
|
674
|
-
* hits the pool missed are hydrated from the index so a content query returns
|
|
675
|
-
* the transcript FTS matched.
|
|
676
|
-
*/
|
|
677
438
|
export function searchContentIndex(sessions, query) {
|
|
678
439
|
if (!query.trim())
|
|
679
440
|
return new Map();
|
|
@@ -695,18 +456,6 @@ export function searchContentIndex(sessions, query) {
|
|
|
695
456
|
}
|
|
696
457
|
return result;
|
|
697
458
|
}
|
|
698
|
-
// ---------------------------------------------------------------------------
|
|
699
|
-
// Incremental scan orchestration
|
|
700
|
-
// ---------------------------------------------------------------------------
|
|
701
|
-
/**
|
|
702
|
-
* For a list of files, stat each, compare to the DB ledger, and return only
|
|
703
|
-
* the ones that need rescanning. One bulk DB query for the whole list.
|
|
704
|
-
*
|
|
705
|
-
* Actively running agents append to their JSONL every few seconds. Without a
|
|
706
|
-
* small debounce, repeated `agents sessions` invocations stream-parse the same
|
|
707
|
-
* growing transcript over and over. The cached row is good enough for a few
|
|
708
|
-
* seconds; once writes settle or the debounce expires, the file is parsed once.
|
|
709
|
-
*/
|
|
710
459
|
export function filterChangedFiles(filePaths) {
|
|
711
460
|
const entries = [];
|
|
712
461
|
for (const filePath of filePaths) {
|
|
@@ -717,19 +466,6 @@ export function filterChangedFiles(filePaths) {
|
|
|
717
466
|
}
|
|
718
467
|
return filterChangedEntries(entries);
|
|
719
468
|
}
|
|
720
|
-
/**
|
|
721
|
-
* Ledger-compare pre-stat'd entries (from the walk's own stat) without a second
|
|
722
|
-
* stat. Same debounce and change-detection as filterChangedFiles; the raw
|
|
723
|
-
* mtime is floored here so warm files match the ledger exactly as the stat path
|
|
724
|
-
* does (Math.floor(stat.mtimeMs)).
|
|
725
|
-
*
|
|
726
|
-
* A file is also treated as "changed" — independent of (mtime, size) — when
|
|
727
|
-
* its ledger row's `extractor_version` is behind {@link CONTENT_INDEX_VERSION}.
|
|
728
|
-
* This is the lever that backfills a content-extractor improvement (e.g.
|
|
729
|
-
* indexing assistant answers, not just user prompts) into every already-scanned
|
|
730
|
-
* session on its next pass, without a `DELETE FROM scan_ledger` that would also
|
|
731
|
-
* discard the Claude/Codex resumable parser_state.
|
|
732
|
-
*/
|
|
733
469
|
export function filterChangedEntries(entries) {
|
|
734
470
|
const ledger = getScanStampsForPaths(entries.map(e => e.filePath));
|
|
735
471
|
const out = [];
|
|
@@ -760,31 +496,10 @@ export function shouldDeferRecentAppend(prev, current, nowMs, debounceMs = ACTIV
|
|
|
760
496
|
return false;
|
|
761
497
|
return nowMs - prev.scannedAt < debounceMs;
|
|
762
498
|
}
|
|
763
|
-
/**
|
|
764
|
-
* Walk a set of leaf transcript directories and return the files that changed,
|
|
765
|
-
* skipping the per-file `stat` of directories whose (mtime, entry_count) matches
|
|
766
|
-
* the dir_ledger.
|
|
767
|
-
*
|
|
768
|
-
* Per leaf dir:
|
|
769
|
-
* - `stat` the dir once and `readdir` it (one cheap syscall) to get the entry
|
|
770
|
-
* count and the file list.
|
|
771
|
-
* - If the dir matches the dir_ledger (floored mtime AND entry_count), no file
|
|
772
|
-
* was created / deleted / renamed since we last walked it. We then stat ONLY
|
|
773
|
-
* the hot files (live-root files, or files scanned within HOT_FILE_WINDOW_MS)
|
|
774
|
-
* and run just those through the ledger compare — so an in-place append to a
|
|
775
|
-
* still-live session is still caught, while immutable backup/version dirs
|
|
776
|
-
* collapse to a single dir stat and zero per-file stats.
|
|
777
|
-
* - Else (changed dir, or no ledger row) we stat every file (today's full
|
|
778
|
-
* walk) and record the fresh dir stamp so the next run can short-circuit.
|
|
779
|
-
*
|
|
780
|
-
* The kill-switch (`AGENTS_SESSIONS_NO_DIR_LEDGER=1`) forces the full-walk branch
|
|
781
|
-
* for every dir and never consults or records the dir_ledger.
|
|
782
|
-
*/
|
|
783
499
|
function collectChangedFilesInLeafDirs(leafDirs, ext) {
|
|
784
500
|
const disabled = dirLedgerDisabled();
|
|
785
501
|
const dirStamps = disabled ? new Map() : getDirLedgerForPaths(leafDirs.map(d => d.dirPath));
|
|
786
502
|
const now = Date.now();
|
|
787
|
-
// Files whose per-file stat we still need to ledger-compare this run.
|
|
788
503
|
const toCompare = [];
|
|
789
504
|
const allFiles = [];
|
|
790
505
|
const dirScansToRecord = [];
|
|
@@ -807,16 +522,6 @@ function collectChangedFilesInLeafDirs(leafDirs, ext) {
|
|
|
807
522
|
const prevDir = dirStamps.get(dirPath);
|
|
808
523
|
const dirUnchanged = !disabled && prevDir !== undefined && prevDir.dirMtimeMs === dirMtimeMs && prevDir.entryCount === entryCount;
|
|
809
524
|
if (dirUnchanged) {
|
|
810
|
-
// Contents did not change (no create/delete/rename). Stat only the hot
|
|
811
|
-
// files; the rest are served from the DB with no stat. An immutable backup
|
|
812
|
-
// dir (not a live root, nothing recently scanned) does zero per-file stats.
|
|
813
|
-
//
|
|
814
|
-
// A live-root dir treats every file as hot — that is the tree an agent
|
|
815
|
-
// appends to live, and an append does NOT bump the parent-dir mtime, so
|
|
816
|
-
// without this a growing session would be silently skipped. A non-live
|
|
817
|
-
// file is hot only if it was scanned within HOT_FILE_WINDOW_MS (bulk ledger
|
|
818
|
-
// lookup), covering a session under a version/backup path that is somehow
|
|
819
|
-
// still being written.
|
|
820
525
|
const stamps = isLiveRoot ? null : getScanStampsForPaths(files);
|
|
821
526
|
for (const filePath of files) {
|
|
822
527
|
let hot = isLiveRoot;
|
|
@@ -832,10 +537,8 @@ function collectChangedFilesInLeafDirs(leafDirs, ext) {
|
|
|
832
537
|
continue;
|
|
833
538
|
toCompare.push({ filePath, fileMtimeMs: stat.mtimeMs, fileSize: stat.size });
|
|
834
539
|
}
|
|
835
|
-
// No dir stamp to record — nothing about the dir changed.
|
|
836
540
|
}
|
|
837
541
|
else {
|
|
838
|
-
// Changed dir (or cold ledger): full per-file stat, exactly as today.
|
|
839
542
|
for (const filePath of files) {
|
|
840
543
|
const stat = safeStatSync(filePath);
|
|
841
544
|
if (!stat)
|
|
@@ -852,13 +555,6 @@ function collectChangedFilesInLeafDirs(leafDirs, ext) {
|
|
|
852
555
|
recordDirScans(dirScansToRecord);
|
|
853
556
|
return { changed, allFiles };
|
|
854
557
|
}
|
|
855
|
-
// ---------------------------------------------------------------------------
|
|
856
|
-
// Multi-version directory scanning
|
|
857
|
-
// ---------------------------------------------------------------------------
|
|
858
|
-
/**
|
|
859
|
-
* Collect all directories to scan for an agent's sessions. Deduplicates by
|
|
860
|
-
* realpath to avoid double-counting symlinked version homes.
|
|
861
|
-
*/
|
|
862
558
|
export function getAgentSessionDirs(agent, subdir) {
|
|
863
559
|
const resolved = new Set();
|
|
864
560
|
const dirs = [];
|
|
@@ -872,9 +568,6 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
872
568
|
resolved.add(key);
|
|
873
569
|
dirs.push(dir);
|
|
874
570
|
}
|
|
875
|
-
// Config-dir name relative to home — handles nested layouts (antigravity →
|
|
876
|
-
// .gemini/antigravity-cli) and ~/.config agents (amp, goose) as well as kimi
|
|
877
|
-
// (.kimi-code). Falls back to `.${agent}` for ids not in the registry.
|
|
878
571
|
const configDirName = agent in AGENTS ? agentConfigDirName(agent) : `.${agent}`;
|
|
879
572
|
addDir(path.join(HOME, configDirName, subdir));
|
|
880
573
|
for (const root of VERSIONS_ROOTS) {
|
|
@@ -886,19 +579,8 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
886
579
|
addDir(path.join(versionsBase, version, 'home', configDirName, subdir));
|
|
887
580
|
}
|
|
888
581
|
}
|
|
889
|
-
catch {
|
|
890
|
-
}
|
|
891
|
-
// Codex's managed home is not always where the version layout says. On macOS
|
|
892
|
-
// the versioned path overflows SUN_LEN (104 bytes) for codex's control socket,
|
|
893
|
-
// so the shim relocates the home to `<agentsUserDir>/.codex-homes/<key>/.codex`
|
|
894
|
-
// (lib/codex-home.ts) — `<key>` is the version for a version home, or
|
|
895
|
-
// `a-<accountId prefix>` (`codexShortKey`) for an account slot under
|
|
896
|
-
// `<historyDir>/accounts/codex/`. Walking `.codex-homes/` directly — rather
|
|
897
|
-
// than deriving keys from the installed-version list — is what catches BOTH:
|
|
898
|
-
// an account short key is not a vendor version and never appears in
|
|
899
|
-
// `versions/codex/`, so iterating only installed versions silently dropped
|
|
900
|
-
// every transcript a codex account slot wrote. addDir skips what does not
|
|
901
|
-
// exist, so this is inert on Linux and for homes that never needed relocating.
|
|
582
|
+
catch { }
|
|
583
|
+
}
|
|
902
584
|
if (agent === 'codex') {
|
|
903
585
|
const codexHomesBase = path.join(getUserAgentsDir(), '.codex-homes');
|
|
904
586
|
try {
|
|
@@ -906,25 +588,15 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
906
588
|
addDir(path.join(codexHomesBase, key, '.codex', subdir));
|
|
907
589
|
}
|
|
908
590
|
}
|
|
909
|
-
catch {
|
|
910
|
-
}
|
|
911
|
-
// Account slots (PHNX-3940): a named account gets its own HOME-shaped dir
|
|
912
|
-
// under `<historyDir>/accounts/<agent>/<accountId>/`, sharing the one managed
|
|
913
|
-
// binary install rather than owning a version home of its own
|
|
914
|
-
// (lib/accounts/slots.ts `slotDir`). A slot-launched transcript lives ONLY
|
|
915
|
-
// there — never under `versions/` — so without this root it is fully
|
|
916
|
-
// discoverable by the account machinery (the account is registered and
|
|
917
|
-
// runnable) yet invisible to `agents sessions`, reading as if the history had
|
|
918
|
-
// vanished. `addDir` follows the realpath, so a codex slot whose `.codex` is a
|
|
919
|
-
// symlink onto its `.codex-homes/<key>` short home (SUN_LEN relocation, above)
|
|
920
|
-
// is deduplicated against that same target rather than double-counted.
|
|
591
|
+
catch { }
|
|
592
|
+
}
|
|
921
593
|
const accountsBase = path.join(getHistoryDir(), 'accounts', agent);
|
|
922
594
|
try {
|
|
923
595
|
for (const accountId of fs.readdirSync(accountsBase)) {
|
|
924
596
|
addDir(path.join(accountsBase, accountId, configDirName, subdir));
|
|
925
597
|
}
|
|
926
598
|
}
|
|
927
|
-
catch {
|
|
599
|
+
catch { }
|
|
928
600
|
const backupsBase = path.join(getHistoryDir(), 'backups', agent);
|
|
929
601
|
if (fs.existsSync(backupsBase)) {
|
|
930
602
|
try {
|
|
@@ -932,18 +604,10 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
932
604
|
addDir(path.join(backupsBase, ts, subdir));
|
|
933
605
|
}
|
|
934
606
|
}
|
|
935
|
-
catch {
|
|
607
|
+
catch { }
|
|
936
608
|
}
|
|
937
609
|
return dirs;
|
|
938
610
|
}
|
|
939
|
-
/**
|
|
940
|
-
* The (agent, subdir) pairs `discoverSessions` walks for JSONL transcripts —
|
|
941
|
-
* the single source of truth for which directories hold live session files.
|
|
942
|
-
* `getSessionRoots` expands each pair to its concrete directories so a consumer
|
|
943
|
-
* (AGI EXT's fs.watch, see issue #741) can configure its watcher
|
|
944
|
-
* from the CLI instead of hardcoding `~/.claude|.codex|.gemini`. Adding a new
|
|
945
|
-
* on-disk agent here makes every consumer watch it automatically.
|
|
946
|
-
*/
|
|
947
611
|
const SESSION_ROOT_SPECS = [
|
|
948
612
|
{ agent: 'claude', subdir: 'projects' },
|
|
949
613
|
{ agent: 'codex', subdir: 'sessions' },
|
|
@@ -956,12 +620,6 @@ const SESSION_ROOT_SPECS = [
|
|
|
956
620
|
function sessionRootSubdir(agent) {
|
|
957
621
|
return SESSION_ROOT_SPECS.find((spec) => spec.agent === agent)?.subdir ?? null;
|
|
958
622
|
}
|
|
959
|
-
/**
|
|
960
|
-
* The directories `agents sessions` scans for each on-disk session agent,
|
|
961
|
-
* resolved to what exists on this machine. Emitted by `agents sessions --roots
|
|
962
|
-
* --json` so external watchers stay in lockstep with the CLI's discovery paths.
|
|
963
|
-
* Agents with no directories present are omitted.
|
|
964
|
-
*/
|
|
965
623
|
export function getSessionRoots() {
|
|
966
624
|
const out = [];
|
|
967
625
|
for (const { agent, subdir } of SESSION_ROOT_SPECS) {
|
|
@@ -1030,9 +688,6 @@ async function readRoutineArchiveMeta(agent, filePath) {
|
|
|
1030
688
|
return null;
|
|
1031
689
|
if (agent === 'claude') {
|
|
1032
690
|
const sessionId = path.basename(filePath).replace(/\.jsonl$/, '');
|
|
1033
|
-
// Routine archives are finalized, immutable transcripts — no live append, so
|
|
1034
|
-
// no continuation to resume. A FULL parse (undefined prior) is correct here;
|
|
1035
|
-
// the returned continuation is unused by this archive path.
|
|
1036
691
|
const stat = safeStatSync(filePath);
|
|
1037
692
|
if (!stat)
|
|
1038
693
|
return null;
|
|
@@ -1045,8 +700,6 @@ async function readRoutineArchiveMeta(agent, filePath) {
|
|
|
1045
700
|
return result ? { ...result, meta: decorateRoutineSession(result.meta, info) } : null;
|
|
1046
701
|
}
|
|
1047
702
|
if (agent === 'cursor') {
|
|
1048
|
-
// PR #1723 archives Cursor routine transcripts; preserve version resolution
|
|
1049
|
-
// when this reader consumes those archives.
|
|
1050
703
|
const currentVersion = await getCurrentAgentVersion('cursor');
|
|
1051
704
|
const result = readCursorMeta(filePath, currentVersion);
|
|
1052
705
|
return result ? { ...result, meta: decorateRoutineSession(result.meta, info) } : null;
|
|
@@ -1096,34 +749,12 @@ async function scanRoutineArchivesIncremental(agent, onProgress) {
|
|
|
1096
749
|
upsertSessionsBatch(entries);
|
|
1097
750
|
recordScans(touched);
|
|
1098
751
|
}
|
|
1099
|
-
// ---------------------------------------------------------------------------
|
|
1100
|
-
// Claude account info
|
|
1101
|
-
// ---------------------------------------------------------------------------
|
|
1102
752
|
let cachedClaudeAccountIndex;
|
|
1103
|
-
/**
|
|
1104
|
-
* The account-attribution index, built at most once per scan pass.
|
|
1105
|
-
*
|
|
1106
|
-
* Rebuilt when `refresh` is set — `scanClaudeIncremental` does that at the start of
|
|
1107
|
-
* each pass so a long-lived process (the daemon) picks up an `agents use` switch or a
|
|
1108
|
-
* fresh login instead of attributing later sessions to a stale set of homes. Per-file
|
|
1109
|
-
* resolution then reads the cached index, because rebuilding it per transcript would
|
|
1110
|
-
* re-read every home's `.claude.json` thousands of times.
|
|
1111
|
-
*/
|
|
1112
753
|
function claudeAccountIndex(refresh = false) {
|
|
1113
754
|
if (refresh || !cachedClaudeAccountIndex)
|
|
1114
755
|
cachedClaudeAccountIndex = buildClaudeAccountIndex();
|
|
1115
756
|
return cachedClaudeAccountIndex;
|
|
1116
757
|
}
|
|
1117
|
-
// ---------------------------------------------------------------------------
|
|
1118
|
-
// Claude
|
|
1119
|
-
// ---------------------------------------------------------------------------
|
|
1120
|
-
/**
|
|
1121
|
-
* Build a map of Claude sessionId -> user-given label from ~/.claude/sessions/*.json.
|
|
1122
|
-
* Each JSON has shape { pid, sessionId, cwd, startedAt, name?, ... }. The
|
|
1123
|
-
* `name` field only exists if the user ran /rename in that session.
|
|
1124
|
-
* For sessionId collisions (re-resume of the same session), prefer the most
|
|
1125
|
-
* recent startedAt.
|
|
1126
|
-
*/
|
|
1127
758
|
export function buildClaudeLabelMap() {
|
|
1128
759
|
const map = new Map();
|
|
1129
760
|
const dir = path.join(HOME, '.claude', 'sessions');
|
|
@@ -1148,23 +779,16 @@ export function buildClaudeLabelMap() {
|
|
|
1148
779
|
map.set(data.sessionId, { label: name, startedAt });
|
|
1149
780
|
}
|
|
1150
781
|
}
|
|
1151
|
-
catch {
|
|
782
|
+
catch { }
|
|
1152
783
|
}
|
|
1153
784
|
const out = new Map();
|
|
1154
785
|
for (const [sid, { label }] of map)
|
|
1155
786
|
out.set(sid, label);
|
|
1156
787
|
return out;
|
|
1157
788
|
}
|
|
1158
|
-
/** Incrementally re-scan changed Claude session files and upsert into the DB. */
|
|
1159
789
|
async function scanClaudeIncremental(onProgress) {
|
|
1160
|
-
// Rebuild once per pass; readClaudeMeta resolves each transcript against it.
|
|
1161
790
|
claudeAccountIndex(true);
|
|
1162
791
|
const labelMap = buildClaudeLabelMap();
|
|
1163
|
-
// Enumerate every leaf project dir across all Claude roots. The FIRST root
|
|
1164
|
-
// returned by getAgentSessionDirs is the agent's live `~/.claude/projects` —
|
|
1165
|
-
// the only tree Claude appends to in place, so its project dirs are live roots
|
|
1166
|
-
// (every file hot). Version-home + backup roots are immutable: their dirs
|
|
1167
|
-
// short-circuit to a single dir stat when unchanged.
|
|
1168
792
|
const roots = getAgentSessionDirs('claude', 'projects');
|
|
1169
793
|
const leafDirs = [];
|
|
1170
794
|
const seenLeaf = new Set();
|
|
@@ -1187,17 +811,6 @@ async function scanClaudeIncremental(onProgress) {
|
|
|
1187
811
|
}
|
|
1188
812
|
});
|
|
1189
813
|
const { changed: changedAll, allFiles } = collectChangedFilesInLeafDirs(leafDirs, '.jsonl');
|
|
1190
|
-
// Restore the pre-A-2 cross-root precedence: a session id present in multiple
|
|
1191
|
-
// roots is ALWAYS served from its live path, never a frozen backup/version
|
|
1192
|
-
// copy. Pre-A-2, dedup happened at enumeration time via a live-first `seen`
|
|
1193
|
-
// set, so a non-live copy was never even stat'd when a live copy existed. This
|
|
1194
|
-
// PR must not regress that to "whichever copy changed this run wins" — a cold
|
|
1195
|
-
// (unchanged) live copy paired with a freshly-written backup snapshot would
|
|
1196
|
-
// otherwise flip the row's file_path to the backup path.
|
|
1197
|
-
//
|
|
1198
|
-
// allFiles is every transcript file across all roots in live-first order, so
|
|
1199
|
-
// the FIRST occurrence of each session id is its live (or highest-precedence)
|
|
1200
|
-
// path — the durable winner, independent of which copy was flagged changed.
|
|
1201
814
|
const sessionIdOf = (fp) => path.basename(fp).replace('.jsonl', '');
|
|
1202
815
|
const winnerBySession = new Map();
|
|
1203
816
|
for (const { filePath } of allFiles) {
|
|
@@ -1205,18 +818,9 @@ async function scanClaudeIncremental(onProgress) {
|
|
|
1205
818
|
if (!winnerBySession.has(id))
|
|
1206
819
|
winnerBySession.set(id, filePath);
|
|
1207
820
|
}
|
|
1208
|
-
// Keep a changed entry only if it is its session's winner. A changed non-live
|
|
1209
|
-
// copy is dropped whenever a live copy exists anywhere; the winning path is
|
|
1210
|
-
// parsed only when it itself changed (a cold winner needs no re-parse — its DB
|
|
1211
|
-
// row already points at the live path).
|
|
1212
821
|
const changed = changedAll.filter(e => winnerBySession.get(sessionIdOf(e.filePath)) === e.filePath);
|
|
1213
822
|
if (changed.length > 0) {
|
|
1214
823
|
onProgress?.({ agent: 'claude', parsed: 0, total: changed.length });
|
|
1215
|
-
// Bulk-fetch each changed file's prior resumable continuation. A file with a
|
|
1216
|
-
// usable prior state + growth goes incremental (re-parse only the appended
|
|
1217
|
-
// bytes); everything else does a FULL from-offset-0 parse. The decision + the
|
|
1218
|
-
// parse both live in scanClaudeSessionResumable so full and incremental share
|
|
1219
|
-
// one reducer and produce identical rows.
|
|
1220
824
|
const priorStates = getParserStatesForPaths(changed.map(c => c.filePath));
|
|
1221
825
|
const entries = [];
|
|
1222
826
|
const touched = [];
|
|
@@ -1252,24 +856,10 @@ async function scanClaudeIncremental(onProgress) {
|
|
|
1252
856
|
upsertSessionsBatch(entries);
|
|
1253
857
|
recordScans(touched);
|
|
1254
858
|
}
|
|
1255
|
-
// Pick up /rename changes on sessions whose JSONL didn't change.
|
|
1256
|
-
// Only bother for sessions we actually have a Claude row for.
|
|
1257
859
|
if (labelMap.size > 0)
|
|
1258
860
|
syncLabels(labelMap);
|
|
1259
861
|
}
|
|
1260
|
-
/**
|
|
1261
|
-
* Stream-parse a single Claude JSONL file to extract session metadata, resuming
|
|
1262
|
-
* from the persisted continuation when the file merely grew (see
|
|
1263
|
-
* {@link scanClaudeSessionResumable}). Returns the row's meta + FTS content plus
|
|
1264
|
-
* the serialized continuation (parser_state + content_text) to persist for the
|
|
1265
|
-
* next scan.
|
|
1266
|
-
*/
|
|
1267
862
|
async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
1268
|
-
// A prior continuation extracted at an older CONTENT_INDEX_VERSION is missing
|
|
1269
|
-
// whatever the current extractor adds (e.g. assistant-answer text) — resuming
|
|
1270
|
-
// from it would fold only the NEWLY appended lines into that gap, never
|
|
1271
|
-
// backfilling the file's earlier assistant text. Treat it as absent so the
|
|
1272
|
-
// file gets one full from-offset-0 reparse instead.
|
|
1273
863
|
const prior = priorRow?.extractorVersion === CONTENT_INDEX_VERSION ? parsePriorClaudeState(priorRow) : null;
|
|
1274
864
|
const { scan, newState, toolCalls, mode } = await scanClaudeSessionResumable(filePath, prior, scanStamp.fileMtimeMs, scanStamp.fileSize, priorRow?.fileMtimeMs);
|
|
1275
865
|
if (mode === 'incremental')
|
|
@@ -1277,9 +867,6 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1277
867
|
else
|
|
1278
868
|
claudeFullScanCount++;
|
|
1279
869
|
const isTeamOrigin = scan.entrypoint === 'sdk-cli';
|
|
1280
|
-
// Which account produced this transcript. Resolved from the path plus the version
|
|
1281
|
-
// recorded inside the file, so rows under the mutable ~/.claude symlink are
|
|
1282
|
-
// attributed to the version that actually wrote them. See claude-accounts.ts.
|
|
1283
870
|
const acct = resolveClaudeAccount(claudeAccountIndex(), filePath, scan.version, readSessionActorRecord(sessionId)?.accountId);
|
|
1284
871
|
let meta;
|
|
1285
872
|
if (scan.timestamp) {
|
|
@@ -1301,8 +888,6 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1301
888
|
accountOrg: acct.orgName ?? undefined,
|
|
1302
889
|
topic: scan.topic,
|
|
1303
890
|
firstUserMessage: scan.firstUserMessage,
|
|
1304
|
-
// The live sessions metadata file contains only user `/rename` values;
|
|
1305
|
-
// prefer it when present, then fall back to the title parsed from JSONL.
|
|
1306
891
|
label: label || scan.label,
|
|
1307
892
|
messageCount: scan.messageCount,
|
|
1308
893
|
toolCallCount: scan.toolCallCount,
|
|
@@ -1343,8 +928,6 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1343
928
|
accountKey: acct.key,
|
|
1344
929
|
accountOrg: acct.orgName ?? undefined,
|
|
1345
930
|
model: scan.model,
|
|
1346
|
-
// Keep parsed ai-title/custom-title values even when the transcript has
|
|
1347
|
-
// no timestamp and therefore takes this stat-backed fallback path.
|
|
1348
931
|
label: label || scan.label,
|
|
1349
932
|
messageCount: scan.messageCount,
|
|
1350
933
|
toolCallCount: scan.toolCallCount,
|
|
@@ -1378,26 +961,14 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1378
961
|
meta,
|
|
1379
962
|
content: scan.contentText || '',
|
|
1380
963
|
assistantContent: scan.assistantText || '',
|
|
1381
|
-
// Persist the continuation so the next scan of this file can resume from the
|
|
1382
|
-
// offset instead of a full reparse. content_text is the same accumulated user
|
|
1383
|
-
// doc, cached so the resume can hydrate userTexts without re-reading the file.
|
|
1384
964
|
parserState: JSON.stringify(newState),
|
|
1385
965
|
contentText: newState.contentText,
|
|
1386
966
|
toolCalls,
|
|
1387
967
|
toolIndexMode: mode === 'full' ? 'replace' : 'append',
|
|
1388
968
|
};
|
|
1389
969
|
}
|
|
1390
|
-
// ---------------------------------------------------------------------------
|
|
1391
|
-
// Codex account info
|
|
1392
|
-
// ---------------------------------------------------------------------------
|
|
1393
970
|
let cachedCodexAccount;
|
|
1394
|
-
/** Number of times the auth.json JWT was actually base64-decoded. Test seam for the lazy-decode contract. */
|
|
1395
971
|
let codexAccountResolveCount = 0;
|
|
1396
|
-
/**
|
|
1397
|
-
* Base64url-decode a JWT and return its `email` claim, if present. Split out so
|
|
1398
|
-
* the decode is a single, testable step — and so it only runs when someone
|
|
1399
|
-
* actually reads the Codex account (see the lazy resolution below).
|
|
1400
|
-
*/
|
|
1401
972
|
export function decodeJwtEmail(idToken) {
|
|
1402
973
|
const parts = idToken.split('.');
|
|
1403
974
|
if (parts.length < 2)
|
|
@@ -1407,17 +978,9 @@ export function decodeJwtEmail(idToken) {
|
|
|
1407
978
|
return typeof payload.email === 'string' ? payload.email : undefined;
|
|
1408
979
|
}
|
|
1409
980
|
catch {
|
|
1410
|
-
return undefined;
|
|
981
|
+
return undefined;
|
|
1411
982
|
}
|
|
1412
983
|
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Extract the Codex account email from the JWT id_token in auth.json.
|
|
1415
|
-
*
|
|
1416
|
-
* Memoized and resolved LAZILY: the credential-harvesting-shaped JWT decode
|
|
1417
|
-
* (base64-decoding ~/.codex/auth.json) only runs when the account is actually
|
|
1418
|
-
* needed to build a session's metadata — never eagerly during the bulk scan.
|
|
1419
|
-
* A scan with no changed Codex files never touches the auth file.
|
|
1420
|
-
*/
|
|
1421
984
|
function getCodexAccount() {
|
|
1422
985
|
if (cachedCodexAccount !== undefined)
|
|
1423
986
|
return cachedCodexAccount || undefined;
|
|
@@ -1432,7 +995,7 @@ function getCodexAccount() {
|
|
|
1432
995
|
candidates.push(path.join(versionsBase, version, 'home', '.codex', 'auth.json'));
|
|
1433
996
|
}
|
|
1434
997
|
}
|
|
1435
|
-
catch {
|
|
998
|
+
catch { }
|
|
1436
999
|
}
|
|
1437
1000
|
for (const candidate of candidates) {
|
|
1438
1001
|
try {
|
|
@@ -1448,59 +1011,37 @@ function getCodexAccount() {
|
|
|
1448
1011
|
}
|
|
1449
1012
|
}
|
|
1450
1013
|
}
|
|
1451
|
-
catch {
|
|
1014
|
+
catch { }
|
|
1452
1015
|
}
|
|
1453
1016
|
cachedCodexAccount = '';
|
|
1454
1017
|
return undefined;
|
|
1455
1018
|
}
|
|
1456
|
-
/** Test seam: how many times getCodexAccount has actually resolved (decoded) since the last reset. */
|
|
1457
1019
|
export function __codexAccountResolveCountForTest() {
|
|
1458
1020
|
return codexAccountResolveCount;
|
|
1459
1021
|
}
|
|
1460
|
-
/** Test seam: clear the memoized account + resolve counter so laziness can be observed from a clean slate. */
|
|
1461
1022
|
export function __resetCodexAccountCacheForTest() {
|
|
1462
1023
|
cachedCodexAccount = undefined;
|
|
1463
1024
|
codexAccountResolveCount = 0;
|
|
1464
1025
|
}
|
|
1465
|
-
// ---------------------------------------------------------------------------
|
|
1466
|
-
// Codex
|
|
1467
|
-
// ---------------------------------------------------------------------------
|
|
1468
|
-
/** Incrementally re-scan changed Codex session files and upsert into the DB. */
|
|
1469
1026
|
async function scanCodexIncremental(onProgress) {
|
|
1470
|
-
// Lazy: getCodexAccount (the auth.json JWT decode) is only resolved by
|
|
1471
|
-
// readCodexMeta when a changed session actually needs it — never eagerly here,
|
|
1472
|
-
// so a no-op scan (changed.length === 0) never touches the credential file.
|
|
1473
1027
|
const currentVersion = await getCurrentAgentVersion('codex');
|
|
1474
1028
|
const prestat = [];
|
|
1475
1029
|
for (const sessionsDir of getAgentSessionDirs('codex', 'sessions')) {
|
|
1476
|
-
// High limit: the walk stats each file once here; parsing is gated by the
|
|
1477
|
-
// ledger match below, which reuses that stat instead of re-stat'ing.
|
|
1478
1030
|
for (const f of walkForFilesWithStat(sessionsDir, '.jsonl', 100_000)) {
|
|
1479
1031
|
prestat.push({ filePath: f.path, fileMtimeMs: f.mtimeMs, fileSize: f.size });
|
|
1480
1032
|
}
|
|
1481
1033
|
}
|
|
1482
1034
|
const changed = filterChangedEntries(prestat);
|
|
1483
|
-
// Codex keeps human-readable titles (`thread_name`) in `session_index.jsonl`,
|
|
1484
|
-
// which updates independently of the rollout files. Stat each index against the
|
|
1485
|
-
// ledger *without reading it*; only read + re-apply titles when the index (or a
|
|
1486
|
-
// rollout) actually changed. On a fully unchanged scan this collapses to a
|
|
1487
|
-
// couple of stat() calls instead of a full read + a `syncTopics` DB pass.
|
|
1488
1035
|
const titleIndex = diffCodexTitleIndexes();
|
|
1489
1036
|
if (changed.length === 0 && !titleIndex.changed)
|
|
1490
1037
|
return;
|
|
1491
1038
|
const titles = readCodexThreadNames();
|
|
1492
1039
|
if (changed.length === 0) {
|
|
1493
|
-
// No rollouts changed, but the title index did — apply the new titles.
|
|
1494
1040
|
syncTopics(titles);
|
|
1495
1041
|
recordScans(titleIndex.stamps);
|
|
1496
1042
|
return;
|
|
1497
1043
|
}
|
|
1498
1044
|
onProgress?.({ agent: 'codex', parsed: 0, total: changed.length });
|
|
1499
|
-
// Bulk-fetch each changed rollout's prior resumable continuation. A file with
|
|
1500
|
-
// a usable prior state + growth goes incremental (re-parse only the appended
|
|
1501
|
-
// bytes); everything else does a FULL from-offset-0 parse. The decision + the
|
|
1502
|
-
// parse both live in scanCodexSessionResumable so full and incremental share
|
|
1503
|
-
// one reducer and produce identical rows.
|
|
1504
1045
|
const priorStates = getParserStatesForPaths(changed.map(c => c.filePath));
|
|
1505
1046
|
const entries = [];
|
|
1506
1047
|
const touched = [];
|
|
@@ -1512,7 +1053,6 @@ async function scanCodexIncremental(onProgress) {
|
|
|
1512
1053
|
const result = await readCodexMeta(filePath, getCodexAccount, currentVersion, scan, priorRow);
|
|
1513
1054
|
if (result && !seen.has(result.meta.id)) {
|
|
1514
1055
|
seen.add(result.meta.id);
|
|
1515
|
-
// Prefer the Codex-generated title over the first-prompt fallback.
|
|
1516
1056
|
const title = titles.get(result.meta.id);
|
|
1517
1057
|
if (title)
|
|
1518
1058
|
result.meta.topic = title;
|
|
@@ -1539,14 +1079,10 @@ async function scanCodexIncremental(onProgress) {
|
|
|
1539
1079
|
}
|
|
1540
1080
|
upsertSessionsBatch(entries);
|
|
1541
1081
|
recordScans(touched);
|
|
1542
|
-
// Only when the title index changed can an *unchanged* rollout have gained a
|
|
1543
|
-
// title since the last scan; the inline titles applied above already cover
|
|
1544
|
-
// every changed session, so skip the extra sync when the index is untouched.
|
|
1545
1082
|
if (titleIndex.changed)
|
|
1546
1083
|
syncTopics(titles);
|
|
1547
1084
|
recordScans(titleIndex.stamps);
|
|
1548
1085
|
}
|
|
1549
|
-
/** Parse the lines of a Codex `session_index.jsonl` into a session id -> title map. */
|
|
1550
1086
|
export function parseCodexThreadNameIndex(raw) {
|
|
1551
1087
|
const titles = new Map();
|
|
1552
1088
|
for (const line of raw.split('\n')) {
|
|
@@ -1560,20 +1096,10 @@ export function parseCodexThreadNameIndex(raw) {
|
|
|
1560
1096
|
titles.set(id, name);
|
|
1561
1097
|
}
|
|
1562
1098
|
catch {
|
|
1563
|
-
// skip malformed line
|
|
1564
1099
|
}
|
|
1565
1100
|
}
|
|
1566
1101
|
return titles;
|
|
1567
1102
|
}
|
|
1568
|
-
/**
|
|
1569
|
-
* Stat every Codex `session_index.jsonl` and diff it against the scan ledger
|
|
1570
|
-
* *without reading it*. Returns the fresh stamps (persisted only after a
|
|
1571
|
-
* successful title sync) and whether any index changed since the last scan — the
|
|
1572
|
-
* signal that lets a no-op scan skip the file read + `syncTopics` entirely.
|
|
1573
|
-
*
|
|
1574
|
-
* The index path is a sibling of `sessions/` (never inside it), so it is never
|
|
1575
|
-
* walked as a rollout and its ledger row can't collide with a transcript's.
|
|
1576
|
-
*/
|
|
1577
1103
|
function diffCodexTitleIndexes() {
|
|
1578
1104
|
const stamps = [];
|
|
1579
1105
|
let changed = false;
|
|
@@ -1581,7 +1107,7 @@ function diffCodexTitleIndexes() {
|
|
|
1581
1107
|
const indexPath = path.join(path.dirname(sessionsDir), 'session_index.jsonl');
|
|
1582
1108
|
const stat = safeStatSync(indexPath);
|
|
1583
1109
|
if (!stat)
|
|
1584
|
-
continue;
|
|
1110
|
+
continue;
|
|
1585
1111
|
const scan = { fileMtimeMs: Math.floor(stat.mtimeMs), fileSize: stat.size };
|
|
1586
1112
|
const prev = getScanStampByPath(indexPath);
|
|
1587
1113
|
if (!prev || prev.fileMtimeMs !== scan.fileMtimeMs || prev.fileSize !== scan.fileSize) {
|
|
@@ -1591,10 +1117,6 @@ function diffCodexTitleIndexes() {
|
|
|
1591
1117
|
}
|
|
1592
1118
|
return { stamps, changed };
|
|
1593
1119
|
}
|
|
1594
|
-
/**
|
|
1595
|
-
* Read Codex session titles across every Codex home (live + versioned). The
|
|
1596
|
-
* `session_index.jsonl` file sits beside each `sessions/` rollout tree.
|
|
1597
|
-
*/
|
|
1598
1120
|
function readCodexThreadNames() {
|
|
1599
1121
|
const titles = new Map();
|
|
1600
1122
|
for (const sessionsDir of getAgentSessionDirs('codex', 'sessions')) {
|
|
@@ -1604,35 +1126,20 @@ function readCodexThreadNames() {
|
|
|
1604
1126
|
raw = fs.readFileSync(indexPath, 'utf-8');
|
|
1605
1127
|
}
|
|
1606
1128
|
catch {
|
|
1607
|
-
continue;
|
|
1129
|
+
continue;
|
|
1608
1130
|
}
|
|
1609
1131
|
for (const [id, name] of parseCodexThreadNameIndex(raw))
|
|
1610
1132
|
titles.set(id, name);
|
|
1611
1133
|
}
|
|
1612
1134
|
return titles;
|
|
1613
1135
|
}
|
|
1614
|
-
/**
|
|
1615
|
-
* Stream-parse a single Codex JSONL file to extract session metadata.
|
|
1616
|
-
*
|
|
1617
|
-
* `resolveAccount` is a lazy thunk (not a resolved string): the JWT decode it
|
|
1618
|
-
* performs is deferred until we know this file is a real session worth building
|
|
1619
|
-
* metadata for, and only then — never during the file walk / stat phase.
|
|
1620
|
-
*/
|
|
1621
1136
|
export async function readCodexMeta(filePath, resolveAccount, currentVersion, scanStamp, priorRow) {
|
|
1622
|
-
// Resume from the persisted continuation when the file merely grew; otherwise
|
|
1623
|
-
// full-parse from byte 0. Both branches share one reducer, so an append yields
|
|
1624
|
-
// a row identical to a from-scratch reparse. When no stamp is supplied (a
|
|
1625
|
-
// caller outside the live scan path), fall back to a plain full parse with no
|
|
1626
|
-
// continuation to persist.
|
|
1627
1137
|
let scan;
|
|
1628
1138
|
let newState;
|
|
1629
1139
|
let newOffset = 0;
|
|
1630
1140
|
let toolCalls;
|
|
1631
1141
|
let toolIndexMode = 'replace';
|
|
1632
1142
|
if (scanStamp) {
|
|
1633
|
-
// See readClaudeMeta: a prior continuation from an older content extractor
|
|
1634
|
-
// is missing what the current extractor adds, so it must not be resumed
|
|
1635
|
-
// from — treat it as absent and force one full reparse.
|
|
1636
1143
|
const prior = priorRow?.extractorVersion === CONTENT_INDEX_VERSION ? parsePriorCodexState(priorRow) : null;
|
|
1637
1144
|
const result = await scanCodexSessionResumable(filePath, prior, scanStamp.fileMtimeMs, scanStamp.fileSize, priorRow?.fileMtimeMs);
|
|
1638
1145
|
if (result.mode === 'incremental')
|
|
@@ -1656,8 +1163,6 @@ export async function readCodexMeta(filePath, resolveAccount, currentVersion, sc
|
|
|
1656
1163
|
id: sessionId,
|
|
1657
1164
|
shortId: deriveShortId(sessionId),
|
|
1658
1165
|
agent: 'codex',
|
|
1659
|
-
// Codex `session_meta` only carries the start time; use file mtime when
|
|
1660
|
-
// it's newer so long-running sessions register as recently active.
|
|
1661
1166
|
timestamp: pickLatestCodexTimestamp(scan.timestamp, filePath),
|
|
1662
1167
|
lastActivity: scan.lastActivity,
|
|
1663
1168
|
project: cwd ? path.basename(cwd) : undefined,
|
|
@@ -1691,21 +1196,12 @@ export async function readCodexMeta(filePath, resolveAccount, currentVersion, sc
|
|
|
1691
1196
|
meta,
|
|
1692
1197
|
content: scan.contentText || '',
|
|
1693
1198
|
assistantContent: scan.assistantText || '',
|
|
1694
|
-
// Persist the continuation so the next scan of this rollout resumes from the
|
|
1695
|
-
// offset instead of a full reparse; content_text caches the accumulated user
|
|
1696
|
-
// doc for the resume's hydrate. Absent when no stamp was supplied.
|
|
1697
1199
|
parserState: newState ? JSON.stringify(newState) : undefined,
|
|
1698
1200
|
contentText: newState?.contentText,
|
|
1699
1201
|
toolCalls,
|
|
1700
1202
|
toolIndexMode,
|
|
1701
1203
|
};
|
|
1702
1204
|
}
|
|
1703
|
-
/**
|
|
1704
|
-
* Codex writes `session_meta` (with the start timestamp) on the first line of a
|
|
1705
|
-
* rollout and never updates it. For long-running sessions that's stale by
|
|
1706
|
-
* hours — `--since 2h` would drop a session still being actively written.
|
|
1707
|
-
* Compare against the file's mtime and use whichever is newer.
|
|
1708
|
-
*/
|
|
1709
1205
|
function pickLatestCodexTimestamp(metaTimestamp, filePath) {
|
|
1710
1206
|
const fallback = new Date().toISOString();
|
|
1711
1207
|
let mtimeIso = null;
|
|
@@ -1713,24 +1209,12 @@ function pickLatestCodexTimestamp(metaTimestamp, filePath) {
|
|
|
1713
1209
|
mtimeIso = fs.statSync(filePath).mtime.toISOString();
|
|
1714
1210
|
}
|
|
1715
1211
|
catch {
|
|
1716
|
-
/* file vanished between scan and stat */
|
|
1717
1212
|
}
|
|
1718
1213
|
const candidates = [metaTimestamp, mtimeIso].filter((v) => !!v);
|
|
1719
1214
|
if (candidates.length === 0)
|
|
1720
1215
|
return fallback;
|
|
1721
1216
|
return candidates.reduce((best, cur) => (cur > best ? cur : best));
|
|
1722
1217
|
}
|
|
1723
|
-
// ---------------------------------------------------------------------------
|
|
1724
|
-
// Antigravity
|
|
1725
|
-
//
|
|
1726
|
-
// Antigravity stores one SQLite DB per conversation at
|
|
1727
|
-
// ~/.gemini/antigravity-cli/conversations/<trajectory-uuid>.db. The filename
|
|
1728
|
-
// (minus .db) is the canonical session id. Each DB is stat'd against the ledger;
|
|
1729
|
-
// only changed DBs are re-parsed (via parseAntigravity, which shells out to
|
|
1730
|
-
// sqlite3). Tool count doubles as the message count; the toolSummary of the
|
|
1731
|
-
// first tool call becomes the topic, and any run_command's Cwd fills in cwd.
|
|
1732
|
-
// ---------------------------------------------------------------------------
|
|
1733
|
-
/** Incrementally re-scan changed Antigravity conversation DBs and upsert into the DB. */
|
|
1734
1218
|
async function scanAntigravityIncremental(onProgress) {
|
|
1735
1219
|
const currentVersion = await getCurrentAgentVersion('antigravity');
|
|
1736
1220
|
const filePaths = [];
|
|
@@ -1779,13 +1263,11 @@ async function scanAntigravityIncremental(onProgress) {
|
|
|
1779
1263
|
upsertSessionsBatch(entries);
|
|
1780
1264
|
recordScans(touched);
|
|
1781
1265
|
}
|
|
1782
|
-
/** Parse a single Antigravity conversation DB to extract session metadata. */
|
|
1783
1266
|
function readAntigravityMeta(filePath, currentVersion) {
|
|
1784
1267
|
const sessionId = path.basename(filePath).replace(/\.db$/, '');
|
|
1785
1268
|
if (!sessionId)
|
|
1786
1269
|
return null;
|
|
1787
1270
|
const events = parseAntigravity(filePath);
|
|
1788
|
-
// cwd: first run_command carries the working directory in its Cwd arg.
|
|
1789
1271
|
let cwd;
|
|
1790
1272
|
const contentParts = [];
|
|
1791
1273
|
for (const e of events) {
|
|
@@ -1795,7 +1277,6 @@ function readAntigravityMeta(filePath, currentVersion) {
|
|
|
1795
1277
|
contentParts.push(e.content);
|
|
1796
1278
|
}
|
|
1797
1279
|
const normalizedCwd = cwd ? normalizeCwd(cwd) : undefined;
|
|
1798
|
-
// Topic: the first tool's human summary is a decent one-line label.
|
|
1799
1280
|
const topic = events.find(e => e.content)?.content;
|
|
1800
1281
|
const stat = safeStatSync(filePath);
|
|
1801
1282
|
const meta = {
|
|
@@ -1812,73 +1293,15 @@ function readAntigravityMeta(filePath, currentVersion) {
|
|
|
1812
1293
|
};
|
|
1813
1294
|
return { meta, content: contentParts.join('\n'), events };
|
|
1814
1295
|
}
|
|
1815
|
-
// ---------------------------------------------------------------------------
|
|
1816
|
-
// OpenCode
|
|
1817
|
-
// ---------------------------------------------------------------------------
|
|
1818
1296
|
const OPENCODE_DB = path.join(HOME, '.local', 'share', 'opencode', 'opencode.db');
|
|
1819
1297
|
let cachedOpenCodeAccount;
|
|
1820
|
-
/**
|
|
1821
|
-
* The active OpenCode account: the provider ids with a valid credential in
|
|
1822
|
-
* `auth.json`, joined (e.g. `"anthropic+muse-spark"`) — see
|
|
1823
|
-
* `resolveOpenCodeAccountId` in `../agents.js`, the single source of truth
|
|
1824
|
-
* `agents view`/`agents doctor` also read.
|
|
1825
|
-
*
|
|
1826
|
-
* OpenCode's `opencode.db` also carries `account`/`account_state`/
|
|
1827
|
-
* `control_account` tables that look like the obvious source, but on a real,
|
|
1828
|
-
* actively-used install (yosemite-s1, 1.16.0, 35 applied migrations) all
|
|
1829
|
-
* three are permanently empty — no migration populates them and no session
|
|
1830
|
-
* has ever written a row. Querying them (the pre-fix behavior here) always
|
|
1831
|
-
* returned undefined, credential or not; `auth.json` is what OpenCode
|
|
1832
|
-
* actually reads at runtime.
|
|
1833
|
-
*/
|
|
1834
1298
|
function getOpenCodeAccount() {
|
|
1835
1299
|
if (cachedOpenCodeAccount !== undefined)
|
|
1836
1300
|
return cachedOpenCodeAccount || undefined;
|
|
1837
1301
|
cachedOpenCodeAccount = resolveOpenCodeAccountId(HOME) ?? '';
|
|
1838
1302
|
return cachedOpenCodeAccount || undefined;
|
|
1839
1303
|
}
|
|
1840
|
-
/**
|
|
1841
|
-
* The per-session ledger stamp for an OpenCode row: the newest write time across
|
|
1842
|
-
* the session's own row, its messages, and its parts, paired with the total byte
|
|
1843
|
-
* length of that session's message + part payloads.
|
|
1844
|
-
*
|
|
1845
|
-
* OpenCode keeps every session in ONE shared SQLite file, so that file's
|
|
1846
|
-
* mtime/size changes whenever *any* session is written. Stamping each session
|
|
1847
|
-
* with the whole-DB stat therefore invalidated every indexed session on every
|
|
1848
|
-
* scan: up to `LIMIT 1000` sessions were re-emitted into `upsertSessionsBatch`,
|
|
1849
|
-
* and its enrichment step re-opened `opencode.db` once per re-emitted entry via
|
|
1850
|
-
* `parseSession` (RUSH-2210).
|
|
1851
|
-
*
|
|
1852
|
-
* `s.time_updated` alone is NOT that session's change signal, which is the trap
|
|
1853
|
-
* this shape exists to avoid. On a real database, parts land long after the
|
|
1854
|
-
* session row was last touched — e.g. `ses_3955202dfffe…` carried
|
|
1855
|
-
* `time_updated = 1771316403087` with its newest part at `1771331512162`, over
|
|
1856
|
-
* four hours later. So the stamp maxes `time_updated` with the newest message
|
|
1857
|
-
* and part times, and pairs it with a byte total that also moves when an
|
|
1858
|
-
* existing part's `data` is rewritten in place (a streaming turn) without any
|
|
1859
|
-
* new row or timestamp.
|
|
1860
|
-
*
|
|
1861
|
-
* The byte total is a REAL size, not a repurposed counter, because
|
|
1862
|
-
* `sessions.file_size` is read back as bytes elsewhere: `ensureToolIndex` uses
|
|
1863
|
-
* it as the tool-backfill byte budget and `toolCallsForBackfill` as the 16 MiB
|
|
1864
|
-
* in-memory parser cap (`tool-index.ts`). A message/part byte total is the
|
|
1865
|
-
* honest cost of parsing that session — strictly better than the whole-DB size
|
|
1866
|
-
* this column used to hold for every OpenCode row.
|
|
1867
|
-
*
|
|
1868
|
-
* The size is `LENGTH(CAST(data AS BLOB))`, not `LENGTH(data)`: SQLite's
|
|
1869
|
-
* `LENGTH()` on a TEXT column counts CHARACTERS, so a CJK/emoji-heavy transcript
|
|
1870
|
-
* would under-report its real size by up to 4x — and this number is a byte
|
|
1871
|
-
* budget downstream.
|
|
1872
|
-
*
|
|
1873
|
-
* A degenerate row — nothing but zeros/NaN for every time AND no payload at all
|
|
1874
|
-
* — falls back to the whole-DB stat, keeping the pre-RUSH-2210 always-rescan
|
|
1875
|
-
* behavior for exactly those rows rather than pinning them to a stamp that never
|
|
1876
|
-
* changes.
|
|
1877
|
-
*/
|
|
1878
1304
|
function openCodeSessionStamp(times, bytes, dbScan) {
|
|
1879
|
-
// A positive time, not merely a finite one: the SQL COALESCEs the aggregates
|
|
1880
|
-
// to 0, so `Number.isFinite` alone would accept a row that told us nothing and
|
|
1881
|
-
// this branch could never fire.
|
|
1882
1305
|
const known = [times.timeUpdated, times.lastMessageAt, times.lastPartAt, times.timeCreated]
|
|
1883
1306
|
.filter(t => Number.isFinite(t) && t > 0);
|
|
1884
1307
|
const size = (Number.isFinite(bytes.messageBytes) ? bytes.messageBytes : 0)
|
|
@@ -1887,24 +1310,18 @@ function openCodeSessionStamp(times, bytes, dbScan) {
|
|
|
1887
1310
|
return dbScan;
|
|
1888
1311
|
return { fileMtimeMs: known.length === 0 ? 0 : Math.floor(Math.max(...known)), fileSize: size };
|
|
1889
1312
|
}
|
|
1890
|
-
/** Scan OpenCode sessions from its SQLite database when the DB file has changed. */
|
|
1891
1313
|
async function scanOpenCodeIncremental(onProgress) {
|
|
1892
1314
|
if (!fs.existsSync(OPENCODE_DB))
|
|
1893
1315
|
return;
|
|
1894
1316
|
const stat = safeStatSync(OPENCODE_DB);
|
|
1895
1317
|
if (!stat)
|
|
1896
1318
|
return;
|
|
1897
|
-
// OpenCode is one big DB. Its mtime/size is the cheap "did ANYTHING change"
|
|
1898
|
-
// short-circuit for the whole harness — not the per-session stamp, which each
|
|
1899
|
-
// row carries itself (see openCodeSessionStamp, RUSH-2210).
|
|
1900
1319
|
const currentScan = {
|
|
1901
1320
|
fileMtimeMs: Math.floor(stat.mtimeMs),
|
|
1902
1321
|
fileSize: stat.size,
|
|
1903
1322
|
};
|
|
1904
1323
|
const prev = getScanStampByPath(OPENCODE_DB);
|
|
1905
1324
|
const extractorUpgrade = prev != null && prev.extractorVersion !== CONTENT_INDEX_VERSION;
|
|
1906
|
-
// OpenCode has a shared database rather than one transcript file per session,
|
|
1907
|
-
// so this container-level gate must honor extractor-version invalidation too.
|
|
1908
1325
|
if (prev &&
|
|
1909
1326
|
prev.fileMtimeMs === currentScan.fileMtimeMs &&
|
|
1910
1327
|
prev.fileSize === currentScan.fileSize &&
|
|
@@ -1912,24 +1329,13 @@ async function scanOpenCodeIncremental(onProgress) {
|
|
|
1912
1329
|
return;
|
|
1913
1330
|
}
|
|
1914
1331
|
const currentVersion = await getCurrentAgentVersion('opencode');
|
|
1915
|
-
// Read through the node/bun SQLite wrapper (not the `sqlite3` CLI) so this
|
|
1916
|
-
// works on every OS — the CLI is absent on Windows.
|
|
1917
1332
|
let db;
|
|
1918
1333
|
try {
|
|
1919
1334
|
db = new Database(OPENCODE_DB);
|
|
1920
|
-
// OpenCode's `session` schema varies by version: `cost` and `model` are newer
|
|
1921
|
-
// columns. Probe once and select NULL where absent, so an older opencode.db
|
|
1922
|
-
// still scans instead of throwing "no such column" and dropping every session.
|
|
1923
1335
|
const sessionCols = new Set(db.prepare('PRAGMA table_info(session);').all()
|
|
1924
1336
|
.map(c => (typeof c.name === 'string' ? c.name : '')));
|
|
1925
1337
|
const costExpr = sessionCols.has('cost') ? 's.cost' : 'NULL';
|
|
1926
1338
|
const modelExpr = sessionCols.has('model') ? 's.model' : 'NULL';
|
|
1927
|
-
// Every `json_extract` / `json_type` below is guarded by `json_valid`.
|
|
1928
|
-
// SQLite raises "malformed JSON" on a non-JSON value, and that aborts the
|
|
1929
|
-
// WHOLE query — so a single unparseable `part`/`message` row anywhere in the
|
|
1930
|
-
// shared DB would drop EVERY OpenCode session from the index, silently in a
|
|
1931
|
-
// non-TTY run (the handler below only prints when stderr is a TTY). A
|
|
1932
|
-
// poisoned row must cost that row, not the harness.
|
|
1933
1339
|
const query = `
|
|
1934
1340
|
SELECT
|
|
1935
1341
|
s.id AS id,
|
|
@@ -1989,10 +1395,6 @@ async function scanOpenCodeIncremental(onProgress) {
|
|
|
1989
1395
|
`.replace(/\n/g, ' ');
|
|
1990
1396
|
const account = getOpenCodeAccount();
|
|
1991
1397
|
const rows = db.prepare(query).all();
|
|
1992
|
-
// Two passes. First derive each row's identity + per-session stamp, then
|
|
1993
|
-
// bulk-load the prior stamps in ONE query and keep only the rows that
|
|
1994
|
-
// actually changed. Skipped rows never reach upsertSessionsBatch, so they
|
|
1995
|
-
// never pay its per-entry `parseSession` re-open of this same DB.
|
|
1996
1398
|
const asInt = (v) => typeof v === 'number' ? v : parseInt(String(v), 10);
|
|
1997
1399
|
const candidates = rows.flatMap(row => {
|
|
1998
1400
|
const id = typeof row.id === 'string' ? row.id : '';
|
|
@@ -2019,8 +1421,6 @@ async function scanOpenCodeIncremental(onProgress) {
|
|
|
2019
1421
|
|| prevStamp.fileSize !== c.scan.fileSize
|
|
2020
1422
|
|| prevStamp.extractorVersion !== CONTENT_INDEX_VERSION;
|
|
2021
1423
|
});
|
|
2022
|
-
// Role lives on the message row and the genuine user text lives on its
|
|
2023
|
-
// ordered text parts. Poisoned JSON is ignored per row, never per database.
|
|
2024
1424
|
const firstUserStmt = db.prepare(`
|
|
2025
1425
|
SELECT json_extract(p.data, '$.text') AS text
|
|
2026
1426
|
FROM part p
|
|
@@ -2054,18 +1454,11 @@ async function scanOpenCodeIncremental(onProgress) {
|
|
|
2054
1454
|
const hasTokenData = asInt(row.has_token_data) === 1;
|
|
2055
1455
|
const toolCallCount = asInt(row.tool_call_count);
|
|
2056
1456
|
const timestamp = isNaN(timeCreated) ? new Date().toISOString() : new Date(timeCreated).toISOString();
|
|
2057
|
-
// OpenCode is one shared DB, not one file per session — its row carries a
|
|
2058
|
-
// per-session updated time. Set lastActivity explicitly (falling back to
|
|
2059
|
-
// creation, never the whole-DB mtime the ScanStamp would otherwise supply).
|
|
2060
1457
|
const lastActivity = Number.isNaN(timeUpdated) ? timestamp : new Date(timeUpdated).toISOString();
|
|
2061
1458
|
const topic = title || undefined;
|
|
2062
|
-
// Duration is the session-row span; a missing/degenerate pair yields no value
|
|
2063
|
-
// rather than a negative or NaN.
|
|
2064
1459
|
const durationMs = Number.isFinite(timeCreated) && Number.isFinite(timeUpdated) && timeUpdated > timeCreated
|
|
2065
1460
|
? timeUpdated - timeCreated
|
|
2066
1461
|
: undefined;
|
|
2067
|
-
// OpenCode stores the model as JSON (`{"id":"…","providerID":"…"}`); the
|
|
2068
|
-
// index tracks the model id.
|
|
2069
1462
|
let model;
|
|
2070
1463
|
if (typeof row.model === 'string' && row.model.trim()) {
|
|
2071
1464
|
try {
|
|
@@ -2073,13 +1466,9 @@ async function scanOpenCodeIncremental(onProgress) {
|
|
|
2073
1466
|
if (typeof parsed?.id === 'string' && parsed.id.trim())
|
|
2074
1467
|
model = parsed.id;
|
|
2075
1468
|
}
|
|
2076
|
-
catch {
|
|
1469
|
+
catch { }
|
|
2077
1470
|
}
|
|
2078
|
-
// `cost` is a REAL rollup OpenCode maintains on the session row (0 for a
|
|
2079
|
-
// zero-priced provider, which is a real value, not "unknown").
|
|
2080
1471
|
const costUsd = typeof row.cost === 'number' ? row.cost : undefined;
|
|
2081
|
-
// Worktree slug is a pure function of cwd (`.agents/worktrees/<slug>/`),
|
|
2082
|
-
// so it derives for OpenCode exactly as it does for every other harness.
|
|
2083
1472
|
const cwd = directory ? normalizeCwd(directory) : undefined;
|
|
2084
1473
|
const worktreeSlug = detectWorktree(cwd)?.slug;
|
|
2085
1474
|
const meta = {
|
|
@@ -2110,13 +1499,7 @@ async function scanOpenCodeIncremental(onProgress) {
|
|
|
2110
1499
|
entries.push({ meta, content: topic || '', scan });
|
|
2111
1500
|
}
|
|
2112
1501
|
upsertSessionsBatch(entries);
|
|
2113
|
-
// Report what this scan indexed. OpenCode is one SQLite DB rather than N
|
|
2114
|
-
// transcript files, so the whole batch lands as a single progress emit —
|
|
2115
|
-
// enough for the daemon's warm tick to count it (RUSH-2691); without it a
|
|
2116
|
-
// tick whose only changed sessions were OpenCode's reported 0 and logged
|
|
2117
|
-
// nothing, the same silent-success class this tick exists to remove.
|
|
2118
1502
|
onProgress?.({ agent: 'opencode', parsed: entries.length, total: entries.length });
|
|
2119
|
-
// Stamp the OpenCode DB itself so we can short-circuit on the next run.
|
|
2120
1503
|
recordScans([{ filePath: OPENCODE_DB, scan: currentScan }]);
|
|
2121
1504
|
}
|
|
2122
1505
|
catch (err) {
|
|
@@ -2128,22 +1511,12 @@ async function scanOpenCodeIncremental(onProgress) {
|
|
|
2128
1511
|
try {
|
|
2129
1512
|
db?.close();
|
|
2130
1513
|
}
|
|
2131
|
-
catch {
|
|
1514
|
+
catch { }
|
|
2132
1515
|
}
|
|
2133
1516
|
}
|
|
2134
|
-
// ---------------------------------------------------------------------------
|
|
2135
|
-
// OpenClaw
|
|
2136
|
-
// ---------------------------------------------------------------------------
|
|
2137
|
-
/** Scan active OpenClaw channels and cron jobs via the openclaw CLI. */
|
|
2138
1517
|
async function scanOpenClawIncremental(onProgress) {
|
|
2139
|
-
// Check if openclaw is installed — silently skip if not. `which` is POSIX-only
|
|
2140
|
-
// (Windows resolves PATH with `where`), so a bare `which` throws ENOENT on every
|
|
2141
|
-
// Windows run and silently disabled the entire OpenClaw scan there — its sessions
|
|
2142
|
-
// never reached the index (RUSH-2286). hasCommand() probes cross-platform.
|
|
2143
1518
|
if (!hasCommand('openclaw'))
|
|
2144
1519
|
return;
|
|
2145
|
-
// TTL cache: skip subprocess calls if we scanned recently. Stored in the
|
|
2146
|
-
// meta table so we skip even when no channels/cron exist to produce rows.
|
|
2147
1520
|
const db = getDB();
|
|
2148
1521
|
const row = db.prepare(`SELECT value FROM meta WHERE key = 'openclaw_last_scan_ms'`).get();
|
|
2149
1522
|
const lastScanMs = row ? parseInt(row.value, 10) : 0;
|
|
@@ -2155,9 +1528,6 @@ async function scanOpenClawIncremental(onProgress) {
|
|
|
2155
1528
|
const scan = { fileMtimeMs: now, fileSize: 0 };
|
|
2156
1529
|
const entries = [];
|
|
2157
1530
|
try {
|
|
2158
|
-
// On Windows `openclaw` resolves to a .cmd/.ps1 shim that execFile can't launch
|
|
2159
|
-
// directly; execFileShellSpec composes a shell-safe invocation there and is a
|
|
2160
|
-
// no-op passthrough on POSIX (RUSH-2286).
|
|
2161
1531
|
const channelsSpec = execFileShellSpec('openclaw', ['channels', 'status']);
|
|
2162
1532
|
const { stdout: output } = await execFileAsync(channelsSpec.command, channelsSpec.args, {
|
|
2163
1533
|
encoding: 'utf-8',
|
|
@@ -2187,7 +1557,6 @@ async function scanOpenClawIncremental(onProgress) {
|
|
|
2187
1557
|
}
|
|
2188
1558
|
}
|
|
2189
1559
|
catch {
|
|
2190
|
-
/* channels command failed */
|
|
2191
1560
|
}
|
|
2192
1561
|
try {
|
|
2193
1562
|
const cronSpec = execFileShellSpec('openclaw', ['cron', 'list']);
|
|
@@ -2225,21 +1594,10 @@ async function scanOpenClawIncremental(onProgress) {
|
|
|
2225
1594
|
}
|
|
2226
1595
|
}
|
|
2227
1596
|
catch {
|
|
2228
|
-
/* cron command failed */
|
|
2229
1597
|
}
|
|
2230
1598
|
upsertSessionsBatch(entries);
|
|
2231
|
-
// Deliberately NO onProgress emit, unlike every other scanner (RUSH-2691).
|
|
2232
|
-
// This one cannot report a delta: its gate is a 60s TTL (not a store stamp),
|
|
2233
|
-
// `scan` above stamps a fresh `now` on every entry every run, and `entries` is
|
|
2234
|
-
// the CURRENT INVENTORY — running channels plus cron jobs — rebuilt from
|
|
2235
|
-
// scratch each pass. Emitting entries.length would report "how many openclaw
|
|
2236
|
-
// things exist", re-counted every 60s forever, which is a worse lie than
|
|
2237
|
-
// silence: the warm tick's number means "transcripts parsed this scan". Giving
|
|
2238
|
-
// OpenClaw a real per-entry stamp is tracked separately; until then it
|
|
2239
|
-
// contributes 0 and the docblock on IncrementalScanResult says so.
|
|
2240
1599
|
db.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('openclaw_last_scan_ms', ?)`).run(String(Date.now()));
|
|
2241
1600
|
}
|
|
2242
|
-
/** Incrementally re-scan changed Rush session files and upsert into the DB. */
|
|
2243
1601
|
async function scanRushIncremental(onProgress) {
|
|
2244
1602
|
if (!fs.existsSync(RUSH_SESSIONS_DIR))
|
|
2245
1603
|
return;
|
|
@@ -2288,7 +1646,6 @@ async function scanRushIncremental(onProgress) {
|
|
|
2288
1646
|
upsertSessionsBatch(entries);
|
|
2289
1647
|
recordScans(touched);
|
|
2290
1648
|
}
|
|
2291
|
-
/** Stream-parse a single Rush messages.jsonl file to extract session metadata. */
|
|
2292
1649
|
async function readRushMeta(filePath, sessionId) {
|
|
2293
1650
|
const scan = await scanRushSession(filePath);
|
|
2294
1651
|
const stat = safeStatSync(filePath);
|
|
@@ -2308,7 +1665,6 @@ async function readRushMeta(filePath, sessionId) {
|
|
|
2308
1665
|
};
|
|
2309
1666
|
return { meta, content: scan.contentText || '', assistantContent: scan.assistantText || '' };
|
|
2310
1667
|
}
|
|
2311
|
-
/** Stream a Rush messages.jsonl file and extract scan-level metadata. */
|
|
2312
1668
|
async function scanRushSession(filePath) {
|
|
2313
1669
|
const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
2314
1670
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -2376,17 +1732,6 @@ async function scanRushSession(filePath) {
|
|
|
2376
1732
|
assistantText: assistantTexts.length > 0 ? assistantTexts.join('\n') : undefined,
|
|
2377
1733
|
};
|
|
2378
1734
|
}
|
|
2379
|
-
// ---------------------------------------------------------------------------
|
|
2380
|
-
// Hermes
|
|
2381
|
-
//
|
|
2382
|
-
// Hermes sessions live at ~/.hermes/sessions/session_<id>.json (one JSON
|
|
2383
|
-
// file per session). Shape:
|
|
2384
|
-
// { session_id, model, platform, session_start, last_updated,
|
|
2385
|
-
// system_prompt, message_count, messages: [{role, content}, ...] }
|
|
2386
|
-
// request_dump_*.json files in the same dir are per-turn debug dumps — skip.
|
|
2387
|
-
// Hermes is a gateway/API agent, so cwd is left unset.
|
|
2388
|
-
// ---------------------------------------------------------------------------
|
|
2389
|
-
/** Incrementally re-scan changed Hermes session files and upsert into the DB. */
|
|
2390
1735
|
async function scanHermesIncremental(onProgress) {
|
|
2391
1736
|
if (!fs.existsSync(HERMES_SESSIONS_DIR))
|
|
2392
1737
|
return;
|
|
@@ -2431,7 +1776,6 @@ async function scanHermesIncremental(onProgress) {
|
|
|
2431
1776
|
upsertSessionsBatch(scanEntries);
|
|
2432
1777
|
recordScans(touched);
|
|
2433
1778
|
}
|
|
2434
|
-
/** Parse a single Hermes session JSON file to extract session metadata. */
|
|
2435
1779
|
function readHermesMeta(filePath) {
|
|
2436
1780
|
let session;
|
|
2437
1781
|
try {
|
|
@@ -2489,14 +1833,6 @@ function readHermesMeta(filePath) {
|
|
|
2489
1833
|
};
|
|
2490
1834
|
return { meta, content: userTexts.join('\n'), assistantContent: assistantTexts.join('\n') };
|
|
2491
1835
|
}
|
|
2492
|
-
/**
|
|
2493
|
-
* Muse Code stores one session per directory under
|
|
2494
|
-
* ~/.local/share/muse/sessions/YYYY/MM/DD/<uuid>/session.jsonl (event-sourced
|
|
2495
|
-
* JSONL). Walk the tree for session.jsonl files and index each one.
|
|
2496
|
-
*
|
|
2497
|
-
* Also scan version homes (`versions/muse/<v>/home/.local/share/muse/sessions`)
|
|
2498
|
-
* so managed/isolated runs that rewrite HOME still show up in `agents sessions`.
|
|
2499
|
-
*/
|
|
2500
1836
|
function scanMuseIncremental(onProgress) {
|
|
2501
1837
|
const roots = [];
|
|
2502
1838
|
if (fs.existsSync(MUSE_SESSIONS_DIR))
|
|
@@ -2513,7 +1849,6 @@ function scanMuseIncremental(onProgress) {
|
|
|
2513
1849
|
}
|
|
2514
1850
|
}
|
|
2515
1851
|
catch {
|
|
2516
|
-
/* unreadable */
|
|
2517
1852
|
}
|
|
2518
1853
|
}
|
|
2519
1854
|
if (roots.length === 0)
|
|
@@ -2530,7 +1865,6 @@ function scanMuseIncremental(onProgress) {
|
|
|
2530
1865
|
for (const ent of entries) {
|
|
2531
1866
|
const full = path.join(dir, ent.name);
|
|
2532
1867
|
if (ent.isDirectory()) {
|
|
2533
|
-
// Skip subagent child sessions — nested under subagent/<id>/
|
|
2534
1868
|
if (ent.name === 'subagent')
|
|
2535
1869
|
continue;
|
|
2536
1870
|
walk(full);
|
|
@@ -2571,9 +1905,7 @@ function scanMuseIncremental(onProgress) {
|
|
|
2571
1905
|
recordScans(touched);
|
|
2572
1906
|
return Promise.resolve();
|
|
2573
1907
|
}
|
|
2574
|
-
/** Parse a Muse session.jsonl for session metadata + first user prompt text. */
|
|
2575
1908
|
function readMuseMeta(filePath) {
|
|
2576
|
-
// Path shape: .../sessions/YYYY/MM/DD/<uuid>/session.jsonl
|
|
2577
1909
|
const sessionId = path.basename(path.dirname(filePath));
|
|
2578
1910
|
if (!/^[0-9a-f-]{36}$/i.test(sessionId))
|
|
2579
1911
|
return null;
|
|
@@ -2605,7 +1937,6 @@ function readMuseMeta(filePath) {
|
|
|
2605
1937
|
}
|
|
2606
1938
|
if (typeof raw.recorded_at === 'number') {
|
|
2607
1939
|
const v = raw.recorded_at;
|
|
2608
|
-
// Muse logs use microseconds; values < 1e14 are treated as milliseconds.
|
|
2609
1940
|
const ms = v > 1e14 ? Math.floor(v / 1000) : v;
|
|
2610
1941
|
const ts = new Date(ms).toISOString();
|
|
2611
1942
|
if (!firstTs)
|
|
@@ -2662,7 +1993,6 @@ function readMuseMeta(filePath) {
|
|
|
2662
1993
|
};
|
|
2663
1994
|
return { meta, content: userTexts.join('\n'), assistantContent: assistantTexts.join('\n') };
|
|
2664
1995
|
}
|
|
2665
|
-
/** Extract plain text from a Hermes message content field (string or list of parts). */
|
|
2666
1996
|
function extractHermesMessageText(content) {
|
|
2667
1997
|
if (typeof content === 'string')
|
|
2668
1998
|
return content.trim();
|
|
@@ -2679,18 +2009,10 @@ function extractHermesMessageText(content) {
|
|
|
2679
2009
|
.join('\n')
|
|
2680
2010
|
.trim();
|
|
2681
2011
|
}
|
|
2682
|
-
/**
|
|
2683
|
-
* Incrementally re-scan changed Droid (Factory) session files and upsert into
|
|
2684
|
-
* the DB. Droid writes one `<uuid>.jsonl` transcript plus a sibling
|
|
2685
|
-
* `<uuid>.settings.json` (model + token usage) under
|
|
2686
|
-
* `~/.factory/sessions/<encoded-cwd>/`.
|
|
2687
|
-
*/
|
|
2688
2012
|
async function scanDroidIncremental(onProgress) {
|
|
2689
2013
|
const currentVersion = await getCurrentAgentVersion('droid');
|
|
2690
2014
|
const prestat = [];
|
|
2691
2015
|
for (const sessionsDir of getAgentSessionDirs('droid', 'sessions')) {
|
|
2692
|
-
// High limit: the walk stats each file once here; parsing is gated by the
|
|
2693
|
-
// ledger match below, which reuses that stat instead of re-stat'ing.
|
|
2694
2016
|
for (const f of walkForFilesWithStat(sessionsDir, '.jsonl', 100_000)) {
|
|
2695
2017
|
prestat.push({ filePath: f.path, fileMtimeMs: f.mtimeMs, fileSize: f.size });
|
|
2696
2018
|
}
|
|
@@ -2723,19 +2045,14 @@ async function scanDroidIncremental(onProgress) {
|
|
|
2723
2045
|
upsertSessionsBatch(entries);
|
|
2724
2046
|
recordScans(touched);
|
|
2725
2047
|
}
|
|
2726
|
-
/** Stream-parse a single Droid JSONL file (+ sibling settings) into session metadata. */
|
|
2727
2048
|
async function readDroidMeta(filePath, currentVersion) {
|
|
2728
2049
|
const scan = await scanDroidSession(filePath);
|
|
2729
|
-
// The filename is the canonical session id; fall back to the session_start id.
|
|
2730
2050
|
const sessionId = path.basename(filePath).replace(/\.jsonl$/, '') || scan.sessionId || '';
|
|
2731
2051
|
if (!sessionId)
|
|
2732
2052
|
return null;
|
|
2733
|
-
// Token usage and cost live only in the sibling `<uuid>.settings.json`.
|
|
2734
2053
|
const settings = readDroidSettings(filePath.replace(/\.jsonl$/, '.settings.json'));
|
|
2735
2054
|
const model = settings.model || scan.model;
|
|
2736
2055
|
const tokenCount = settings.tokenCount;
|
|
2737
|
-
// Droid records a full split (input / cache-read / cache-write / output) in its
|
|
2738
|
-
// settings sidecar, so both the actual and no-cache cost are derivable (RUSH-2287).
|
|
2739
2056
|
const usageForCost = model && settings.usage
|
|
2740
2057
|
? {
|
|
2741
2058
|
model,
|
|
@@ -2774,7 +2091,6 @@ async function readDroidMeta(filePath, currentVersion) {
|
|
|
2774
2091
|
};
|
|
2775
2092
|
return { meta, content: scan.contentText || '', assistantContent: scan.assistantText || '' };
|
|
2776
2093
|
}
|
|
2777
|
-
/** Read model + token usage from a Droid `<uuid>.settings.json` sidecar. */
|
|
2778
2094
|
function readDroidSettings(settingsPath) {
|
|
2779
2095
|
try {
|
|
2780
2096
|
const data = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
@@ -2800,7 +2116,6 @@ function readDroidSettings(settingsPath) {
|
|
|
2800
2116
|
return {};
|
|
2801
2117
|
}
|
|
2802
2118
|
}
|
|
2803
|
-
/** Stream a Droid JSONL file and extract scan-level metadata (id, cwd, topic, model, duration). */
|
|
2804
2119
|
async function scanDroidSession(filePath) {
|
|
2805
2120
|
const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
2806
2121
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -2831,7 +2146,6 @@ async function scanDroidSession(filePath) {
|
|
|
2831
2146
|
if (parsed.type === 'session_start') {
|
|
2832
2147
|
sessionId = typeof parsed.id === 'string' ? parsed.id : sessionId;
|
|
2833
2148
|
cwd = typeof parsed.cwd === 'string' ? parsed.cwd : cwd;
|
|
2834
|
-
// Droid auto-generates `sessionTitle`; `title` is the raw first prompt.
|
|
2835
2149
|
if (typeof parsed.sessionTitle === 'string' && parsed.sessionTitle.trim()) {
|
|
2836
2150
|
sessionTitle = parsed.sessionTitle.trim();
|
|
2837
2151
|
}
|
|
@@ -2842,7 +2156,6 @@ async function scanDroidSession(filePath) {
|
|
|
2842
2156
|
}
|
|
2843
2157
|
if (parsed.type !== 'message')
|
|
2844
2158
|
continue;
|
|
2845
|
-
// Track duration across every timestamped message.
|
|
2846
2159
|
if (typeof parsed.timestamp === 'string') {
|
|
2847
2160
|
const ms = new Date(parsed.timestamp).getTime();
|
|
2848
2161
|
if (!Number.isNaN(ms)) {
|
|
@@ -2884,8 +2197,6 @@ async function scanDroidSession(filePath) {
|
|
|
2884
2197
|
sessionId,
|
|
2885
2198
|
timestamp,
|
|
2886
2199
|
cwd,
|
|
2887
|
-
// Prefer Droid's auto-title, then the raw first-prompt title, then the
|
|
2888
|
-
// derived first-user-message topic.
|
|
2889
2200
|
topic: sessionTitle || title || firstUserTopic,
|
|
2890
2201
|
firstUserMessage,
|
|
2891
2202
|
model,
|
|
@@ -2896,7 +2207,6 @@ async function scanDroidSession(filePath) {
|
|
|
2896
2207
|
assistantText: assistantTexts.length > 0 ? assistantTexts.join('\n') : undefined,
|
|
2897
2208
|
};
|
|
2898
2209
|
}
|
|
2899
|
-
/** Extract plain text from a Droid message content field (Anthropic-shaped blocks). */
|
|
2900
2210
|
function extractDroidMessageText(content) {
|
|
2901
2211
|
if (typeof content === 'string')
|
|
2902
2212
|
return content.trim();
|
|
@@ -2904,13 +2214,10 @@ function extractDroidMessageText(content) {
|
|
|
2904
2214
|
return '';
|
|
2905
2215
|
return content
|
|
2906
2216
|
.map((part) => (typeof part?.text === 'string' && part.type === 'text' ? part.text : ''))
|
|
2907
|
-
// Droid front-loads injected context (date, skills list) as <system-reminder>
|
|
2908
|
-
// text blocks on the first user turn — drop them so topic/content stay clean.
|
|
2909
2217
|
.filter((text) => text.trim() && !text.trim().startsWith('<system-reminder>'))
|
|
2910
2218
|
.join('\n')
|
|
2911
2219
|
.trim();
|
|
2912
2220
|
}
|
|
2913
|
-
/** Zero-value accumulator for a fresh (from-byte-0) Claude parse. */
|
|
2914
2221
|
export function initClaudeParseState() {
|
|
2915
2222
|
return {
|
|
2916
2223
|
subAgents: 0,
|
|
@@ -2960,13 +2267,8 @@ const DIRECTORY_TOOLS = new Set(['Edit', 'Write', 'edit_file', 'write_file', 'cr
|
|
|
2960
2267
|
function foldDerivedToolState(state, event) {
|
|
2961
2268
|
if (CHECKLIST_TOOLS.has(event.tool ?? ''))
|
|
2962
2269
|
state.checklistEvents.push(event);
|
|
2963
|
-
// #12: skill/slash-command usage, held here instead of re-parsed later — the
|
|
2964
|
-
// same reason checklistEvents is folded incrementally rather than recomputed
|
|
2965
|
-
// from a full re-parse (see session/db.ts's writeResourceUsage doc comment).
|
|
2966
2270
|
if (isSkillInvocation(event))
|
|
2967
2271
|
state.skillEvents.push(event);
|
|
2968
|
-
// Fan-out tallies (RUSH-3091/3095): counted, not held — we need the number,
|
|
2969
|
-
// and a heavy orchestrator session can spawn hundreds of each.
|
|
2970
2272
|
if (state.subAgents !== undefined && isSubAgentTool(event.tool ?? '', event.command ?? '')) {
|
|
2971
2273
|
state.subAgents += 1;
|
|
2972
2274
|
}
|
|
@@ -2990,23 +2292,11 @@ function foldDerivedToolState(state, event) {
|
|
|
2990
2292
|
if (state.recentDirectoriesTouched.length > 10)
|
|
2991
2293
|
state.recentDirectoriesTouched.splice(0, state.recentDirectoriesTouched.length - 10);
|
|
2992
2294
|
}
|
|
2993
|
-
/**
|
|
2994
|
-
* Fold one parsed transcript line into the accumulator. This is the exact loop
|
|
2995
|
-
* body {@link scanClaudeSession} used to run inline — extracted verbatim,
|
|
2996
|
-
* mutating `state.*` in place. `parsed` is the already-`JSON.parse`d line (the
|
|
2997
|
-
* malformed-line skip happens in the caller, as before).
|
|
2998
|
-
*/
|
|
2999
2295
|
export function applyClaudeLine(state, parsed) {
|
|
3000
2296
|
collectClaudeToolCalls(state.toolCollector, parsed);
|
|
3001
|
-
// entrypoint ships on the first envelope event (attachment/user/assistant)
|
|
3002
|
-
// and is the clean structural signal for "was this a team spawn?"
|
|
3003
2297
|
if (!state.entrypoint && typeof parsed.entrypoint === 'string') {
|
|
3004
2298
|
state.entrypoint = parsed.entrypoint;
|
|
3005
2299
|
}
|
|
3006
|
-
// Produced-artifact signals, structurally (independent of the PR gate below):
|
|
3007
|
-
// - a Bash `agents teams create/add` command → the team it spawned
|
|
3008
|
-
// - a Linear create_issue / `gh issue create` tool_use → its result carries
|
|
3009
|
-
// the new ticket ref, read from the matching tool_result.
|
|
3010
2300
|
if (parsed.type === 'assistant' && Array.isArray(parsed.message?.content)) {
|
|
3011
2301
|
for (const b of parsed.message.content) {
|
|
3012
2302
|
if (b?.type !== 'tool_use')
|
|
@@ -3020,8 +2310,6 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3020
2310
|
if (typeof b?.id === 'string' && isTicketCreateTool(b?.name, b?.input?.command)) {
|
|
3021
2311
|
state.pendingTicketTools.add(b.id);
|
|
3022
2312
|
}
|
|
3023
|
-
// ExitPlanMode plan markdown — last one wins so a re-planned session
|
|
3024
|
-
// reports its most recent plan.
|
|
3025
2313
|
if (b?.name === 'ExitPlanMode' && typeof b?.input?.plan === 'string') {
|
|
3026
2314
|
const p = b.input.plan.trim();
|
|
3027
2315
|
if (p)
|
|
@@ -3048,8 +2336,6 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3048
2336
|
state.createdTickets.add(t);
|
|
3049
2337
|
}
|
|
3050
2338
|
}
|
|
3051
|
-
// PR signal, structurally: a Bash tool_use whose command is `gh pr create`
|
|
3052
|
-
// marks intent; the pull URL is then read from a tool_result's output.
|
|
3053
2339
|
if (!state.prUrl) {
|
|
3054
2340
|
if (!state.sawPrCreate && parsed.type === 'assistant' && Array.isArray(parsed.message?.content)) {
|
|
3055
2341
|
for (const b of parsed.message.content) {
|
|
@@ -3073,7 +2359,6 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3073
2359
|
}
|
|
3074
2360
|
}
|
|
3075
2361
|
}
|
|
3076
|
-
// Track duration across every timestamped event, not just the first.
|
|
3077
2362
|
if (typeof parsed.timestamp === 'string') {
|
|
3078
2363
|
const ms = new Date(parsed.timestamp).getTime();
|
|
3079
2364
|
if (!Number.isNaN(ms)) {
|
|
@@ -3110,9 +2395,6 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3110
2395
|
state.userTexts.push(text);
|
|
3111
2396
|
if (!state.topic)
|
|
3112
2397
|
state.topic = extractSessionTopic(text);
|
|
3113
|
-
// #12: the USER typing a slash command — Claude injects a <command-name>
|
|
3114
|
-
// wrapper as the message content (extractClaudeUserText returns it
|
|
3115
|
-
// un-stripped; isLocalCommandMessage only filters bash-echo wrappers).
|
|
3116
2398
|
const slashCommand = extractSlashCommandName(text);
|
|
3117
2399
|
if (slashCommand) {
|
|
3118
2400
|
state.slashCommandEvents.push({
|
|
@@ -3145,8 +2427,6 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3145
2427
|
}
|
|
3146
2428
|
if (typeof usageObj?.output_tokens === 'number')
|
|
3147
2429
|
state.outputTokens += usageObj.output_tokens;
|
|
3148
|
-
// Burn split (RUSH-2287): accumulate the raw directional counts so `agents
|
|
3149
|
-
// output` can report uncached-input / cache-read / cache-write separately.
|
|
3150
2430
|
if (usageObj && typeof usageObj === 'object') {
|
|
3151
2431
|
if (typeof usageObj.input_tokens === 'number')
|
|
3152
2432
|
state.inputTokens += usageObj.input_tokens;
|
|
@@ -3155,8 +2435,6 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3155
2435
|
if (typeof usageObj.cache_creation_input_tokens === 'number')
|
|
3156
2436
|
state.cacheWriteTokens += usageObj.cache_creation_input_tokens;
|
|
3157
2437
|
}
|
|
3158
|
-
// Per-assistant-message cost: each event carries its own model, so we
|
|
3159
|
-
// multiply that event's raw token directions by that model's price.
|
|
3160
2438
|
const model = parsed.message?.model;
|
|
3161
2439
|
if (typeof model === 'string' && model)
|
|
3162
2440
|
state.model = model;
|
|
@@ -3171,26 +2449,15 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3171
2449
|
const eventCost = costOfUsage(usageForCost);
|
|
3172
2450
|
if (eventCost > 0) {
|
|
3173
2451
|
state.costUsd += eventCost;
|
|
3174
|
-
// No-cache scenario: reprice this same event's cache tokens at the input rate.
|
|
3175
2452
|
state.costUsdNoCache += costOfUsageNoCache(usageForCost);
|
|
3176
2453
|
state.sawCost = true;
|
|
3177
2454
|
}
|
|
3178
2455
|
}
|
|
3179
2456
|
}
|
|
3180
|
-
|
|
3181
|
-
* Build the {@link ClaudeSessionScan} return object from an accumulator. This is
|
|
3182
|
-
* the exact return-building {@link scanClaudeSession} used to run inline.
|
|
3183
|
-
*/
|
|
3184
|
-
export function finalizeClaudeScan(state) {
|
|
2457
|
+
export function finalizeClaudeScan(state, sessionFile) {
|
|
3185
2458
|
const durationMs = state.firstTsMs !== undefined && state.lastTsMs !== undefined && state.lastTsMs > state.firstTsMs
|
|
3186
2459
|
? state.lastTsMs - state.firstTsMs
|
|
3187
2460
|
: undefined;
|
|
3188
|
-
// A topic is the first meaningful prompt. Harness-owned names travel in the
|
|
3189
|
-
// separate label field so consumers can replace an early topic once Claude's
|
|
3190
|
-
// generated title (or a later `/rename`) arrives.
|
|
3191
|
-
// Generated titles (Claude `ai-title`, Cursor `chatMeta.title`) go through
|
|
3192
|
-
// one shared cleaner so a skill-preamble echo collapses to `/<skill>`. A
|
|
3193
|
-
// `/rename` (`custom-title`) is the user's own words and is never rewritten.
|
|
3194
2461
|
const label = state.customTitle || cleanGeneratedSessionLabel(state.aiTitle);
|
|
3195
2462
|
const worktree = detectWorktree(state.cwd, state.gitBranch);
|
|
3196
2463
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
@@ -3228,11 +2495,10 @@ export function finalizeClaudeScan(state) {
|
|
|
3228
2495
|
recentDirectoriesTouched: state.recentDirectoriesTouched.length ? state.recentDirectoriesTouched : undefined,
|
|
3229
2496
|
skillsUsed: state.skillEvents.length ? extractSkills(state.skillEvents) : undefined,
|
|
3230
2497
|
slashCommandsUsed: state.slashCommandEvents.length ? extractSlashCommands(state.slashCommandEvents) : undefined,
|
|
3231
|
-
subAgentCount: state.subAgents,
|
|
2498
|
+
subAgentCount: resolvedSubAgentCount(sessionFile ? claudeSubagentFiles(sessionFile) : undefined, state.subAgents),
|
|
3232
2499
|
backgroundShellCount: state.backgroundShells,
|
|
3233
2500
|
};
|
|
3234
2501
|
}
|
|
3235
|
-
/** Stream a Claude JSONL file and extract scan-level metadata (timestamp, cwd, topic, tokens). */
|
|
3236
2502
|
export async function scanClaudeSession(filePath) {
|
|
3237
2503
|
const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
3238
2504
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -3255,15 +2521,9 @@ export async function scanClaudeSession(filePath) {
|
|
|
3255
2521
|
rl.close();
|
|
3256
2522
|
stream.destroy();
|
|
3257
2523
|
}
|
|
3258
|
-
return finalizeClaudeScan(state);
|
|
2524
|
+
return finalizeClaudeScan(state, filePath);
|
|
3259
2525
|
}
|
|
3260
|
-
/** Cap on the FIFO window of recent assistant ids persisted in the continuation. */
|
|
3261
2526
|
const SEEN_IDS_RECENT_CAP = 256;
|
|
3262
|
-
/**
|
|
3263
|
-
* Snapshot a live {@link ClaudeParseState} into its serializable form at
|
|
3264
|
-
* `offset` bytes consumed. Round-trips through {@link hydrateClaudeParseState}
|
|
3265
|
-
* so incremental replay equals a full parse.
|
|
3266
|
-
*/
|
|
3267
2527
|
export function serializeClaudeParserState(state, offset, jsonlDroppingOversizedLine = false) {
|
|
3268
2528
|
const allIds = [...state.seenAssistantIds];
|
|
3269
2529
|
const seenIdsRecent = allIds.length > SEEN_IDS_RECENT_CAP
|
|
@@ -3308,10 +2568,6 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3308
2568
|
pendingTicketTools: [...state.pendingTicketTools],
|
|
3309
2569
|
createdTickets: [...state.createdTickets],
|
|
3310
2570
|
spawnedTeam: state.spawnedTeam,
|
|
3311
|
-
// ticketId is derived at finalize time; persist it (and content_text) so a
|
|
3312
|
-
// consumer (B-2) can rebuild the row + FTS doc on append without re-reading
|
|
3313
|
-
// the whole file. worktreeSlug is re-derived from cwd/gitBranch, so it need
|
|
3314
|
-
// not be persisted.
|
|
3315
2571
|
ticketId: ticket?.id,
|
|
3316
2572
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
3317
2573
|
assistantContentText: state.assistantTexts.length > 0 ? state.assistantTexts.join('\n') : undefined,
|
|
@@ -3322,26 +2578,8 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3322
2578
|
slashCommandEvents: state.slashCommandEvents,
|
|
3323
2579
|
};
|
|
3324
2580
|
}
|
|
3325
|
-
/**
|
|
3326
|
-
* Rebuild a live {@link ClaudeParseState} from a persisted continuation so that
|
|
3327
|
-
* applying the appended lines yields the same accumulator a full parse would.
|
|
3328
|
-
*
|
|
3329
|
-
* `seenAssistantIds` is rehydrated from the recent-id FIFO window, then padded
|
|
3330
|
-
* with unique sentinel entries so its `.size` matches the true prior count
|
|
3331
|
-
* (`seenIdsSize`) — the fallback id `${ts}:${size}` must line up with the full
|
|
3332
|
-
* parse even when the window dropped older ids. Padding sentinels can never
|
|
3333
|
-
* collide with a real logical id (real ids are message ids/uuids or
|
|
3334
|
-
* `${ts}:${n}`; the sentinel prefix is not JSON-line-derived).
|
|
3335
|
-
*
|
|
3336
|
-
* `userTexts` is rehydrated as a single joined blob from `contentText`: only
|
|
3337
|
-
* `userTexts.join('\n')` (detectTicket + contentText) and `userTexts.length > 0`
|
|
3338
|
-
* are ever read downstream, and both are preserved by a one-element array
|
|
3339
|
-
* holding the joined content.
|
|
3340
|
-
*/
|
|
3341
2581
|
export function hydrateClaudeParseState(prior) {
|
|
3342
2582
|
const seen = new Set(prior.seenIdsRecent);
|
|
3343
|
-
// Pad to the true prior size so `seenAssistantIds.size` (which feeds the
|
|
3344
|
-
// fallback logical id) is exact even when older ids fell out of the window.
|
|
3345
2583
|
let pad = 0;
|
|
3346
2584
|
while (seen.size < prior.seenIdsSize) {
|
|
3347
2585
|
seen.add(`pad:${pad++}`);
|
|
@@ -3391,35 +2629,13 @@ export function hydrateClaudeParseState(prior) {
|
|
|
3391
2629
|
slashCommandEvents: prior.slashCommandEvents ?? [],
|
|
3392
2630
|
};
|
|
3393
2631
|
}
|
|
3394
|
-
/**
|
|
3395
|
-
* Resume a Claude parse from `fromOffset` bytes into the file, folding only the
|
|
3396
|
-
* newly-appended lines into `prior`. Returns the finalized scan, the next
|
|
3397
|
-
* serialized continuation, and the byte offset to resume from next time —
|
|
3398
|
-
* `newOffset` stops at the last `'\n'` seen so a half-written trailing record is
|
|
3399
|
-
* re-read (not lost) on the next append.
|
|
3400
|
-
*
|
|
3401
|
-
* NOT wired into the live scan path yet (that is B-2); {@link scanClaudeSession}
|
|
3402
|
-
* remains the only caller-facing entry point. This exists so the parity harness
|
|
3403
|
-
* can prove full === hydrate(state@k) + apply(k+1..n).
|
|
3404
|
-
*/
|
|
3405
2632
|
export async function scanClaudeSessionIncremental(filePath, fromOffset, prior) {
|
|
3406
2633
|
const state = hydrateClaudeParseState(prior);
|
|
3407
|
-
// Read the appended byte range and apply ONLY newline-terminated lines. The
|
|
3408
|
-
// applied lines and `newOffset` MUST stay consistent: readline (like the full
|
|
3409
|
-
// parse) would emit a trailing UNTERMINATED final line at EOF, but `newOffset`
|
|
3410
|
-
// stops before it — so the next pass, after that record's '\n' is flushed,
|
|
3411
|
-
// would re-read and re-apply the same line and double-count it (user events
|
|
3412
|
-
// have no dedup, unlike assistant `seenAssistantIds`). A record written
|
|
3413
|
-
// non-atomically — bytes first, then '\n' in a second write — is exactly this
|
|
3414
|
-
// case. So we slice at the last '\n' ourselves: everything up to and including
|
|
3415
|
-
// it is a run of complete lines we apply and commit. An ordinary tail after
|
|
3416
|
-
// it is deferred until its '\n' lands; after a tail exceeds 1 MiB, its offset
|
|
3417
|
-
// advances with a persisted discard-until-newline bit.
|
|
3418
2634
|
const append = await applyJsonlAppend(filePath, fromOffset, prior.jsonlDroppingOversizedLine === true, (parsed) => applyClaudeLine(state, parsed));
|
|
3419
2635
|
if (append.skippedOversizedLine)
|
|
3420
2636
|
state.toolCollector.recordIndexLimit();
|
|
3421
2637
|
const newOffset = fromOffset + append.consumedBytes;
|
|
3422
|
-
const scan = finalizeClaudeScan(state);
|
|
2638
|
+
const scan = finalizeClaudeScan(state, filePath);
|
|
3423
2639
|
const toolCalls = state.toolCollector.drainChanged();
|
|
3424
2640
|
return {
|
|
3425
2641
|
scan,
|
|
@@ -3428,39 +2644,10 @@ export async function scanClaudeSessionIncremental(filePath, fromOffset, prior)
|
|
|
3428
2644
|
toolCalls,
|
|
3429
2645
|
};
|
|
3430
2646
|
}
|
|
3431
|
-
/** Serialized zero-value continuation: a fresh accumulator at offset 0, used to drive a FULL parse from the start through the same resumable path. */
|
|
3432
2647
|
function freshClaudeParserState() {
|
|
3433
2648
|
return serializeClaudeParserState(initClaudeParseState(), 0);
|
|
3434
2649
|
}
|
|
3435
|
-
/**
|
|
3436
|
-
* Decide full-vs-incremental for one Claude file and parse it uniformly, always
|
|
3437
|
-
* returning a finalized scan plus the continuation to persist. Both branches run
|
|
3438
|
-
* through the SAME reducer (via {@link scanClaudeSessionIncremental}), so the row
|
|
3439
|
-
* an append produces is identical to a from-scratch full reparse by construction
|
|
3440
|
-
* (the B-1 parity harness proves this at the function level).
|
|
3441
|
-
*
|
|
3442
|
-
* INCREMENTAL when a prior continuation exists AND the file grew past the
|
|
3443
|
-
* persisted offset AND its mtime did not go backwards — an in-place append.
|
|
3444
|
-
* FULL (from byte 0, fresh state) otherwise: cold start (no prior), truncation /
|
|
3445
|
-
* rewrite (size shrank to at or below the offset), or a clock rewind / restore
|
|
3446
|
-
* (mtime older than the last parse). A FULL parse still produces a continuation,
|
|
3447
|
-
* so the file's very next append can go incremental.
|
|
3448
|
-
*
|
|
3449
|
-
* `mode` is returned so the caller (and tests) can confirm which branch ran.
|
|
3450
|
-
*/
|
|
3451
2650
|
export async function scanClaudeSessionResumable(filePath, prior, currentFileMtimeMs, currentFileSize, priorFileMtimeMs) {
|
|
3452
|
-
// File size + mtime cannot distinguish an APPEND from an in-place rewrite or a
|
|
3453
|
-
// restore that dropped DIFFERENT, larger content at the same path: both grow
|
|
3454
|
-
// the file and move mtime forward. Resuming from the stored offset across that
|
|
3455
|
-
// boundary would fold the new file's bytes into an accumulator hydrated from
|
|
3456
|
-
// the OLD session, so the persisted row silently diverges from a full reparse.
|
|
3457
|
-
// So the metadata gate below only makes a file ELIGIBLE; before trusting the
|
|
3458
|
-
// offset we re-read the transcript's first user/assistant timestamp and require
|
|
3459
|
-
// it to still match the prior continuation's. An append keeps that identity
|
|
3460
|
-
// byte-for-byte; a rewrite/restore of a different session changes it. A
|
|
3461
|
-
// mismatch — or an identity we cannot derive — falls back to a FULL parse,
|
|
3462
|
-
// which is always correct. (A shrink is already handled: currentFileSize is not
|
|
3463
|
-
// > prior.offset, so it takes the FULL branch.)
|
|
3464
2651
|
let canIncrement = false;
|
|
3465
2652
|
if (prior !== null &&
|
|
3466
2653
|
currentFileSize > prior.offset &&
|
|
@@ -3475,14 +2662,6 @@ export async function scanClaudeSessionResumable(filePath, prior, currentFileMti
|
|
|
3475
2662
|
const result = await scanClaudeSessionIncremental(filePath, 0, freshClaudeParserState());
|
|
3476
2663
|
return { ...result, mode: 'full' };
|
|
3477
2664
|
}
|
|
3478
|
-
/**
|
|
3479
|
-
* Cheaply derive a Claude transcript's session identity — the first
|
|
3480
|
-
* user/assistant event `timestamp` — by streaming only the START of the file
|
|
3481
|
-
* (at most `maxBytes`) and stopping at the first such event. Used by
|
|
3482
|
-
* {@link scanClaudeSessionResumable} to confirm a grown file is still the SAME
|
|
3483
|
-
* session before resuming from a stored parse offset. Returns undefined when no
|
|
3484
|
-
* user/assistant event appears within the budget, which forces a FULL parse.
|
|
3485
|
-
*/
|
|
3486
2665
|
async function claudeSessionIdentityAt(filePath, maxBytes = 1_048_576) {
|
|
3487
2666
|
const state = initClaudeParseState();
|
|
3488
2667
|
const stream = fs.createReadStream(filePath, { start: 0, end: maxBytes - 1, encoding: 'utf-8' });
|
|
@@ -3509,12 +2688,6 @@ async function claudeSessionIdentityAt(filePath, maxBytes = 1_048_576) {
|
|
|
3509
2688
|
}
|
|
3510
2689
|
return state.timestamp;
|
|
3511
2690
|
}
|
|
3512
|
-
/**
|
|
3513
|
-
* Parse the prior continuation blob for a changed file into a usable
|
|
3514
|
-
* {@link ClaudeParserState}, or null when there is none / it is unusable. A blob
|
|
3515
|
-
* from a different serialization version is treated as absent so the file falls
|
|
3516
|
-
* back to a clean FULL parse rather than resuming against a stale shape.
|
|
3517
|
-
*/
|
|
3518
2691
|
function parsePriorClaudeState(row) {
|
|
3519
2692
|
if (!row?.parserState)
|
|
3520
2693
|
return null;
|
|
@@ -3528,20 +2701,15 @@ function parsePriorClaudeState(row) {
|
|
|
3528
2701
|
return null;
|
|
3529
2702
|
}
|
|
3530
2703
|
}
|
|
3531
|
-
/** Test seam: how many times the incremental (append-resume) branch was taken since the last reset. */
|
|
3532
2704
|
let claudeIncrementalScanCount = 0;
|
|
3533
|
-
/** Test seam: how many times a full (from-offset-0) Claude parse ran since the last reset. */
|
|
3534
2705
|
let claudeFullScanCount = 0;
|
|
3535
|
-
/** Test seam: read the (incremental, full) Claude parse counters. */
|
|
3536
2706
|
export function __claudeScanBranchCountsForTest() {
|
|
3537
2707
|
return { incremental: claudeIncrementalScanCount, full: claudeFullScanCount };
|
|
3538
2708
|
}
|
|
3539
|
-
/** Test seam: reset the Claude parse-branch counters to observe a scan from a clean slate. */
|
|
3540
2709
|
export function __resetClaudeScanBranchCountsForTest() {
|
|
3541
2710
|
claudeIncrementalScanCount = 0;
|
|
3542
2711
|
claudeFullScanCount = 0;
|
|
3543
2712
|
}
|
|
3544
|
-
/** Zero-value accumulator for a fresh (from-byte-0) Codex parse. */
|
|
3545
2713
|
export function initCodexParseState() {
|
|
3546
2714
|
return {
|
|
3547
2715
|
sessionId: undefined,
|
|
@@ -3572,16 +2740,8 @@ export function initCodexParseState() {
|
|
|
3572
2740
|
slashCommandEvents: [],
|
|
3573
2741
|
};
|
|
3574
2742
|
}
|
|
3575
|
-
/**
|
|
3576
|
-
* Fold one parsed Codex line into the accumulator — the exact loop body
|
|
3577
|
-
* {@link scanCodexSession} used to run inline, extracted verbatim and mutating
|
|
3578
|
-
* `state.*` in place. `parsed` is the already-`JSON.parse`d line (the
|
|
3579
|
-
* malformed-line skip happens in the caller, as before).
|
|
3580
|
-
*/
|
|
3581
2743
|
function applyCodexLine(state, parsed) {
|
|
3582
2744
|
collectCodexToolCalls(state.toolCollector, parsed);
|
|
3583
|
-
// PR signal, structurally: a Codex `function_call` whose command is
|
|
3584
|
-
// `gh pr create`, then the pull URL from a `function_call_output`.
|
|
3585
2745
|
if (parsed.type === 'response_item') {
|
|
3586
2746
|
const p = parsed.payload || {};
|
|
3587
2747
|
if (p.type === 'function_call') {
|
|
@@ -3591,7 +2751,7 @@ function applyCodexLine(state, parsed) {
|
|
|
3591
2751
|
args = typeof p.arguments === 'string' ? JSON.parse(p.arguments) : (p.arguments || {});
|
|
3592
2752
|
cmd = String(args.command || args.cmd || '');
|
|
3593
2753
|
}
|
|
3594
|
-
catch {
|
|
2754
|
+
catch { }
|
|
3595
2755
|
foldDerivedToolState(state, {
|
|
3596
2756
|
type: 'tool_use', agent: 'codex', timestamp: parsed.timestamp || '', tool: p.name, args,
|
|
3597
2757
|
path: args.file_path || args.path, command: cmd || undefined,
|
|
@@ -3623,7 +2783,6 @@ function applyCodexLine(state, parsed) {
|
|
|
3623
2783
|
}
|
|
3624
2784
|
}
|
|
3625
2785
|
}
|
|
3626
|
-
// Track duration across every timestamped event.
|
|
3627
2786
|
if (typeof parsed.timestamp === 'string') {
|
|
3628
2787
|
const ms = new Date(parsed.timestamp).getTime();
|
|
3629
2788
|
if (!Number.isNaN(ms)) {
|
|
@@ -3635,8 +2794,6 @@ function applyCodexLine(state, parsed) {
|
|
|
3635
2794
|
}
|
|
3636
2795
|
if (parsed.type === 'session_meta') {
|
|
3637
2796
|
const payload = parsed.payload || {};
|
|
3638
|
-
// Native forks prepend child ownership before inherited parent metadata.
|
|
3639
|
-
// The first valid identity owns this file in both full and resumed scans.
|
|
3640
2797
|
if (state.sessionId || typeof payload.id !== 'string' || !payload.id.trim())
|
|
3641
2798
|
return;
|
|
3642
2799
|
state.sessionId = payload.id;
|
|
@@ -3647,10 +2804,6 @@ function applyCodexLine(state, parsed) {
|
|
|
3647
2804
|
state.model = payload.model || state.model;
|
|
3648
2805
|
return;
|
|
3649
2806
|
}
|
|
3650
|
-
// Codex rollouts put per-turn metadata (including the model) on
|
|
3651
|
-
// `turn_context` events. Use them as a fallback when session_meta itself
|
|
3652
|
-
// does not carry the field, otherwise `agents sessions` shows blank model
|
|
3653
|
-
// info for Codex.
|
|
3654
2807
|
if (parsed.type === 'turn_context') {
|
|
3655
2808
|
const payload = parsed.payload || {};
|
|
3656
2809
|
if (!state.model && typeof payload.model === 'string')
|
|
@@ -3673,9 +2826,6 @@ function applyCodexLine(state, parsed) {
|
|
|
3673
2826
|
if (!genuine)
|
|
3674
2827
|
return;
|
|
3675
2828
|
state.userTexts.push(genuine);
|
|
3676
|
-
// Codex records developer instructions as response_item messages too.
|
|
3677
|
-
// They remain searchable for compatibility, but only an actual user role
|
|
3678
|
-
// may become the session's first request.
|
|
3679
2829
|
if (payloadRole === 'user' && !state.firstUserMessage)
|
|
3680
2830
|
state.firstUserMessage = genuine;
|
|
3681
2831
|
if (!state.topic)
|
|
@@ -3691,24 +2841,13 @@ function applyCodexLine(state, parsed) {
|
|
|
3691
2841
|
const total = getCodexTokenCount(totalUsage);
|
|
3692
2842
|
if (total !== null)
|
|
3693
2843
|
state.tokenCount = total;
|
|
3694
|
-
// token_count is cumulative — keep the latest snapshot and price it once
|
|
3695
|
-
// after the stream, so we don't double-count across intermediate events.
|
|
3696
2844
|
if (totalUsage && typeof totalUsage === 'object')
|
|
3697
2845
|
state.lastTotalTokenUsage = totalUsage;
|
|
3698
|
-
// Codex also stamps the model on the rate_limits/token_count payload on
|
|
3699
|
-
// some versions; prefer session_meta but fall back to it.
|
|
3700
2846
|
if (!state.model && typeof parsed.payload.info?.model === 'string')
|
|
3701
2847
|
state.model = parsed.payload.info.model;
|
|
3702
2848
|
}
|
|
3703
2849
|
}
|
|
3704
|
-
/**
|
|
3705
|
-
* Build the {@link CodexSessionScan} return object from an accumulator — the
|
|
3706
|
-
* exact return-building {@link scanCodexSession} used to run inline.
|
|
3707
|
-
*/
|
|
3708
2850
|
function finalizeCodexScan(state) {
|
|
3709
|
-
// Codex reports one cumulative snapshot: uncached input, cached (cache-read)
|
|
3710
|
-
// input, and output+reasoning. It has no cache-write bucket. Derive the burn
|
|
3711
|
-
// split and both costs (actual + no-cache) from that final snapshot (RUSH-2287).
|
|
3712
2851
|
const snap = state.lastTotalTokenUsage;
|
|
3713
2852
|
const outputTokens = snap
|
|
3714
2853
|
? (snap.output_tokens ?? 0) + (snap.reasoning_output_tokens ?? 0)
|
|
@@ -3721,7 +2860,6 @@ function finalizeCodexScan(state) {
|
|
|
3721
2860
|
inputTokens = typeof snap.input_tokens === 'number' ? snap.input_tokens : undefined;
|
|
3722
2861
|
cacheReadTokens = typeof snap.cached_input_tokens === 'number' ? snap.cached_input_tokens : undefined;
|
|
3723
2862
|
}
|
|
3724
|
-
// Price the final cumulative token snapshot once, against the session model.
|
|
3725
2863
|
if (state.model && snap) {
|
|
3726
2864
|
const usage = {
|
|
3727
2865
|
model: state.model,
|
|
@@ -3771,7 +2909,6 @@ function finalizeCodexScan(state) {
|
|
|
3771
2909
|
recentDirectoriesTouched: state.recentDirectoriesTouched.length ? state.recentDirectoriesTouched : undefined,
|
|
3772
2910
|
};
|
|
3773
2911
|
}
|
|
3774
|
-
/** Stream a Codex JSONL file and extract scan-level metadata (session ID, cwd, topic, tokens). */
|
|
3775
2912
|
async function scanCodexSession(filePath) {
|
|
3776
2913
|
const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
3777
2914
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -3796,11 +2933,6 @@ async function scanCodexSession(filePath) {
|
|
|
3796
2933
|
}
|
|
3797
2934
|
return finalizeCodexScan(state);
|
|
3798
2935
|
}
|
|
3799
|
-
/**
|
|
3800
|
-
* Snapshot a live {@link CodexParseState} into its serializable form at `offset`
|
|
3801
|
-
* bytes consumed. Round-trips through {@link hydrateCodexParseState} so
|
|
3802
|
-
* incremental replay equals a full parse.
|
|
3803
|
-
*/
|
|
3804
2936
|
export function serializeCodexParserState(state, offset, jsonlDroppingOversizedLine = false) {
|
|
3805
2937
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3806
2938
|
return {
|
|
@@ -3826,10 +2958,6 @@ export function serializeCodexParserState(state, offset, jsonlDroppingOversizedL
|
|
|
3826
2958
|
pendingTicketTools: [...state.pendingTicketTools],
|
|
3827
2959
|
createdTickets: [...state.createdTickets],
|
|
3828
2960
|
spawnedTeam: state.spawnedTeam,
|
|
3829
|
-
// ticketId is derived at finalize time; persist it (and content_text) so a
|
|
3830
|
-
// consumer can rebuild the row + FTS doc on append without re-reading the
|
|
3831
|
-
// whole file. worktreeSlug is re-derived from cwd/gitBranch, so it need not
|
|
3832
|
-
// be persisted.
|
|
3833
2961
|
ticketId: ticket?.id,
|
|
3834
2962
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
3835
2963
|
assistantContentText: state.assistantTexts.length > 0 ? state.assistantTexts.join('\n') : undefined,
|
|
@@ -3838,16 +2966,6 @@ export function serializeCodexParserState(state, offset, jsonlDroppingOversizedL
|
|
|
3838
2966
|
toolCalls: state.toolCollector.snapshot(),
|
|
3839
2967
|
};
|
|
3840
2968
|
}
|
|
3841
|
-
/**
|
|
3842
|
-
* Rebuild a live {@link CodexParseState} from a persisted continuation so that
|
|
3843
|
-
* applying the appended lines yields the same accumulator a full parse would.
|
|
3844
|
-
*
|
|
3845
|
-
* `userTexts` is rehydrated as a single joined blob from `contentText`: only
|
|
3846
|
-
* `userTexts.join('\n')` (detectTicket + contentText) and `userTexts.length > 0`
|
|
3847
|
-
* are ever read downstream, and both are preserved by a one-element array
|
|
3848
|
-
* holding the joined content. Topic is first-wins and already persisted, so a
|
|
3849
|
-
* collapsed userTexts never changes it.
|
|
3850
|
-
*/
|
|
3851
2969
|
function hydrateCodexParseState(prior) {
|
|
3852
2970
|
return {
|
|
3853
2971
|
sessionId: prior.sessionId,
|
|
@@ -3876,26 +2994,10 @@ function hydrateCodexParseState(prior) {
|
|
|
3876
2994
|
checklistEvents: prior.checklistEvents ?? [],
|
|
3877
2995
|
recentDirectoriesTouched: prior.recentDirectoriesTouched ?? [],
|
|
3878
2996
|
toolCollector: new ToolCallCollector(prior.toolCalls),
|
|
3879
|
-
// Not persisted in CodexParserState (always empty for Codex today — see
|
|
3880
|
-
// CodexParseState.skillEvents) — a resume starts fresh rather than
|
|
3881
|
-
// round-tripping an always-empty array through the continuation blob.
|
|
3882
2997
|
skillEvents: [],
|
|
3883
2998
|
slashCommandEvents: [],
|
|
3884
2999
|
};
|
|
3885
3000
|
}
|
|
3886
|
-
/**
|
|
3887
|
-
* Resume a Codex parse from `fromOffset` bytes into the file, folding only the
|
|
3888
|
-
* newly-appended lines into `prior`. Returns the finalized scan, the next
|
|
3889
|
-
* serialized continuation, and the byte offset to resume from next time.
|
|
3890
|
-
*
|
|
3891
|
-
* Same trailing-line discipline as {@link scanClaudeSessionIncremental}: apply
|
|
3892
|
-
* ONLY the run of newline-terminated lines (slice at the last `'\n'`), and set
|
|
3893
|
-
* `newOffset = fromOffset + consumedBytes`. An ordinary tail after the last
|
|
3894
|
-
* `'\n'` is deferred; an oversized one advances under a persisted discard bit.
|
|
3895
|
-
* Codex `messageCount` is additive with
|
|
3896
|
-
* NO dedup, so re-reading a still-unterminated complete line would double-count
|
|
3897
|
-
* it; deferring prevents that (the bug class prix-cloud caught for Claude).
|
|
3898
|
-
*/
|
|
3899
3001
|
export async function scanCodexSessionIncremental(filePath, fromOffset, prior) {
|
|
3900
3002
|
const state = hydrateCodexParseState(prior);
|
|
3901
3003
|
const append = await applyJsonlAppend(filePath, fromOffset, prior.jsonlDroppingOversizedLine === true, (parsed) => applyCodexLine(state, parsed));
|
|
@@ -3911,20 +3013,9 @@ export async function scanCodexSessionIncremental(filePath, fromOffset, prior) {
|
|
|
3911
3013
|
toolCalls,
|
|
3912
3014
|
};
|
|
3913
3015
|
}
|
|
3914
|
-
/** Serialized zero-value continuation: a fresh accumulator at offset 0, used to drive a FULL parse from the start through the same resumable path. */
|
|
3915
3016
|
function freshCodexParserState() {
|
|
3916
3017
|
return serializeCodexParserState(initCodexParseState(), 0);
|
|
3917
3018
|
}
|
|
3918
|
-
/**
|
|
3919
|
-
* Cheaply derive a Codex rollout's session identity — the `session_meta` id — by
|
|
3920
|
-
* streaming only the START of the file (at most `maxBytes`) and stopping once the
|
|
3921
|
-
* id is known. Mirrors {@link claudeSessionIdentityAt}: used by
|
|
3922
|
-
* {@link scanCodexSessionResumable} to confirm a grown file is still the SAME
|
|
3923
|
-
* session before resuming from a stored parse offset. Codex writes `session_meta`
|
|
3924
|
-
* (carrying the durable session UUID) on the first line of every rollout, so the
|
|
3925
|
-
* id is reached almost immediately. Returns undefined when no id appears within
|
|
3926
|
-
* the budget, which forces a FULL parse.
|
|
3927
|
-
*/
|
|
3928
3019
|
async function codexSessionIdentityAt(filePath, maxBytes = 1_048_576) {
|
|
3929
3020
|
const state = initCodexParseState();
|
|
3930
3021
|
const stream = fs.createReadStream(filePath, { start: 0, end: maxBytes - 1, encoding: 'utf-8' });
|
|
@@ -3951,27 +3042,7 @@ async function codexSessionIdentityAt(filePath, maxBytes = 1_048_576) {
|
|
|
3951
3042
|
}
|
|
3952
3043
|
return state.sessionId;
|
|
3953
3044
|
}
|
|
3954
|
-
/**
|
|
3955
|
-
* Decide full-vs-incremental for one Codex rollout and parse it uniformly,
|
|
3956
|
-
* always returning a finalized scan plus the continuation to persist. Both
|
|
3957
|
-
* branches run through the SAME reducer (via {@link scanCodexSessionIncremental}),
|
|
3958
|
-
* so an append produces a row identical to a from-scratch full reparse by
|
|
3959
|
-
* construction. INCREMENTAL when a prior continuation exists AND the file grew
|
|
3960
|
-
* past the persisted offset AND its mtime did not go backwards; FULL (from byte
|
|
3961
|
-
* 0, fresh state) otherwise (cold start, truncation/rewrite, clock rewind).
|
|
3962
|
-
*/
|
|
3963
3045
|
export async function scanCodexSessionResumable(filePath, prior, currentFileMtimeMs, currentFileSize, priorFileMtimeMs) {
|
|
3964
|
-
// File size + mtime cannot distinguish an APPEND from an in-place rewrite or a
|
|
3965
|
-
// restore that dropped a DIFFERENT, larger rollout at the same path: both grow
|
|
3966
|
-
// the file and move mtime forward. Resuming from the stored offset across that
|
|
3967
|
-
// boundary would fold the new session's bytes into an accumulator hydrated from
|
|
3968
|
-
// the OLD session, so the persisted row silently diverges from a full reparse.
|
|
3969
|
-
// So the metadata gate below only makes a file ELIGIBLE; before trusting the
|
|
3970
|
-
// offset we re-read the rollout's `session_meta` id and require it to still
|
|
3971
|
-
// match the prior continuation's. An append keeps that id; a rewrite/restore of
|
|
3972
|
-
// a different session changes it. A mismatch — or an id we cannot derive —
|
|
3973
|
-
// falls back to a FULL parse, which is always correct. (A shrink is already
|
|
3974
|
-
// handled: currentFileSize is not > prior.offset, so it takes the FULL branch.)
|
|
3975
3046
|
let canIncrement = false;
|
|
3976
3047
|
if (prior !== null &&
|
|
3977
3048
|
currentFileSize > prior.offset &&
|
|
@@ -3986,12 +3057,6 @@ export async function scanCodexSessionResumable(filePath, prior, currentFileMtim
|
|
|
3986
3057
|
const result = await scanCodexSessionIncremental(filePath, 0, freshCodexParserState());
|
|
3987
3058
|
return { ...result, mode: 'full' };
|
|
3988
3059
|
}
|
|
3989
|
-
/**
|
|
3990
|
-
* Parse the prior continuation blob for a changed Codex file into a usable
|
|
3991
|
-
* {@link CodexParserState}, or null when there is none / it is unusable. A blob
|
|
3992
|
-
* from a different serialization version is treated as absent so the file falls
|
|
3993
|
-
* back to a clean FULL parse rather than resuming against a stale shape.
|
|
3994
|
-
*/
|
|
3995
3060
|
function parsePriorCodexState(row) {
|
|
3996
3061
|
if (!row?.parserState)
|
|
3997
3062
|
return null;
|
|
@@ -4005,20 +3070,15 @@ function parsePriorCodexState(row) {
|
|
|
4005
3070
|
return null;
|
|
4006
3071
|
}
|
|
4007
3072
|
}
|
|
4008
|
-
/** Test seam: how many times the incremental (append-resume) branch was taken since the last reset. */
|
|
4009
3073
|
let codexIncrementalScanCount = 0;
|
|
4010
|
-
/** Test seam: how many times a full (from-offset-0) Codex parse ran since the last reset. */
|
|
4011
3074
|
let codexFullScanCount = 0;
|
|
4012
|
-
/** Test seam: read the (incremental, full) Codex parse counters. */
|
|
4013
3075
|
export function __codexScanBranchCountsForTest() {
|
|
4014
3076
|
return { incremental: codexIncrementalScanCount, full: codexFullScanCount };
|
|
4015
3077
|
}
|
|
4016
|
-
/** Test seam: reset the Codex parse-branch counters to observe a scan from a clean slate. */
|
|
4017
3078
|
export function __resetCodexScanBranchCountsForTest() {
|
|
4018
3079
|
codexIncrementalScanCount = 0;
|
|
4019
3080
|
codexFullScanCount = 0;
|
|
4020
3081
|
}
|
|
4021
|
-
/** Resolve the working directory for an OpenClaw agent from its workspace config. */
|
|
4022
3082
|
function getOpenClawSessionCwd(agentId) {
|
|
4023
3083
|
const workspace = agentId ? getOpenClawWorkspaceMap().get(agentId) : undefined;
|
|
4024
3084
|
if (workspace)
|
|
@@ -4026,7 +3086,6 @@ function getOpenClawSessionCwd(agentId) {
|
|
|
4026
3086
|
const configDir = AGENTS.openclaw.configDir;
|
|
4027
3087
|
return safeRealpathSync(configDir) || configDir;
|
|
4028
3088
|
}
|
|
4029
|
-
/** Build a cached map of OpenClaw agent ID to workspace path from openclaw.json. */
|
|
4030
3089
|
function getOpenClawWorkspaceMap() {
|
|
4031
3090
|
if (cachedOpenClawWorkspaces)
|
|
4032
3091
|
return cachedOpenClawWorkspaces;
|
|
@@ -4045,19 +3104,13 @@ function getOpenClawWorkspaceMap() {
|
|
|
4045
3104
|
}
|
|
4046
3105
|
}
|
|
4047
3106
|
catch {
|
|
4048
|
-
// Ignore invalid OpenClaw config and fall back to ~/.openclaw.
|
|
4049
3107
|
}
|
|
4050
3108
|
cachedOpenClawWorkspaces = workspaces;
|
|
4051
3109
|
return workspaces;
|
|
4052
3110
|
}
|
|
4053
|
-
// ---------------------------------------------------------------------------
|
|
4054
|
-
// Utilities
|
|
4055
|
-
// ---------------------------------------------------------------------------
|
|
4056
|
-
/** Compute the SHA-256 hex digest of a string. */
|
|
4057
3111
|
function sha256(input) {
|
|
4058
3112
|
return crypto.createHash('sha256').update(input).digest('hex');
|
|
4059
3113
|
}
|
|
4060
|
-
/** Stat a path, returning null on any error. */
|
|
4061
3114
|
function safeStatSync(p) {
|
|
4062
3115
|
try {
|
|
4063
3116
|
return fs.statSync(p);
|
|
@@ -4066,7 +3119,6 @@ function safeStatSync(p) {
|
|
|
4066
3119
|
return null;
|
|
4067
3120
|
}
|
|
4068
3121
|
}
|
|
4069
|
-
/** Resolve a path to its real path, returning null on any error. */
|
|
4070
3122
|
function safeRealpathSync(p) {
|
|
4071
3123
|
try {
|
|
4072
3124
|
return fs.realpathSync(p);
|
|
@@ -4075,7 +3127,6 @@ function safeRealpathSync(p) {
|
|
|
4075
3127
|
return null;
|
|
4076
3128
|
}
|
|
4077
3129
|
}
|
|
4078
|
-
/** Extract meaningful user text from a Claude JSONL user event, skipping meta and local-command messages. */
|
|
4079
3130
|
function extractClaudeUserText(parsed) {
|
|
4080
3131
|
if (parsed.isMeta === true)
|
|
4081
3132
|
return undefined;
|
|
@@ -4094,18 +3145,9 @@ function extractClaudeUserText(parsed) {
|
|
|
4094
3145
|
return undefined;
|
|
4095
3146
|
return text;
|
|
4096
3147
|
}
|
|
4097
|
-
/** Check whether a message is a local-command wrapper rather than real user input. */
|
|
4098
3148
|
function isLocalCommandMessage(text) {
|
|
4099
3149
|
return /<local-command-caveat>|<bash-(input|stdout|stderr)>/i.test(text);
|
|
4100
3150
|
}
|
|
4101
|
-
/**
|
|
4102
|
-
* Extract the assistant's own answer text from a Claude JSONL assistant event —
|
|
4103
|
-
* the reply text blocks, skipping tool_use/tool_result blocks and an
|
|
4104
|
-
* interrupted turn, mirroring {@link extractClaudeUserText}'s filters. Unlike
|
|
4105
|
-
* the user side (which keeps only the first text block), a single assistant
|
|
4106
|
-
* turn can legitimately carry multiple text blocks around tool calls, so every
|
|
4107
|
-
* one is joined.
|
|
4108
|
-
*/
|
|
4109
3151
|
function extractClaudeAssistantText(parsed) {
|
|
4110
3152
|
const content = parsed.message?.content;
|
|
4111
3153
|
if (typeof content === 'string') {
|
|
@@ -4121,7 +3163,6 @@ function extractClaudeAssistantText(parsed) {
|
|
|
4121
3163
|
.join('\n');
|
|
4122
3164
|
return text || undefined;
|
|
4123
3165
|
}
|
|
4124
|
-
/** Sum all token usage fields from a Claude assistant message's usage object. */
|
|
4125
3166
|
function getClaudeUsageTotal(usage) {
|
|
4126
3167
|
if (!usage || typeof usage !== 'object')
|
|
4127
3168
|
return null;
|
|
@@ -4132,7 +3173,6 @@ function getClaudeUsageTotal(usage) {
|
|
|
4132
3173
|
usage.cache_read_input_tokens,
|
|
4133
3174
|
]);
|
|
4134
3175
|
}
|
|
4135
|
-
/** Extract text from Codex message content blocks; the caller classifies user scaffolding. */
|
|
4136
3176
|
function extractCodexMessageText(contentBlocks, role) {
|
|
4137
3177
|
if (!Array.isArray(contentBlocks))
|
|
4138
3178
|
return undefined;
|
|
@@ -4144,22 +3184,15 @@ function extractCodexMessageText(contentBlocks, role) {
|
|
|
4144
3184
|
.find((value) => !!value);
|
|
4145
3185
|
return text || undefined;
|
|
4146
3186
|
}
|
|
4147
|
-
/** Trim and normalize a version string, returning undefined for empty values. */
|
|
4148
3187
|
function normalizeVersion(version) {
|
|
4149
3188
|
const trimmed = version?.trim();
|
|
4150
3189
|
return trimmed ? trimmed : undefined;
|
|
4151
3190
|
}
|
|
4152
|
-
/** Extract the version number from a managed versions/<agent>/<version>/... path under either repo. */
|
|
4153
3191
|
export function extractVersionFromManagedPath(agent, sourcePath) {
|
|
4154
3192
|
if (!sourcePath)
|
|
4155
3193
|
return undefined;
|
|
4156
3194
|
const candidates = [sourcePath, safeRealpathSync(sourcePath) || ''];
|
|
4157
3195
|
const markers = [`/.agents/versions/${agent}/`, `/.agents-system/versions/${agent}/`];
|
|
4158
|
-
// Codex is relocated by CODEX_HOME to `~/.agents/.codex-homes/<version>/`
|
|
4159
|
-
// (shims.ts `codexHomeShimBash`), NOT the `versions/<agent>/<version>/home/…`
|
|
4160
|
-
// layout every other isolated agent uses — so its rollout transcripts live
|
|
4161
|
-
// under `.codex-homes/<version>/sessions/…` and the markers above never match,
|
|
4162
|
-
// leaving `version` NULL and native resume degraded to `/continue` (PHNX-3626).
|
|
4163
3196
|
if (agent === 'codex')
|
|
4164
3197
|
markers.push('/.agents/.codex-homes/');
|
|
4165
3198
|
for (const candidate of candidates) {
|
|
@@ -4177,7 +3210,6 @@ export function extractVersionFromManagedPath(agent, sourcePath) {
|
|
|
4177
3210
|
}
|
|
4178
3211
|
return undefined;
|
|
4179
3212
|
}
|
|
4180
|
-
/** Resolve the current version of an agent CLI (symlink version or live CLI output, cached). */
|
|
4181
3213
|
async function getCurrentAgentVersion(agent) {
|
|
4182
3214
|
const cached = cachedAgentVersions.get(agent);
|
|
4183
3215
|
if (cached)
|
|
@@ -4191,13 +3223,11 @@ async function getCurrentAgentVersion(agent) {
|
|
|
4191
3223
|
cachedAgentVersions.set(agent, promise);
|
|
4192
3224
|
return promise;
|
|
4193
3225
|
}
|
|
4194
|
-
/** Resolve a session's version: embedded in file > extracted from managed path > current CLI version. */
|
|
4195
3226
|
function resolveSessionVersion(agent, sourcePath, embeddedVersion, currentVersion) {
|
|
4196
3227
|
return normalizeVersion(embeddedVersion)
|
|
4197
3228
|
|| extractVersionFromManagedPath(agent, sourcePath)
|
|
4198
3229
|
|| normalizeVersion(currentVersion);
|
|
4199
3230
|
}
|
|
4200
|
-
/** Sum all token usage fields from a Codex total_token_usage object. */
|
|
4201
3231
|
function getCodexTokenCount(totalTokenUsage) {
|
|
4202
3232
|
if (!totalTokenUsage || typeof totalTokenUsage !== 'object')
|
|
4203
3233
|
return null;
|
|
@@ -4208,7 +3238,6 @@ function getCodexTokenCount(totalTokenUsage) {
|
|
|
4208
3238
|
totalTokenUsage.reasoning_output_tokens,
|
|
4209
3239
|
]);
|
|
4210
3240
|
}
|
|
4211
|
-
/** Sum all numeric values in an array, returning null if none are valid numbers. */
|
|
4212
3241
|
function sumKnownNumbers(values) {
|
|
4213
3242
|
let total = 0;
|
|
4214
3243
|
let found = false;
|
|
@@ -4220,20 +3249,6 @@ function sumKnownNumbers(values) {
|
|
|
4220
3249
|
}
|
|
4221
3250
|
return found ? total : null;
|
|
4222
3251
|
}
|
|
4223
|
-
// ---------------------------------------------------------------------------
|
|
4224
|
-
// Time range parsing
|
|
4225
|
-
// ---------------------------------------------------------------------------
|
|
4226
|
-
// ---------------------------------------------------------------------------
|
|
4227
|
-
// Cursor
|
|
4228
|
-
// ---------------------------------------------------------------------------
|
|
4229
|
-
// Cursor writes the conversation to
|
|
4230
|
-
// projects/<encoded-cwd>/agent-transcripts/<uuid>/<uuid>.jsonl and metadata to
|
|
4231
|
-
// chats/<workspace-hash>/<uuid>/meta.json. Discovery deliberately starts from
|
|
4232
|
-
// transcripts, then joins metadata by UUID: chat directories with no transcript
|
|
4233
|
-
// are empty or abandoned sessions and must not become broken zero-event rows.
|
|
4234
|
-
// Routine archives may contain only the transcript; those remain browsable with
|
|
4235
|
-
// file timestamps and without guessed cwd/title metadata.
|
|
4236
|
-
/** Incrementally re-scan changed Cursor transcript files and upsert into the DB. */
|
|
4237
3252
|
async function scanCursorIncremental(onProgress) {
|
|
4238
3253
|
const currentVersion = await getCurrentAgentVersion('cursor');
|
|
4239
3254
|
const prestat = [];
|
|
@@ -4303,12 +3318,10 @@ function readCursorChatMeta(filePath, sessionId) {
|
|
|
4303
3318
|
return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
4304
3319
|
}
|
|
4305
3320
|
catch {
|
|
4306
|
-
// This workspace hash does not own the session, or its metadata is unreadable.
|
|
4307
3321
|
}
|
|
4308
3322
|
}
|
|
4309
3323
|
return undefined;
|
|
4310
3324
|
}
|
|
4311
|
-
/** Parse one Cursor transcript and enrich it with the matching chat meta.json. */
|
|
4312
3325
|
export function readCursorMeta(filePath, currentVersion) {
|
|
4313
3326
|
const sessionId = path.basename(filePath).replace(/\.jsonl$/, '');
|
|
4314
3327
|
if (!sessionId || path.basename(path.dirname(filePath)) !== sessionId)
|
|
@@ -4354,13 +3367,6 @@ export function readCursorMeta(filePath, currentVersion) {
|
|
|
4354
3367
|
};
|
|
4355
3368
|
return { meta, content: userTexts.join('\n'), assistantContent: assistantTexts.join('\n'), events };
|
|
4356
3369
|
}
|
|
4357
|
-
// ---------------------------------------------------------------------------
|
|
4358
|
-
// Kimi
|
|
4359
|
-
// ---------------------------------------------------------------------------
|
|
4360
|
-
// Kimi stores sessions under ~/.kimi-code/sessions/<workdir_hash>/session_<uuid>/.
|
|
4361
|
-
// Each session has state.json (metadata) and agents/main/wire.jsonl (conversation).
|
|
4362
|
-
// A session_index.jsonl at ~/.kimi-code/ maps session IDs to directories.
|
|
4363
|
-
/** Incrementally re-scan changed Kimi session state.json files and upsert into the DB. */
|
|
4364
3370
|
async function scanKimiIncremental(onProgress) {
|
|
4365
3371
|
const filePaths = [];
|
|
4366
3372
|
for (const sessionsDir of getAgentSessionDirs('kimi', 'sessions')) {
|
|
@@ -4399,9 +3405,6 @@ async function scanKimiIncremental(onProgress) {
|
|
|
4399
3405
|
if (changed.length === 0)
|
|
4400
3406
|
return;
|
|
4401
3407
|
onProgress?.({ agent: 'kimi', parsed: 0, total: changed.length });
|
|
4402
|
-
// Bulk-fetch each changed session's prior wire-parse continuation (offset +
|
|
4403
|
-
// counter bases). A session whose wire.jsonl grew resumes from the offset;
|
|
4404
|
-
// everything else (cold start, truncation) full-parses from byte 0.
|
|
4405
3408
|
const priorStates = getParserStatesForPaths(changed.map(c => c.filePath));
|
|
4406
3409
|
const scanEntries = [];
|
|
4407
3410
|
const touched = [];
|
|
@@ -4427,7 +3430,6 @@ async function scanKimiIncremental(onProgress) {
|
|
|
4427
3430
|
upsertSessionsBatch(scanEntries);
|
|
4428
3431
|
recordScans(touched);
|
|
4429
3432
|
}
|
|
4430
|
-
/** Parse a single Kimi session state.json file to extract session metadata. */
|
|
4431
3433
|
export function readKimiMeta(filePath, priorRow) {
|
|
4432
3434
|
let state;
|
|
4433
3435
|
try {
|
|
@@ -4445,17 +3447,10 @@ export function readKimiMeta(filePath, priorRow) {
|
|
|
4445
3447
|
const topic = title || lastPrompt || undefined;
|
|
4446
3448
|
const createdAt = typeof state.createdAt === 'string' ? state.createdAt : undefined;
|
|
4447
3449
|
const updatedAt = typeof state.updatedAt === 'string' ? state.updatedAt : undefined;
|
|
4448
|
-
// Coerce to never-null, the same way every other parser does (Rush/Hermes/Droid/…):
|
|
4449
|
-
// a real createdAt/updatedAt still wins; otherwise fall back to the state.json mtime.
|
|
4450
|
-
// Kimi was the lone parser that could yield `undefined`, which binds NULL into
|
|
4451
|
-
// `timestamp TEXT NOT NULL` and aborts the whole batch index. mtime also matches how
|
|
4452
|
-
// the listing already ranks Kimi (last_activity resolves to the file mtime).
|
|
4453
3450
|
const stat = safeStatSync(filePath);
|
|
4454
3451
|
const timestamp = updatedAt || createdAt
|
|
4455
3452
|
|| (stat ? stat.mtime.toISOString() : new Date().toISOString());
|
|
4456
3453
|
const shortId = deriveShortId(sessionId, /^session_/);
|
|
4457
|
-
// Try to infer project from session directory path
|
|
4458
|
-
// ~/.kimi-code/sessions/<workdir_hash>/session_<uuid>/
|
|
4459
3454
|
const workDirName = path.basename(path.dirname(sessionDir));
|
|
4460
3455
|
let project;
|
|
4461
3456
|
if (workDirName.startsWith('wd_')) {
|
|
@@ -4464,9 +3459,6 @@ export function readKimiMeta(filePath, priorRow) {
|
|
|
4464
3459
|
project = parts.slice(0, -1).join('/');
|
|
4465
3460
|
}
|
|
4466
3461
|
}
|
|
4467
|
-
// Parse wire.jsonl incrementally: resume from the persisted offset + counter
|
|
4468
|
-
// bases when the wire grew, else full-parse from byte 0. The continuation is
|
|
4469
|
-
// persisted on this session's state.json ledger row.
|
|
4470
3462
|
const prior = parsePriorKimiState(priorRow);
|
|
4471
3463
|
const { messageCount, tokenCount, outputTokens, newState } = parseKimiWireMetricsIncremental(sessionDir, prior);
|
|
4472
3464
|
const meta = {
|
|
@@ -4495,7 +3487,6 @@ function kimiWireUserText(event) {
|
|
|
4495
3487
|
: '';
|
|
4496
3488
|
return cleanFirstUserMessage(raw);
|
|
4497
3489
|
}
|
|
4498
|
-
/** Fold one parsed Kimi wire event into the additive counters, in place. */
|
|
4499
3490
|
function applyKimiWireEvent(acc, event) {
|
|
4500
3491
|
if (event.type === 'context.append_message') {
|
|
4501
3492
|
acc.messageCount++;
|
|
@@ -4503,49 +3494,17 @@ function applyKimiWireEvent(acc, event) {
|
|
|
4503
3494
|
acc.firstUserMessage = kimiWireUserText(event);
|
|
4504
3495
|
}
|
|
4505
3496
|
else if (event.type === 'usage.record' && event.usage) {
|
|
4506
|
-
// Kimi usage structure: inputOther + output + inputCacheRead + inputCacheCreation
|
|
4507
3497
|
const u = event.usage;
|
|
4508
3498
|
acc.tokenCount += (u.inputOther || 0) + (u.output || 0) + (u.inputCacheRead || 0) + (u.inputCacheCreation || 0);
|
|
4509
3499
|
acc.outputTokens += (u.output || 0);
|
|
4510
3500
|
}
|
|
4511
3501
|
}
|
|
4512
|
-
/**
|
|
4513
|
-
* Incrementally parse Kimi's wire.jsonl for message-count and token counters,
|
|
4514
|
-
* resuming from a persisted continuation instead of re-reading from byte 0 every
|
|
4515
|
-
* scan. Returns the finalized counters and the next {@link KimiParserState} to
|
|
4516
|
-
* persist (offset + the three counter bases).
|
|
4517
|
-
*
|
|
4518
|
-
* Same trailing-line discipline as {@link scanClaudeSessionIncremental}: read
|
|
4519
|
-
* only the appended byte range from `prior.offset`, apply ONLY the run of
|
|
4520
|
-
* newline-terminated lines (slice at the last `'\n'`), and advance the offset to
|
|
4521
|
-
* `prior.offset + consumedBytes`. A complete-but-not-yet-terminated last record
|
|
4522
|
-
* is DEFERRED to the next pass; because these counters are additive with no
|
|
4523
|
-
* dedup, re-reading such a line would double-count it. FULL parse from byte 0
|
|
4524
|
-
* (fresh counters) when there is no prior OR the file shrank below the stored
|
|
4525
|
-
* offset (truncation/rewrite).
|
|
4526
|
-
*/
|
|
4527
3502
|
export function parseKimiWireMetricsIncremental(sessionDir, prior) {
|
|
4528
3503
|
const wirePath = path.join(sessionDir, 'agents', 'main', 'wire.jsonl');
|
|
4529
3504
|
const stat = safeStatSync(wirePath);
|
|
4530
3505
|
if (!stat) {
|
|
4531
|
-
// No wire.jsonl (yet): zero counters, offset 0 so a later append is a clean
|
|
4532
|
-
// full parse.
|
|
4533
3506
|
return { messageCount: 0, tokenCount: 0, outputTokens: 0, newState: { v: 2, offset: 0, messageCount: 0, tokenCount: 0, outputTokens: 0 } };
|
|
4534
3507
|
}
|
|
4535
|
-
// INCREMENTAL only when a usable prior exists AND the file grew past its
|
|
4536
|
-
// offset; otherwise FULL from byte 0 with fresh counters (cold start OR the
|
|
4537
|
-
// file shrank to/below the offset — a truncation/rewrite).
|
|
4538
|
-
//
|
|
4539
|
-
// No session-identity re-check is needed here (unlike Claude's
|
|
4540
|
-
// claudeSessionIdentityAt / Codex's codexSessionIdentityAt, which guard against
|
|
4541
|
-
// an in-place rewrite dropping a DIFFERENT session at the same path). A Kimi
|
|
4542
|
-
// wire.jsonl is uniquely keyed by its session dir — `.../session_<uuid>/agents/
|
|
4543
|
-
// main/wire.jsonl` (see readKimiMeta: sessionId is `session_<uuid>` and must
|
|
4544
|
-
// start with `session_`) — and Kimi only ever APPENDS to that per-session log.
|
|
4545
|
-
// The path therefore cannot host a different session's transcript, so a
|
|
4546
|
-
// size-grew wire.jsonl is always the same session's append. (A truncation/
|
|
4547
|
-
// rewrite — the only way its bytes could diverge — already shrinks it to/below
|
|
4548
|
-
// the offset and takes the FULL branch above.)
|
|
4549
3508
|
const canIncrement = prior !== null && stat.size > prior.offset;
|
|
4550
3509
|
const fromOffset = canIncrement ? prior.offset : 0;
|
|
4551
3510
|
const acc = canIncrement
|
|
@@ -4554,12 +3513,6 @@ export function parseKimiWireMetricsIncremental(sessionDir, prior) {
|
|
|
4554
3513
|
let consumedBytes = 0;
|
|
4555
3514
|
let fd;
|
|
4556
3515
|
try {
|
|
4557
|
-
// Read ONLY the appended byte range [fromOffset, stat.size) — not the whole
|
|
4558
|
-
// file. readSync from an explicit position keeps this function synchronous
|
|
4559
|
-
// (its callers are sync) while making the disk read + allocation scale with
|
|
4560
|
-
// the appended delta, not total file size, matching scanCodexSessionIncremental
|
|
4561
|
-
// / scanClaudeSessionIncremental. Bytes past the stat'd size are a concurrent
|
|
4562
|
-
// append and are deferred to the next scan.
|
|
4563
3516
|
const bytesToRead = Math.max(0, stat.size - fromOffset);
|
|
4564
3517
|
const appended = Buffer.allocUnsafe(bytesToRead);
|
|
4565
3518
|
if (bytesToRead > 0) {
|
|
@@ -4572,7 +3525,6 @@ export function parseKimiWireMetricsIncremental(sessionDir, prior) {
|
|
|
4572
3525
|
read += n;
|
|
4573
3526
|
}
|
|
4574
3527
|
const chunk = read === bytesToRead ? appended : appended.subarray(0, read);
|
|
4575
|
-
// Bytes up to AND INCLUDING the last '\n' are the committed, complete-line run.
|
|
4576
3528
|
const lastNl = chunk.lastIndexOf(0x0a);
|
|
4577
3529
|
consumedBytes = lastNl === -1 ? 0 : lastNl + 1;
|
|
4578
3530
|
if (consumedBytes > 0) {
|
|
@@ -4583,22 +3535,19 @@ export function parseKimiWireMetricsIncremental(sessionDir, prior) {
|
|
|
4583
3535
|
applyKimiWireEvent(acc, JSON.parse(line));
|
|
4584
3536
|
}
|
|
4585
3537
|
catch {
|
|
4586
|
-
// Malformed line, skip
|
|
4587
3538
|
}
|
|
4588
3539
|
}
|
|
4589
3540
|
}
|
|
4590
3541
|
}
|
|
4591
3542
|
}
|
|
4592
3543
|
catch {
|
|
4593
|
-
// If wire.jsonl can't be read, keep the accumulated counters (0s on a cold
|
|
4594
|
-
// parse) — graceful degradation, matching the pre-incremental behavior.
|
|
4595
3544
|
}
|
|
4596
3545
|
finally {
|
|
4597
3546
|
if (fd !== undefined) {
|
|
4598
3547
|
try {
|
|
4599
3548
|
fs.closeSync(fd);
|
|
4600
3549
|
}
|
|
4601
|
-
catch {
|
|
3550
|
+
catch { }
|
|
4602
3551
|
}
|
|
4603
3552
|
}
|
|
4604
3553
|
return {
|
|
@@ -4608,12 +3557,6 @@ export function parseKimiWireMetricsIncremental(sessionDir, prior) {
|
|
|
4608
3557
|
newState: { v: 2, offset: fromOffset + consumedBytes, messageCount: acc.messageCount, tokenCount: acc.tokenCount, outputTokens: acc.outputTokens, firstUserMessage: acc.firstUserMessage },
|
|
4609
3558
|
};
|
|
4610
3559
|
}
|
|
4611
|
-
/**
|
|
4612
|
-
* Parse the prior continuation blob for a changed Kimi session into a usable
|
|
4613
|
-
* {@link KimiParserState}, or null when there is none / it is unusable. A blob
|
|
4614
|
-
* from a different serialization version is treated as absent so the wire parse
|
|
4615
|
-
* falls back to a clean FULL parse rather than resuming against a stale shape.
|
|
4616
|
-
*/
|
|
4617
3560
|
function parsePriorKimiState(row) {
|
|
4618
3561
|
if (!row?.parserState)
|
|
4619
3562
|
return null;
|
|
@@ -4627,14 +3570,6 @@ function parsePriorKimiState(row) {
|
|
|
4627
3570
|
return null;
|
|
4628
3571
|
}
|
|
4629
3572
|
}
|
|
4630
|
-
/**
|
|
4631
|
-
* Scan Grok sessions. Grok stores one directory per session under
|
|
4632
|
-
* ~/.grok/sessions/<url-encoded-cwd>/<uuid>/, each holding a summary.json with
|
|
4633
|
-
* structured metadata (id, cwd, title, timestamps, message count). Same
|
|
4634
|
-
* dir-per-session (L3) shape as Kimi, so it walks two levels and gates the
|
|
4635
|
-
* summary.json read through the scan ledger. Before this, Grok had a type slot
|
|
4636
|
-
* and a placeholder parser but no scanner, so `agents sessions` never indexed it.
|
|
4637
|
-
*/
|
|
4638
3573
|
async function scanGrokIncremental(onProgress) {
|
|
4639
3574
|
const currentVersion = await getCurrentAgentVersion('grok');
|
|
4640
3575
|
const filePaths = [];
|
|
@@ -4696,18 +3631,6 @@ async function scanGrokIncremental(onProgress) {
|
|
|
4696
3631
|
upsertSessionsBatch(scanEntries);
|
|
4697
3632
|
recordScans(touched);
|
|
4698
3633
|
}
|
|
4699
|
-
/**
|
|
4700
|
-
* Bounded read of a Grok `chat_history.jsonl` for the genuine full first user
|
|
4701
|
-
* turn (PHNX-3621 leftover from #3359). Grok's cheap scan reads only
|
|
4702
|
-
* `summary.json` (a LARGE-transcript harness whose full message log is
|
|
4703
|
-
* deliberately not parsed on the hot tick), but the first turn is at the START
|
|
4704
|
-
* of the log, so a bounded prefix read recovers it without paying the full-file
|
|
4705
|
-
* cost. Production logs lead with a `type:system` record, then a huge
|
|
4706
|
-
* `<user_info>` user dump (no `synthetic_reason`), then a `synthetic_reason`
|
|
4707
|
-
* reminder, then the originating turn at `prompt_index: 0` wrapped in
|
|
4708
|
-
* `<user_query>`. Skip that scaffolding and prefer `prompt_index` when present.
|
|
4709
|
-
* Returns undefined when no genuine user turn appears within the budget.
|
|
4710
|
-
*/
|
|
4711
3634
|
function readGrokFirstUserMessage(sessionDir, maxBytes = 262_144) {
|
|
4712
3635
|
const historyPath = path.join(sessionDir, 'chat_history.jsonl');
|
|
4713
3636
|
let fd;
|
|
@@ -4728,7 +3651,6 @@ function readGrokFirstUserMessage(sessionDir, maxBytes = 262_144) {
|
|
|
4728
3651
|
read += n;
|
|
4729
3652
|
}
|
|
4730
3653
|
const chunk = buf.subarray(0, read).toString('utf-8');
|
|
4731
|
-
// Drop a trailing partial line so we never parse half a JSON record.
|
|
4732
3654
|
const lastNl = chunk.lastIndexOf('\n');
|
|
4733
3655
|
const complete = lastNl === -1 ? chunk : chunk.slice(0, lastNl);
|
|
4734
3656
|
let firstGenuine;
|
|
@@ -4770,11 +3692,10 @@ function readGrokFirstUserMessage(sessionDir, maxBytes = 262_144) {
|
|
|
4770
3692
|
try {
|
|
4771
3693
|
fs.closeSync(fd);
|
|
4772
3694
|
}
|
|
4773
|
-
catch {
|
|
3695
|
+
catch { }
|
|
4774
3696
|
}
|
|
4775
3697
|
}
|
|
4776
3698
|
}
|
|
4777
|
-
/** Parse a single Grok session summary.json into session metadata. */
|
|
4778
3699
|
export function readGrokMeta(filePath, currentVersion) {
|
|
4779
3700
|
let summary;
|
|
4780
3701
|
try {
|
|
@@ -4784,7 +3705,6 @@ export function readGrokMeta(filePath, currentVersion) {
|
|
|
4784
3705
|
return null;
|
|
4785
3706
|
}
|
|
4786
3707
|
const sessionDir = path.dirname(filePath);
|
|
4787
|
-
// The uuid directory name is the canonical id; summary.info.id mirrors it.
|
|
4788
3708
|
const sessionId = (typeof summary?.info?.id === 'string' && summary.info.id) || path.basename(sessionDir);
|
|
4789
3709
|
if (!sessionId)
|
|
4790
3710
|
return null;
|
|
@@ -4792,9 +3712,6 @@ export function readGrokMeta(filePath, currentVersion) {
|
|
|
4792
3712
|
const topic = (typeof summary?.generated_title === 'string' && summary.generated_title.trim()) ||
|
|
4793
3713
|
(typeof summary?.session_summary === 'string' && summary.session_summary.trim()) ||
|
|
4794
3714
|
undefined;
|
|
4795
|
-
// created_at is the session start; last_active_at/updated_at is the latest
|
|
4796
|
-
// activity. Coerce timestamp to never-null (NOT NULL column) via the file mtime,
|
|
4797
|
-
// matching how the other dir-per-session parsers (Kimi) fall back.
|
|
4798
3715
|
const createdAt = typeof summary?.created_at === 'string' ? summary.created_at : undefined;
|
|
4799
3716
|
const lastActivity = (typeof summary?.last_active_at === 'string' && summary.last_active_at) ||
|
|
4800
3717
|
(typeof summary?.updated_at === 'string' && summary.updated_at) ||
|
|
@@ -4806,11 +3723,6 @@ export function readGrokMeta(filePath, currentVersion) {
|
|
|
4806
3723
|
: typeof summary?.num_messages === 'number'
|
|
4807
3724
|
? summary.num_messages
|
|
4808
3725
|
: undefined;
|
|
4809
|
-
// Grok records its managed home in summary.grok_home
|
|
4810
|
-
// (…/versions/grok/<version>/home/.grok) — recover the version from it. The
|
|
4811
|
-
// value is written by the Grok CLI in the writing host's native separators, so
|
|
4812
|
-
// a Windows-authored summary is backslash-separated; normalize to `/` before
|
|
4813
|
-
// matching or the version never resolves on Windows (RUSH-2286).
|
|
4814
3726
|
let embeddedVersion;
|
|
4815
3727
|
if (typeof summary?.grok_home === 'string') {
|
|
4816
3728
|
embeddedVersion = summary.grok_home.replace(/\\/g, '/').match(/versions\/grok\/([^/]+)\//)?.[1];
|
|
@@ -4831,9 +3743,4 @@ export function readGrokMeta(filePath, currentVersion) {
|
|
|
4831
3743
|
};
|
|
4832
3744
|
return { meta, content: topic || '' };
|
|
4833
3745
|
}
|
|
4834
|
-
// parseTimeFilter moved to ./relative-time.js — a leaf module — so a caller that
|
|
4835
|
-
// only needs the duration parser does not pull this file's `../sqlite.js` import
|
|
4836
|
-
// (and Node's SQLite ExperimentalWarning on stderr) into its module graph.
|
|
4837
|
-
// Re-exported so every existing importer keeps working unchanged. Imported too,
|
|
4838
|
-
// because a bare re-export does not bind the name for this file's own callers.
|
|
4839
3746
|
export { parseTimeFilter } from './relative-time.js';
|