claude-code-workflow 6.2.2 → 6.2.4
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/ccw/dist/cli.d.ts +2 -0
- package/ccw/dist/cli.d.ts.map +1 -0
- package/ccw/dist/cli.js +219 -0
- package/ccw/dist/cli.js.map +1 -0
- package/ccw/dist/commands/cli.d.ts +32 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -0
- package/ccw/dist/commands/cli.js +619 -0
- package/ccw/dist/commands/cli.js.map +1 -0
- package/ccw/dist/commands/core-memory.d.ts +32 -0
- package/ccw/dist/commands/core-memory.d.ts.map +1 -0
- package/ccw/dist/commands/core-memory.js +640 -0
- package/ccw/dist/commands/core-memory.js.map +1 -0
- package/ccw/dist/commands/hook.d.ts +16 -0
- package/ccw/dist/commands/hook.d.ts.map +1 -0
- package/ccw/dist/commands/hook.js +276 -0
- package/ccw/dist/commands/hook.js.map +1 -0
- package/ccw/dist/commands/install.d.ts +12 -0
- package/ccw/dist/commands/install.d.ts.map +1 -0
- package/ccw/dist/commands/install.js +443 -0
- package/ccw/dist/commands/install.js.map +1 -0
- package/ccw/dist/commands/list.d.ts +5 -0
- package/ccw/dist/commands/list.d.ts.map +1 -0
- package/ccw/dist/commands/list.js +32 -0
- package/ccw/dist/commands/list.js.map +1 -0
- package/ccw/dist/commands/memory.d.ts +57 -0
- package/ccw/dist/commands/memory.d.ts.map +1 -0
- package/ccw/dist/commands/memory.js +890 -0
- package/ccw/dist/commands/memory.js.map +1 -0
- package/ccw/dist/commands/serve.d.ts +12 -0
- package/ccw/dist/commands/serve.d.ts.map +1 -0
- package/ccw/dist/commands/serve.js +63 -0
- package/ccw/dist/commands/serve.js.map +1 -0
- package/ccw/dist/commands/session-path-resolver.d.ts +45 -0
- package/ccw/dist/commands/session-path-resolver.d.ts.map +1 -0
- package/ccw/dist/commands/session-path-resolver.js +302 -0
- package/ccw/dist/commands/session-path-resolver.js.map +1 -0
- package/ccw/dist/commands/session.d.ts +12 -0
- package/ccw/dist/commands/session.d.ts.map +1 -0
- package/ccw/dist/commands/session.js +954 -0
- package/ccw/dist/commands/session.js.map +1 -0
- package/ccw/dist/commands/stop.d.ts +11 -0
- package/ccw/dist/commands/stop.d.ts.map +1 -0
- package/ccw/dist/commands/stop.js +96 -0
- package/ccw/dist/commands/stop.js.map +1 -0
- package/ccw/dist/commands/tool.d.ts +29 -0
- package/ccw/dist/commands/tool.d.ts.map +1 -0
- package/ccw/dist/commands/tool.js +173 -0
- package/ccw/dist/commands/tool.js.map +1 -0
- package/ccw/dist/commands/uninstall.d.ts +9 -0
- package/ccw/dist/commands/uninstall.d.ts.map +1 -0
- package/ccw/dist/commands/uninstall.js +239 -0
- package/ccw/dist/commands/uninstall.js.map +1 -0
- package/ccw/dist/commands/upgrade.d.ts +10 -0
- package/ccw/dist/commands/upgrade.d.ts.map +1 -0
- package/ccw/dist/commands/upgrade.js +288 -0
- package/ccw/dist/commands/upgrade.js.map +1 -0
- package/ccw/dist/commands/view.d.ts +14 -0
- package/ccw/dist/commands/view.d.ts.map +1 -0
- package/ccw/dist/commands/view.js +100 -0
- package/ccw/dist/commands/view.js.map +1 -0
- package/ccw/dist/config/storage-paths.d.ts +184 -0
- package/ccw/dist/config/storage-paths.d.ts.map +1 -0
- package/ccw/dist/config/storage-paths.js +536 -0
- package/ccw/dist/config/storage-paths.js.map +1 -0
- package/ccw/dist/core/cache-manager.d.ts +80 -0
- package/ccw/dist/core/cache-manager.d.ts.map +1 -0
- package/ccw/dist/core/cache-manager.js +260 -0
- package/ccw/dist/core/cache-manager.js.map +1 -0
- package/ccw/dist/core/claude-freshness.d.ts +53 -0
- package/ccw/dist/core/claude-freshness.d.ts.map +1 -0
- package/ccw/dist/core/claude-freshness.js +232 -0
- package/ccw/dist/core/claude-freshness.js.map +1 -0
- package/ccw/dist/core/core-memory-store.d.ts +320 -0
- package/ccw/dist/core/core-memory-store.d.ts.map +1 -0
- package/ccw/dist/core/core-memory-store.js +1177 -0
- package/ccw/dist/core/core-memory-store.js.map +1 -0
- package/ccw/dist/core/dashboard-generator-patch.d.ts +2 -0
- package/ccw/dist/core/dashboard-generator-patch.d.ts.map +1 -0
- package/ccw/dist/core/dashboard-generator-patch.js +48 -0
- package/ccw/dist/core/dashboard-generator-patch.js.map +1 -0
- package/ccw/dist/core/dashboard-generator.d.ts +8 -0
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -0
- package/ccw/dist/core/dashboard-generator.js +695 -0
- package/ccw/dist/core/dashboard-generator.js.map +1 -0
- package/ccw/dist/core/data-aggregator.d.ts +145 -0
- package/ccw/dist/core/data-aggregator.d.ts.map +1 -0
- package/ccw/dist/core/data-aggregator.js +416 -0
- package/ccw/dist/core/data-aggregator.js.map +1 -0
- package/ccw/dist/core/history-importer.d.ts +102 -0
- package/ccw/dist/core/history-importer.d.ts.map +1 -0
- package/ccw/dist/core/history-importer.js +493 -0
- package/ccw/dist/core/history-importer.js.map +1 -0
- package/ccw/dist/core/lite-scanner-complete.d.ts +81 -0
- package/ccw/dist/core/lite-scanner-complete.d.ts.map +1 -0
- package/ccw/dist/core/lite-scanner-complete.js +368 -0
- package/ccw/dist/core/lite-scanner-complete.js.map +1 -0
- package/ccw/dist/core/lite-scanner.d.ts +81 -0
- package/ccw/dist/core/lite-scanner.d.ts.map +1 -0
- package/ccw/dist/core/lite-scanner.js +368 -0
- package/ccw/dist/core/lite-scanner.js.map +1 -0
- package/ccw/dist/core/manifest.d.ts +88 -0
- package/ccw/dist/core/manifest.d.ts.map +1 -0
- package/ccw/dist/core/manifest.js +214 -0
- package/ccw/dist/core/manifest.js.map +1 -0
- package/ccw/dist/core/memory-embedder-bridge.d.ts +83 -0
- package/ccw/dist/core/memory-embedder-bridge.d.ts.map +1 -0
- package/ccw/dist/core/memory-embedder-bridge.js +181 -0
- package/ccw/dist/core/memory-embedder-bridge.js.map +1 -0
- package/ccw/dist/core/memory-store.d.ts +249 -0
- package/ccw/dist/core/memory-store.d.ts.map +1 -0
- package/ccw/dist/core/memory-store.js +781 -0
- package/ccw/dist/core/memory-store.js.map +1 -0
- package/ccw/dist/core/routes/ccw-routes.d.ts +20 -0
- package/ccw/dist/core/routes/ccw-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/ccw-routes.js +70 -0
- package/ccw/dist/core/routes/ccw-routes.js.map +1 -0
- package/ccw/dist/core/routes/claude-routes.d.ts +19 -0
- package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/claude-routes.js +1017 -0
- package/ccw/dist/core/routes/claude-routes.js.map +1 -0
- package/ccw/dist/core/routes/cli-routes.d.ts +20 -0
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/cli-routes.js +468 -0
- package/ccw/dist/core/routes/cli-routes.js.map +1 -0
- package/ccw/dist/core/routes/codexlens-routes.d.ts +20 -0
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/codexlens-routes.js +754 -0
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -0
- package/ccw/dist/core/routes/core-memory-routes.d.ts +21 -0
- package/ccw/dist/core/routes/core-memory-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/core-memory-routes.js +520 -0
- package/ccw/dist/core/routes/core-memory-routes.js.map +1 -0
- package/ccw/dist/core/routes/files-routes.d.ts +20 -0
- package/ccw/dist/core/routes/files-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/files-routes.js +374 -0
- package/ccw/dist/core/routes/files-routes.js.map +1 -0
- package/ccw/dist/core/routes/graph-routes.d.ts +20 -0
- package/ccw/dist/core/routes/graph-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/graph-routes.js +517 -0
- package/ccw/dist/core/routes/graph-routes.js.map +1 -0
- package/ccw/dist/core/routes/help-routes.d.ts +20 -0
- package/ccw/dist/core/routes/help-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/help-routes.js +250 -0
- package/ccw/dist/core/routes/help-routes.js.map +1 -0
- package/ccw/dist/core/routes/hooks-routes.d.ts +21 -0
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/hooks-routes.js +346 -0
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.d.ts +20 -0
- package/ccw/dist/core/routes/mcp-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.js +1129 -0
- package/ccw/dist/core/routes/mcp-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-templates-db.d.ts +54 -0
- package/ccw/dist/core/routes/mcp-templates-db.d.ts.map +1 -0
- package/ccw/dist/core/routes/mcp-templates-db.js +226 -0
- package/ccw/dist/core/routes/mcp-templates-db.js.map +1 -0
- package/ccw/dist/core/routes/memory-routes.d.ts +21 -0
- package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/memory-routes.js +1095 -0
- package/ccw/dist/core/routes/memory-routes.js.map +1 -0
- package/ccw/dist/core/routes/rules-routes.d.ts +20 -0
- package/ccw/dist/core/routes/rules-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/rules-routes.js +442 -0
- package/ccw/dist/core/routes/rules-routes.js.map +1 -0
- package/ccw/dist/core/routes/session-routes.d.ts +20 -0
- package/ccw/dist/core/routes/session-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/session-routes.js +423 -0
- package/ccw/dist/core/routes/session-routes.js.map +1 -0
- package/ccw/dist/core/routes/skills-routes.d.ts +20 -0
- package/ccw/dist/core/routes/skills-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/skills-routes.js +533 -0
- package/ccw/dist/core/routes/skills-routes.js.map +1 -0
- package/ccw/dist/core/routes/status-routes.d.ts +20 -0
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/status-routes.js +38 -0
- package/ccw/dist/core/routes/status-routes.js.map +1 -0
- package/ccw/dist/core/routes/system-routes.d.ts +22 -0
- package/ccw/dist/core/routes/system-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/system-routes.js +354 -0
- package/ccw/dist/core/routes/system-routes.js.map +1 -0
- package/ccw/dist/core/server.d.ts +17 -0
- package/ccw/dist/core/server.d.ts.map +1 -0
- package/ccw/dist/core/server.js +386 -0
- package/ccw/dist/core/server.js.map +1 -0
- package/ccw/dist/core/session-clustering-service.d.ts +153 -0
- package/ccw/dist/core/session-clustering-service.d.ts.map +1 -0
- package/ccw/dist/core/session-clustering-service.js +1065 -0
- package/ccw/dist/core/session-clustering-service.js.map +1 -0
- package/ccw/dist/core/session-scanner.d.ts +32 -0
- package/ccw/dist/core/session-scanner.d.ts.map +1 -0
- package/ccw/dist/core/session-scanner.js +253 -0
- package/ccw/dist/core/session-scanner.js.map +1 -0
- package/ccw/dist/core/websocket.d.ts +23 -0
- package/ccw/dist/core/websocket.d.ts.map +1 -0
- package/ccw/dist/core/websocket.js +168 -0
- package/ccw/dist/core/websocket.js.map +1 -0
- package/ccw/dist/index.d.ts +10 -0
- package/ccw/dist/index.d.ts.map +1 -0
- package/ccw/dist/index.js +10 -0
- package/ccw/dist/index.js.map +1 -0
- package/ccw/dist/mcp-server/index.d.ts +7 -0
- package/ccw/dist/mcp-server/index.d.ts.map +1 -0
- package/ccw/dist/mcp-server/index.js +157 -0
- package/ccw/dist/mcp-server/index.js.map +1 -0
- package/ccw/dist/tools/classify-folders.d.ts +26 -0
- package/ccw/dist/tools/classify-folders.d.ts.map +1 -0
- package/ccw/dist/tools/classify-folders.js +201 -0
- package/ccw/dist/tools/classify-folders.js.map +1 -0
- package/ccw/dist/tools/cli-config-manager.d.ts +62 -0
- package/ccw/dist/tools/cli-config-manager.d.ts.map +1 -0
- package/ccw/dist/tools/cli-config-manager.js +221 -0
- package/ccw/dist/tools/cli-config-manager.js.map +1 -0
- package/ccw/dist/tools/cli-executor.d.ts +373 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -0
- package/ccw/dist/tools/cli-executor.js +1625 -0
- package/ccw/dist/tools/cli-executor.js.map +1 -0
- package/ccw/dist/tools/cli-history-store.d.ts +330 -0
- package/ccw/dist/tools/cli-history-store.d.ts.map +1 -0
- package/ccw/dist/tools/cli-history-store.js +916 -0
- package/ccw/dist/tools/cli-history-store.js.map +1 -0
- package/ccw/dist/tools/codex-lens.d.ts +118 -0
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -0
- package/ccw/dist/tools/codex-lens.js +962 -0
- package/ccw/dist/tools/codex-lens.js.map +1 -0
- package/ccw/dist/tools/convert-tokens-to-css.d.ts +14 -0
- package/ccw/dist/tools/convert-tokens-to-css.d.ts.map +1 -0
- package/ccw/dist/tools/convert-tokens-to-css.js +244 -0
- package/ccw/dist/tools/convert-tokens-to-css.js.map +1 -0
- package/ccw/dist/tools/core-memory.d.ts +66 -0
- package/ccw/dist/tools/core-memory.d.ts.map +1 -0
- package/ccw/dist/tools/core-memory.js +324 -0
- package/ccw/dist/tools/core-memory.js.map +1 -0
- package/ccw/dist/tools/detect-changed-modules.d.ts +24 -0
- package/ccw/dist/tools/detect-changed-modules.d.ts.map +1 -0
- package/ccw/dist/tools/detect-changed-modules.js +277 -0
- package/ccw/dist/tools/detect-changed-modules.js.map +1 -0
- package/ccw/dist/tools/discover-design-files.d.ts +36 -0
- package/ccw/dist/tools/discover-design-files.d.ts.map +1 -0
- package/ccw/dist/tools/discover-design-files.js +147 -0
- package/ccw/dist/tools/discover-design-files.js.map +1 -0
- package/ccw/dist/tools/edit-file.d.ts +28 -0
- package/ccw/dist/tools/edit-file.d.ts.map +1 -0
- package/ccw/dist/tools/edit-file.js +479 -0
- package/ccw/dist/tools/edit-file.js.map +1 -0
- package/ccw/dist/tools/generate-module-docs.d.ts +22 -0
- package/ccw/dist/tools/generate-module-docs.d.ts.map +1 -0
- package/ccw/dist/tools/generate-module-docs.js +379 -0
- package/ccw/dist/tools/generate-module-docs.js.map +1 -0
- package/ccw/dist/tools/get-modules-by-depth.d.ts +15 -0
- package/ccw/dist/tools/get-modules-by-depth.d.ts.map +1 -0
- package/ccw/dist/tools/get-modules-by-depth.js +296 -0
- package/ccw/dist/tools/get-modules-by-depth.js.map +1 -0
- package/ccw/dist/tools/index.d.ts +55 -0
- package/ccw/dist/tools/index.d.ts.map +1 -0
- package/ccw/dist/tools/index.js +304 -0
- package/ccw/dist/tools/index.js.map +1 -0
- package/ccw/dist/tools/native-session-discovery.d.ts +97 -0
- package/ccw/dist/tools/native-session-discovery.d.ts.map +1 -0
- package/ccw/dist/tools/native-session-discovery.js +700 -0
- package/ccw/dist/tools/native-session-discovery.js.map +1 -0
- package/ccw/dist/tools/notifier.d.ts +50 -0
- package/ccw/dist/tools/notifier.d.ts.map +1 -0
- package/ccw/dist/tools/notifier.js +90 -0
- package/ccw/dist/tools/notifier.js.map +1 -0
- package/ccw/dist/tools/read-file.d.ts +32 -0
- package/ccw/dist/tools/read-file.d.ts.map +1 -0
- package/ccw/dist/tools/read-file.js +329 -0
- package/ccw/dist/tools/read-file.js.map +1 -0
- package/ccw/dist/tools/resume-strategy.d.ts +48 -0
- package/ccw/dist/tools/resume-strategy.d.ts.map +1 -0
- package/ccw/dist/tools/resume-strategy.js +248 -0
- package/ccw/dist/tools/resume-strategy.js.map +1 -0
- package/ccw/dist/tools/session-content-parser.d.ts +58 -0
- package/ccw/dist/tools/session-content-parser.d.ts.map +1 -0
- package/ccw/dist/tools/session-content-parser.js +420 -0
- package/ccw/dist/tools/session-content-parser.js.map +1 -0
- package/ccw/dist/tools/session-manager.d.ts +9 -0
- package/ccw/dist/tools/session-manager.d.ts.map +1 -0
- package/ccw/dist/tools/session-manager.js +834 -0
- package/ccw/dist/tools/session-manager.js.map +1 -0
- package/ccw/dist/tools/smart-context.d.ts +35 -0
- package/ccw/dist/tools/smart-context.d.ts.map +1 -0
- package/ccw/dist/tools/smart-context.js +182 -0
- package/ccw/dist/tools/smart-context.js.map +1 -0
- package/ccw/dist/tools/smart-search.d.ts +105 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -0
- package/ccw/dist/tools/smart-search.js +1753 -0
- package/ccw/dist/tools/smart-search.js.map +1 -0
- package/ccw/dist/tools/storage-manager.d.ts +114 -0
- package/ccw/dist/tools/storage-manager.d.ts.map +1 -0
- package/ccw/dist/tools/storage-manager.js +392 -0
- package/ccw/dist/tools/storage-manager.js.map +1 -0
- package/ccw/dist/tools/ui-generate-preview.d.ts +39 -0
- package/ccw/dist/tools/ui-generate-preview.d.ts.map +1 -0
- package/ccw/dist/tools/ui-generate-preview.js +300 -0
- package/ccw/dist/tools/ui-generate-preview.js.map +1 -0
- package/ccw/dist/tools/ui-instantiate-prototypes.d.ts +75 -0
- package/ccw/dist/tools/ui-instantiate-prototypes.d.ts.map +1 -0
- package/ccw/dist/tools/ui-instantiate-prototypes.js +256 -0
- package/ccw/dist/tools/ui-instantiate-prototypes.js.map +1 -0
- package/ccw/dist/tools/update-module-claude.d.ts +80 -0
- package/ccw/dist/tools/update-module-claude.d.ts.map +1 -0
- package/ccw/dist/tools/update-module-claude.js +351 -0
- package/ccw/dist/tools/update-module-claude.js.map +1 -0
- package/ccw/dist/tools/write-file.d.ts +19 -0
- package/ccw/dist/tools/write-file.d.ts.map +1 -0
- package/ccw/dist/tools/write-file.js +193 -0
- package/ccw/dist/tools/write-file.js.map +1 -0
- package/ccw/dist/types/config.d.ts +11 -0
- package/ccw/dist/types/config.d.ts.map +1 -0
- package/ccw/dist/types/config.js +2 -0
- package/ccw/dist/types/config.js.map +1 -0
- package/ccw/dist/types/index.d.ts +4 -0
- package/ccw/dist/types/index.d.ts.map +1 -0
- package/ccw/dist/types/index.js +4 -0
- package/ccw/dist/types/index.js.map +1 -0
- package/ccw/dist/types/session.d.ts +20 -0
- package/ccw/dist/types/session.d.ts.map +1 -0
- package/ccw/dist/types/session.js +2 -0
- package/ccw/dist/types/session.js.map +1 -0
- package/ccw/dist/types/tool.d.ts +36 -0
- package/ccw/dist/types/tool.d.ts.map +1 -0
- package/ccw/dist/types/tool.js +11 -0
- package/ccw/dist/types/tool.js.map +1 -0
- package/ccw/dist/utils/browser-launcher.d.ts +13 -0
- package/ccw/dist/utils/browser-launcher.d.ts.map +1 -0
- package/ccw/dist/utils/browser-launcher.js +60 -0
- package/ccw/dist/utils/browser-launcher.js.map +1 -0
- package/ccw/dist/utils/file-utils.d.ts +25 -0
- package/ccw/dist/utils/file-utils.d.ts.map +1 -0
- package/ccw/dist/utils/file-utils.js +48 -0
- package/ccw/dist/utils/file-utils.js.map +1 -0
- package/ccw/dist/utils/path-resolver.d.ts +80 -0
- package/ccw/dist/utils/path-resolver.d.ts.map +1 -0
- package/ccw/dist/utils/path-resolver.js +260 -0
- package/ccw/dist/utils/path-resolver.js.map +1 -0
- package/ccw/dist/utils/path-validator.d.ts +49 -0
- package/ccw/dist/utils/path-validator.d.ts.map +1 -0
- package/ccw/dist/utils/path-validator.js +123 -0
- package/ccw/dist/utils/path-validator.js.map +1 -0
- package/ccw/dist/utils/ui.d.ts +62 -0
- package/ccw/dist/utils/ui.d.ts.map +1 -0
- package/ccw/dist/utils/ui.js +129 -0
- package/ccw/dist/utils/ui.js.map +1 -0
- package/ccw/package.json +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Session Discovery - Discovers and tracks native CLI tool sessions
|
|
3
|
+
* Supports Gemini, Qwen, and Codex session formats
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
6
|
+
import { join, basename, resolve } from 'path';
|
|
7
|
+
// basename is used for extracting session ID from filename
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
/**
|
|
11
|
+
* Calculate project hash (same algorithm as Gemini/Qwen)
|
|
12
|
+
* Note: Gemini/Qwen use the absolute path AS-IS without normalization
|
|
13
|
+
* On Windows, this means using backslashes and original case
|
|
14
|
+
*/
|
|
15
|
+
export function calculateProjectHash(projectDir) {
|
|
16
|
+
// resolve() returns absolute path with native separators (backslash on Windows)
|
|
17
|
+
const absolutePath = resolve(projectDir);
|
|
18
|
+
return createHash('sha256').update(absolutePath).digest('hex');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get home directory path
|
|
22
|
+
*/
|
|
23
|
+
function getHomePath() {
|
|
24
|
+
return homedir().replace(/\\/g, '/');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Base session discoverer interface
|
|
28
|
+
*/
|
|
29
|
+
class SessionDiscoverer {
|
|
30
|
+
/**
|
|
31
|
+
* Get the latest session
|
|
32
|
+
*/
|
|
33
|
+
getLatestSession(options) {
|
|
34
|
+
const sessions = this.getSessions({ ...options, limit: 1 });
|
|
35
|
+
return sessions.length > 0 ? sessions[0] : null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Track new session created during execution
|
|
39
|
+
* @param beforeTimestamp - Filter sessions created after this time
|
|
40
|
+
* @param workingDir - Project working directory
|
|
41
|
+
* @param prompt - Optional prompt content for precise matching (fallback)
|
|
42
|
+
*/
|
|
43
|
+
async trackNewSession(beforeTimestamp, workingDir, prompt) {
|
|
44
|
+
const sessions = this.getSessions({
|
|
45
|
+
workingDir,
|
|
46
|
+
afterTimestamp: beforeTimestamp,
|
|
47
|
+
limit: 10 // Get more candidates for prompt matching
|
|
48
|
+
});
|
|
49
|
+
if (sessions.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
// If only one session or no prompt provided, return the latest
|
|
52
|
+
if (sessions.length === 1 || !prompt) {
|
|
53
|
+
return sessions[0];
|
|
54
|
+
}
|
|
55
|
+
// Try to match by prompt content (fallback for parallel execution)
|
|
56
|
+
const matched = this.matchSessionByPrompt(sessions, prompt);
|
|
57
|
+
return matched || sessions[0]; // Fallback to latest if no match
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Match session by prompt content
|
|
61
|
+
* Searches for the prompt in session's user messages
|
|
62
|
+
*/
|
|
63
|
+
matchSessionByPrompt(sessions, prompt) {
|
|
64
|
+
// Normalize prompt for comparison (first 200 chars)
|
|
65
|
+
const promptPrefix = prompt.substring(0, 200).trim();
|
|
66
|
+
if (!promptPrefix)
|
|
67
|
+
return null;
|
|
68
|
+
for (const session of sessions) {
|
|
69
|
+
try {
|
|
70
|
+
const userMessage = this.extractFirstUserMessage(session.filePath);
|
|
71
|
+
if (userMessage && userMessage.includes(promptPrefix)) {
|
|
72
|
+
return session;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Skip sessions that can't be read
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Gemini Session Discoverer
|
|
84
|
+
* Path: ~/.gemini/tmp/<projectHash>/chats/session-*.json
|
|
85
|
+
*/
|
|
86
|
+
class GeminiSessionDiscoverer extends SessionDiscoverer {
|
|
87
|
+
tool = 'gemini';
|
|
88
|
+
basePath = join(getHomePath(), '.gemini', 'tmp');
|
|
89
|
+
getSessions(options = {}) {
|
|
90
|
+
const { workingDir, limit, afterTimestamp } = options;
|
|
91
|
+
const sessions = [];
|
|
92
|
+
try {
|
|
93
|
+
if (!existsSync(this.basePath))
|
|
94
|
+
return [];
|
|
95
|
+
// If workingDir provided, only look in that project's folder
|
|
96
|
+
let projectDirs;
|
|
97
|
+
if (workingDir) {
|
|
98
|
+
const projectHash = calculateProjectHash(workingDir);
|
|
99
|
+
const projectPath = join(this.basePath, projectHash);
|
|
100
|
+
projectDirs = existsSync(projectPath) ? [projectHash] : [];
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
projectDirs = readdirSync(this.basePath).filter(d => {
|
|
104
|
+
const fullPath = join(this.basePath, d);
|
|
105
|
+
return statSync(fullPath).isDirectory();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
for (const projectHash of projectDirs) {
|
|
109
|
+
const chatsDir = join(this.basePath, projectHash, 'chats');
|
|
110
|
+
if (!existsSync(chatsDir))
|
|
111
|
+
continue;
|
|
112
|
+
const sessionFiles = readdirSync(chatsDir)
|
|
113
|
+
.filter(f => f.startsWith('session-') && f.endsWith('.json'))
|
|
114
|
+
.map(f => ({
|
|
115
|
+
name: f,
|
|
116
|
+
path: join(chatsDir, f),
|
|
117
|
+
stat: statSync(join(chatsDir, f))
|
|
118
|
+
}))
|
|
119
|
+
.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
120
|
+
for (const file of sessionFiles) {
|
|
121
|
+
if (afterTimestamp && file.stat.mtime <= afterTimestamp)
|
|
122
|
+
continue;
|
|
123
|
+
try {
|
|
124
|
+
const content = JSON.parse(readFileSync(file.path, 'utf8'));
|
|
125
|
+
sessions.push({
|
|
126
|
+
sessionId: content.sessionId,
|
|
127
|
+
tool: this.tool,
|
|
128
|
+
filePath: file.path,
|
|
129
|
+
projectHash,
|
|
130
|
+
createdAt: new Date(content.startTime || file.stat.birthtime),
|
|
131
|
+
updatedAt: new Date(content.lastUpdated || file.stat.mtime)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Skip invalid files
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Sort by updatedAt descending
|
|
140
|
+
sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
141
|
+
return limit ? sessions.slice(0, limit) : sessions;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
findSessionById(sessionId) {
|
|
148
|
+
const sessions = this.getSessions();
|
|
149
|
+
return sessions.find(s => s.sessionId === sessionId) || null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Extract first user message from Gemini session file
|
|
153
|
+
* Format: { "messages": [{ "type": "user", "content": "..." }] }
|
|
154
|
+
*/
|
|
155
|
+
extractFirstUserMessage(filePath) {
|
|
156
|
+
try {
|
|
157
|
+
const content = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
158
|
+
if (content.messages && Array.isArray(content.messages)) {
|
|
159
|
+
const userMsg = content.messages.find((m) => m.type === 'user');
|
|
160
|
+
return userMsg?.content || null;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Encode a path to Qwen's project folder name format
|
|
171
|
+
* D:\Claude_dms3 -> D--Claude-dms3
|
|
172
|
+
* Rules: : -> -, \ -> -, _ -> -
|
|
173
|
+
*/
|
|
174
|
+
function encodeQwenProjectPath(projectDir) {
|
|
175
|
+
const absolutePath = resolve(projectDir);
|
|
176
|
+
// Replace : -> -, \ -> -, _ -> -
|
|
177
|
+
return absolutePath
|
|
178
|
+
.replace(/:/g, '-')
|
|
179
|
+
.replace(/\\/g, '-')
|
|
180
|
+
.replace(/_/g, '-');
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Qwen Session Discoverer
|
|
184
|
+
* New path: ~/.qwen/projects/<path-encoded>/chats/<uuid>.jsonl
|
|
185
|
+
* Old path: ~/.qwen/tmp/<projectHash>/chats/session-*.json (deprecated, fallback)
|
|
186
|
+
*/
|
|
187
|
+
class QwenSessionDiscoverer extends SessionDiscoverer {
|
|
188
|
+
tool = 'qwen';
|
|
189
|
+
basePath = join(getHomePath(), '.qwen', 'projects');
|
|
190
|
+
legacyBasePath = join(getHomePath(), '.qwen', 'tmp');
|
|
191
|
+
getSessions(options = {}) {
|
|
192
|
+
const { workingDir, limit, afterTimestamp } = options;
|
|
193
|
+
const sessions = [];
|
|
194
|
+
// Try new format first (projects folder)
|
|
195
|
+
try {
|
|
196
|
+
if (existsSync(this.basePath)) {
|
|
197
|
+
let projectDirs;
|
|
198
|
+
if (workingDir) {
|
|
199
|
+
const encodedPath = encodeQwenProjectPath(workingDir);
|
|
200
|
+
const projectPath = join(this.basePath, encodedPath);
|
|
201
|
+
projectDirs = existsSync(projectPath) ? [encodedPath] : [];
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
projectDirs = readdirSync(this.basePath).filter(d => {
|
|
205
|
+
const fullPath = join(this.basePath, d);
|
|
206
|
+
return statSync(fullPath).isDirectory();
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
for (const projectFolder of projectDirs) {
|
|
210
|
+
const chatsDir = join(this.basePath, projectFolder, 'chats');
|
|
211
|
+
if (!existsSync(chatsDir))
|
|
212
|
+
continue;
|
|
213
|
+
// New format: <uuid>.jsonl files
|
|
214
|
+
const sessionFiles = readdirSync(chatsDir)
|
|
215
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
216
|
+
.map(f => ({
|
|
217
|
+
name: f,
|
|
218
|
+
path: join(chatsDir, f),
|
|
219
|
+
stat: statSync(join(chatsDir, f))
|
|
220
|
+
}))
|
|
221
|
+
.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
222
|
+
for (const file of sessionFiles) {
|
|
223
|
+
if (afterTimestamp && file.stat.mtime <= afterTimestamp)
|
|
224
|
+
continue;
|
|
225
|
+
try {
|
|
226
|
+
// Parse JSONL - read first line for session info
|
|
227
|
+
const content = readFileSync(file.path, 'utf8');
|
|
228
|
+
const firstLine = content.split('\n')[0];
|
|
229
|
+
const firstEntry = JSON.parse(firstLine);
|
|
230
|
+
// Session ID is in the filename or first entry
|
|
231
|
+
const sessionId = firstEntry.sessionId || basename(file.name, '.jsonl');
|
|
232
|
+
// Find timestamp from entries
|
|
233
|
+
let createdAt = file.stat.birthtime;
|
|
234
|
+
let updatedAt = file.stat.mtime;
|
|
235
|
+
if (firstEntry.timestamp) {
|
|
236
|
+
createdAt = new Date(firstEntry.timestamp);
|
|
237
|
+
}
|
|
238
|
+
// Get last entry for updatedAt
|
|
239
|
+
const lines = content.trim().split('\n').filter(l => l.trim());
|
|
240
|
+
if (lines.length > 0) {
|
|
241
|
+
try {
|
|
242
|
+
const lastEntry = JSON.parse(lines[lines.length - 1]);
|
|
243
|
+
if (lastEntry.timestamp) {
|
|
244
|
+
updatedAt = new Date(lastEntry.timestamp);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch { /* ignore */ }
|
|
248
|
+
}
|
|
249
|
+
sessions.push({
|
|
250
|
+
sessionId,
|
|
251
|
+
tool: this.tool,
|
|
252
|
+
filePath: file.path,
|
|
253
|
+
projectHash: projectFolder, // Using encoded path as project identifier
|
|
254
|
+
createdAt,
|
|
255
|
+
updatedAt
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Skip invalid files
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch { /* ignore errors */ }
|
|
266
|
+
// Fallback to legacy format (tmp folder with hash)
|
|
267
|
+
try {
|
|
268
|
+
if (existsSync(this.legacyBasePath)) {
|
|
269
|
+
let projectDirs;
|
|
270
|
+
if (workingDir) {
|
|
271
|
+
const projectHash = calculateProjectHash(workingDir);
|
|
272
|
+
const projectPath = join(this.legacyBasePath, projectHash);
|
|
273
|
+
projectDirs = existsSync(projectPath) ? [projectHash] : [];
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
projectDirs = readdirSync(this.legacyBasePath).filter(d => {
|
|
277
|
+
const fullPath = join(this.legacyBasePath, d);
|
|
278
|
+
return statSync(fullPath).isDirectory();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
for (const projectHash of projectDirs) {
|
|
282
|
+
const chatsDir = join(this.legacyBasePath, projectHash, 'chats');
|
|
283
|
+
if (!existsSync(chatsDir))
|
|
284
|
+
continue;
|
|
285
|
+
const sessionFiles = readdirSync(chatsDir)
|
|
286
|
+
.filter(f => f.startsWith('session-') && f.endsWith('.json'))
|
|
287
|
+
.map(f => ({
|
|
288
|
+
name: f,
|
|
289
|
+
path: join(chatsDir, f),
|
|
290
|
+
stat: statSync(join(chatsDir, f))
|
|
291
|
+
}))
|
|
292
|
+
.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
293
|
+
for (const file of sessionFiles) {
|
|
294
|
+
if (afterTimestamp && file.stat.mtime <= afterTimestamp)
|
|
295
|
+
continue;
|
|
296
|
+
try {
|
|
297
|
+
const content = JSON.parse(readFileSync(file.path, 'utf8'));
|
|
298
|
+
sessions.push({
|
|
299
|
+
sessionId: content.sessionId,
|
|
300
|
+
tool: this.tool,
|
|
301
|
+
filePath: file.path,
|
|
302
|
+
projectHash,
|
|
303
|
+
createdAt: new Date(content.startTime || file.stat.birthtime),
|
|
304
|
+
updatedAt: new Date(content.lastUpdated || file.stat.mtime)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Skip invalid files
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch { /* ignore errors */ }
|
|
315
|
+
// Sort by updatedAt descending and dedupe by sessionId
|
|
316
|
+
sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
317
|
+
// Dedupe (new format takes precedence as it's checked first)
|
|
318
|
+
const seen = new Set();
|
|
319
|
+
const uniqueSessions = sessions.filter(s => {
|
|
320
|
+
if (seen.has(s.sessionId))
|
|
321
|
+
return false;
|
|
322
|
+
seen.add(s.sessionId);
|
|
323
|
+
return true;
|
|
324
|
+
});
|
|
325
|
+
return limit ? uniqueSessions.slice(0, limit) : uniqueSessions;
|
|
326
|
+
}
|
|
327
|
+
findSessionById(sessionId) {
|
|
328
|
+
const sessions = this.getSessions();
|
|
329
|
+
return sessions.find(s => s.sessionId === sessionId) || null;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Extract first user message from Qwen session file
|
|
333
|
+
* New format (.jsonl): { type: "user", message: { role: "user", parts: [{ text: "..." }] } }
|
|
334
|
+
* Legacy format (.json): { "messages": [{ "type": "user", "content": "..." }] }
|
|
335
|
+
*/
|
|
336
|
+
extractFirstUserMessage(filePath) {
|
|
337
|
+
try {
|
|
338
|
+
const content = readFileSync(filePath, 'utf8');
|
|
339
|
+
// Check if JSONL (new format) or JSON (legacy)
|
|
340
|
+
if (filePath.endsWith('.jsonl')) {
|
|
341
|
+
// JSONL format - find first user message
|
|
342
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
343
|
+
for (const line of lines) {
|
|
344
|
+
try {
|
|
345
|
+
const entry = JSON.parse(line);
|
|
346
|
+
// New Qwen format: { type: "user", message: { parts: [{ text: "..." }] } }
|
|
347
|
+
if (entry.type === 'user' && entry.message?.parts?.[0]?.text) {
|
|
348
|
+
return entry.message.parts[0].text;
|
|
349
|
+
}
|
|
350
|
+
// Alternative format
|
|
351
|
+
if (entry.role === 'user' && entry.content) {
|
|
352
|
+
return entry.content;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch { /* skip invalid lines */ }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// Legacy JSON format
|
|
360
|
+
const data = JSON.parse(content);
|
|
361
|
+
if (data.messages && Array.isArray(data.messages)) {
|
|
362
|
+
const userMsg = data.messages.find((m) => m.type === 'user');
|
|
363
|
+
return userMsg?.content || null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Codex Session Discoverer
|
|
375
|
+
* Path: ~/.codex/sessions/YYYY/MM/DD/rollout-*-<uuid>.jsonl
|
|
376
|
+
*/
|
|
377
|
+
class CodexSessionDiscoverer extends SessionDiscoverer {
|
|
378
|
+
tool = 'codex';
|
|
379
|
+
basePath = join(getHomePath(), '.codex', 'sessions');
|
|
380
|
+
getSessions(options = {}) {
|
|
381
|
+
const { limit, afterTimestamp } = options;
|
|
382
|
+
const sessions = [];
|
|
383
|
+
try {
|
|
384
|
+
if (!existsSync(this.basePath))
|
|
385
|
+
return [];
|
|
386
|
+
// Get year directories (e.g., 2025)
|
|
387
|
+
const yearDirs = readdirSync(this.basePath)
|
|
388
|
+
.filter(d => /^\d{4}$/.test(d))
|
|
389
|
+
.sort((a, b) => b.localeCompare(a)); // Descending
|
|
390
|
+
for (const year of yearDirs) {
|
|
391
|
+
const yearPath = join(this.basePath, year);
|
|
392
|
+
if (!statSync(yearPath).isDirectory())
|
|
393
|
+
continue;
|
|
394
|
+
// Get month directories
|
|
395
|
+
const monthDirs = readdirSync(yearPath)
|
|
396
|
+
.filter(d => /^\d{2}$/.test(d))
|
|
397
|
+
.sort((a, b) => b.localeCompare(a));
|
|
398
|
+
for (const month of monthDirs) {
|
|
399
|
+
const monthPath = join(yearPath, month);
|
|
400
|
+
if (!statSync(monthPath).isDirectory())
|
|
401
|
+
continue;
|
|
402
|
+
// Get day directories
|
|
403
|
+
const dayDirs = readdirSync(monthPath)
|
|
404
|
+
.filter(d => /^\d{2}$/.test(d))
|
|
405
|
+
.sort((a, b) => b.localeCompare(a));
|
|
406
|
+
for (const day of dayDirs) {
|
|
407
|
+
const dayPath = join(monthPath, day);
|
|
408
|
+
if (!statSync(dayPath).isDirectory())
|
|
409
|
+
continue;
|
|
410
|
+
// Get session files
|
|
411
|
+
const sessionFiles = readdirSync(dayPath)
|
|
412
|
+
.filter(f => f.startsWith('rollout-') && f.endsWith('.jsonl'))
|
|
413
|
+
.map(f => ({
|
|
414
|
+
name: f,
|
|
415
|
+
path: join(dayPath, f),
|
|
416
|
+
stat: statSync(join(dayPath, f))
|
|
417
|
+
}))
|
|
418
|
+
.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
419
|
+
for (const file of sessionFiles) {
|
|
420
|
+
if (afterTimestamp && file.stat.mtime <= afterTimestamp)
|
|
421
|
+
continue;
|
|
422
|
+
try {
|
|
423
|
+
// Parse first line for session_meta
|
|
424
|
+
const firstLine = readFileSync(file.path, 'utf8').split('\n')[0];
|
|
425
|
+
const meta = JSON.parse(firstLine);
|
|
426
|
+
if (meta.type === 'session_meta' && meta.payload?.id) {
|
|
427
|
+
sessions.push({
|
|
428
|
+
sessionId: meta.payload.id,
|
|
429
|
+
tool: this.tool,
|
|
430
|
+
filePath: file.path,
|
|
431
|
+
createdAt: new Date(meta.payload.timestamp || file.stat.birthtime),
|
|
432
|
+
updatedAt: file.stat.mtime
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// Try extracting UUID from filename
|
|
438
|
+
const uuidMatch = file.name.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i);
|
|
439
|
+
if (uuidMatch) {
|
|
440
|
+
sessions.push({
|
|
441
|
+
sessionId: uuidMatch[1],
|
|
442
|
+
tool: this.tool,
|
|
443
|
+
filePath: file.path,
|
|
444
|
+
createdAt: file.stat.birthtime,
|
|
445
|
+
updatedAt: file.stat.mtime
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
454
|
+
return limit ? sessions.slice(0, limit) : sessions;
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return [];
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
findSessionById(sessionId) {
|
|
461
|
+
const sessions = this.getSessions();
|
|
462
|
+
return sessions.find(s => s.sessionId === sessionId) || null;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Extract first user message from Codex session file (.jsonl)
|
|
466
|
+
* Format: {"type":"event_msg","payload":{"type":"user_message","message":"..."}}
|
|
467
|
+
*/
|
|
468
|
+
extractFirstUserMessage(filePath) {
|
|
469
|
+
try {
|
|
470
|
+
const content = readFileSync(filePath, 'utf8');
|
|
471
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
472
|
+
for (const line of lines) {
|
|
473
|
+
try {
|
|
474
|
+
const entry = JSON.parse(line);
|
|
475
|
+
// Look for user_message event
|
|
476
|
+
if (entry.type === 'event_msg' &&
|
|
477
|
+
entry.payload?.type === 'user_message' &&
|
|
478
|
+
entry.payload?.message) {
|
|
479
|
+
return entry.payload.message;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch { /* skip invalid lines */ }
|
|
483
|
+
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Claude Code Session Discoverer
|
|
493
|
+
* Path: ~/.claude/projects/<projectHash>/sessions/*.jsonl
|
|
494
|
+
* Claude Code stores sessions with UUID-based session IDs
|
|
495
|
+
*/
|
|
496
|
+
class ClaudeSessionDiscoverer extends SessionDiscoverer {
|
|
497
|
+
tool = 'claude';
|
|
498
|
+
basePath = join(getHomePath(), '.claude', 'projects');
|
|
499
|
+
getSessions(options = {}) {
|
|
500
|
+
const { workingDir, limit, afterTimestamp } = options;
|
|
501
|
+
const sessions = [];
|
|
502
|
+
try {
|
|
503
|
+
if (!existsSync(this.basePath))
|
|
504
|
+
return [];
|
|
505
|
+
// If workingDir provided, only look in that project's folder
|
|
506
|
+
let projectDirs;
|
|
507
|
+
if (workingDir) {
|
|
508
|
+
const projectHash = calculateProjectHash(workingDir);
|
|
509
|
+
const projectPath = join(this.basePath, projectHash);
|
|
510
|
+
projectDirs = existsSync(projectPath) ? [projectHash] : [];
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
projectDirs = readdirSync(this.basePath).filter(d => {
|
|
514
|
+
const fullPath = join(this.basePath, d);
|
|
515
|
+
return statSync(fullPath).isDirectory();
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
for (const projectHash of projectDirs) {
|
|
519
|
+
// Claude Code stores session files directly in project folder (not in 'sessions' subdirectory)
|
|
520
|
+
// e.g., ~/.claude/projects/D--Claude-dms3/<uuid>.jsonl
|
|
521
|
+
const projectDir = join(this.basePath, projectHash);
|
|
522
|
+
if (!existsSync(projectDir))
|
|
523
|
+
continue;
|
|
524
|
+
const sessionFiles = readdirSync(projectDir)
|
|
525
|
+
.filter(f => f.endsWith('.jsonl') || f.endsWith('.json'))
|
|
526
|
+
.map(f => ({
|
|
527
|
+
name: f,
|
|
528
|
+
path: join(projectDir, f),
|
|
529
|
+
stat: statSync(join(projectDir, f))
|
|
530
|
+
}))
|
|
531
|
+
.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
532
|
+
for (const file of sessionFiles) {
|
|
533
|
+
if (afterTimestamp && file.stat.mtime <= afterTimestamp)
|
|
534
|
+
continue;
|
|
535
|
+
try {
|
|
536
|
+
// Extract session ID from filename or content
|
|
537
|
+
const uuidMatch = file.name.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
538
|
+
if (uuidMatch) {
|
|
539
|
+
sessions.push({
|
|
540
|
+
sessionId: uuidMatch[1],
|
|
541
|
+
tool: this.tool,
|
|
542
|
+
filePath: file.path,
|
|
543
|
+
projectHash,
|
|
544
|
+
createdAt: file.stat.birthtime,
|
|
545
|
+
updatedAt: file.stat.mtime
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
// Try reading first line for session metadata
|
|
550
|
+
const firstLine = readFileSync(file.path, 'utf8').split('\n')[0];
|
|
551
|
+
const meta = JSON.parse(firstLine);
|
|
552
|
+
if (meta.session_id) {
|
|
553
|
+
sessions.push({
|
|
554
|
+
sessionId: meta.session_id,
|
|
555
|
+
tool: this.tool,
|
|
556
|
+
filePath: file.path,
|
|
557
|
+
projectHash,
|
|
558
|
+
createdAt: new Date(meta.timestamp || file.stat.birthtime),
|
|
559
|
+
updatedAt: file.stat.mtime
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
// Skip invalid files
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
570
|
+
return limit ? sessions.slice(0, limit) : sessions;
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return [];
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
findSessionById(sessionId) {
|
|
577
|
+
const sessions = this.getSessions();
|
|
578
|
+
return sessions.find(s => s.sessionId === sessionId) || null;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Extract first user message from Claude Code session file (.jsonl)
|
|
582
|
+
* Format: {"type":"user","message":{"role":"user","content":"..."},"isMeta":false,...}
|
|
583
|
+
*/
|
|
584
|
+
extractFirstUserMessage(filePath) {
|
|
585
|
+
try {
|
|
586
|
+
const content = readFileSync(filePath, 'utf8');
|
|
587
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
588
|
+
for (const line of lines) {
|
|
589
|
+
try {
|
|
590
|
+
const entry = JSON.parse(line);
|
|
591
|
+
// Claude Code format: type="user", message.role="user", message.content="..."
|
|
592
|
+
// Skip meta messages and command messages
|
|
593
|
+
if (entry.type === 'user' &&
|
|
594
|
+
entry.message?.role === 'user' &&
|
|
595
|
+
entry.message?.content &&
|
|
596
|
+
!entry.isMeta &&
|
|
597
|
+
!entry.message.content.startsWith('<command-')) {
|
|
598
|
+
return entry.message.content;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
catch { /* skip invalid lines */ }
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// Singleton discoverers
|
|
611
|
+
const discoverers = {
|
|
612
|
+
gemini: new GeminiSessionDiscoverer(),
|
|
613
|
+
qwen: new QwenSessionDiscoverer(),
|
|
614
|
+
codex: new CodexSessionDiscoverer(),
|
|
615
|
+
claude: new ClaudeSessionDiscoverer()
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* Get session discoverer for a tool
|
|
619
|
+
*/
|
|
620
|
+
export function getDiscoverer(tool) {
|
|
621
|
+
return discoverers[tool] || null;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Get latest native session for a tool
|
|
625
|
+
*/
|
|
626
|
+
export function getLatestNativeSession(tool, workingDir) {
|
|
627
|
+
const discoverer = discoverers[tool];
|
|
628
|
+
if (!discoverer)
|
|
629
|
+
return null;
|
|
630
|
+
return discoverer.getLatestSession({ workingDir });
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Find native session by ID
|
|
634
|
+
*/
|
|
635
|
+
export function findNativeSessionById(tool, sessionId) {
|
|
636
|
+
const discoverer = discoverers[tool];
|
|
637
|
+
if (!discoverer)
|
|
638
|
+
return null;
|
|
639
|
+
return discoverer.findSessionById(sessionId);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Track new session created during execution
|
|
643
|
+
* @param tool - CLI tool name (gemini, qwen, codex, claude)
|
|
644
|
+
* @param beforeTimestamp - Filter sessions created after this time
|
|
645
|
+
* @param workingDir - Project working directory
|
|
646
|
+
* @param prompt - Optional prompt for precise matching in parallel execution
|
|
647
|
+
*/
|
|
648
|
+
export async function trackNewSession(tool, beforeTimestamp, workingDir, prompt) {
|
|
649
|
+
const discoverer = discoverers[tool];
|
|
650
|
+
if (!discoverer)
|
|
651
|
+
return null;
|
|
652
|
+
return discoverer.trackNewSession(beforeTimestamp, workingDir, prompt);
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get all sessions for a tool
|
|
656
|
+
*/
|
|
657
|
+
export function getNativeSessions(tool, options) {
|
|
658
|
+
const discoverer = discoverers[tool];
|
|
659
|
+
if (!discoverer)
|
|
660
|
+
return [];
|
|
661
|
+
return discoverer.getSessions(options);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Check if a tool supports native resume
|
|
665
|
+
*/
|
|
666
|
+
export function supportsNativeResume(tool) {
|
|
667
|
+
return tool in discoverers;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Get native resume command arguments for a tool
|
|
671
|
+
*/
|
|
672
|
+
export function getNativeResumeArgs(tool, sessionId) {
|
|
673
|
+
switch (tool) {
|
|
674
|
+
case 'gemini':
|
|
675
|
+
// gemini -r <uuid> or -r latest
|
|
676
|
+
return ['-r', sessionId];
|
|
677
|
+
case 'qwen':
|
|
678
|
+
// qwen --continue (latest) or --resume <uuid>
|
|
679
|
+
if (sessionId === 'latest') {
|
|
680
|
+
return ['--continue'];
|
|
681
|
+
}
|
|
682
|
+
return ['--resume', sessionId];
|
|
683
|
+
case 'codex':
|
|
684
|
+
// codex resume <uuid> or codex resume --last
|
|
685
|
+
if (sessionId === 'latest') {
|
|
686
|
+
return ['resume', '--last'];
|
|
687
|
+
}
|
|
688
|
+
return ['resume', sessionId];
|
|
689
|
+
default:
|
|
690
|
+
return [];
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Get base path for a tool's sessions
|
|
695
|
+
*/
|
|
696
|
+
export function getToolSessionPath(tool) {
|
|
697
|
+
const discoverer = discoverers[tool];
|
|
698
|
+
return discoverer?.basePath || null;
|
|
699
|
+
}
|
|
700
|
+
//# sourceMappingURL=native-session-discovery.js.map
|