byterover-cli 0.3.5 → 0.4.0
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/README.md +119 -63
- package/bin/dev.js +8 -1
- package/bin/run.js +7 -0
- package/dist/commands/cipher-agent/run.d.ts +30 -0
- package/dist/commands/cipher-agent/run.js +123 -61
- package/dist/commands/cipher-agent/set-prompt.d.ts +2 -0
- package/dist/commands/cipher-agent/set-prompt.js +13 -8
- package/dist/commands/cipher-agent/show-prompt.d.ts +2 -0
- package/dist/commands/cipher-agent/show-prompt.js +17 -12
- package/dist/commands/curate.d.ts +3 -60
- package/dist/commands/curate.js +45 -341
- package/dist/commands/foo.d.ts +4 -2
- package/dist/commands/foo.js +21 -16
- package/dist/commands/main.d.ts +9 -0
- package/dist/commands/main.js +34 -0
- package/dist/commands/query.d.ts +2 -48
- package/dist/commands/query.js +19 -287
- package/dist/commands/status.d.ts +2 -13
- package/dist/commands/status.js +12 -91
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +23 -19
- package/dist/config/environment.d.ts +1 -1
- package/dist/config/environment.js +2 -2
- package/dist/constants.d.ts +4 -5
- package/dist/constants.js +5 -5
- package/dist/core/domain/cipher/errors/storage-error.d.ts +89 -0
- package/dist/core/domain/cipher/errors/storage-error.js +130 -0
- package/dist/core/domain/cipher/queue/types.d.ts +71 -0
- package/dist/core/domain/cipher/queue/types.js +9 -0
- package/dist/core/domain/cipher/storage/message-storage-types.d.ts +218 -0
- package/dist/core/domain/cipher/storage/message-storage-types.js +18 -0
- package/dist/core/domain/cipher/tools/constants.d.ts +1 -0
- package/dist/core/domain/cipher/tools/constants.js +1 -0
- package/dist/core/domain/entities/event.d.ts +1 -1
- package/dist/core/domain/entities/event.js +5 -0
- package/dist/core/domain/entities/global-config.d.ts +36 -0
- package/dist/core/domain/entities/global-config.js +66 -0
- package/dist/core/domain/knowledge/directory-manager.d.ts +10 -0
- package/dist/core/domain/knowledge/directory-manager.js +18 -0
- package/dist/core/domain/knowledge/markdown-writer.d.ts +9 -0
- package/dist/core/domain/knowledge/markdown-writer.js +51 -1
- package/dist/core/interfaces/cipher/i-agent-storage.d.ts +152 -0
- package/dist/core/interfaces/cipher/i-agent-storage.js +1 -0
- package/dist/core/interfaces/cipher/i-cipher-agent.d.ts +2 -0
- package/dist/core/interfaces/cipher/i-key-storage.d.ts +91 -0
- package/dist/core/interfaces/cipher/i-key-storage.js +1 -0
- package/dist/core/interfaces/i-global-config-store.d.ts +34 -0
- package/dist/core/interfaces/i-global-config-store.js +1 -0
- package/dist/core/interfaces/i-onboarding-preference-store.d.ts +20 -0
- package/dist/core/interfaces/i-onboarding-preference-store.js +1 -0
- package/dist/core/interfaces/i-terminal.d.ts +146 -0
- package/dist/core/interfaces/i-terminal.js +1 -0
- package/dist/core/interfaces/i-workspace-detector-service.d.ts +8 -0
- package/dist/core/interfaces/i-workspace-detector-service.js +1 -0
- package/dist/core/interfaces/usecase/i-clear-use-case.d.ts +6 -0
- package/dist/core/interfaces/usecase/i-clear-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-curate-use-case.d.ts +10 -0
- package/dist/core/interfaces/usecase/i-curate-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-generate-rules-use-case.d.ts +3 -0
- package/dist/core/interfaces/usecase/i-generate-rules-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-init-use-case.d.ts +5 -0
- package/dist/core/interfaces/usecase/i-init-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-login-use-case.d.ts +3 -0
- package/dist/core/interfaces/usecase/i-login-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-logout-use-case.d.ts +5 -0
- package/dist/core/interfaces/usecase/i-logout-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-pull-use-case.d.ts +5 -0
- package/dist/core/interfaces/usecase/i-pull-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-push-use-case.d.ts +6 -0
- package/dist/core/interfaces/usecase/i-push-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-query-use-case.d.ts +9 -0
- package/dist/core/interfaces/usecase/i-query-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-space-list-use-case.d.ts +3 -0
- package/dist/core/interfaces/usecase/i-space-list-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-space-switch-use-case.d.ts +3 -0
- package/dist/core/interfaces/usecase/i-space-switch-use-case.js +1 -0
- package/dist/core/interfaces/usecase/i-status-use-case.d.ts +5 -0
- package/dist/core/interfaces/usecase/i-status-use-case.js +1 -0
- package/dist/hooks/init/welcome.js +1 -2
- package/dist/infra/cipher/agent-service-factory.d.ts +13 -6
- package/dist/infra/cipher/agent-service-factory.js +40 -16
- package/dist/infra/cipher/cipher-agent.js +4 -4
- package/dist/infra/cipher/consumer/consumer-lock.d.ts +20 -0
- package/dist/infra/cipher/consumer/consumer-lock.js +40 -0
- package/dist/infra/cipher/consumer/consumer-service.d.ts +99 -0
- package/dist/infra/cipher/consumer/consumer-service.js +165 -0
- package/dist/infra/cipher/consumer/execution-consumer.d.ts +121 -0
- package/dist/infra/cipher/consumer/execution-consumer.js +523 -0
- package/dist/infra/cipher/consumer/index.d.ts +33 -0
- package/dist/infra/cipher/consumer/index.js +33 -0
- package/dist/infra/cipher/consumer/queue-polling-service.d.ts +120 -0
- package/dist/infra/cipher/consumer/queue-polling-service.js +248 -0
- package/dist/infra/cipher/http/internal-llm-http-service.d.ts +94 -0
- package/dist/infra/cipher/http/internal-llm-http-service.js +118 -0
- package/dist/infra/cipher/llm/context/compaction/compaction-service.d.ts +106 -0
- package/dist/infra/cipher/llm/context/compaction/compaction-service.js +132 -0
- package/dist/infra/cipher/llm/context/compaction/index.d.ts +9 -0
- package/dist/infra/cipher/llm/context/compaction/index.js +9 -0
- package/dist/infra/cipher/llm/context/context-manager.d.ts +46 -2
- package/dist/infra/cipher/llm/context/context-manager.js +68 -4
- package/dist/infra/cipher/llm/context/rw-lock.d.ts +72 -0
- package/dist/infra/cipher/llm/context/rw-lock.js +145 -0
- package/dist/infra/cipher/llm/generators/byterover-content-generator.d.ts +7 -7
- package/dist/infra/cipher/llm/generators/byterover-content-generator.js +8 -8
- package/dist/infra/cipher/llm/internal-llm-service.js +2 -0
- package/dist/infra/cipher/session/session-manager.d.ts +4 -4
- package/dist/infra/cipher/session/session-manager.js +5 -5
- package/dist/infra/cipher/storage/agent-storage.d.ts +246 -0
- package/dist/infra/cipher/storage/agent-storage.js +956 -0
- package/dist/infra/cipher/storage/dual-format-history-storage.d.ts +77 -0
- package/dist/infra/cipher/storage/dual-format-history-storage.js +149 -0
- package/dist/infra/cipher/storage/granular-history-storage.d.ts +65 -0
- package/dist/infra/cipher/storage/granular-history-storage.js +118 -0
- package/dist/infra/cipher/storage/message-storage-service.d.ts +108 -0
- package/dist/infra/cipher/storage/message-storage-service.js +529 -0
- package/dist/infra/cipher/storage/process-utils.d.ts +16 -0
- package/dist/infra/cipher/storage/process-utils.js +43 -0
- package/dist/infra/cipher/storage/sqlite-key-storage.d.ts +105 -0
- package/dist/infra/cipher/storage/sqlite-key-storage.js +404 -0
- package/dist/infra/cipher/system-prompt/simple-prompt-factory.d.ts +1 -0
- package/dist/infra/cipher/system-prompt/simple-prompt-factory.js +7 -0
- package/dist/infra/cipher/tools/default-policy-rules.js +1 -1
- package/dist/infra/cipher/tools/implementations/curate-tool.d.ts +10 -0
- package/dist/infra/cipher/tools/implementations/curate-tool.js +371 -0
- package/dist/infra/cipher/tools/implementations/find-knowledge-topics-tool.js +11 -8
- package/dist/infra/cipher/tools/tool-manager.d.ts +8 -2
- package/dist/infra/cipher/tools/tool-manager.js +29 -2
- package/dist/infra/cipher/tools/tool-registry.js +7 -0
- package/dist/infra/http/authenticated-http-client.d.ts +21 -0
- package/dist/infra/http/authenticated-http-client.js +38 -0
- package/dist/infra/repl/commands/arg-parser.d.ts +97 -0
- package/dist/infra/repl/commands/arg-parser.js +129 -0
- package/dist/infra/repl/commands/clear-command.d.ts +5 -0
- package/dist/infra/repl/commands/clear-command.js +61 -0
- package/dist/infra/repl/commands/curate-command.d.ts +9 -0
- package/dist/infra/repl/commands/curate-command.js +88 -0
- package/dist/infra/repl/commands/gen-rules-command.d.ts +7 -0
- package/dist/infra/repl/commands/gen-rules-command.js +38 -0
- package/dist/infra/repl/commands/index.d.ts +8 -0
- package/dist/infra/repl/commands/index.js +36 -0
- package/dist/infra/repl/commands/init-command.d.ts +7 -0
- package/dist/infra/repl/commands/init-command.js +83 -0
- package/dist/infra/repl/commands/login-command.d.ts +7 -0
- package/dist/infra/repl/commands/login-command.js +50 -0
- package/dist/infra/repl/commands/logout-command.d.ts +5 -0
- package/dist/infra/repl/commands/logout-command.js +48 -0
- package/dist/infra/repl/commands/pull-command.d.ts +5 -0
- package/dist/infra/repl/commands/pull-command.js +61 -0
- package/dist/infra/repl/commands/push-command.d.ts +5 -0
- package/dist/infra/repl/commands/push-command.js +66 -0
- package/dist/infra/repl/commands/query-command.d.ts +5 -0
- package/dist/infra/repl/commands/query-command.js +66 -0
- package/dist/infra/repl/commands/space/index.d.ts +5 -0
- package/dist/infra/repl/commands/space/index.js +14 -0
- package/dist/infra/repl/commands/space/list-command.d.ts +5 -0
- package/dist/infra/repl/commands/space/list-command.js +70 -0
- package/dist/infra/repl/commands/space/switch-command.d.ts +5 -0
- package/dist/infra/repl/commands/space/switch-command.js +37 -0
- package/dist/infra/repl/commands/status-command.d.ts +5 -0
- package/dist/infra/repl/commands/status-command.js +39 -0
- package/dist/infra/repl/repl-startup.d.ts +18 -0
- package/dist/infra/repl/repl-startup.js +26 -0
- package/dist/infra/storage/file-global-config-store.d.ts +22 -0
- package/dist/infra/storage/file-global-config-store.js +65 -0
- package/dist/infra/storage/file-onboarding-preference-store.d.ts +10 -0
- package/dist/infra/storage/file-onboarding-preference-store.js +46 -0
- package/dist/infra/terminal/oclif-terminal.d.ts +19 -0
- package/dist/infra/terminal/oclif-terminal.js +60 -0
- package/dist/infra/terminal/repl-terminal.d.ts +31 -0
- package/dist/infra/terminal/repl-terminal.js +116 -0
- package/dist/infra/tracking/mixpanel-tracking-service.d.ts +11 -1
- package/dist/infra/tracking/mixpanel-tracking-service.js +18 -13
- package/dist/infra/usecase/clear-use-case.d.ts +20 -0
- package/dist/infra/usecase/clear-use-case.js +58 -0
- package/dist/infra/usecase/curate-use-case.d.ts +66 -0
- package/dist/infra/usecase/curate-use-case.js +283 -0
- package/dist/{commands/gen-rules.d.ts → infra/usecase/generate-rules-use-case.d.ts} +14 -20
- package/dist/{commands/gen-rules.js → infra/usecase/generate-rules-use-case.js} +59 -78
- package/dist/infra/usecase/init-use-case.d.ts +139 -0
- package/dist/{commands/init.js → infra/usecase/init-use-case.js} +184 -230
- package/dist/infra/usecase/login-use-case.d.ts +28 -0
- package/dist/infra/usecase/login-use-case.js +88 -0
- package/dist/infra/usecase/logout-use-case.d.ts +22 -0
- package/dist/infra/usecase/logout-use-case.js +51 -0
- package/dist/infra/usecase/pull-use-case.d.ts +35 -0
- package/dist/infra/usecase/pull-use-case.js +89 -0
- package/dist/infra/usecase/push-use-case.d.ts +37 -0
- package/dist/infra/usecase/push-use-case.js +124 -0
- package/dist/infra/usecase/query-use-case.d.ts +78 -0
- package/dist/infra/usecase/query-use-case.js +401 -0
- package/dist/infra/usecase/space-list-use-case.d.ts +27 -0
- package/dist/infra/usecase/space-list-use-case.js +64 -0
- package/dist/infra/usecase/space-switch-use-case.d.ts +36 -0
- package/dist/infra/usecase/space-switch-use-case.js +140 -0
- package/dist/infra/usecase/status-use-case.d.ts +27 -0
- package/dist/infra/usecase/status-use-case.js +97 -0
- package/dist/infra/workspace/workspace-detector-service.d.ts +3 -6
- package/dist/resources/prompts/curate-context-tree-curation.yml +23 -11
- package/dist/resources/prompts/query-context-tree-retrieval.yml +3 -4
- package/dist/resources/prompts/system-prompt.yml +1 -1
- package/dist/resources/prompts/tool-outputs.yml +4 -3
- package/dist/templates/sections/command-reference.md +12 -0
- package/dist/templates/sections/workflow.md +10 -1
- package/dist/tui/app.d.ts +9 -0
- package/dist/tui/app.js +26 -0
- package/dist/tui/components/enter-prompt.d.ts +13 -0
- package/dist/tui/components/enter-prompt.js +15 -0
- package/dist/tui/components/execution/execution-changes.d.ts +14 -0
- package/dist/tui/components/execution/execution-changes.js +15 -0
- package/dist/tui/components/execution/execution-content.d.ts +25 -0
- package/dist/tui/components/execution/execution-content.js +67 -0
- package/dist/tui/components/execution/execution-input.d.ts +12 -0
- package/dist/tui/components/execution/execution-input.js +16 -0
- package/dist/tui/components/execution/execution-progress.d.ts +21 -0
- package/dist/tui/components/execution/execution-progress.js +21 -0
- package/dist/tui/components/execution/execution-status.d.ts +13 -0
- package/dist/tui/components/execution/execution-status.js +19 -0
- package/dist/tui/components/execution/index.d.ts +11 -0
- package/dist/tui/components/execution/index.js +11 -0
- package/dist/tui/components/execution/log-item.d.ts +17 -0
- package/dist/tui/components/execution/log-item.js +25 -0
- package/dist/tui/components/footer.d.ts +5 -0
- package/dist/tui/components/footer.js +12 -0
- package/dist/tui/components/header.d.ts +18 -0
- package/dist/tui/components/header.js +18 -0
- package/dist/tui/components/index.d.ts +17 -0
- package/dist/tui/components/index.js +14 -0
- package/dist/tui/components/inline-prompts/index.d.ts +15 -0
- package/dist/tui/components/inline-prompts/index.js +10 -0
- package/dist/tui/components/inline-prompts/inline-confirm.d.ts +17 -0
- package/dist/tui/components/inline-prompts/inline-confirm.js +32 -0
- package/dist/tui/components/inline-prompts/inline-file-selector.d.ts +43 -0
- package/dist/tui/components/inline-prompts/inline-file-selector.js +185 -0
- package/dist/tui/components/inline-prompts/inline-input.d.ts +19 -0
- package/dist/tui/components/inline-prompts/inline-input.js +32 -0
- package/dist/tui/components/inline-prompts/inline-search.d.ts +20 -0
- package/dist/tui/components/inline-prompts/inline-search.js +50 -0
- package/dist/tui/components/inline-prompts/inline-select.d.ts +20 -0
- package/dist/tui/components/inline-prompts/inline-select.js +34 -0
- package/dist/tui/components/logo.d.ts +43 -0
- package/dist/tui/components/logo.js +103 -0
- package/dist/tui/components/message-item.d.ts +12 -0
- package/dist/tui/components/message-item.js +12 -0
- package/dist/tui/components/onboarding/copyable-prompt.d.ts +15 -0
- package/dist/tui/components/onboarding/copyable-prompt.js +65 -0
- package/dist/tui/components/onboarding/index.d.ts +7 -0
- package/dist/tui/components/onboarding/index.js +6 -0
- package/dist/tui/components/onboarding/onboarding-flow.d.ts +13 -0
- package/dist/tui/components/onboarding/onboarding-flow.js +304 -0
- package/dist/tui/components/onboarding/onboarding-step.d.ts +23 -0
- package/dist/tui/components/onboarding/onboarding-step.js +12 -0
- package/dist/tui/components/output-log.d.ts +14 -0
- package/dist/tui/components/output-log.js +13 -0
- package/dist/tui/components/scrollable-list.d.ts +30 -0
- package/dist/tui/components/scrollable-list.js +121 -0
- package/dist/tui/components/suggestions.d.ts +16 -0
- package/dist/tui/components/suggestions.js +162 -0
- package/dist/tui/components/tab-bar.d.ts +10 -0
- package/dist/tui/components/tab-bar.js +12 -0
- package/dist/tui/constants.d.ts +11 -0
- package/dist/tui/constants.js +13 -0
- package/dist/tui/contexts/auth-context.d.ts +30 -0
- package/dist/tui/contexts/auth-context.js +153 -0
- package/dist/tui/contexts/consumer.d.ts +31 -0
- package/dist/tui/contexts/consumer.js +56 -0
- package/dist/tui/contexts/index.d.ts +6 -0
- package/dist/tui/contexts/index.js +6 -0
- package/dist/tui/contexts/onboarding-context.d.ts +43 -0
- package/dist/tui/contexts/onboarding-context.js +181 -0
- package/dist/tui/contexts/services-context.d.ts +29 -0
- package/dist/tui/contexts/services-context.js +20 -0
- package/dist/tui/contexts/use-commands.d.ts +29 -0
- package/dist/tui/contexts/use-commands.js +53 -0
- package/dist/tui/contexts/use-mode.d.ts +43 -0
- package/dist/tui/contexts/use-mode.js +76 -0
- package/dist/tui/contexts/use-theme.d.ts +53 -0
- package/dist/tui/contexts/use-theme.js +60 -0
- package/dist/tui/hooks/index.d.ts +17 -0
- package/dist/tui/hooks/index.js +14 -0
- package/dist/tui/hooks/use-activity-logs.d.ts +26 -0
- package/dist/tui/hooks/use-activity-logs.js +90 -0
- package/dist/tui/hooks/use-consumer.d.ts +12 -0
- package/dist/tui/hooks/use-consumer.js +50 -0
- package/dist/tui/hooks/use-onboarding.d.ts +7 -0
- package/dist/tui/hooks/use-onboarding.js +6 -0
- package/dist/tui/hooks/use-queue-polling.d.ts +31 -0
- package/dist/tui/hooks/use-queue-polling.js +90 -0
- package/dist/tui/hooks/use-slash-command-processor.d.ts +16 -0
- package/dist/tui/hooks/use-slash-command-processor.js +132 -0
- package/dist/tui/hooks/use-slash-completion.d.ts +30 -0
- package/dist/tui/hooks/use-slash-completion.js +230 -0
- package/dist/tui/hooks/use-tab-navigation.d.ts +10 -0
- package/dist/tui/hooks/use-tab-navigation.js +35 -0
- package/dist/tui/hooks/use-visible-window.d.ts +22 -0
- package/dist/tui/hooks/use-visible-window.js +37 -0
- package/dist/tui/index.d.ts +1 -0
- package/dist/tui/index.js +1 -0
- package/dist/tui/providers/app-providers.d.ts +25 -0
- package/dist/tui/providers/app-providers.js +9 -0
- package/dist/tui/types/commands.d.ts +252 -0
- package/dist/tui/types/commands.js +16 -0
- package/dist/tui/types/dialogs.d.ts +37 -0
- package/dist/tui/types/dialogs.js +4 -0
- package/dist/tui/types/index.d.ts +11 -0
- package/dist/tui/types/index.js +7 -0
- package/dist/tui/types/messages.d.ts +55 -0
- package/dist/tui/types/messages.js +4 -0
- package/dist/tui/types/prompts.d.ts +100 -0
- package/dist/tui/types/prompts.js +4 -0
- package/dist/tui/types/ui.d.ts +14 -0
- package/dist/tui/types/ui.js +4 -0
- package/dist/tui/types.d.ts +1 -0
- package/dist/tui/types.js +1 -0
- package/dist/tui/views/command-view.d.ts +12 -0
- package/dist/tui/views/command-view.js +451 -0
- package/dist/tui/views/index.d.ts +6 -0
- package/dist/tui/views/index.js +6 -0
- package/dist/tui/views/login-view.d.ts +10 -0
- package/dist/tui/views/login-view.js +30 -0
- package/dist/tui/views/logs-view.d.ts +11 -0
- package/dist/tui/views/logs-view.js +73 -0
- package/dist/utils/file-validator.d.ts +16 -0
- package/dist/utils/file-validator.js +81 -0
- package/dist/utils/global-config-path.d.ts +15 -0
- package/dist/utils/global-config-path.js +38 -0
- package/oclif.manifest.json +29 -315
- package/package.json +11 -4
- package/dist/commands/clear.d.ts +0 -19
- package/dist/commands/clear.js +0 -78
- package/dist/commands/init.d.ts +0 -130
- package/dist/commands/login.d.ts +0 -22
- package/dist/commands/login.js +0 -108
- package/dist/commands/logout.d.ts +0 -16
- package/dist/commands/logout.js +0 -61
- package/dist/commands/pull.d.ts +0 -33
- package/dist/commands/pull.js +0 -115
- package/dist/commands/push.d.ts +0 -35
- package/dist/commands/push.js +0 -160
- package/dist/commands/space/list.d.ts +0 -25
- package/dist/commands/space/list.js +0 -114
- package/dist/commands/space/switch.d.ts +0 -36
- package/dist/commands/space/switch.js +0 -160
- package/dist/infra/cipher/grpc/internal-llm-grpc-service.d.ts +0 -149
- package/dist/infra/cipher/grpc/internal-llm-grpc-service.js +0 -364
- package/dist/infra/cipher/grpc/internal-llm-grpc.proto +0 -94
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { COMPACTED_TOOL_OUTPUT_PLACEHOLDER } from '../../../core/domain/cipher/storage/message-storage-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Service for granular message and part storage.
|
|
5
|
+
*
|
|
6
|
+
* Handles the conversion between InternalMessage format and the granular
|
|
7
|
+
* StoredMessage/StoredPart format, enabling:
|
|
8
|
+
* - Streaming message loading (newest to oldest)
|
|
9
|
+
* - Selective tool output pruning
|
|
10
|
+
* - Compaction boundary markers
|
|
11
|
+
*
|
|
12
|
+
* Key structure:
|
|
13
|
+
* - ["session", sessionId] → SessionRecord
|
|
14
|
+
* - ["message", sessionId, messageId] → StoredMessage
|
|
15
|
+
* - ["part", messageId, partId] → StoredPart
|
|
16
|
+
*/
|
|
17
|
+
export class MessageStorageService {
|
|
18
|
+
keyStorage;
|
|
19
|
+
constructor(keyStorage) {
|
|
20
|
+
this.keyStorage = keyStorage;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Delete a session and all its messages and parts.
|
|
24
|
+
*/
|
|
25
|
+
async deleteSession(sessionId) {
|
|
26
|
+
const session = await this.getSession(sessionId);
|
|
27
|
+
if (!session) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
// Delete all messages and their parts
|
|
31
|
+
const messageKeys = await this.keyStorage.list(this.messagePrefix(sessionId));
|
|
32
|
+
for (const messageKey of messageKeys) {
|
|
33
|
+
const messageId = messageKey[2];
|
|
34
|
+
// Delete all parts for this message
|
|
35
|
+
// eslint-disable-next-line no-await-in-loop
|
|
36
|
+
const partKeys = await this.keyStorage.list(this.partPrefix(messageId));
|
|
37
|
+
for (const partKey of partKeys) {
|
|
38
|
+
// eslint-disable-next-line no-await-in-loop
|
|
39
|
+
await this.keyStorage.delete(partKey);
|
|
40
|
+
}
|
|
41
|
+
// Delete the message
|
|
42
|
+
// eslint-disable-next-line no-await-in-loop
|
|
43
|
+
await this.keyStorage.delete(messageKey);
|
|
44
|
+
}
|
|
45
|
+
// Delete the session record
|
|
46
|
+
return this.keyStorage.delete(this.sessionKey(sessionId));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get the session record, if it exists.
|
|
50
|
+
*/
|
|
51
|
+
async getSession(sessionId) {
|
|
52
|
+
return this.keyStorage.get(this.sessionKey(sessionId));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if a session exists in granular format.
|
|
56
|
+
*/
|
|
57
|
+
async hasSession(sessionId) {
|
|
58
|
+
return this.keyStorage.exists(this.sessionKey(sessionId));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Insert a compaction boundary marker.
|
|
62
|
+
* Creates a special message that signals where to stop loading history.
|
|
63
|
+
*/
|
|
64
|
+
async insertCompactionBoundary(sessionId, summary) {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const messageId = randomUUID();
|
|
67
|
+
// Get session
|
|
68
|
+
const session = await this.getSession(sessionId);
|
|
69
|
+
if (!session) {
|
|
70
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
71
|
+
}
|
|
72
|
+
// Create compaction boundary message
|
|
73
|
+
const boundaryMessage = {
|
|
74
|
+
compactionBoundary: true,
|
|
75
|
+
compactionSummary: summary,
|
|
76
|
+
content: summary,
|
|
77
|
+
createdAt: now,
|
|
78
|
+
id: messageId,
|
|
79
|
+
partIds: [],
|
|
80
|
+
prevMessageId: session.newestMessageId,
|
|
81
|
+
role: 'user', // Compaction boundaries are modeled as user messages
|
|
82
|
+
sessionId,
|
|
83
|
+
updatedAt: now,
|
|
84
|
+
};
|
|
85
|
+
// Update the previous newest message
|
|
86
|
+
if (session.newestMessageId) {
|
|
87
|
+
await this.keyStorage.update(this.messageKey(sessionId, session.newestMessageId), (prev) => {
|
|
88
|
+
if (!prev)
|
|
89
|
+
throw new Error(`Previous message ${session.newestMessageId} not found`);
|
|
90
|
+
return { ...prev, nextMessageId: messageId, updatedAt: now };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Save boundary message
|
|
94
|
+
await this.keyStorage.set(this.messageKey(sessionId, messageId), boundaryMessage);
|
|
95
|
+
// Update session
|
|
96
|
+
session.newestMessageId = messageId;
|
|
97
|
+
session.lastCompactionMessageId = messageId;
|
|
98
|
+
session.messageCount += 1;
|
|
99
|
+
session.updatedAt = now;
|
|
100
|
+
await this.saveSession(session);
|
|
101
|
+
return boundaryMessage;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* List all session IDs in granular format.
|
|
105
|
+
*/
|
|
106
|
+
async listSessions() {
|
|
107
|
+
const sessionKeys = await this.keyStorage.list(['session']);
|
|
108
|
+
return sessionKeys.map((key) => key[1]); // ["session", sessionId] -> sessionId
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Load messages from a session, stopping at compaction boundary.
|
|
112
|
+
* Returns messages in chronological order (oldest first).
|
|
113
|
+
*/
|
|
114
|
+
async loadMessages(sessionId, options) {
|
|
115
|
+
const session = await this.getSession(sessionId);
|
|
116
|
+
if (!session || !session.newestMessageId) {
|
|
117
|
+
return { hitCompactionBoundary: false, messages: [] };
|
|
118
|
+
}
|
|
119
|
+
const stopAtCompaction = options?.stopAtCompaction ?? true;
|
|
120
|
+
const messages = [];
|
|
121
|
+
let hitCompactionBoundary = false;
|
|
122
|
+
// Traverse from newest to oldest
|
|
123
|
+
let currentMessageId = session.newestMessageId;
|
|
124
|
+
while (currentMessageId) {
|
|
125
|
+
// eslint-disable-next-line no-await-in-loop
|
|
126
|
+
const storedMsg = await this.keyStorage.get(this.messageKey(sessionId, currentMessageId));
|
|
127
|
+
if (!storedMsg)
|
|
128
|
+
break;
|
|
129
|
+
// Load parts for this message
|
|
130
|
+
// eslint-disable-next-line no-await-in-loop
|
|
131
|
+
const parts = await this.loadParts(storedMsg);
|
|
132
|
+
const messageWithParts = { ...storedMsg, parts };
|
|
133
|
+
// Check for compaction boundary
|
|
134
|
+
if (stopAtCompaction && storedMsg.compactionBoundary) {
|
|
135
|
+
// Include the compaction boundary message itself
|
|
136
|
+
messages.unshift(messageWithParts);
|
|
137
|
+
hitCompactionBoundary = true;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
messages.unshift(messageWithParts);
|
|
141
|
+
currentMessageId = storedMsg.prevMessageId;
|
|
142
|
+
}
|
|
143
|
+
return { hitCompactionBoundary, messages };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Prune old tool outputs by marking them as compacted.
|
|
147
|
+
* Keeps the most recent tool outputs up to the specified token limit.
|
|
148
|
+
*/
|
|
149
|
+
async pruneToolOutputs(options) {
|
|
150
|
+
const { keepTokens = 40_000, sessionId } = options;
|
|
151
|
+
const session = await this.getSession(sessionId);
|
|
152
|
+
if (!session || !session.newestMessageId) {
|
|
153
|
+
return { compactedCount: 0, tokensSaved: 0 };
|
|
154
|
+
}
|
|
155
|
+
// Collect all tool output parts, newest first
|
|
156
|
+
const toolOutputParts = [];
|
|
157
|
+
// Traverse messages from newest to oldest
|
|
158
|
+
let currentMessageId = session.newestMessageId;
|
|
159
|
+
while (currentMessageId) {
|
|
160
|
+
// eslint-disable-next-line no-await-in-loop
|
|
161
|
+
const storedMsg = await this.keyStorage.get(this.messageKey(sessionId, currentMessageId));
|
|
162
|
+
if (!storedMsg)
|
|
163
|
+
break;
|
|
164
|
+
// Collect tool output parts from this message
|
|
165
|
+
for (const partId of storedMsg.partIds) {
|
|
166
|
+
// eslint-disable-next-line no-await-in-loop
|
|
167
|
+
const part = await this.keyStorage.get(this.partKey(storedMsg.id, partId));
|
|
168
|
+
if (part && part.type === 'tool_output' && !part.compactedAt) {
|
|
169
|
+
toolOutputParts.push({
|
|
170
|
+
key: this.partKey(storedMsg.id, partId),
|
|
171
|
+
part,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
currentMessageId = storedMsg.prevMessageId;
|
|
176
|
+
}
|
|
177
|
+
// Estimate tokens (rough: 1 token ≈ 4 chars)
|
|
178
|
+
let keptTokens = 0;
|
|
179
|
+
let compactedCount = 0;
|
|
180
|
+
let tokensSaved = 0;
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
for (const { key, part } of toolOutputParts) {
|
|
183
|
+
const estimatedTokens = Math.ceil(part.content.length / 4);
|
|
184
|
+
if (keptTokens < keepTokens) {
|
|
185
|
+
keptTokens += estimatedTokens;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Mark this part as compacted
|
|
189
|
+
// eslint-disable-next-line no-await-in-loop
|
|
190
|
+
await this.keyStorage.set(key, {
|
|
191
|
+
...part,
|
|
192
|
+
compactedAt: now,
|
|
193
|
+
content: '', // Clear the content to save space
|
|
194
|
+
});
|
|
195
|
+
compactedCount++;
|
|
196
|
+
tokensSaved += estimatedTokens;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { compactedCount, tokensSaved };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Save a single message with its parts.
|
|
203
|
+
* Updates the session record to maintain linked list pointers.
|
|
204
|
+
*/
|
|
205
|
+
async saveMessage(sessionId, message) {
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const messageId = randomUUID();
|
|
208
|
+
// Convert InternalMessage content to parts
|
|
209
|
+
const { content, parts } = this.extractParts(message, messageId, now);
|
|
210
|
+
// Get or create session
|
|
211
|
+
let session = await this.getSession(sessionId);
|
|
212
|
+
const isNewSession = !session;
|
|
213
|
+
if (isNewSession) {
|
|
214
|
+
session = {
|
|
215
|
+
createdAt: now,
|
|
216
|
+
messageCount: 0,
|
|
217
|
+
sessionId,
|
|
218
|
+
updatedAt: now,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// Create stored message
|
|
222
|
+
const storedMessage = {
|
|
223
|
+
content,
|
|
224
|
+
createdAt: now,
|
|
225
|
+
id: messageId,
|
|
226
|
+
name: message.name,
|
|
227
|
+
partIds: parts.map((p) => p.id),
|
|
228
|
+
prevMessageId: session.newestMessageId,
|
|
229
|
+
reasoning: message.reasoning,
|
|
230
|
+
role: message.role,
|
|
231
|
+
sessionId,
|
|
232
|
+
thought: message.thought,
|
|
233
|
+
thoughtSummary: message.thoughtSummary,
|
|
234
|
+
toolCallId: message.toolCallId,
|
|
235
|
+
toolCalls: message.toolCalls,
|
|
236
|
+
updatedAt: now,
|
|
237
|
+
};
|
|
238
|
+
// Update the previous newest message to point to this one
|
|
239
|
+
if (session.newestMessageId) {
|
|
240
|
+
await this.keyStorage.update(this.messageKey(sessionId, session.newestMessageId), (prev) => {
|
|
241
|
+
if (!prev)
|
|
242
|
+
throw new Error(`Previous message ${session.newestMessageId} not found`);
|
|
243
|
+
return { ...prev, nextMessageId: messageId, updatedAt: now };
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// Save parts
|
|
247
|
+
for (const part of parts) {
|
|
248
|
+
// eslint-disable-next-line no-await-in-loop
|
|
249
|
+
await this.keyStorage.set(this.partKey(part.messageId, part.id), part);
|
|
250
|
+
}
|
|
251
|
+
// Save message
|
|
252
|
+
await this.keyStorage.set(this.messageKey(sessionId, messageId), storedMessage);
|
|
253
|
+
// Update session
|
|
254
|
+
session.newestMessageId = messageId;
|
|
255
|
+
if (!session.oldestMessageId) {
|
|
256
|
+
session.oldestMessageId = messageId;
|
|
257
|
+
}
|
|
258
|
+
session.messageCount += 1;
|
|
259
|
+
session.updatedAt = now;
|
|
260
|
+
await this.saveSession(session);
|
|
261
|
+
return storedMessage;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Save multiple messages in order.
|
|
265
|
+
* More efficient than calling saveMessage multiple times.
|
|
266
|
+
*/
|
|
267
|
+
async saveMessages(sessionId, messages) {
|
|
268
|
+
const results = [];
|
|
269
|
+
for (const message of messages) {
|
|
270
|
+
// eslint-disable-next-line no-await-in-loop
|
|
271
|
+
const stored = await this.saveMessage(sessionId, message);
|
|
272
|
+
results.push(stored);
|
|
273
|
+
}
|
|
274
|
+
return results;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Create or update a session record.
|
|
278
|
+
*/
|
|
279
|
+
async saveSession(session) {
|
|
280
|
+
await this.keyStorage.set(this.sessionKey(session.sessionId), session);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Stream messages from newest to oldest using an async generator.
|
|
284
|
+
* More memory efficient for large histories.
|
|
285
|
+
*
|
|
286
|
+
* @yields StoredMessageWithParts - Messages from the session history
|
|
287
|
+
*/
|
|
288
|
+
async *streamMessages(options) {
|
|
289
|
+
const { limit, sessionId, stopAtCompaction = true } = options;
|
|
290
|
+
const session = await this.getSession(sessionId);
|
|
291
|
+
if (!session || !session.newestMessageId) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
let count = 0;
|
|
295
|
+
let currentMessageId = session.newestMessageId;
|
|
296
|
+
while (currentMessageId) {
|
|
297
|
+
if (limit && count >= limit)
|
|
298
|
+
break;
|
|
299
|
+
// eslint-disable-next-line no-await-in-loop
|
|
300
|
+
const storedMsg = await this.keyStorage.get(this.messageKey(sessionId, currentMessageId));
|
|
301
|
+
if (!storedMsg)
|
|
302
|
+
break;
|
|
303
|
+
// Load parts for this message
|
|
304
|
+
// eslint-disable-next-line no-await-in-loop
|
|
305
|
+
const parts = await this.loadParts(storedMsg);
|
|
306
|
+
const messageWithParts = { ...storedMsg, parts };
|
|
307
|
+
yield messageWithParts;
|
|
308
|
+
count++;
|
|
309
|
+
// Stop at compaction boundary (but yield it first)
|
|
310
|
+
if (stopAtCompaction && storedMsg.compactionBoundary) {
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
currentMessageId = storedMsg.prevMessageId;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Convert a single stored message with parts back to InternalMessage format.
|
|
318
|
+
*/
|
|
319
|
+
toInternalMessage(message) {
|
|
320
|
+
// Reconstruct content from parts or use stored content
|
|
321
|
+
const { content: messageContent, parts } = message;
|
|
322
|
+
let content = messageContent;
|
|
323
|
+
// If we have parts, reconstruct the content array
|
|
324
|
+
if (parts.length > 0) {
|
|
325
|
+
const contentParts = [];
|
|
326
|
+
for (const part of parts) {
|
|
327
|
+
if (part.compactedAt) {
|
|
328
|
+
// Show placeholder for compacted parts
|
|
329
|
+
contentParts.push({
|
|
330
|
+
text: part.type === 'tool_output' ? COMPACTED_TOOL_OUTPUT_PLACEHOLDER : '[Content cleared]',
|
|
331
|
+
type: 'text',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
switch (part.type) {
|
|
336
|
+
case 'file': {
|
|
337
|
+
contentParts.push({
|
|
338
|
+
data: part.content,
|
|
339
|
+
filename: part.filename,
|
|
340
|
+
mimeType: part.mimeType || 'application/octet-stream',
|
|
341
|
+
type: 'file',
|
|
342
|
+
});
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case 'image': {
|
|
346
|
+
contentParts.push({
|
|
347
|
+
image: part.content,
|
|
348
|
+
mimeType: part.mimeType,
|
|
349
|
+
type: 'image',
|
|
350
|
+
});
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case 'text': {
|
|
354
|
+
contentParts.push({ text: part.content, type: 'text' });
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
case 'tool_output': {
|
|
358
|
+
// Tool output becomes text content for tool messages
|
|
359
|
+
content = part.content;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Only use content parts array if we have non-tool parts
|
|
366
|
+
if (contentParts.length > 0 && message.role === 'user') {
|
|
367
|
+
content = contentParts;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const { name, reasoning, role, thought, thoughtSummary, toolCallId, toolCalls } = message;
|
|
371
|
+
return {
|
|
372
|
+
content,
|
|
373
|
+
name,
|
|
374
|
+
reasoning,
|
|
375
|
+
role,
|
|
376
|
+
thought,
|
|
377
|
+
thoughtSummary,
|
|
378
|
+
toolCallId,
|
|
379
|
+
toolCalls,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Convert loaded messages back to InternalMessage format.
|
|
384
|
+
*/
|
|
385
|
+
toInternalMessages(messages) {
|
|
386
|
+
return messages.map((msg) => this.toInternalMessage(msg));
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Extract parts from an InternalMessage.
|
|
390
|
+
*/
|
|
391
|
+
extractParts(message, messageId, now) {
|
|
392
|
+
const parts = [];
|
|
393
|
+
let content = null;
|
|
394
|
+
if (message.content === null) {
|
|
395
|
+
// Assistant message with only tool calls
|
|
396
|
+
content = null;
|
|
397
|
+
}
|
|
398
|
+
else if (typeof message.content === 'string') {
|
|
399
|
+
// Tool result or simple text message
|
|
400
|
+
if (message.role === 'tool') {
|
|
401
|
+
// Store tool output as a part for selective pruning
|
|
402
|
+
const partId = randomUUID();
|
|
403
|
+
parts.push({
|
|
404
|
+
content: message.content,
|
|
405
|
+
createdAt: now,
|
|
406
|
+
id: partId,
|
|
407
|
+
messageId,
|
|
408
|
+
toolName: message.name,
|
|
409
|
+
type: 'tool_output',
|
|
410
|
+
});
|
|
411
|
+
content = message.content; // Also keep in content for quick access
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// System or simple assistant message
|
|
415
|
+
content = message.content;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else if (Array.isArray(message.content)) {
|
|
419
|
+
// User message with multiple parts
|
|
420
|
+
for (const part of message.content) {
|
|
421
|
+
const partId = randomUUID();
|
|
422
|
+
switch (part.type) {
|
|
423
|
+
case 'file': {
|
|
424
|
+
parts.push({
|
|
425
|
+
content: this.serializeFileContent(part.data),
|
|
426
|
+
createdAt: now,
|
|
427
|
+
filename: part.filename,
|
|
428
|
+
id: partId,
|
|
429
|
+
messageId,
|
|
430
|
+
mimeType: part.mimeType,
|
|
431
|
+
type: 'file',
|
|
432
|
+
});
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case 'image': {
|
|
436
|
+
parts.push({
|
|
437
|
+
content: this.serializeImageContent(part.image),
|
|
438
|
+
createdAt: now,
|
|
439
|
+
id: partId,
|
|
440
|
+
messageId,
|
|
441
|
+
mimeType: part.mimeType,
|
|
442
|
+
type: 'image',
|
|
443
|
+
});
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case 'text': {
|
|
447
|
+
parts.push({
|
|
448
|
+
content: part.text,
|
|
449
|
+
createdAt: now,
|
|
450
|
+
id: partId,
|
|
451
|
+
messageId,
|
|
452
|
+
type: 'text',
|
|
453
|
+
});
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// For user messages, content is reconstructed from parts
|
|
459
|
+
// but we can store a text preview
|
|
460
|
+
const textParts = message.content.filter((p) => p.type === 'text');
|
|
461
|
+
if (textParts.length > 0) {
|
|
462
|
+
content = textParts.map((p) => p.text).join('\n');
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return { content, parts };
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Load parts for a message.
|
|
469
|
+
*/
|
|
470
|
+
async loadParts(message) {
|
|
471
|
+
const parts = [];
|
|
472
|
+
for (const partId of message.partIds) {
|
|
473
|
+
// eslint-disable-next-line no-await-in-loop
|
|
474
|
+
const part = await this.keyStorage.get(this.partKey(message.id, partId));
|
|
475
|
+
if (part) {
|
|
476
|
+
parts.push(part);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return parts;
|
|
480
|
+
}
|
|
481
|
+
// Key builders
|
|
482
|
+
messageKey(sessionId, messageId) {
|
|
483
|
+
return ['message', sessionId, messageId];
|
|
484
|
+
}
|
|
485
|
+
messagePrefix(sessionId) {
|
|
486
|
+
return ['message', sessionId];
|
|
487
|
+
}
|
|
488
|
+
partKey(messageId, partId) {
|
|
489
|
+
return ['part', messageId, partId];
|
|
490
|
+
}
|
|
491
|
+
partPrefix(messageId) {
|
|
492
|
+
return ['part', messageId];
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Serialize file content for storage.
|
|
496
|
+
*/
|
|
497
|
+
serializeFileContent(data) {
|
|
498
|
+
return this.serializeImageContent(data);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Serialize image content for storage.
|
|
502
|
+
*/
|
|
503
|
+
serializeImageContent(image) {
|
|
504
|
+
if (typeof image === 'string') {
|
|
505
|
+
return image;
|
|
506
|
+
}
|
|
507
|
+
if (image instanceof URL) {
|
|
508
|
+
return image.toString();
|
|
509
|
+
}
|
|
510
|
+
if (image instanceof ArrayBuffer) {
|
|
511
|
+
return Buffer.from(image).toString('base64');
|
|
512
|
+
}
|
|
513
|
+
if (image instanceof Uint8Array) {
|
|
514
|
+
return Buffer.from(image).toString('base64');
|
|
515
|
+
}
|
|
516
|
+
// Buffer.isBuffer check is redundant since Buffer extends Uint8Array,
|
|
517
|
+
// but we keep it explicit for clarity
|
|
518
|
+
return image.toString('base64');
|
|
519
|
+
}
|
|
520
|
+
sessionKey(sessionId) {
|
|
521
|
+
return ['session', sessionId];
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Factory function to create MessageStorageService.
|
|
526
|
+
*/
|
|
527
|
+
export function createMessageStorageService(keyStorage) {
|
|
528
|
+
return new MessageStorageService(keyStorage);
|
|
529
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process detection utilities for orphan cleanup
|
|
3
|
+
*
|
|
4
|
+
* Used by cleanupStaleConsumers() to detect dead query processes.
|
|
5
|
+
* Provides cross-platform process existence checks.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Check if a process is still running
|
|
9
|
+
*
|
|
10
|
+
* Uses signal 0 to check process existence without actually sending a signal.
|
|
11
|
+
* This is a POSIX standard way to check if a process exists.
|
|
12
|
+
*
|
|
13
|
+
* @param pid - Process ID to check
|
|
14
|
+
* @returns true if running, false if dead, null if can't determine
|
|
15
|
+
*/
|
|
16
|
+
export declare function isProcessRunning(pid: number): boolean | null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process detection utilities for orphan cleanup
|
|
3
|
+
*
|
|
4
|
+
* Used by cleanupStaleConsumers() to detect dead query processes.
|
|
5
|
+
* Provides cross-platform process existence checks.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Check if a process is still running
|
|
9
|
+
*
|
|
10
|
+
* Uses signal 0 to check process existence without actually sending a signal.
|
|
11
|
+
* This is a POSIX standard way to check if a process exists.
|
|
12
|
+
*
|
|
13
|
+
* @param pid - Process ID to check
|
|
14
|
+
* @returns true if running, false if dead, null if can't determine
|
|
15
|
+
*/
|
|
16
|
+
export function isProcessRunning(pid) {
|
|
17
|
+
// Guard against invalid PIDs
|
|
18
|
+
// - Must be positive integer (PIDs are always > 0)
|
|
19
|
+
// - Negative PIDs are dangerous: kill(-1, 0) signals ALL processes!
|
|
20
|
+
// - PID 0 refers to current process group - not what we want
|
|
21
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
22
|
+
return null; // Can't determine → fallback to activity check
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
// Signal 0 = check existence without killing
|
|
26
|
+
// If process exists and we have permission, this succeeds silently
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const { code } = error;
|
|
32
|
+
if (code === 'ESRCH') {
|
|
33
|
+
// ESRCH = No such process - definitely dead
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (code === 'EPERM') {
|
|
37
|
+
// EPERM = No permission to signal, but process exists
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
// Unknown error - can't determine (return null for fallback to activity check)
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { BatchOperation, IKeyStorage, StorageKey } from '../../../core/interfaces/cipher/i-key-storage.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for SQLite key storage
|
|
4
|
+
*/
|
|
5
|
+
export interface SqliteKeyStorageConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Database file path (relative or absolute).
|
|
8
|
+
* Defaults to '.brv/context.db'
|
|
9
|
+
*/
|
|
10
|
+
dbPath?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Enable in-memory mode for testing.
|
|
13
|
+
* Defaults to false.
|
|
14
|
+
*/
|
|
15
|
+
inMemory?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Storage directory for the database file.
|
|
18
|
+
* Defaults to process.cwd()/.brv
|
|
19
|
+
*/
|
|
20
|
+
storageDir?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* SQLite-based key storage implementation.
|
|
24
|
+
*
|
|
25
|
+
* Stores key-value pairs where keys are hierarchical (StorageKey = string[])
|
|
26
|
+
* and values are JSON-serializable objects.
|
|
27
|
+
*
|
|
28
|
+
* Key path structure:
|
|
29
|
+
* - ["session", sessionId] → Session metadata
|
|
30
|
+
* - ["message", sessionId, messageId] → Individual message
|
|
31
|
+
* - ["part", messageId, partId] → Message part (tool output, file, etc.)
|
|
32
|
+
*
|
|
33
|
+
* Schema:
|
|
34
|
+
* - key_store: key_path (primary), value (JSON blob), created_at, updated_at
|
|
35
|
+
*
|
|
36
|
+
* Features:
|
|
37
|
+
* - Hierarchical key organization with prefix-based listing
|
|
38
|
+
* - Reader-writer locks for concurrent access
|
|
39
|
+
* - Atomic batch operations
|
|
40
|
+
* - ACID transactions via SQLite
|
|
41
|
+
*/
|
|
42
|
+
export declare class SqliteKeyStorage implements IKeyStorage {
|
|
43
|
+
private db;
|
|
44
|
+
private readonly dbPath;
|
|
45
|
+
private initialized;
|
|
46
|
+
private readonly inMemory;
|
|
47
|
+
private readonly storageDir;
|
|
48
|
+
constructor(config?: SqliteKeyStorageConfig);
|
|
49
|
+
/**
|
|
50
|
+
* Execute batch operations atomically.
|
|
51
|
+
* All operations succeed or fail together.
|
|
52
|
+
*/
|
|
53
|
+
batch(operations: BatchOperation[]): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Close the database connection.
|
|
56
|
+
*/
|
|
57
|
+
close(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Delete a value by its composite key.
|
|
60
|
+
*/
|
|
61
|
+
delete(key: StorageKey): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Check if a key exists.
|
|
64
|
+
*/
|
|
65
|
+
exists(key: StorageKey): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Get a value by its composite key.
|
|
68
|
+
*/
|
|
69
|
+
get<T>(key: StorageKey): Promise<T | undefined>;
|
|
70
|
+
/**
|
|
71
|
+
* Initialize the storage backend.
|
|
72
|
+
* Creates the database and runs migrations.
|
|
73
|
+
*/
|
|
74
|
+
initialize(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* List all keys matching a prefix.
|
|
77
|
+
*/
|
|
78
|
+
list(prefix: StorageKey): Promise<StorageKey[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Set a value at a composite key.
|
|
81
|
+
*/
|
|
82
|
+
set<T>(key: StorageKey, value: T): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Atomic update with optimistic locking.
|
|
85
|
+
*/
|
|
86
|
+
update<T>(key: StorageKey, updater: (current: T | undefined) => T): Promise<T>;
|
|
87
|
+
/**
|
|
88
|
+
* Deserialize a key path string back to StorageKey.
|
|
89
|
+
* Converts "message:session123:msg456" to ["message", "session123", "msg456"]
|
|
90
|
+
*/
|
|
91
|
+
private deserializeKey;
|
|
92
|
+
/**
|
|
93
|
+
* Ensure storage has been initialized.
|
|
94
|
+
*/
|
|
95
|
+
private ensureInitialized;
|
|
96
|
+
/**
|
|
97
|
+
* Serialize a StorageKey to a string path.
|
|
98
|
+
* Converts ["message", "session123", "msg456"] to "message:session123:msg456"
|
|
99
|
+
*/
|
|
100
|
+
private serializeKey;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Factory function to create SqliteKeyStorage with common defaults.
|
|
104
|
+
*/
|
|
105
|
+
export declare function createKeyStorage(config?: SqliteKeyStorageConfig): SqliteKeyStorage;
|