@swarmify/agents-cli 1.12.0 → 1.13.1
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 +33 -0
- package/LICENSE +21 -0
- package/README.md +212 -232
- package/dist/commands/__tests__/sessions.test.js +90 -20
- package/dist/commands/__tests__/sessions.test.js.map +1 -1
- package/dist/commands/cloud.d.ts +11 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +363 -0
- package/dist/commands/cloud.js.map +1 -0
- package/dist/commands/commands.d.ts +9 -0
- package/dist/commands/commands.d.ts.map +1 -1
- package/dist/commands/commands.js +330 -139
- package/dist/commands/commands.js.map +1 -1
- package/dist/commands/daemon.d.ts +8 -0
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +38 -222
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/drive.d.ts +8 -0
- package/dist/commands/drive.d.ts.map +1 -1
- package/dist/commands/drive.js +70 -7
- package/dist/commands/drive.js.map +1 -1
- package/dist/commands/exec.d.ts +9 -1
- package/dist/commands/exec.d.ts.map +1 -1
- package/dist/commands/exec.js +198 -24
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/factory.d.ts +11 -0
- package/dist/commands/factory.d.ts.map +1 -0
- package/dist/commands/factory.js +445 -0
- package/dist/commands/factory.js.map +1 -0
- package/dist/commands/fork.d.ts +8 -0
- package/dist/commands/fork.d.ts.map +1 -1
- package/dist/commands/fork.js +38 -4
- package/dist/commands/fork.js.map +1 -1
- package/dist/commands/hooks.d.ts +9 -0
- package/dist/commands/hooks.d.ts.map +1 -1
- package/dist/commands/hooks.js +252 -18
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +137 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp.d.ts +9 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +250 -169
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/models.d.ts +11 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/models.js +170 -0
- package/dist/commands/models.js.map +1 -0
- package/dist/commands/packages.d.ts +8 -0
- package/dist/commands/packages.d.ts.map +1 -1
- package/dist/commands/packages.js +155 -14
- package/dist/commands/packages.js.map +1 -1
- package/dist/commands/permissions.d.ts +9 -0
- package/dist/commands/permissions.d.ts.map +1 -1
- package/dist/commands/permissions.js +72 -13
- package/dist/commands/permissions.js.map +1 -1
- package/dist/commands/plugins.d.ts +8 -0
- package/dist/commands/plugins.d.ts.map +1 -1
- package/dist/commands/plugins.js +265 -44
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/profiles.d.ts +12 -0
- package/dist/commands/profiles.d.ts.map +1 -0
- package/dist/commands/profiles.js +255 -0
- package/dist/commands/profiles.js.map +1 -0
- package/dist/commands/pty.d.ts +1 -0
- package/dist/commands/pty.d.ts.map +1 -1
- package/dist/commands/pty.js +133 -22
- package/dist/commands/pty.js.map +1 -1
- package/dist/commands/pull.d.ts +8 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +103 -14
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts +8 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +37 -3
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/refresh-memory.d.ts +16 -0
- package/dist/commands/refresh-memory.d.ts.map +1 -0
- package/dist/commands/refresh-memory.js +52 -0
- package/dist/commands/refresh-memory.js.map +1 -0
- package/dist/commands/resource-view.d.ts +39 -0
- package/dist/commands/resource-view.d.ts.map +1 -0
- package/dist/commands/resource-view.js +197 -0
- package/dist/commands/resource-view.js.map +1 -0
- package/dist/commands/routines.d.ts +8 -0
- package/dist/commands/routines.d.ts.map +1 -1
- package/dist/commands/routines.js +163 -34
- package/dist/commands/routines.js.map +1 -1
- package/dist/commands/rules.d.ts +9 -0
- package/dist/commands/rules.d.ts.map +1 -1
- package/dist/commands/rules.js +83 -12
- package/dist/commands/rules.js.map +1 -1
- package/dist/commands/secrets.d.ts +11 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +352 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/sessions-picker.d.ts +18 -0
- package/dist/commands/sessions-picker.d.ts.map +1 -0
- package/dist/commands/sessions-picker.js +265 -0
- package/dist/commands/sessions-picker.js.map +1 -0
- package/dist/commands/sessions.d.ts +15 -0
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/commands/sessions.js +700 -263
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +355 -233
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +7 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/subagents.d.ts +8 -0
- package/dist/commands/subagents.d.ts.map +1 -1
- package/dist/commands/subagents.js +214 -74
- package/dist/commands/subagents.js.map +1 -1
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +15 -7
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/teams-picker.d.ts +18 -0
- package/dist/commands/teams-picker.d.ts.map +1 -0
- package/dist/commands/teams-picker.js +290 -0
- package/dist/commands/teams-picker.js.map +1 -0
- package/dist/commands/teams.d.ts +18 -0
- package/dist/commands/teams.d.ts.map +1 -0
- package/dist/commands/teams.js +1098 -0
- package/dist/commands/teams.js.map +1 -0
- package/dist/commands/utils.d.ts +20 -0
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +34 -0
- package/dist/commands/utils.js.map +1 -1
- package/dist/commands/versions.d.ts +8 -0
- package/dist/commands/versions.d.ts.map +1 -1
- package/dist/commands/versions.js +71 -8
- package/dist/commands/versions.js.map +1 -1
- package/dist/commands/view.d.ts +36 -1
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +375 -15
- package/dist/commands/view.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +136 -51
- package/dist/index.js.map +1 -1
- package/dist/lib/__tests__/bugfixes.test.js +3 -3
- package/dist/lib/__tests__/bugfixes.test.js.map +1 -1
- package/dist/lib/__tests__/exec.test.js +125 -19
- package/dist/lib/__tests__/exec.test.js.map +1 -1
- package/dist/lib/__tests__/hooks.test.d.ts +2 -0
- package/dist/lib/__tests__/hooks.test.d.ts.map +1 -0
- package/dist/lib/__tests__/hooks.test.js +203 -0
- package/dist/lib/__tests__/hooks.test.js.map +1 -0
- package/dist/lib/__tests__/memory-compile.test.d.ts +2 -0
- package/dist/lib/__tests__/memory-compile.test.d.ts.map +1 -0
- package/dist/lib/__tests__/memory-compile.test.js +95 -0
- package/dist/lib/__tests__/memory-compile.test.js.map +1 -0
- package/dist/lib/__tests__/models.test.d.ts +2 -0
- package/dist/lib/__tests__/models.test.d.ts.map +1 -0
- package/dist/lib/__tests__/models.test.js +239 -0
- package/dist/lib/__tests__/models.test.js.map +1 -0
- package/dist/lib/__tests__/rotate.test.d.ts +2 -0
- package/dist/lib/__tests__/rotate.test.d.ts.map +1 -0
- package/dist/lib/__tests__/rotate.test.js +80 -0
- package/dist/lib/__tests__/rotate.test.js.map +1 -0
- package/dist/lib/__tests__/secrets-bundles.test.d.ts +2 -0
- package/dist/lib/__tests__/secrets-bundles.test.d.ts.map +1 -0
- package/dist/lib/__tests__/secrets-bundles.test.js +104 -0
- package/dist/lib/__tests__/secrets-bundles.test.js.map +1 -0
- package/dist/lib/__tests__/secrets.test.d.ts +2 -0
- package/dist/lib/__tests__/secrets.test.d.ts.map +1 -0
- package/dist/lib/__tests__/secrets.test.js +90 -0
- package/dist/lib/__tests__/secrets.test.js.map +1 -0
- package/dist/lib/__tests__/shims.test.d.ts +2 -0
- package/dist/lib/__tests__/shims.test.d.ts.map +1 -0
- package/dist/lib/__tests__/shims.test.js +39 -0
- package/dist/lib/__tests__/shims.test.js.map +1 -0
- package/dist/lib/__tests__/usage.test.js +4 -2
- package/dist/lib/__tests__/usage.test.js.map +1 -1
- package/dist/lib/__tests__/versions.test.d.ts +2 -0
- package/dist/lib/__tests__/versions.test.d.ts.map +1 -0
- package/dist/lib/__tests__/versions.test.js +63 -0
- package/dist/lib/__tests__/versions.test.js.map +1 -0
- package/dist/lib/agents.d.ts +53 -1
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +178 -37
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/artifact-actions.d.ts +8 -3
- package/dist/lib/artifact-actions.d.ts.map +1 -1
- package/dist/lib/artifact-actions.js +8 -5
- package/dist/lib/artifact-actions.js.map +1 -1
- package/dist/lib/cloud/codex.d.ts +26 -0
- package/dist/lib/cloud/codex.d.ts.map +1 -0
- package/dist/lib/cloud/codex.js +237 -0
- package/dist/lib/cloud/codex.js.map +1 -0
- package/dist/lib/cloud/factory.d.ts +32 -0
- package/dist/lib/cloud/factory.d.ts.map +1 -0
- package/dist/lib/cloud/factory.js +43 -0
- package/dist/lib/cloud/factory.js.map +1 -0
- package/dist/lib/cloud/registry.d.ts +16 -0
- package/dist/lib/cloud/registry.d.ts.map +1 -0
- package/dist/lib/cloud/registry.js +68 -0
- package/dist/lib/cloud/registry.js.map +1 -0
- package/dist/lib/cloud/rush.d.ts +37 -0
- package/dist/lib/cloud/rush.d.ts.map +1 -0
- package/dist/lib/cloud/rush.js +230 -0
- package/dist/lib/cloud/rush.js.map +1 -0
- package/dist/lib/cloud/rush.test.d.ts +2 -0
- package/dist/lib/cloud/rush.test.d.ts.map +1 -0
- package/dist/lib/cloud/rush.test.js +63 -0
- package/dist/lib/cloud/rush.test.js.map +1 -0
- package/dist/lib/cloud/store.d.ts +23 -0
- package/dist/lib/cloud/store.d.ts.map +1 -0
- package/dist/lib/cloud/store.js +116 -0
- package/dist/lib/cloud/store.js.map +1 -0
- package/dist/lib/cloud/stream.d.ts +24 -0
- package/dist/lib/cloud/stream.d.ts.map +1 -0
- package/dist/lib/cloud/stream.js +145 -0
- package/dist/lib/cloud/stream.js.map +1 -0
- package/dist/lib/cloud/types.d.ts +109 -0
- package/dist/lib/cloud/types.d.ts.map +1 -0
- package/dist/lib/cloud/types.js +33 -0
- package/dist/lib/cloud/types.js.map +1 -0
- package/dist/lib/cloud/types.test.d.ts +2 -0
- package/dist/lib/cloud/types.test.d.ts.map +1 -0
- package/dist/lib/cloud/types.test.js +41 -0
- package/dist/lib/cloud/types.test.js.map +1 -0
- package/dist/lib/commands.d.ts +67 -0
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +161 -2
- package/dist/lib/commands.js.map +1 -1
- package/dist/lib/convert.d.ts +10 -0
- package/dist/lib/convert.d.ts.map +1 -1
- package/dist/lib/convert.js +9 -0
- package/dist/lib/convert.js.map +1 -1
- package/dist/lib/daemon.d.ts +21 -0
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/daemon.js +21 -0
- package/dist/lib/daemon.js.map +1 -1
- package/dist/lib/drive-sync.d.ts +18 -0
- package/dist/lib/drive-sync.d.ts.map +1 -1
- package/dist/lib/drive-sync.js +16 -0
- package/dist/lib/drive-sync.js.map +1 -1
- package/dist/lib/exec.d.ts +64 -3
- package/dist/lib/exec.d.ts.map +1 -1
- package/dist/lib/exec.js +218 -80
- package/dist/lib/exec.js.map +1 -1
- package/dist/lib/factory/__tests__/config.test.d.ts +2 -0
- package/dist/lib/factory/__tests__/config.test.d.ts.map +1 -0
- package/dist/lib/factory/__tests__/config.test.js +128 -0
- package/dist/lib/factory/__tests__/config.test.js.map +1 -0
- package/dist/lib/factory/config.d.ts +49 -0
- package/dist/lib/factory/config.d.ts.map +1 -0
- package/dist/lib/factory/config.js +127 -0
- package/dist/lib/factory/config.js.map +1 -0
- package/dist/lib/factory.js +1 -1
- package/dist/lib/factory.js.map +1 -1
- package/dist/lib/git.d.ts +16 -1
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +33 -4
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/help.d.ts +7 -0
- package/dist/lib/help.d.ts.map +1 -1
- package/dist/lib/help.js +3 -0
- package/dist/lib/help.js.map +1 -1
- package/dist/lib/hooks.d.ts +59 -3
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/hooks.js +413 -33
- package/dist/lib/hooks.js.map +1 -1
- package/dist/lib/ledger/__tests__/local.test.d.ts +2 -0
- package/dist/lib/ledger/__tests__/local.test.d.ts.map +1 -0
- package/dist/lib/ledger/__tests__/local.test.js +177 -0
- package/dist/lib/ledger/__tests__/local.test.js.map +1 -0
- package/dist/lib/ledger/__tests__/sync.test.d.ts +2 -0
- package/dist/lib/ledger/__tests__/sync.test.d.ts.map +1 -0
- package/dist/lib/ledger/__tests__/sync.test.js +117 -0
- package/dist/lib/ledger/__tests__/sync.test.js.map +1 -0
- package/dist/lib/ledger/index.d.ts +18 -0
- package/dist/lib/ledger/index.d.ts.map +1 -0
- package/dist/lib/ledger/index.js +32 -0
- package/dist/lib/ledger/index.js.map +1 -0
- package/dist/lib/ledger/local.d.ts +22 -0
- package/dist/lib/ledger/local.d.ts.map +1 -0
- package/dist/lib/ledger/local.js +333 -0
- package/dist/lib/ledger/local.js.map +1 -0
- package/dist/lib/ledger/r2.d.ts +41 -0
- package/dist/lib/ledger/r2.d.ts.map +1 -0
- package/dist/lib/ledger/r2.js +335 -0
- package/dist/lib/ledger/r2.js.map +1 -0
- package/dist/lib/ledger/sync.d.ts +33 -0
- package/dist/lib/ledger/sync.d.ts.map +1 -0
- package/dist/lib/ledger/sync.js +106 -0
- package/dist/lib/ledger/sync.js.map +1 -0
- package/dist/lib/ledger/types.d.ts +100 -0
- package/dist/lib/ledger/types.d.ts.map +1 -0
- package/dist/lib/ledger/types.js +21 -0
- package/dist/lib/ledger/types.js.map +1 -0
- package/dist/lib/manifest.d.ts +6 -0
- package/dist/lib/manifest.d.ts.map +1 -1
- package/dist/lib/manifest.js +12 -0
- package/dist/lib/manifest.js.map +1 -1
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +6 -0
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/mcp.d.ts +0 -9
- package/dist/lib/mcp.d.ts.map +1 -1
- package/dist/lib/mcp.js +0 -20
- package/dist/lib/mcp.js.map +1 -1
- package/dist/lib/memory-compile.d.ts +65 -0
- package/dist/lib/memory-compile.d.ts.map +1 -0
- package/dist/lib/memory-compile.js +174 -0
- package/dist/lib/memory-compile.js.map +1 -0
- package/dist/lib/memory.d.ts +8 -0
- package/dist/lib/memory.d.ts.map +1 -1
- package/dist/lib/memory.js +8 -0
- package/dist/lib/memory.js.map +1 -1
- package/dist/lib/models.d.ts +98 -0
- package/dist/lib/models.d.ts.map +1 -0
- package/dist/lib/models.js +728 -0
- package/dist/lib/models.js.map +1 -0
- package/dist/lib/permissions.d.ts +24 -1
- package/dist/lib/permissions.d.ts.map +1 -1
- package/dist/lib/permissions.js +52 -3
- package/dist/lib/permissions.js.map +1 -1
- package/dist/lib/picker.d.ts +27 -0
- package/dist/lib/picker.d.ts.map +1 -0
- package/dist/lib/picker.js +110 -0
- package/dist/lib/picker.js.map +1 -0
- package/dist/lib/plugins.d.ts +22 -0
- package/dist/lib/plugins.d.ts.map +1 -1
- package/dist/lib/plugins.js +114 -0
- package/dist/lib/plugins.js.map +1 -1
- package/dist/lib/profiles-keychain.d.ts +11 -0
- package/dist/lib/profiles-keychain.d.ts.map +1 -0
- package/dist/lib/profiles-keychain.js +14 -0
- package/dist/lib/profiles-keychain.js.map +1 -0
- package/dist/lib/profiles-presets.d.ts +25 -0
- package/dist/lib/profiles-presets.d.ts.map +1 -0
- package/dist/lib/profiles-presets.js +104 -0
- package/dist/lib/profiles-presets.js.map +1 -0
- package/dist/lib/profiles.d.ts +70 -0
- package/dist/lib/profiles.d.ts.map +1 -0
- package/dist/lib/profiles.js +145 -0
- package/dist/lib/profiles.js.map +1 -0
- package/dist/lib/pty-client.d.ts +1 -0
- package/dist/lib/pty-client.d.ts.map +1 -1
- package/dist/lib/pty-client.js.map +1 -1
- package/dist/lib/pty-server.d.ts +5 -0
- package/dist/lib/pty-server.d.ts.map +1 -1
- package/dist/lib/pty-server.js +5 -0
- package/dist/lib/pty-server.js.map +1 -1
- package/dist/lib/registry.d.ts +17 -0
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/registry.js +17 -0
- package/dist/lib/registry.js.map +1 -1
- package/dist/lib/resources.d.ts +5 -0
- package/dist/lib/resources.d.ts.map +1 -1
- package/dist/lib/resources.js.map +1 -1
- package/dist/lib/rotate.d.ts +58 -0
- package/dist/lib/rotate.d.ts.map +1 -0
- package/dist/lib/rotate.js +93 -0
- package/dist/lib/rotate.js.map +1 -0
- package/dist/lib/routines.d.ts +30 -1
- package/dist/lib/routines.d.ts.map +1 -1
- package/dist/lib/routines.js +31 -4
- package/dist/lib/routines.js.map +1 -1
- package/dist/lib/runner.d.ts +14 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +45 -11
- package/dist/lib/runner.js.map +1 -1
- package/dist/lib/sandbox.d.ts +16 -0
- package/dist/lib/sandbox.d.ts.map +1 -1
- package/dist/lib/sandbox.js +19 -2
- package/dist/lib/sandbox.js.map +1 -1
- package/dist/lib/scheduler.d.ts +8 -0
- package/dist/lib/scheduler.d.ts.map +1 -1
- package/dist/lib/scheduler.js +8 -0
- package/dist/lib/scheduler.js.map +1 -1
- package/dist/lib/secrets-bundles.d.ts +38 -0
- package/dist/lib/secrets-bundles.d.ts.map +1 -0
- package/dist/lib/secrets-bundles.js +176 -0
- package/dist/lib/secrets-bundles.js.map +1 -0
- package/dist/lib/secrets.d.ts +53 -0
- package/dist/lib/secrets.d.ts.map +1 -0
- package/dist/lib/secrets.js +140 -0
- package/dist/lib/secrets.js.map +1 -0
- package/dist/lib/session/__tests__/db.test.d.ts +2 -0
- package/dist/lib/session/__tests__/db.test.d.ts.map +1 -0
- package/dist/lib/session/__tests__/db.test.js +54 -0
- package/dist/lib/session/__tests__/db.test.js.map +1 -0
- package/dist/lib/session/__tests__/discover.test.js +54 -91
- package/dist/lib/session/__tests__/discover.test.js.map +1 -1
- package/dist/lib/session/__tests__/prompt.test.d.ts +2 -0
- package/dist/lib/session/__tests__/prompt.test.d.ts.map +1 -0
- package/dist/lib/session/__tests__/prompt.test.js +44 -0
- package/dist/lib/session/__tests__/prompt.test.js.map +1 -0
- package/dist/lib/session/__tests__/render.test.d.ts +2 -0
- package/dist/lib/session/__tests__/render.test.d.ts.map +1 -0
- package/dist/lib/session/__tests__/render.test.js +602 -0
- package/dist/lib/session/__tests__/render.test.js.map +1 -0
- package/dist/lib/session/artifacts.d.ts +15 -0
- package/dist/lib/session/artifacts.d.ts.map +1 -0
- package/dist/lib/session/artifacts.js +86 -0
- package/dist/lib/session/artifacts.js.map +1 -0
- package/dist/lib/session/db.d.ts +140 -0
- package/dist/lib/session/db.d.ts.map +1 -0
- package/dist/lib/session/db.js +599 -0
- package/dist/lib/session/db.js.map +1 -0
- package/dist/lib/session/discover.d.ts +44 -32
- package/dist/lib/session/discover.d.ts.map +1 -1
- package/dist/lib/session/discover.js +418 -464
- package/dist/lib/session/discover.js.map +1 -1
- package/dist/lib/session/parse.d.ts +11 -0
- package/dist/lib/session/parse.d.ts.map +1 -1
- package/dist/lib/session/parse.js +50 -0
- package/dist/lib/session/parse.js.map +1 -1
- package/dist/lib/session/prompt.d.ts +10 -0
- package/dist/lib/session/prompt.d.ts.map +1 -1
- package/dist/lib/session/prompt.js +38 -2
- package/dist/lib/session/prompt.js.map +1 -1
- package/dist/lib/session/prompt.test.d.ts +2 -0
- package/dist/lib/session/prompt.test.d.ts.map +1 -0
- package/dist/lib/session/prompt.test.js +57 -0
- package/dist/lib/session/prompt.test.js.map +1 -0
- package/dist/lib/session/render.d.ts +91 -10
- package/dist/lib/session/render.d.ts.map +1 -1
- package/dist/lib/session/render.js +708 -180
- package/dist/lib/session/render.js.map +1 -1
- package/dist/lib/session/team-filter.d.ts +35 -0
- package/dist/lib/session/team-filter.d.ts.map +1 -0
- package/dist/lib/session/team-filter.js +75 -0
- package/dist/lib/session/team-filter.js.map +1 -0
- package/dist/lib/session/team-filter.test.d.ts +2 -0
- package/dist/lib/session/team-filter.test.d.ts.map +1 -0
- package/dist/lib/session/team-filter.test.js +157 -0
- package/dist/lib/session/team-filter.test.js.map +1 -0
- package/dist/lib/session/types.d.ts +48 -6
- package/dist/lib/session/types.d.ts.map +1 -1
- package/dist/lib/session/types.js +9 -0
- package/dist/lib/session/types.js.map +1 -1
- package/dist/lib/shims.d.ts +101 -2
- package/dist/lib/shims.d.ts.map +1 -1
- package/dist/lib/shims.js +282 -25
- package/dist/lib/shims.js.map +1 -1
- package/dist/lib/skills.d.ts +68 -0
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +267 -1
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/state.d.ts +41 -2
- package/dist/lib/state.d.ts.map +1 -1
- package/dist/lib/state.js +63 -4
- package/dist/lib/state.js.map +1 -1
- package/dist/lib/subagents.d.ts +9 -0
- package/dist/lib/subagents.d.ts.map +1 -1
- package/dist/lib/subagents.js +9 -1
- package/dist/lib/subagents.js.map +1 -1
- package/dist/lib/teams/__tests__/oracle.test.d.ts +2 -0
- package/dist/lib/teams/__tests__/oracle.test.d.ts.map +1 -0
- package/dist/lib/teams/__tests__/oracle.test.js +89 -0
- package/dist/lib/teams/__tests__/oracle.test.js.map +1 -0
- package/dist/lib/teams/__tests__/supervisor.test.d.ts +2 -0
- package/dist/lib/teams/__tests__/supervisor.test.d.ts.map +1 -0
- package/dist/lib/teams/__tests__/supervisor.test.js +179 -0
- package/dist/lib/teams/__tests__/supervisor.test.js.map +1 -0
- package/dist/lib/teams/agents.d.ts +247 -0
- package/dist/lib/teams/agents.d.ts.map +1 -0
- package/dist/lib/teams/agents.js +1244 -0
- package/dist/lib/teams/agents.js.map +1 -0
- package/dist/lib/teams/api.d.ts +91 -0
- package/dist/lib/teams/api.d.ts.map +1 -0
- package/dist/lib/teams/api.js +239 -0
- package/dist/lib/teams/api.js.map +1 -0
- package/dist/lib/teams/cloud.d.ts +11 -0
- package/dist/lib/teams/cloud.d.ts.map +1 -0
- package/dist/lib/teams/cloud.js +169 -0
- package/dist/lib/teams/cloud.js.map +1 -0
- package/dist/lib/teams/debug.d.ts +8 -0
- package/dist/lib/teams/debug.d.ts.map +1 -0
- package/dist/lib/teams/debug.js +12 -0
- package/dist/lib/teams/debug.js.map +1 -0
- package/dist/lib/teams/file_ops.d.ts +13 -0
- package/dist/lib/teams/file_ops.d.ts.map +1 -0
- package/dist/lib/teams/file_ops.js +66 -0
- package/dist/lib/teams/file_ops.js.map +1 -0
- package/dist/lib/teams/index.d.ts +16 -0
- package/dist/lib/teams/index.d.ts.map +1 -0
- package/dist/lib/teams/index.js +15 -0
- package/dist/lib/teams/index.js.map +1 -0
- package/dist/lib/teams/oracle.d.ts +20 -0
- package/dist/lib/teams/oracle.d.ts.map +1 -0
- package/dist/lib/teams/oracle.js +59 -0
- package/dist/lib/teams/oracle.js.map +1 -0
- package/dist/lib/teams/parsers.d.ts +9 -0
- package/dist/lib/teams/parsers.d.ts.map +1 -0
- package/dist/lib/teams/parsers.js +837 -0
- package/dist/lib/teams/parsers.js.map +1 -0
- package/dist/lib/teams/persistence.d.ts +43 -0
- package/dist/lib/teams/persistence.d.ts.map +1 -0
- package/dist/lib/teams/persistence.js +299 -0
- package/dist/lib/teams/persistence.js.map +1 -0
- package/dist/lib/teams/ralph.d.ts +8 -0
- package/dist/lib/teams/ralph.d.ts.map +1 -0
- package/dist/lib/teams/ralph.js +59 -0
- package/dist/lib/teams/ralph.js.map +1 -0
- package/dist/lib/teams/registry.d.ts +18 -0
- package/dist/lib/teams/registry.d.ts.map +1 -0
- package/dist/lib/teams/registry.js +68 -0
- package/dist/lib/teams/registry.js.map +1 -0
- package/dist/lib/teams/summarizer.d.ts +73 -0
- package/dist/lib/teams/summarizer.d.ts.map +1 -0
- package/dist/lib/teams/summarizer.js +780 -0
- package/dist/lib/teams/summarizer.js.map +1 -0
- package/dist/lib/teams/supervisor.d.ts +49 -0
- package/dist/lib/teams/supervisor.d.ts.map +1 -0
- package/dist/lib/teams/supervisor.js +74 -0
- package/dist/lib/teams/supervisor.js.map +1 -0
- package/dist/lib/template.d.ts +8 -5
- package/dist/lib/template.d.ts.map +1 -1
- package/dist/lib/template.js +8 -5
- package/dist/lib/template.js.map +1 -1
- package/dist/lib/types.d.ts +58 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +16 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/usage.d.ts +48 -0
- package/dist/lib/usage.d.ts.map +1 -1
- package/dist/lib/usage.js +106 -14
- package/dist/lib/usage.js.map +1 -1
- package/dist/lib/versions.d.ts +12 -1
- package/dist/lib/versions.d.ts.map +1 -1
- package/dist/lib/versions.js +124 -41
- package/dist/lib/versions.js.map +1 -1
- package/package.json +20 -6
- package/scripts/postinstall.js +1 -1
- package/scripts/rebuild-sqlite.sh +46 -0
- package/dist/commands/sessions.test.d.ts +0 -2
- package/dist/commands/sessions.test.d.ts.map +0 -1
- package/dist/commands/sessions.test.js +0 -53
- package/dist/commands/sessions.test.js.map +0 -1
|
@@ -1,3 +1,11 @@
|
|
|
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
|
+
*/
|
|
1
9
|
import * as fs from 'fs';
|
|
2
10
|
import * as path from 'path';
|
|
3
11
|
import * as os from 'os';
|
|
@@ -8,79 +16,72 @@ import { AGENTS, getCliVersion } from '../agents.js';
|
|
|
8
16
|
import { getConfigSymlinkVersion } from '../shims.js';
|
|
9
17
|
import { SESSION_AGENTS } from './types.js';
|
|
10
18
|
import { extractSessionTopic } from './prompt.js';
|
|
19
|
+
import { getDB, getScanStampByPath, getScanStampsForPaths, recordScans, syncLabels, upsertSessionsBatch, querySessions, countSessions, ftsSearch, } from './db.js';
|
|
11
20
|
const HOME = os.homedir();
|
|
12
21
|
const AGENTS_DIR = path.join(HOME, '.agents');
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const CONTENT_INDEX_PATH = path.join(SESSIONS_DIR, 'content_index.jsonl');
|
|
22
|
+
/** How long OpenClaw channel/cron snapshots stay valid before we re-shell-out. */
|
|
23
|
+
const OPENCLAW_TTL_MS = 60_000;
|
|
16
24
|
let cachedOpenClawWorkspaces = null;
|
|
17
25
|
const cachedAgentVersions = new Map();
|
|
18
26
|
/**
|
|
19
|
-
* Discover sessions
|
|
20
|
-
*
|
|
21
|
-
* Returns SessionMeta[] sorted by timestamp descending (most recent first).
|
|
27
|
+
* Discover sessions. Scans only files whose (mtime, size) have changed since
|
|
28
|
+
* the last run; everything else is served from the SQLite cache.
|
|
22
29
|
*/
|
|
23
30
|
export async function discoverSessions(options) {
|
|
31
|
+
// Touch the DB so the schema is ready and connection is cached for this run.
|
|
32
|
+
getDB();
|
|
24
33
|
const agents = options?.agent ? [options.agent] : SESSION_AGENTS;
|
|
25
|
-
const
|
|
26
|
-
|
|
34
|
+
const onProgress = options?.onProgress;
|
|
35
|
+
// Incrementally re-scan changed files across all selected agents in parallel.
|
|
36
|
+
await Promise.all(agents.map(agent => {
|
|
27
37
|
switch (agent) {
|
|
28
|
-
case 'claude': return
|
|
29
|
-
case 'codex': return
|
|
30
|
-
case 'gemini': return
|
|
31
|
-
case 'opencode': return
|
|
32
|
-
case 'openclaw': return
|
|
38
|
+
case 'claude': return scanClaudeIncremental(onProgress);
|
|
39
|
+
case 'codex': return scanCodexIncremental(onProgress);
|
|
40
|
+
case 'gemini': return scanGeminiIncremental(onProgress);
|
|
41
|
+
case 'opencode': return scanOpenCodeIncremental();
|
|
42
|
+
case 'openclaw': return scanOpenClawIncremental();
|
|
33
43
|
}
|
|
34
44
|
}));
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
toSave.set(s.id, s);
|
|
50
|
-
}
|
|
51
|
-
saveIndex([...toSave.values()]);
|
|
52
|
-
// Build BM25 content index for all discovered sessions
|
|
53
|
-
const bm25 = buildBM25Index(sessions);
|
|
54
|
-
saveBM25Index(bm25);
|
|
45
|
+
const sessions = querySessions(buildQueryOptions(options, agents, { includeLimit: true }));
|
|
46
|
+
return sessions;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Count sessions in scope without running an incremental scan. Assumes the DB
|
|
50
|
+
* is already fresh (typically true because `discoverSessions` ran first this
|
|
51
|
+
* turn). Uses the exact same filter shape as the discover query.
|
|
52
|
+
*/
|
|
53
|
+
export function countSessionsInScope(options) {
|
|
54
|
+
const agents = options.agent ? [options.agent] : SESSION_AGENTS;
|
|
55
|
+
return countSessions(buildQueryOptions(options, agents, { includeLimit: false }));
|
|
56
|
+
}
|
|
57
|
+
/** Translate DiscoverOptions into the QueryOptions shape expected by the DB layer. */
|
|
58
|
+
function buildQueryOptions(options, agents, opts) {
|
|
55
59
|
const projectQuery = options?.project?.trim();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (options?.since || options?.until) {
|
|
63
|
-
const sinceMs = options.since ? parseTimeFilter(options.since) : 0;
|
|
64
|
-
const untilMs = options.until ? new Date(options.until).getTime() : Infinity;
|
|
65
|
-
sessions = sessions.filter(s => {
|
|
66
|
-
const ts = new Date(s.timestamp).getTime();
|
|
67
|
-
return ts >= sinceMs && ts <= untilMs;
|
|
68
|
-
});
|
|
60
|
+
const sinceMs = options?.since ? parseTimeFilter(options.since) : undefined;
|
|
61
|
+
const untilMs = options?.until ? new Date(options.until).getTime() : undefined;
|
|
62
|
+
let cwdFilter;
|
|
63
|
+
let cwdPrefixFilter;
|
|
64
|
+
if (options?.cwdPrefix) {
|
|
65
|
+
cwdPrefixFilter = normalizeCwd(options.cwdPrefix);
|
|
69
66
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!options?.all && !projectQuery) {
|
|
73
|
-
const currentDir = normalizeCwd(options?.cwd || process.cwd());
|
|
74
|
-
sessions = sessions.filter(s => normalizeCwd(s.cwd) === currentDir);
|
|
67
|
+
else if (!options?.all && !projectQuery) {
|
|
68
|
+
cwdFilter = normalizeCwd(options?.cwd || process.cwd());
|
|
75
69
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
return {
|
|
71
|
+
agent: options?.agent,
|
|
72
|
+
agents: options?.agent ? undefined : agents,
|
|
73
|
+
version: options?.version,
|
|
74
|
+
cwd: cwdFilter,
|
|
75
|
+
cwdPrefix: cwdPrefixFilter,
|
|
76
|
+
project: projectQuery,
|
|
77
|
+
sinceMs,
|
|
78
|
+
untilMs: Number.isFinite(untilMs) ? untilMs : undefined,
|
|
79
|
+
limit: opts.includeLimit ? (options?.limit ?? 50) : undefined,
|
|
80
|
+
excludeTeamOrigin: options?.excludeTeamOrigin,
|
|
81
|
+
onlyTeamOrigin: options?.onlyTeamOrigin,
|
|
82
|
+
};
|
|
83
83
|
}
|
|
84
|
+
/** Resolve and canonicalize a working directory path (follows symlinks). */
|
|
84
85
|
function normalizeCwd(cwd) {
|
|
85
86
|
if (!cwd)
|
|
86
87
|
return '';
|
|
@@ -88,195 +89,76 @@ function normalizeCwd(cwd) {
|
|
|
88
89
|
return safeRealpathSync(resolved) || resolved;
|
|
89
90
|
}
|
|
90
91
|
/**
|
|
91
|
-
* Resolve a session by full or short ID
|
|
92
|
+
* Resolve a session by full or short ID. Accepts a pre-loaded session list
|
|
93
|
+
* (fast path from discoverSessions) and falls back to a DB lookup for the
|
|
94
|
+
* "I only know the id" case.
|
|
92
95
|
*/
|
|
93
96
|
export function resolveSessionById(sessions, idQuery) {
|
|
94
97
|
const query = idQuery.toLowerCase();
|
|
95
|
-
// Exact match first (full id or shortId)
|
|
96
98
|
const exact = sessions.filter(s => s.id.toLowerCase() === query || s.shortId.toLowerCase() === query);
|
|
97
99
|
if (exact.length > 0)
|
|
98
100
|
return exact;
|
|
99
|
-
// Prefix match (against both id and shortId)
|
|
100
101
|
return sessions.filter(s => s.id.toLowerCase().startsWith(query) || s.shortId.toLowerCase().startsWith(query));
|
|
101
102
|
}
|
|
102
103
|
// ---------------------------------------------------------------------------
|
|
103
|
-
//
|
|
104
|
+
// Content-index search (FTS5-backed)
|
|
104
105
|
// ---------------------------------------------------------------------------
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
function saveIndex(sessions) {
|
|
128
|
-
try {
|
|
129
|
-
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
130
|
-
// Deduplicate by id, keeping the first occurrence (live sessions take priority)
|
|
131
|
-
const seen = new Set();
|
|
132
|
-
const lines = [];
|
|
133
|
-
for (const s of sessions) {
|
|
134
|
-
if (seen.has(s.id))
|
|
135
|
-
continue;
|
|
136
|
-
seen.add(s.id);
|
|
137
|
-
lines.push(JSON.stringify(s));
|
|
138
|
-
}
|
|
139
|
-
fs.writeFileSync(INDEX_PATH, lines.join('\n') + '\n', 'utf-8');
|
|
140
|
-
}
|
|
141
|
-
catch (err) {
|
|
142
|
-
console.error(`Warning: Could not save session cache: ${err.message}`);
|
|
106
|
+
/**
|
|
107
|
+
* Run an FTS5 search over the DB and intersect with the given session list,
|
|
108
|
+
* preserving the existing SessionMeta[] contract so sessions.ts is unchanged.
|
|
109
|
+
*/
|
|
110
|
+
export function searchContentIndex(sessions, query) {
|
|
111
|
+
if (!query.trim())
|
|
112
|
+
return new Map();
|
|
113
|
+
const hits = ftsSearch(query);
|
|
114
|
+
if (hits.length === 0)
|
|
115
|
+
return new Map();
|
|
116
|
+
const byId = new Map(sessions.map(s => [s.id, s]));
|
|
117
|
+
const result = new Map();
|
|
118
|
+
for (const hit of hits) {
|
|
119
|
+
const session = byId.get(hit.sessionId);
|
|
120
|
+
if (!session)
|
|
121
|
+
continue;
|
|
122
|
+
result.set(hit.sessionId, {
|
|
123
|
+
...session,
|
|
124
|
+
_matchedTerms: hit.matchedTerms,
|
|
125
|
+
_bm25Score: hit.score,
|
|
126
|
+
});
|
|
143
127
|
}
|
|
128
|
+
return result;
|
|
144
129
|
}
|
|
145
130
|
// ---------------------------------------------------------------------------
|
|
146
|
-
//
|
|
131
|
+
// Incremental scan orchestration
|
|
147
132
|
// ---------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
for (const
|
|
156
|
-
|
|
133
|
+
/**
|
|
134
|
+
* For a list of files, stat each, compare to the DB ledger, and return only
|
|
135
|
+
* the ones that need rescanning. One bulk DB query for the whole list.
|
|
136
|
+
*/
|
|
137
|
+
function filterChangedFiles(filePaths) {
|
|
138
|
+
const ledger = getScanStampsForPaths(filePaths);
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const filePath of filePaths) {
|
|
141
|
+
const stat = safeStatSync(filePath);
|
|
142
|
+
if (!stat)
|
|
157
143
|
continue;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const parts = [];
|
|
165
|
-
if (s.topic)
|
|
166
|
-
parts.push(s.topic);
|
|
167
|
-
if (s.project)
|
|
168
|
-
parts.push(s.project);
|
|
169
|
-
if (s.cwd)
|
|
170
|
-
parts.push(s.cwd);
|
|
171
|
-
if (s.gitBranch)
|
|
172
|
-
parts.push(s.gitBranch);
|
|
173
|
-
if (s.account)
|
|
174
|
-
parts.push(s.account);
|
|
175
|
-
if (s._userTerms)
|
|
176
|
-
parts.push(s._userTerms.join('\n'));
|
|
177
|
-
return parts.join('\n');
|
|
178
|
-
}
|
|
179
|
-
export function buildBM25Index(sessions) {
|
|
180
|
-
const docLengths = new Map();
|
|
181
|
-
const postings = new Map();
|
|
182
|
-
let totalLength = 0;
|
|
183
|
-
for (const session of sessions) {
|
|
184
|
-
const { length, counts } = tokenizeCounted(collectSessionText(session));
|
|
185
|
-
docLengths.set(session.id, length);
|
|
186
|
-
totalLength += length;
|
|
187
|
-
for (const [term, tf] of counts) {
|
|
188
|
-
let termPostings = postings.get(term);
|
|
189
|
-
if (!termPostings) {
|
|
190
|
-
termPostings = new Map();
|
|
191
|
-
postings.set(term, termPostings);
|
|
192
|
-
}
|
|
193
|
-
termPostings.set(session.id, tf);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const N = sessions.length;
|
|
197
|
-
const avgdl = N > 0 ? totalLength / N : 0;
|
|
198
|
-
return { N, avgdl, docLengths, postings };
|
|
199
|
-
}
|
|
200
|
-
function saveBM25Index(index) {
|
|
201
|
-
try {
|
|
202
|
-
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
203
|
-
const lines = [];
|
|
204
|
-
lines.push(JSON.stringify({ v: BM25_INDEX_VERSION, N: index.N, avgdl: index.avgdl }));
|
|
205
|
-
for (const [sid, len] of index.docLengths) {
|
|
206
|
-
lines.push(JSON.stringify({ d: sid, l: len }));
|
|
207
|
-
}
|
|
208
|
-
for (const [term, termPostings] of index.postings) {
|
|
209
|
-
const p = [];
|
|
210
|
-
for (const [sid, tf] of termPostings)
|
|
211
|
-
p.push([sid, tf]);
|
|
212
|
-
lines.push(JSON.stringify({ t: term, p }));
|
|
213
|
-
}
|
|
214
|
-
fs.writeFileSync(CONTENT_INDEX_PATH, lines.join('\n') + '\n', 'utf-8');
|
|
215
|
-
}
|
|
216
|
-
catch { /* non-fatal */ }
|
|
217
|
-
}
|
|
218
|
-
function loadBM25Index() {
|
|
219
|
-
if (!fs.existsSync(CONTENT_INDEX_PATH))
|
|
220
|
-
return null;
|
|
221
|
-
let content;
|
|
222
|
-
try {
|
|
223
|
-
content = fs.readFileSync(CONTENT_INDEX_PATH, 'utf-8');
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
const lines = content.split('\n').filter(l => l.trim());
|
|
229
|
-
if (lines.length === 0)
|
|
230
|
-
return null;
|
|
231
|
-
let header;
|
|
232
|
-
try {
|
|
233
|
-
header = JSON.parse(lines[0]);
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
if (header?.v !== BM25_INDEX_VERSION)
|
|
239
|
-
return null;
|
|
240
|
-
const index = {
|
|
241
|
-
N: Number(header.N) || 0,
|
|
242
|
-
avgdl: Number(header.avgdl) || 0,
|
|
243
|
-
docLengths: new Map(),
|
|
244
|
-
postings: new Map(),
|
|
245
|
-
};
|
|
246
|
-
for (let i = 1; i < lines.length; i++) {
|
|
247
|
-
let entry;
|
|
248
|
-
try {
|
|
249
|
-
entry = JSON.parse(lines[i]);
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
144
|
+
const scan = {
|
|
145
|
+
fileMtimeMs: Math.floor(stat.mtimeMs),
|
|
146
|
+
fileSize: stat.size,
|
|
147
|
+
};
|
|
148
|
+
const prev = ledger.get(filePath);
|
|
149
|
+
if (prev && prev.fileMtimeMs === scan.fileMtimeMs && prev.fileSize === scan.fileSize) {
|
|
252
150
|
continue;
|
|
253
151
|
}
|
|
254
|
-
|
|
255
|
-
index.docLengths.set(entry.d, entry.l);
|
|
256
|
-
}
|
|
257
|
-
else if (typeof entry.t === 'string' && Array.isArray(entry.p)) {
|
|
258
|
-
const termPostings = new Map();
|
|
259
|
-
for (const pair of entry.p) {
|
|
260
|
-
if (Array.isArray(pair) && pair.length >= 2) {
|
|
261
|
-
termPostings.set(String(pair[0]), Number(pair[1]));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
index.postings.set(entry.t, termPostings);
|
|
265
|
-
}
|
|
152
|
+
out.push({ filePath, scan });
|
|
266
153
|
}
|
|
267
|
-
return
|
|
154
|
+
return out;
|
|
268
155
|
}
|
|
269
156
|
// ---------------------------------------------------------------------------
|
|
270
157
|
// Multi-version directory scanning
|
|
271
158
|
// ---------------------------------------------------------------------------
|
|
272
159
|
/**
|
|
273
|
-
* Collect all directories to scan for an agent's sessions.
|
|
274
|
-
*
|
|
275
|
-
* Deduplicates by realpath to avoid double-counting the active symlink.
|
|
276
|
-
*
|
|
277
|
-
* @param agent - Agent name (claude, codex, gemini)
|
|
278
|
-
* @param subdir - Subdirectory within the agent's config dir where sessions live
|
|
279
|
-
* (e.g., 'projects' for Claude, 'sessions' for Codex, 'tmp' for Gemini)
|
|
160
|
+
* Collect all directories to scan for an agent's sessions. Deduplicates by
|
|
161
|
+
* realpath to avoid double-counting symlinked version homes.
|
|
280
162
|
*/
|
|
281
163
|
export function getAgentSessionDirs(agent, subdir) {
|
|
282
164
|
const resolved = new Set();
|
|
@@ -291,9 +173,7 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
291
173
|
resolved.add(key);
|
|
292
174
|
dirs.push(dir);
|
|
293
175
|
}
|
|
294
|
-
// 1. Active config (may be a symlink to the current version's home)
|
|
295
176
|
addDir(path.join(HOME, `.${agent}`, subdir));
|
|
296
|
-
// 2. All installed version homes
|
|
297
177
|
const versionsBase = path.join(AGENTS_DIR, 'versions', agent);
|
|
298
178
|
if (fs.existsSync(versionsBase)) {
|
|
299
179
|
try {
|
|
@@ -301,9 +181,8 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
301
181
|
addDir(path.join(versionsBase, version, 'home', `.${agent}`, subdir));
|
|
302
182
|
}
|
|
303
183
|
}
|
|
304
|
-
catch { /* dir unreadable
|
|
184
|
+
catch { /* dir unreadable */ }
|
|
305
185
|
}
|
|
306
|
-
// 3. Backups (from before version management was enabled)
|
|
307
186
|
const backupsBase = path.join(AGENTS_DIR, 'backups', agent);
|
|
308
187
|
if (fs.existsSync(backupsBase)) {
|
|
309
188
|
try {
|
|
@@ -311,7 +190,7 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
311
190
|
addDir(path.join(backupsBase, ts, subdir));
|
|
312
191
|
}
|
|
313
192
|
}
|
|
314
|
-
catch { /* dir unreadable
|
|
193
|
+
catch { /* dir unreadable */ }
|
|
315
194
|
}
|
|
316
195
|
return dirs;
|
|
317
196
|
}
|
|
@@ -319,18 +198,22 @@ export function getAgentSessionDirs(agent, subdir) {
|
|
|
319
198
|
// Claude account info
|
|
320
199
|
// ---------------------------------------------------------------------------
|
|
321
200
|
let cachedClaudeAccount;
|
|
201
|
+
/** Read the Claude OAuth account email from .claude.json across all version homes. */
|
|
322
202
|
function getClaudeAccount() {
|
|
323
203
|
if (cachedClaudeAccount !== undefined)
|
|
324
204
|
return cachedClaudeAccount || undefined;
|
|
325
|
-
//
|
|
205
|
+
// Claude's active config lives at $CLAUDE_CONFIG_DIR/.claude.json; for our shim
|
|
206
|
+
// that's <version>/home/.claude/.claude.json. The home-level .claude.json is a
|
|
207
|
+
// legacy path used when Claude runs without CLAUDE_CONFIG_DIR set.
|
|
326
208
|
const candidates = [
|
|
209
|
+
path.join(HOME, '.claude', '.claude.json'),
|
|
327
210
|
path.join(HOME, '.claude.json'),
|
|
328
211
|
];
|
|
329
|
-
// Also check version homes (auth files are symlinked there)
|
|
330
212
|
const versionsBase = path.join(AGENTS_DIR, 'versions', 'claude');
|
|
331
213
|
if (fs.existsSync(versionsBase)) {
|
|
332
214
|
try {
|
|
333
215
|
for (const version of fs.readdirSync(versionsBase)) {
|
|
216
|
+
candidates.push(path.join(versionsBase, version, 'home', '.claude', '.claude.json'));
|
|
334
217
|
candidates.push(path.join(versionsBase, version, 'home', '.claude.json'));
|
|
335
218
|
}
|
|
336
219
|
}
|
|
@@ -355,11 +238,50 @@ function getClaudeAccount() {
|
|
|
355
238
|
// ---------------------------------------------------------------------------
|
|
356
239
|
// Claude
|
|
357
240
|
// ---------------------------------------------------------------------------
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Build a map of Claude sessionId -> user-given label from ~/.claude/sessions/*.json.
|
|
243
|
+
* Each JSON has shape { pid, sessionId, cwd, startedAt, name?, ... }. The
|
|
244
|
+
* `name` field only exists if the user ran /rename in that session.
|
|
245
|
+
* For sessionId collisions (re-resume of the same session), prefer the most
|
|
246
|
+
* recent startedAt.
|
|
247
|
+
*/
|
|
248
|
+
function buildClaudeLabelMap() {
|
|
249
|
+
const map = new Map();
|
|
250
|
+
const dir = path.join(HOME, '.claude', 'sessions');
|
|
251
|
+
if (!fs.existsSync(dir))
|
|
252
|
+
return new Map();
|
|
253
|
+
let files;
|
|
254
|
+
try {
|
|
255
|
+
files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return new Map();
|
|
259
|
+
}
|
|
260
|
+
for (const f of files) {
|
|
261
|
+
try {
|
|
262
|
+
const data = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf-8'));
|
|
263
|
+
if (typeof data.sessionId !== 'string')
|
|
264
|
+
continue;
|
|
265
|
+
const name = typeof data.name === 'string' && data.name.trim() ? data.name.trim() : null;
|
|
266
|
+
const startedAt = typeof data.startedAt === 'number' ? data.startedAt : 0;
|
|
267
|
+
const existing = map.get(data.sessionId);
|
|
268
|
+
if (!existing || startedAt > existing.startedAt) {
|
|
269
|
+
map.set(data.sessionId, { label: name, startedAt });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch { /* unreadable session metadata file */ }
|
|
273
|
+
}
|
|
274
|
+
const out = new Map();
|
|
275
|
+
for (const [sid, { label }] of map)
|
|
276
|
+
out.set(sid, label);
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
/** Incrementally re-scan changed Claude session files and upsert into the DB. */
|
|
280
|
+
async function scanClaudeIncremental(onProgress) {
|
|
361
281
|
const account = getClaudeAccount();
|
|
362
|
-
|
|
282
|
+
const labelMap = buildClaudeLabelMap();
|
|
283
|
+
const filePaths = [];
|
|
284
|
+
const seen = new Set();
|
|
363
285
|
for (const projectsDir of getAgentSessionDirs('claude', 'projects')) {
|
|
364
286
|
let projectDirs;
|
|
365
287
|
try {
|
|
@@ -385,28 +307,50 @@ async function discoverClaudeSessions() {
|
|
|
385
307
|
if (seen.has(sessionId))
|
|
386
308
|
continue;
|
|
387
309
|
seen.add(sessionId);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
310
|
+
filePaths.push(path.join(dirPath, file));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const changed = filterChangedFiles(filePaths);
|
|
315
|
+
if (changed.length > 0) {
|
|
316
|
+
onProgress?.({ agent: 'claude', parsed: 0, total: changed.length });
|
|
317
|
+
const entries = [];
|
|
318
|
+
const touched = [];
|
|
319
|
+
let parsed = 0;
|
|
320
|
+
for (const { filePath, scan } of changed) {
|
|
321
|
+
try {
|
|
322
|
+
const sessionId = path.basename(filePath).replace('.jsonl', '');
|
|
323
|
+
const label = labelMap.get(sessionId) ?? undefined;
|
|
324
|
+
const result = await readClaudeMeta(filePath, sessionId, account, label);
|
|
325
|
+
if (result) {
|
|
326
|
+
entries.push({ meta: result.meta, content: result.content, scan });
|
|
393
327
|
}
|
|
394
|
-
|
|
395
|
-
|
|
328
|
+
else {
|
|
329
|
+
touched.push({ filePath, scan });
|
|
396
330
|
}
|
|
397
331
|
}
|
|
332
|
+
catch {
|
|
333
|
+
touched.push({ filePath, scan });
|
|
334
|
+
}
|
|
335
|
+
parsed++;
|
|
336
|
+
onProgress?.({ agent: 'claude', parsed, total: changed.length });
|
|
398
337
|
}
|
|
338
|
+
upsertSessionsBatch(entries);
|
|
339
|
+
recordScans(touched);
|
|
399
340
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
341
|
+
// Pick up /rename changes on sessions whose JSONL didn't change.
|
|
342
|
+
// Only bother for sessions we actually have a Claude row for.
|
|
343
|
+
if (labelMap.size > 0)
|
|
344
|
+
syncLabels(labelMap);
|
|
404
345
|
}
|
|
405
|
-
|
|
346
|
+
/** Stream-parse a single Claude JSONL file to extract session metadata. */
|
|
347
|
+
async function readClaudeMeta(filePath, sessionId, account, label) {
|
|
406
348
|
const scan = await scanClaudeSession(filePath);
|
|
349
|
+
const isTeamOrigin = scan.entrypoint === 'sdk-cli';
|
|
350
|
+
let meta;
|
|
407
351
|
if (scan.timestamp) {
|
|
408
|
-
const cwd = scan.cwd || '';
|
|
409
|
-
|
|
352
|
+
const cwd = normalizeCwd(scan.cwd || '');
|
|
353
|
+
meta = {
|
|
410
354
|
id: sessionId,
|
|
411
355
|
shortId: sessionId.slice(0, 8),
|
|
412
356
|
agent: 'claude',
|
|
@@ -418,37 +362,39 @@ async function readClaudeMeta(filePath, sessionId, account) {
|
|
|
418
362
|
version: scan.version,
|
|
419
363
|
account,
|
|
420
364
|
topic: scan.topic,
|
|
365
|
+
label,
|
|
421
366
|
messageCount: scan.messageCount,
|
|
422
367
|
tokenCount: scan.tokenCount,
|
|
423
|
-
|
|
368
|
+
isTeamOrigin,
|
|
424
369
|
};
|
|
425
370
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
371
|
+
else {
|
|
372
|
+
const stat = safeStatSync(filePath);
|
|
373
|
+
meta = {
|
|
374
|
+
id: sessionId,
|
|
375
|
+
shortId: sessionId.slice(0, 8),
|
|
376
|
+
agent: 'claude',
|
|
377
|
+
timestamp: stat ? stat.mtime.toISOString() : new Date().toISOString(),
|
|
378
|
+
filePath,
|
|
379
|
+
account,
|
|
380
|
+
label,
|
|
381
|
+
messageCount: scan.messageCount,
|
|
382
|
+
tokenCount: scan.tokenCount,
|
|
383
|
+
topic: scan.topic,
|
|
384
|
+
isTeamOrigin,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return { meta, content: scan.contentText || '' };
|
|
440
388
|
}
|
|
441
389
|
// ---------------------------------------------------------------------------
|
|
442
390
|
// Codex account info
|
|
443
391
|
// ---------------------------------------------------------------------------
|
|
444
392
|
let cachedCodexAccount;
|
|
393
|
+
/** Extract the Codex account email from the JWT id_token in auth.json. */
|
|
445
394
|
function getCodexAccount() {
|
|
446
395
|
if (cachedCodexAccount !== undefined)
|
|
447
396
|
return cachedCodexAccount || undefined;
|
|
448
|
-
const candidates = [
|
|
449
|
-
path.join(HOME, '.codex', 'auth.json'),
|
|
450
|
-
];
|
|
451
|
-
// Also check version homes
|
|
397
|
+
const candidates = [path.join(HOME, '.codex', 'auth.json')];
|
|
452
398
|
const versionsBase = path.join(AGENTS_DIR, 'versions', 'codex');
|
|
453
399
|
if (fs.existsSync(versionsBase)) {
|
|
454
400
|
try {
|
|
@@ -463,7 +409,6 @@ function getCodexAccount() {
|
|
|
463
409
|
if (!fs.existsSync(candidate))
|
|
464
410
|
continue;
|
|
465
411
|
const data = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
|
|
466
|
-
// Extract email from JWT id_token payload
|
|
467
412
|
const idToken = data.tokens?.id_token;
|
|
468
413
|
if (idToken) {
|
|
469
414
|
const parts = idToken.split('.');
|
|
@@ -484,39 +429,53 @@ function getCodexAccount() {
|
|
|
484
429
|
// ---------------------------------------------------------------------------
|
|
485
430
|
// Codex
|
|
486
431
|
// ---------------------------------------------------------------------------
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const seen = new Set();
|
|
432
|
+
/** Incrementally re-scan changed Codex session files and upsert into the DB. */
|
|
433
|
+
async function scanCodexIncremental(onProgress) {
|
|
490
434
|
const account = getCodexAccount();
|
|
491
435
|
const currentVersion = await getCurrentAgentVersion('codex');
|
|
492
|
-
|
|
436
|
+
const filePaths = [];
|
|
493
437
|
for (const sessionsDir of getAgentSessionDirs('codex', 'sessions')) {
|
|
494
|
-
|
|
495
|
-
for (const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
438
|
+
// High limit: we only stat files here, parsing is gated by ledger match.
|
|
439
|
+
for (const fp of walkForFiles(sessionsDir, '.jsonl', 100_000)) {
|
|
440
|
+
filePaths.push(fp);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const changed = filterChangedFiles(filePaths);
|
|
444
|
+
if (changed.length === 0)
|
|
445
|
+
return;
|
|
446
|
+
onProgress?.({ agent: 'codex', parsed: 0, total: changed.length });
|
|
447
|
+
const entries = [];
|
|
448
|
+
const touched = [];
|
|
449
|
+
const seen = new Set();
|
|
450
|
+
let parsed = 0;
|
|
451
|
+
for (const { filePath, scan } of changed) {
|
|
452
|
+
try {
|
|
453
|
+
const result = await readCodexMeta(filePath, account, currentVersion);
|
|
454
|
+
if (result && !seen.has(result.meta.id)) {
|
|
455
|
+
seen.add(result.meta.id);
|
|
456
|
+
entries.push({ meta: result.meta, content: result.content, scan });
|
|
502
457
|
}
|
|
503
|
-
|
|
504
|
-
|
|
458
|
+
else {
|
|
459
|
+
touched.push({ filePath, scan });
|
|
505
460
|
}
|
|
506
461
|
}
|
|
462
|
+
catch {
|
|
463
|
+
touched.push({ filePath, scan });
|
|
464
|
+
}
|
|
465
|
+
parsed++;
|
|
466
|
+
onProgress?.({ agent: 'codex', parsed, total: changed.length });
|
|
507
467
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
return sessions;
|
|
468
|
+
upsertSessionsBatch(entries);
|
|
469
|
+
recordScans(touched);
|
|
512
470
|
}
|
|
471
|
+
/** Stream-parse a single Codex JSONL file to extract session metadata. */
|
|
513
472
|
async function readCodexMeta(filePath, account, currentVersion) {
|
|
514
473
|
const scan = await scanCodexSession(filePath);
|
|
515
474
|
const sessionId = scan.sessionId || '';
|
|
516
475
|
if (!sessionId)
|
|
517
476
|
return null;
|
|
518
|
-
const cwd = scan.cwd || '';
|
|
519
|
-
|
|
477
|
+
const cwd = normalizeCwd(scan.cwd || '');
|
|
478
|
+
const meta = {
|
|
520
479
|
id: sessionId,
|
|
521
480
|
shortId: sessionId.slice(0, 8),
|
|
522
481
|
agent: 'codex',
|
|
@@ -530,18 +489,17 @@ async function readCodexMeta(filePath, account, currentVersion) {
|
|
|
530
489
|
messageCount: scan.messageCount,
|
|
531
490
|
tokenCount: scan.tokenCount,
|
|
532
491
|
account,
|
|
533
|
-
_userTerms: scan.userTerms?.flatMap(splitLines),
|
|
534
492
|
};
|
|
493
|
+
return { meta, content: scan.contentText || '' };
|
|
535
494
|
}
|
|
536
495
|
// ---------------------------------------------------------------------------
|
|
537
496
|
// Gemini
|
|
538
497
|
// ---------------------------------------------------------------------------
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const sessions = [];
|
|
542
|
-
const seen = new Set();
|
|
498
|
+
/** Incrementally re-scan changed Gemini session files and upsert into the DB. */
|
|
499
|
+
async function scanGeminiIncremental(onProgress) {
|
|
543
500
|
const currentVersion = await getCurrentAgentVersion('gemini');
|
|
544
|
-
|
|
501
|
+
const projectMap = buildGeminiProjectMap();
|
|
502
|
+
const filePaths = [];
|
|
545
503
|
for (const tmpDir of getAgentSessionDirs('gemini', 'tmp')) {
|
|
546
504
|
let hashDirs;
|
|
547
505
|
try {
|
|
@@ -562,25 +520,44 @@ async function discoverGeminiSessions() {
|
|
|
562
520
|
continue;
|
|
563
521
|
}
|
|
564
522
|
for (const file of chatFiles) {
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
const meta = readGeminiMeta(filePath, hashDir, projectMap, currentVersion);
|
|
568
|
-
if (meta && !seen.has(meta.id)) {
|
|
569
|
-
seen.add(meta.id);
|
|
570
|
-
sessions.push(meta);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
catch {
|
|
574
|
-
skipped++;
|
|
575
|
-
}
|
|
523
|
+
filePaths.push({ filePath: path.join(chatsDir, file), hashDir });
|
|
576
524
|
}
|
|
577
525
|
}
|
|
578
526
|
}
|
|
579
|
-
|
|
580
|
-
|
|
527
|
+
const changedPaths = filterChangedFiles(filePaths.map(f => f.filePath));
|
|
528
|
+
const changedByPath = new Map(changedPaths.map(c => [c.filePath, c.scan]));
|
|
529
|
+
if (changedByPath.size === 0)
|
|
530
|
+
return;
|
|
531
|
+
onProgress?.({ agent: 'gemini', parsed: 0, total: changedByPath.size });
|
|
532
|
+
const entries = [];
|
|
533
|
+
const touched = [];
|
|
534
|
+
const seen = new Set();
|
|
535
|
+
let parsed = 0;
|
|
536
|
+
for (const { filePath, hashDir } of filePaths) {
|
|
537
|
+
const scan = changedByPath.get(filePath);
|
|
538
|
+
if (!scan)
|
|
539
|
+
continue;
|
|
540
|
+
try {
|
|
541
|
+
const result = readGeminiMeta(filePath, hashDir, projectMap, currentVersion);
|
|
542
|
+
if (result && !seen.has(result.meta.id)) {
|
|
543
|
+
seen.add(result.meta.id);
|
|
544
|
+
entries.push({ meta: result.meta, content: result.content, scan });
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
// Gemini file without a sessionId — record scan so we don't re-parse it next run.
|
|
548
|
+
touched.push({ filePath, scan });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
touched.push({ filePath, scan });
|
|
553
|
+
}
|
|
554
|
+
parsed++;
|
|
555
|
+
onProgress?.({ agent: 'gemini', parsed, total: changedByPath.size });
|
|
581
556
|
}
|
|
582
|
-
|
|
557
|
+
upsertSessionsBatch(entries);
|
|
558
|
+
recordScans(touched);
|
|
583
559
|
}
|
|
560
|
+
/** Parse a single Gemini JSON session file to extract session metadata. */
|
|
584
561
|
function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
585
562
|
let session;
|
|
586
563
|
try {
|
|
@@ -599,23 +576,22 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
599
576
|
: undefined;
|
|
600
577
|
if (!sessionId)
|
|
601
578
|
return null;
|
|
602
|
-
// Resolve project name from hash
|
|
603
579
|
const projectInfo = projectMap.get(projectHash || hashDir);
|
|
604
580
|
const project = projectInfo?.name || hashDir.slice(0, 12);
|
|
605
|
-
const cwd = projectInfo?.path;
|
|
581
|
+
const cwd = projectInfo?.path ? normalizeCwd(projectInfo.path) : undefined;
|
|
606
582
|
const stat = safeStatSync(filePath);
|
|
607
583
|
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
608
584
|
let topic;
|
|
609
585
|
let messageCount = 0;
|
|
610
586
|
let tokenCount = 0;
|
|
611
587
|
let sawTokenCount = false;
|
|
612
|
-
const
|
|
588
|
+
const userTexts = [];
|
|
613
589
|
for (const message of messages) {
|
|
614
590
|
if (message.type === 'user') {
|
|
615
591
|
const text = extractGeminiMessageText(message.content);
|
|
616
592
|
if (text) {
|
|
617
593
|
messageCount++;
|
|
618
|
-
|
|
594
|
+
userTexts.push(text);
|
|
619
595
|
if (!topic)
|
|
620
596
|
topic = extractSessionTopic(text);
|
|
621
597
|
}
|
|
@@ -631,7 +607,7 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
631
607
|
sawTokenCount = true;
|
|
632
608
|
}
|
|
633
609
|
}
|
|
634
|
-
|
|
610
|
+
const meta = {
|
|
635
611
|
id: sessionId,
|
|
636
612
|
shortId: sessionId.slice(0, 8),
|
|
637
613
|
agent: 'gemini',
|
|
@@ -643,40 +619,37 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
643
619
|
topic,
|
|
644
620
|
messageCount,
|
|
645
621
|
tokenCount: sawTokenCount ? tokenCount : undefined,
|
|
646
|
-
_userTerms: userTerms.length > 0 ? userTerms : undefined,
|
|
647
622
|
};
|
|
623
|
+
return { meta, content: userTexts.join('\n') };
|
|
648
624
|
}
|
|
625
|
+
/** Build a hash-to-project mapping from Gemini's projects.json and history directories. */
|
|
649
626
|
function buildGeminiProjectMap() {
|
|
650
627
|
const map = new Map();
|
|
651
628
|
const projectsJsonPath = path.join(HOME, '.gemini', 'projects.json');
|
|
652
|
-
if (
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
// Also try the raw directory name
|
|
665
|
-
map.set(p, { name: path.basename(p), path: p });
|
|
629
|
+
if (fs.existsSync(projectsJsonPath)) {
|
|
630
|
+
try {
|
|
631
|
+
const data = JSON.parse(fs.readFileSync(projectsJsonPath, 'utf-8'));
|
|
632
|
+
const projects = data.projects;
|
|
633
|
+
if (typeof projects === 'object' && projects !== null) {
|
|
634
|
+
if (Array.isArray(projects)) {
|
|
635
|
+
for (const p of projects) {
|
|
636
|
+
if (typeof p === 'string') {
|
|
637
|
+
const hash = sha256(p);
|
|
638
|
+
map.set(hash, { name: path.basename(p), path: p });
|
|
639
|
+
map.set(p, { name: path.basename(p), path: p });
|
|
640
|
+
}
|
|
666
641
|
}
|
|
667
642
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
map.set(hash, { name: String(name), path: p });
|
|
643
|
+
else {
|
|
644
|
+
for (const [p, name] of Object.entries(projects)) {
|
|
645
|
+
const hash = sha256(p);
|
|
646
|
+
map.set(hash, { name: String(name), path: p });
|
|
647
|
+
}
|
|
674
648
|
}
|
|
675
649
|
}
|
|
676
650
|
}
|
|
651
|
+
catch { /* projects.json missing or malformed */ }
|
|
677
652
|
}
|
|
678
|
-
catch { /* projects.json missing or malformed */ }
|
|
679
|
-
// Also check ~/.gemini/history/*/.project_root for additional mappings
|
|
680
653
|
const historyDir = path.join(HOME, '.gemini', 'history');
|
|
681
654
|
if (fs.existsSync(historyDir)) {
|
|
682
655
|
try {
|
|
@@ -703,10 +676,10 @@ function buildGeminiProjectMap() {
|
|
|
703
676
|
// ---------------------------------------------------------------------------
|
|
704
677
|
const OPENCODE_DB = path.join(HOME, '.local', 'share', 'opencode', 'opencode.db');
|
|
705
678
|
let cachedOpenCodeAccount;
|
|
679
|
+
/** Query the active OpenCode account email from its SQLite database. */
|
|
706
680
|
function getOpenCodeAccount() {
|
|
707
681
|
if (cachedOpenCodeAccount !== undefined)
|
|
708
682
|
return cachedOpenCodeAccount || undefined;
|
|
709
|
-
// Try control_account table in the DB
|
|
710
683
|
try {
|
|
711
684
|
if (fs.existsSync(OPENCODE_DB)) {
|
|
712
685
|
const out = execSync(`sqlite3 "${OPENCODE_DB}" "SELECT email FROM control_account WHERE active=1 LIMIT 1;"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
@@ -720,14 +693,26 @@ function getOpenCodeAccount() {
|
|
|
720
693
|
cachedOpenCodeAccount = '';
|
|
721
694
|
return undefined;
|
|
722
695
|
}
|
|
723
|
-
|
|
696
|
+
/** Scan OpenCode sessions from its SQLite database when the DB file has changed. */
|
|
697
|
+
async function scanOpenCodeIncremental() {
|
|
724
698
|
if (!fs.existsSync(OPENCODE_DB))
|
|
725
|
-
return
|
|
699
|
+
return;
|
|
700
|
+
const stat = safeStatSync(OPENCODE_DB);
|
|
701
|
+
if (!stat)
|
|
702
|
+
return;
|
|
703
|
+
// OpenCode is one big DB; we use its mtime/size as the ledger for the
|
|
704
|
+
// entire fleet of OpenCode sessions.
|
|
705
|
+
const currentScan = {
|
|
706
|
+
fileMtimeMs: Math.floor(stat.mtimeMs),
|
|
707
|
+
fileSize: stat.size,
|
|
708
|
+
};
|
|
709
|
+
const prev = getScanStampByPath(OPENCODE_DB);
|
|
710
|
+
if (prev && prev.fileMtimeMs === currentScan.fileMtimeMs && prev.fileSize === currentScan.fileSize) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
726
713
|
const account = getOpenCodeAccount();
|
|
727
714
|
const currentVersion = await getCurrentAgentVersion('opencode');
|
|
728
715
|
try {
|
|
729
|
-
// Query sessions. time_created is millisecond epoch. Limit to 200 most recent.
|
|
730
|
-
// Use session.title as topic (OpenCode auto-generates good titles).
|
|
731
716
|
const query = `
|
|
732
717
|
SELECT
|
|
733
718
|
s.id,
|
|
@@ -756,10 +741,10 @@ async function discoverOpenCodeSessions() {
|
|
|
756
741
|
) stats ON stats.session_id = s.id
|
|
757
742
|
WHERE s.parent_id IS NULL
|
|
758
743
|
ORDER BY time_created DESC
|
|
759
|
-
LIMIT
|
|
744
|
+
LIMIT 1000;
|
|
760
745
|
`.replace(/\n/g, ' ');
|
|
761
746
|
const out = execSync(`sqlite3 -separator '|||' "${OPENCODE_DB}"`, { encoding: 'utf-8', input: query, stdio: ['pipe', 'pipe', 'ignore'], timeout: 5000 });
|
|
762
|
-
const
|
|
747
|
+
const entries = [];
|
|
763
748
|
for (const line of out.split('\n')) {
|
|
764
749
|
if (!line.trim())
|
|
765
750
|
continue;
|
|
@@ -772,118 +757,128 @@ async function discoverOpenCodeSessions() {
|
|
|
772
757
|
const hasTokenData = hasTokenDataStr === '1';
|
|
773
758
|
const timestamp = isNaN(timeCreated) ? new Date().toISOString() : new Date(timeCreated).toISOString();
|
|
774
759
|
const topic = title || undefined;
|
|
775
|
-
|
|
760
|
+
const meta = {
|
|
776
761
|
id,
|
|
777
762
|
shortId: id.replace(/^ses_/, '').slice(0, 8),
|
|
778
763
|
agent: 'opencode',
|
|
779
764
|
timestamp,
|
|
780
765
|
project: directory ? path.basename(directory) : undefined,
|
|
781
|
-
cwd: directory
|
|
766
|
+
cwd: directory ? normalizeCwd(directory) : undefined,
|
|
782
767
|
filePath: `${OPENCODE_DB}#${id}`,
|
|
783
768
|
version: resolveSessionVersion('opencode', OPENCODE_DB, version || undefined, currentVersion),
|
|
784
769
|
account,
|
|
785
770
|
topic,
|
|
786
771
|
messageCount: Number.isNaN(messageCount) ? undefined : messageCount,
|
|
787
772
|
tokenCount: hasTokenData && !Number.isNaN(tokenCount) ? tokenCount : undefined,
|
|
788
|
-
}
|
|
773
|
+
};
|
|
774
|
+
entries.push({ meta, content: topic || '', scan: currentScan });
|
|
789
775
|
}
|
|
790
|
-
|
|
776
|
+
upsertSessionsBatch(entries);
|
|
777
|
+
// Stamp the OpenCode DB itself so we can short-circuit on the next run.
|
|
778
|
+
recordScans([{ filePath: OPENCODE_DB, scan: currentScan }]);
|
|
791
779
|
}
|
|
792
780
|
catch (err) {
|
|
793
781
|
if (process.stderr.isTTY) {
|
|
794
782
|
console.error(`Warning: Could not query OpenCode sessions: ${err.message}`);
|
|
795
783
|
}
|
|
796
|
-
return [];
|
|
797
784
|
}
|
|
798
785
|
}
|
|
799
786
|
// ---------------------------------------------------------------------------
|
|
800
787
|
// OpenClaw
|
|
801
788
|
// ---------------------------------------------------------------------------
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
// Check if openclaw is installed
|
|
789
|
+
/** Scan active OpenClaw channels and cron jobs via the openclaw CLI. */
|
|
790
|
+
async function scanOpenClawIncremental() {
|
|
791
|
+
// Check if openclaw is installed — silently skip if not.
|
|
805
792
|
try {
|
|
806
793
|
execSync('which openclaw', { stdio: 'ignore' });
|
|
807
794
|
}
|
|
808
795
|
catch {
|
|
809
|
-
return
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
// TTL cache: skip subprocess calls if we scanned recently. Stored in the
|
|
799
|
+
// meta table so we skip even when no channels/cron exist to produce rows.
|
|
800
|
+
const db = getDB();
|
|
801
|
+
const row = db.prepare(`SELECT value FROM meta WHERE key = 'openclaw_last_scan_ms'`).get();
|
|
802
|
+
const lastScanMs = row ? parseInt(row.value, 10) : 0;
|
|
803
|
+
if (lastScanMs && Date.now() - lastScanMs < OPENCLAW_TTL_MS) {
|
|
804
|
+
return;
|
|
810
805
|
}
|
|
811
806
|
const currentVersion = await getCurrentAgentVersion('openclaw');
|
|
812
|
-
|
|
813
|
-
|
|
807
|
+
const now = Date.now();
|
|
808
|
+
const scan = { fileMtimeMs: now, fileSize: 0 };
|
|
809
|
+
const entries = [];
|
|
814
810
|
try {
|
|
815
811
|
const output = execSync('openclaw channels status', {
|
|
816
812
|
encoding: 'utf-8',
|
|
817
813
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
818
814
|
});
|
|
819
815
|
for (const line of output.split('\n')) {
|
|
820
|
-
// Match: "- Telegram <agentId> (<Name>): ..., running, ..."
|
|
821
816
|
const match = line.match(/^-\s+\w+\s+(\S+)\s+\((\w+)\):\s*(.+)/);
|
|
822
817
|
if (!match)
|
|
823
818
|
continue;
|
|
824
819
|
const [, agentId, name, statusStr] = match;
|
|
825
|
-
|
|
826
|
-
if (!isRunning)
|
|
820
|
+
if (!statusStr.includes('running'))
|
|
827
821
|
continue;
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
822
|
+
entries.push({
|
|
823
|
+
meta: {
|
|
824
|
+
id: `openclaw-${agentId}`,
|
|
825
|
+
shortId: agentId.slice(0, 8),
|
|
826
|
+
agent: 'openclaw',
|
|
827
|
+
timestamp: new Date().toISOString(),
|
|
828
|
+
project: name,
|
|
829
|
+
cwd: getOpenClawSessionCwd(agentId),
|
|
830
|
+
version: currentVersion,
|
|
831
|
+
filePath: '',
|
|
832
|
+
},
|
|
833
|
+
content: `${name} ${agentId}`,
|
|
834
|
+
scan,
|
|
837
835
|
});
|
|
838
836
|
}
|
|
839
837
|
}
|
|
840
838
|
catch {
|
|
841
|
-
|
|
839
|
+
/* channels command failed */
|
|
842
840
|
}
|
|
843
|
-
// Discover cron jobs
|
|
844
|
-
// Output format (fixed-width columns, 1 space between UUID and name):
|
|
845
|
-
// 6ec2cffe-39f8-480b-821f-0b20a2062550 paul-hourly cron */30 ... in 7h 48m ago ok isolated paul -
|
|
846
|
-
// UUID is always 36 chars. Extract it first, then parse the rest.
|
|
847
841
|
try {
|
|
848
842
|
const output = execSync('openclaw cron list', {
|
|
849
843
|
encoding: 'utf-8',
|
|
850
844
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
851
845
|
});
|
|
852
846
|
const lines = output.split('\n');
|
|
853
|
-
// Skip header row
|
|
854
847
|
for (let i = 1; i < lines.length; i++) {
|
|
855
848
|
const line = lines[i].trim();
|
|
856
849
|
if (!line)
|
|
857
850
|
continue;
|
|
858
|
-
// Extract UUID (36 chars) and name from start of line
|
|
859
851
|
const headMatch = line.match(/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\s+(\S+)/);
|
|
860
852
|
if (!headMatch)
|
|
861
853
|
continue;
|
|
862
854
|
const jobId = headMatch[1];
|
|
863
855
|
const jobName = headMatch[2];
|
|
864
|
-
// Parse remaining columns (2+ whitespace separated)
|
|
865
|
-
// Schedule+Next merge (cron expressions have internal spaces), so cols are:
|
|
866
|
-
// [schedule+next, last, status, target, agentId, model]
|
|
867
856
|
const rest = line.slice(headMatch[0].length).trim();
|
|
868
857
|
const cols = rest.split(/\s{2,}/);
|
|
869
858
|
const agentId = cols[4] || '';
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
859
|
+
entries.push({
|
|
860
|
+
meta: {
|
|
861
|
+
id: `openclaw-cron-${jobId}`,
|
|
862
|
+
shortId: jobId.slice(0, 8),
|
|
863
|
+
agent: 'openclaw',
|
|
864
|
+
timestamp: new Date().toISOString(),
|
|
865
|
+
project: `${jobName} (${agentId || 'unknown'})`,
|
|
866
|
+
cwd: getOpenClawSessionCwd(agentId),
|
|
867
|
+
version: currentVersion,
|
|
868
|
+
filePath: '',
|
|
869
|
+
},
|
|
870
|
+
content: `${jobName} ${agentId}`,
|
|
871
|
+
scan,
|
|
879
872
|
});
|
|
880
873
|
}
|
|
881
874
|
}
|
|
882
875
|
catch {
|
|
883
|
-
|
|
876
|
+
/* cron command failed */
|
|
884
877
|
}
|
|
885
|
-
|
|
878
|
+
upsertSessionsBatch(entries);
|
|
879
|
+
db.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('openclaw_last_scan_ms', ?)`).run(String(Date.now()));
|
|
886
880
|
}
|
|
881
|
+
/** Stream a Claude JSONL file and extract scan-level metadata (timestamp, cwd, topic, tokens). */
|
|
887
882
|
async function scanClaudeSession(filePath) {
|
|
888
883
|
const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
889
884
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -892,6 +887,7 @@ async function scanClaudeSession(filePath) {
|
|
|
892
887
|
let gitBranch;
|
|
893
888
|
let version;
|
|
894
889
|
let topic;
|
|
890
|
+
let entrypoint;
|
|
895
891
|
let messageCount = 0;
|
|
896
892
|
let tokenCount = 0;
|
|
897
893
|
let sawTokenCount = false;
|
|
@@ -908,6 +904,11 @@ async function scanClaudeSession(filePath) {
|
|
|
908
904
|
catch {
|
|
909
905
|
continue;
|
|
910
906
|
}
|
|
907
|
+
// entrypoint ships on the first envelope event (attachment/user/assistant)
|
|
908
|
+
// and is the clean structural signal for "was this a team spawn?"
|
|
909
|
+
if (!entrypoint && typeof parsed.entrypoint === 'string') {
|
|
910
|
+
entrypoint = parsed.entrypoint;
|
|
911
|
+
}
|
|
911
912
|
if (!timestamp && (parsed.type === 'user' || parsed.type === 'assistant') && parsed.timestamp) {
|
|
912
913
|
timestamp = parsed.timestamp;
|
|
913
914
|
cwd = parsed.cwd || '';
|
|
@@ -953,11 +954,13 @@ async function scanClaudeSession(filePath) {
|
|
|
953
954
|
gitBranch,
|
|
954
955
|
version,
|
|
955
956
|
topic,
|
|
957
|
+
entrypoint,
|
|
956
958
|
messageCount,
|
|
957
959
|
tokenCount: sawTokenCount ? tokenCount : undefined,
|
|
958
|
-
|
|
960
|
+
contentText: userTexts.length > 0 ? userTexts.join('\n') : undefined,
|
|
959
961
|
};
|
|
960
962
|
}
|
|
963
|
+
/** Stream a Codex JSONL file and extract scan-level metadata (session ID, cwd, topic, tokens). */
|
|
961
964
|
async function scanCodexSession(filePath) {
|
|
962
965
|
const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
963
966
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -1025,9 +1028,10 @@ async function scanCodexSession(filePath) {
|
|
|
1025
1028
|
topic,
|
|
1026
1029
|
messageCount,
|
|
1027
1030
|
tokenCount,
|
|
1028
|
-
|
|
1031
|
+
contentText: userTexts.length > 0 ? userTexts.join('\n') : undefined,
|
|
1029
1032
|
};
|
|
1030
1033
|
}
|
|
1034
|
+
/** Resolve the working directory for an OpenClaw agent from its workspace config. */
|
|
1031
1035
|
function getOpenClawSessionCwd(agentId) {
|
|
1032
1036
|
const workspace = agentId ? getOpenClawWorkspaceMap().get(agentId) : undefined;
|
|
1033
1037
|
if (workspace)
|
|
@@ -1035,6 +1039,7 @@ function getOpenClawSessionCwd(agentId) {
|
|
|
1035
1039
|
const configDir = AGENTS.openclaw.configDir;
|
|
1036
1040
|
return safeRealpathSync(configDir) || configDir;
|
|
1037
1041
|
}
|
|
1042
|
+
/** Build a cached map of OpenClaw agent ID to workspace path from openclaw.json. */
|
|
1038
1043
|
function getOpenClawWorkspaceMap() {
|
|
1039
1044
|
if (cachedOpenClawWorkspaces)
|
|
1040
1045
|
return cachedOpenClawWorkspaces;
|
|
@@ -1061,6 +1066,7 @@ function getOpenClawWorkspaceMap() {
|
|
|
1061
1066
|
// ---------------------------------------------------------------------------
|
|
1062
1067
|
// Utilities
|
|
1063
1068
|
// ---------------------------------------------------------------------------
|
|
1069
|
+
/** Read up to maxLines non-empty lines from the beginning of a file. */
|
|
1064
1070
|
export function readFirstLines(filePath, maxLines) {
|
|
1065
1071
|
return new Promise((resolve) => {
|
|
1066
1072
|
const lines = [];
|
|
@@ -1081,13 +1087,12 @@ export function readFirstLines(filePath, maxLines) {
|
|
|
1081
1087
|
}
|
|
1082
1088
|
/**
|
|
1083
1089
|
* Walk a directory recursively for files with a given extension.
|
|
1084
|
-
* Returns at most `limit` files, sorted by mtime descending.
|
|
1085
1090
|
*/
|
|
1086
1091
|
export function walkForFiles(dir, ext, limit) {
|
|
1087
1092
|
const results = [];
|
|
1088
1093
|
function walk(d, depth) {
|
|
1089
1094
|
if (depth > 5)
|
|
1090
|
-
return;
|
|
1095
|
+
return;
|
|
1091
1096
|
let entries;
|
|
1092
1097
|
try {
|
|
1093
1098
|
entries = fs.readdirSync(d);
|
|
@@ -1109,13 +1114,14 @@ export function walkForFiles(dir, ext, limit) {
|
|
|
1109
1114
|
}
|
|
1110
1115
|
}
|
|
1111
1116
|
walk(dir, 0);
|
|
1112
|
-
// Sort by mtime descending and limit
|
|
1113
1117
|
results.sort((a, b) => b.mtime - a.mtime);
|
|
1114
1118
|
return results.slice(0, limit).map(r => r.path);
|
|
1115
1119
|
}
|
|
1120
|
+
/** Compute the SHA-256 hex digest of a string. */
|
|
1116
1121
|
function sha256(input) {
|
|
1117
1122
|
return crypto.createHash('sha256').update(input).digest('hex');
|
|
1118
1123
|
}
|
|
1124
|
+
/** Stat a path, returning null on any error. */
|
|
1119
1125
|
function safeStatSync(p) {
|
|
1120
1126
|
try {
|
|
1121
1127
|
return fs.statSync(p);
|
|
@@ -1124,6 +1130,7 @@ function safeStatSync(p) {
|
|
|
1124
1130
|
return null;
|
|
1125
1131
|
}
|
|
1126
1132
|
}
|
|
1133
|
+
/** Resolve a path to its real path, returning null on any error. */
|
|
1127
1134
|
function safeRealpathSync(p) {
|
|
1128
1135
|
try {
|
|
1129
1136
|
return fs.realpathSync(p);
|
|
@@ -1132,6 +1139,7 @@ function safeRealpathSync(p) {
|
|
|
1132
1139
|
return null;
|
|
1133
1140
|
}
|
|
1134
1141
|
}
|
|
1142
|
+
/** Extract meaningful user text from a Claude JSONL user event, skipping meta and local-command messages. */
|
|
1135
1143
|
function extractClaudeUserText(parsed) {
|
|
1136
1144
|
if (parsed.isMeta === true)
|
|
1137
1145
|
return undefined;
|
|
@@ -1150,12 +1158,11 @@ function extractClaudeUserText(parsed) {
|
|
|
1150
1158
|
return undefined;
|
|
1151
1159
|
return text;
|
|
1152
1160
|
}
|
|
1161
|
+
/** Check whether a message is a local-command wrapper rather than real user input. */
|
|
1153
1162
|
function isLocalCommandMessage(text) {
|
|
1154
1163
|
return /<local-command-caveat>|<bash-(input|stdout|stderr)>/i.test(text);
|
|
1155
1164
|
}
|
|
1156
|
-
|
|
1157
|
-
return text.split('\n').map(l => l.trim()).filter(Boolean);
|
|
1158
|
-
}
|
|
1165
|
+
/** Sum all token usage fields from a Claude assistant message's usage object. */
|
|
1159
1166
|
function getClaudeUsageTotal(usage) {
|
|
1160
1167
|
if (!usage || typeof usage !== 'object')
|
|
1161
1168
|
return null;
|
|
@@ -1166,6 +1173,7 @@ function getClaudeUsageTotal(usage) {
|
|
|
1166
1173
|
usage.cache_read_input_tokens,
|
|
1167
1174
|
]);
|
|
1168
1175
|
}
|
|
1176
|
+
/** Extract text from Codex message content blocks, filtering out system instructions for user messages. */
|
|
1169
1177
|
function extractCodexMessageText(contentBlocks, role) {
|
|
1170
1178
|
if (!Array.isArray(contentBlocks))
|
|
1171
1179
|
return undefined;
|
|
@@ -1184,10 +1192,12 @@ function extractCodexMessageText(contentBlocks, role) {
|
|
|
1184
1192
|
});
|
|
1185
1193
|
return text || undefined;
|
|
1186
1194
|
}
|
|
1195
|
+
/** Trim and normalize a version string, returning undefined for empty values. */
|
|
1187
1196
|
function normalizeVersion(version) {
|
|
1188
1197
|
const trimmed = version?.trim();
|
|
1189
1198
|
return trimmed ? trimmed : undefined;
|
|
1190
1199
|
}
|
|
1200
|
+
/** Extract the version number from a managed ~/.agents/versions/<agent>/<version>/... path. */
|
|
1191
1201
|
function extractVersionFromManagedPath(agent, sourcePath) {
|
|
1192
1202
|
if (!sourcePath)
|
|
1193
1203
|
return undefined;
|
|
@@ -1206,6 +1216,7 @@ function extractVersionFromManagedPath(agent, sourcePath) {
|
|
|
1206
1216
|
}
|
|
1207
1217
|
return undefined;
|
|
1208
1218
|
}
|
|
1219
|
+
/** Resolve the current version of an agent CLI (symlink version or live CLI output, cached). */
|
|
1209
1220
|
async function getCurrentAgentVersion(agent) {
|
|
1210
1221
|
const cached = cachedAgentVersions.get(agent);
|
|
1211
1222
|
if (cached)
|
|
@@ -1219,11 +1230,13 @@ async function getCurrentAgentVersion(agent) {
|
|
|
1219
1230
|
cachedAgentVersions.set(agent, promise);
|
|
1220
1231
|
return promise;
|
|
1221
1232
|
}
|
|
1233
|
+
/** Resolve a session's version: embedded in file > extracted from managed path > current CLI version. */
|
|
1222
1234
|
function resolveSessionVersion(agent, sourcePath, embeddedVersion, currentVersion) {
|
|
1223
1235
|
return normalizeVersion(embeddedVersion)
|
|
1224
1236
|
|| extractVersionFromManagedPath(agent, sourcePath)
|
|
1225
1237
|
|| normalizeVersion(currentVersion);
|
|
1226
1238
|
}
|
|
1239
|
+
/** Sum all token usage fields from a Codex total_token_usage object. */
|
|
1227
1240
|
function getCodexTokenCount(totalTokenUsage) {
|
|
1228
1241
|
if (!totalTokenUsage || typeof totalTokenUsage !== 'object')
|
|
1229
1242
|
return null;
|
|
@@ -1234,6 +1247,7 @@ function getCodexTokenCount(totalTokenUsage) {
|
|
|
1234
1247
|
totalTokenUsage.reasoning_output_tokens,
|
|
1235
1248
|
]);
|
|
1236
1249
|
}
|
|
1250
|
+
/** Extract text from a Gemini message content field (string or array of parts). */
|
|
1237
1251
|
function extractGeminiMessageText(content) {
|
|
1238
1252
|
if (typeof content === 'string')
|
|
1239
1253
|
return content.trim();
|
|
@@ -1251,6 +1265,7 @@ function extractGeminiMessageText(content) {
|
|
|
1251
1265
|
}
|
|
1252
1266
|
return '';
|
|
1253
1267
|
}
|
|
1268
|
+
/** Extract the total token count from a Gemini message's tokens object. */
|
|
1254
1269
|
function getGeminiTokenCount(tokens) {
|
|
1255
1270
|
if (!tokens || typeof tokens !== 'object')
|
|
1256
1271
|
return null;
|
|
@@ -1264,6 +1279,7 @@ function getGeminiTokenCount(tokens) {
|
|
|
1264
1279
|
tokens.tool,
|
|
1265
1280
|
]);
|
|
1266
1281
|
}
|
|
1282
|
+
/** Sum all numeric values in an array, returning null if none are valid numbers. */
|
|
1267
1283
|
function sumKnownNumbers(values) {
|
|
1268
1284
|
let total = 0;
|
|
1269
1285
|
let found = false;
|
|
@@ -1278,11 +1294,16 @@ function sumKnownNumbers(values) {
|
|
|
1278
1294
|
// ---------------------------------------------------------------------------
|
|
1279
1295
|
// Time range parsing
|
|
1280
1296
|
// ---------------------------------------------------------------------------
|
|
1297
|
+
/** Parse a time filter string (relative like '7d' or ISO timestamp) into epoch milliseconds. */
|
|
1281
1298
|
export function parseTimeFilter(input) {
|
|
1282
|
-
const relativeMatch = input.match(/^(\d+)([
|
|
1299
|
+
const relativeMatch = input.match(/^(\d+)([mhdw])$/i);
|
|
1283
1300
|
if (relativeMatch) {
|
|
1284
1301
|
const value = parseInt(relativeMatch[1], 10);
|
|
1285
1302
|
const unit = relativeMatch[2].toLowerCase();
|
|
1303
|
+
if (unit === 'm')
|
|
1304
|
+
return Date.now() - value * 60_000;
|
|
1305
|
+
if (unit === 'h')
|
|
1306
|
+
return Date.now() - value * 3_600_000;
|
|
1286
1307
|
if (unit === 'd')
|
|
1287
1308
|
return Date.now() - value * 86_400_000;
|
|
1288
1309
|
if (unit === 'w')
|
|
@@ -1291,71 +1312,4 @@ export function parseTimeFilter(input) {
|
|
|
1291
1312
|
const ts = new Date(input).getTime();
|
|
1292
1313
|
return Number.isNaN(ts) ? 0 : ts;
|
|
1293
1314
|
}
|
|
1294
|
-
// ---------------------------------------------------------------------------
|
|
1295
|
-
// BM25 content index search
|
|
1296
|
-
// ---------------------------------------------------------------------------
|
|
1297
|
-
/**
|
|
1298
|
-
* Pure BM25 scorer over an in-memory index. Returns sessionId -> score+matched terms,
|
|
1299
|
-
* sorted by score descending (Map preserves insertion order).
|
|
1300
|
-
*/
|
|
1301
|
-
export function scoreBM25(index, query) {
|
|
1302
|
-
const result = new Map();
|
|
1303
|
-
if (index.N === 0)
|
|
1304
|
-
return result;
|
|
1305
|
-
const { counts: queryTerms } = tokenizeCounted(query);
|
|
1306
|
-
if (queryTerms.size === 0)
|
|
1307
|
-
return result;
|
|
1308
|
-
const avgdl = index.avgdl || 1;
|
|
1309
|
-
const scored = new Map();
|
|
1310
|
-
for (const [term] of queryTerms) {
|
|
1311
|
-
const termPostings = index.postings.get(term);
|
|
1312
|
-
if (!termPostings)
|
|
1313
|
-
continue;
|
|
1314
|
-
const df = termPostings.size;
|
|
1315
|
-
const idf = Math.log(1 + (index.N - df + 0.5) / (df + 0.5));
|
|
1316
|
-
for (const [sessionId, tf] of termPostings) {
|
|
1317
|
-
const dl = index.docLengths.get(sessionId) ?? avgdl;
|
|
1318
|
-
const norm = 1 - BM25_B + BM25_B * (dl / avgdl);
|
|
1319
|
-
const termScore = idf * (tf * (BM25_K1 + 1)) / (tf + BM25_K1 * norm);
|
|
1320
|
-
const entry = scored.get(sessionId);
|
|
1321
|
-
if (!entry) {
|
|
1322
|
-
scored.set(sessionId, { score: termScore, matchedTerms: [term] });
|
|
1323
|
-
}
|
|
1324
|
-
else {
|
|
1325
|
-
entry.score += termScore;
|
|
1326
|
-
if (!entry.matchedTerms.includes(term))
|
|
1327
|
-
entry.matchedTerms.push(term);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
const sorted = [...scored.entries()].sort((a, b) => b[1].score - a[1].score);
|
|
1332
|
-
for (const [sid, info] of sorted)
|
|
1333
|
-
result.set(sid, info);
|
|
1334
|
-
return result;
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Score sessions using Okapi BM25 against the persisted content index.
|
|
1338
|
-
* Returns a Map sorted by score descending, with matched terms attached.
|
|
1339
|
-
*/
|
|
1340
|
-
export function searchContentIndex(sessions, query) {
|
|
1341
|
-
const index = loadBM25Index();
|
|
1342
|
-
if (!index)
|
|
1343
|
-
return new Map();
|
|
1344
|
-
const scored = scoreBM25(index, query);
|
|
1345
|
-
if (scored.size === 0)
|
|
1346
|
-
return new Map();
|
|
1347
|
-
const sessionsById = new Map(sessions.map(s => [s.id, s]));
|
|
1348
|
-
const result = new Map();
|
|
1349
|
-
for (const [sessionId, info] of scored) {
|
|
1350
|
-
const session = sessionsById.get(sessionId);
|
|
1351
|
-
if (session) {
|
|
1352
|
-
result.set(sessionId, {
|
|
1353
|
-
...session,
|
|
1354
|
-
_matchedTerms: info.matchedTerms,
|
|
1355
|
-
_bm25Score: info.score,
|
|
1356
|
-
});
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
return result;
|
|
1360
|
-
}
|
|
1361
1315
|
//# sourceMappingURL=discover.js.map
|