byterover-cli 0.3.4 → 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/update-notifier.js +1 -5
- 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,523 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { getCurrentConfig } from '../../../config/environment.js';
|
|
3
|
+
import { PROJECT } from '../../../constants.js';
|
|
4
|
+
import { CipherAgent } from '../cipher-agent.js';
|
|
5
|
+
import { closeAgentStorage, getAgentStorage, getAgentStorageSync } from '../storage/agent-storage.js';
|
|
6
|
+
// Heartbeat interval for consumer liveness detection (10 seconds)
|
|
7
|
+
const HEARTBEAT_INTERVAL_MS = 1000;
|
|
8
|
+
// Consumer is considered stale after 5 seconds without heartbeat
|
|
9
|
+
const STALE_TIMEOUT_MS = 5000;
|
|
10
|
+
// Check for orphaned executions every N poll cycles (10 seconds with 1s poll interval)
|
|
11
|
+
const ORPHAN_CHECK_INTERVAL_CYCLES = 10;
|
|
12
|
+
/**
|
|
13
|
+
* Sleep for specified milliseconds
|
|
14
|
+
*/
|
|
15
|
+
function sleep(ms) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
setTimeout(resolve, ms);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create result summary for display
|
|
22
|
+
*/
|
|
23
|
+
function createResultSummary(result) {
|
|
24
|
+
if (typeof result !== 'string') {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
const lines = result.split('\n').length;
|
|
28
|
+
const chars = result.length;
|
|
29
|
+
return `${lines} lines, ${chars} chars`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Calculate lines and chars count from result
|
|
33
|
+
*/
|
|
34
|
+
function calculateMetrics(result) {
|
|
35
|
+
if (typeof result !== 'string' || result.length === 0) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
charsCount: result.length,
|
|
40
|
+
linesCount: result.split('\n').length,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* ExecutionConsumer - Polls queue and executes jobs in parallel
|
|
45
|
+
*
|
|
46
|
+
* Features:
|
|
47
|
+
* - DB-based lock with heartbeat (detects dead consumers)
|
|
48
|
+
* - Poll loop every 1 second
|
|
49
|
+
* - Parallel processing with configurable concurrency (default 5)
|
|
50
|
+
* - Process curate executions
|
|
51
|
+
* - Track tool calls to database
|
|
52
|
+
* - Cleanup old executions periodically
|
|
53
|
+
*/
|
|
54
|
+
export class ExecutionConsumer {
|
|
55
|
+
activeJobs = new Set(); // Track running execution IDs
|
|
56
|
+
authToken;
|
|
57
|
+
brvConfig;
|
|
58
|
+
consumerId; // Unique ID for this consumer instance
|
|
59
|
+
heartbeatInterval;
|
|
60
|
+
maxConcurrency;
|
|
61
|
+
pollInterval;
|
|
62
|
+
running = false;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this.brvConfig = options?.brvConfig;
|
|
65
|
+
this.authToken = options?.authToken;
|
|
66
|
+
this.maxConcurrency = options?.maxConcurrency ?? 5; // Default 5 concurrent jobs
|
|
67
|
+
this.pollInterval = options?.pollInterval ?? 1000; // 1 second default
|
|
68
|
+
this.consumerId = randomUUID(); // Generate unique consumer ID
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the unique consumer ID for this instance
|
|
72
|
+
*/
|
|
73
|
+
getConsumerId() {
|
|
74
|
+
return this.consumerId;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if consumer is running
|
|
78
|
+
*/
|
|
79
|
+
isRunning() {
|
|
80
|
+
return this.running;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Set auth token (can be set after construction)
|
|
84
|
+
*/
|
|
85
|
+
setAuthToken(token) {
|
|
86
|
+
this.authToken = token;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Start the consumer
|
|
90
|
+
* @returns true if started successfully, false if another consumer is running
|
|
91
|
+
*/
|
|
92
|
+
async start() {
|
|
93
|
+
// Initialize storage (auto-detects .brv/blobs from cwd)
|
|
94
|
+
const storage = await getAgentStorage();
|
|
95
|
+
// Cleanup stale consumers and orphan their executions
|
|
96
|
+
const orphaned = storage.cleanupStaleConsumers(STALE_TIMEOUT_MS);
|
|
97
|
+
if (orphaned > 0) {
|
|
98
|
+
console.log(`[Consumer] Cleaned up ${orphaned} orphaned executions from dead consumers`);
|
|
99
|
+
}
|
|
100
|
+
// Try acquire DB-based lock
|
|
101
|
+
if (!storage.acquireConsumerLock(this.consumerId)) {
|
|
102
|
+
console.log('[Consumer] Failed to acquire consumer lock');
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
// Register cleanup handlers
|
|
106
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
107
|
+
const cleanup = () => {
|
|
108
|
+
this.stop();
|
|
109
|
+
closeAgentStorage();
|
|
110
|
+
};
|
|
111
|
+
// Use 'once' to prevent multiple handler calls
|
|
112
|
+
// 'exit' is always fired, so just cleanup (no need for process.exit)
|
|
113
|
+
process.once('exit', cleanup);
|
|
114
|
+
process.once('SIGTERM', () => {
|
|
115
|
+
cleanup();
|
|
116
|
+
// eslint-disable-next-line n/no-process-exit
|
|
117
|
+
process.exit(0);
|
|
118
|
+
});
|
|
119
|
+
process.once('SIGINT', () => {
|
|
120
|
+
cleanup();
|
|
121
|
+
// eslint-disable-next-line n/no-process-exit
|
|
122
|
+
process.exit(0);
|
|
123
|
+
});
|
|
124
|
+
this.running = true;
|
|
125
|
+
// Start heartbeat to keep lock alive
|
|
126
|
+
this.heartbeatInterval = setInterval(() => {
|
|
127
|
+
try {
|
|
128
|
+
// Note: DB reconnect is handled in poll() loop
|
|
129
|
+
// Heartbeat just updates if lock exists, poll() handles reconnection
|
|
130
|
+
const currentStorage = getAgentStorageSync();
|
|
131
|
+
currentStorage.updateConsumerHeartbeat(this.consumerId);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Ignore heartbeat errors - poll() will handle reconnection
|
|
135
|
+
// This prevents noisy errors when DB is being replaced
|
|
136
|
+
}
|
|
137
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
138
|
+
// Log initial queue status
|
|
139
|
+
// const queued = storage.getQueuedExecutions()
|
|
140
|
+
// const running = storage.getRunningExecutions()
|
|
141
|
+
// console.log(
|
|
142
|
+
// `[Consumer] Started (${this.consumerId.slice(0, 8)}). Concurrency: ${this.maxConcurrency}, Queue: ${
|
|
143
|
+
// queued.length
|
|
144
|
+
// } pending, ${running.length} running`,
|
|
145
|
+
// )
|
|
146
|
+
// Start poll loop (fire and forget)
|
|
147
|
+
this.poll().catch((error) => {
|
|
148
|
+
console.error('[Consumer] Poll error:', error);
|
|
149
|
+
this.stop();
|
|
150
|
+
});
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Stop the consumer
|
|
155
|
+
* Idempotent - safe to call multiple times (only runs cleanup once)
|
|
156
|
+
*/
|
|
157
|
+
stop() {
|
|
158
|
+
// Guard: Only run cleanup once
|
|
159
|
+
if (!this.running && !this.heartbeatInterval) {
|
|
160
|
+
return; // Already stopped
|
|
161
|
+
}
|
|
162
|
+
this.running = false;
|
|
163
|
+
// Stop heartbeat
|
|
164
|
+
if (this.heartbeatInterval) {
|
|
165
|
+
clearInterval(this.heartbeatInterval);
|
|
166
|
+
this.heartbeatInterval = undefined;
|
|
167
|
+
}
|
|
168
|
+
// Release DB lock
|
|
169
|
+
try {
|
|
170
|
+
const storage = getAgentStorageSync();
|
|
171
|
+
storage.releaseConsumerLock(this.consumerId);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Ignore errors during shutdown
|
|
175
|
+
}
|
|
176
|
+
// console.log(`[Consumer] Stopped (${this.consumerId.slice(0, 8)})`)
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Execute a curate job
|
|
180
|
+
*/
|
|
181
|
+
async executeCurate(execution) {
|
|
182
|
+
const storage = getAgentStorageSync();
|
|
183
|
+
// Parse input
|
|
184
|
+
let input;
|
|
185
|
+
try {
|
|
186
|
+
input = JSON.parse(execution.input);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
throw new Error('Invalid curate input: failed to parse JSON');
|
|
190
|
+
}
|
|
191
|
+
if (!input.content) {
|
|
192
|
+
throw new Error('Invalid curate input: missing content');
|
|
193
|
+
}
|
|
194
|
+
// Check auth token
|
|
195
|
+
if (!this.authToken) {
|
|
196
|
+
throw new Error('No auth token available. Consumer needs authentication.');
|
|
197
|
+
}
|
|
198
|
+
// Create LLM config
|
|
199
|
+
const model = input.flags?.model ?? (input.flags?.apiKey ? 'google/gemini-2.5-pro' : 'gemini-2.5-pro');
|
|
200
|
+
const envConfig = getCurrentConfig();
|
|
201
|
+
const llmConfig = {
|
|
202
|
+
accessToken: this.authToken.accessToken,
|
|
203
|
+
apiBaseUrl: envConfig.llmApiBaseUrl,
|
|
204
|
+
fileSystemConfig: { workingDirectory: process.cwd() },
|
|
205
|
+
maxIterations: 10,
|
|
206
|
+
maxTokens: 8192,
|
|
207
|
+
model,
|
|
208
|
+
openRouterApiKey: input.flags?.apiKey,
|
|
209
|
+
projectId: PROJECT,
|
|
210
|
+
sessionKey: this.authToken.sessionKey,
|
|
211
|
+
teamId: this.brvConfig?.teamId ?? '',
|
|
212
|
+
temperature: 0.7,
|
|
213
|
+
verbose: input.flags?.verbose ?? false,
|
|
214
|
+
};
|
|
215
|
+
// Create and start CipherAgent
|
|
216
|
+
const agent = new CipherAgent(llmConfig, this.brvConfig);
|
|
217
|
+
await agent.start();
|
|
218
|
+
try {
|
|
219
|
+
const sessionId = randomUUID();
|
|
220
|
+
// Setup event listeners for tool call tracking
|
|
221
|
+
this.setupToolCallTracking(agent, execution.id);
|
|
222
|
+
// Execute with autonomous mode
|
|
223
|
+
const prompt = `Add the following context to the context tree:\n\n${input.content}`;
|
|
224
|
+
const response = await agent.execute(prompt, sessionId, {
|
|
225
|
+
executionContext: {
|
|
226
|
+
commandType: 'curate',
|
|
227
|
+
fileReferenceInstructions: input.fileReferenceInstructions,
|
|
228
|
+
},
|
|
229
|
+
mode: 'autonomous',
|
|
230
|
+
});
|
|
231
|
+
// Mark completed
|
|
232
|
+
storage.updateExecutionStatus(execution.id, 'completed', response);
|
|
233
|
+
console.log(`[Consumer] Execution ${execution.id} completed`);
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
// Agent cleanup (if needed in future)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Log execution start (extracted to reduce nesting depth)
|
|
241
|
+
*/
|
|
242
|
+
logExecutionStart(execution) {
|
|
243
|
+
try {
|
|
244
|
+
const input = JSON.parse(execution.input);
|
|
245
|
+
const content = input.content ?? '';
|
|
246
|
+
const preview = content.slice(0, 100).replaceAll('\n', ' ');
|
|
247
|
+
console.log(`[Consumer] Starting (${this.activeJobs.size}/${this.maxConcurrency}): "${preview}${content.length > 100 ? '...' : ''}"`);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
console.log(`[Consumer] Starting (${this.activeJobs.size}/${this.maxConcurrency}): ${execution.id}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Main poll loop - dequeues and processes jobs in parallel up to maxConcurrency
|
|
255
|
+
*/
|
|
256
|
+
async poll() {
|
|
257
|
+
let storage = getAgentStorageSync();
|
|
258
|
+
let cleanupCounter = 0;
|
|
259
|
+
let orphanCheckCounter = 0;
|
|
260
|
+
while (this.running) {
|
|
261
|
+
try {
|
|
262
|
+
// Check if our lock is still valid (handles race condition when QueuePollingService reconnects first)
|
|
263
|
+
// This covers both: DB file replaced AND another component already reconnected
|
|
264
|
+
if (!storage.hasConsumerLock(this.consumerId)) {
|
|
265
|
+
console.log('[Consumer] Lock lost, re-acquiring...');
|
|
266
|
+
// If DB file also changed, reconnect first
|
|
267
|
+
if (storage.isDbFileChanged()) {
|
|
268
|
+
console.log('[Consumer] DB file changed, reconnecting...');
|
|
269
|
+
// eslint-disable-next-line no-await-in-loop -- Must wait for reconnect before continuing
|
|
270
|
+
await storage.reconnect();
|
|
271
|
+
storage = getAgentStorageSync(); // Get fresh reference after reconnect
|
|
272
|
+
}
|
|
273
|
+
// Re-acquire lock
|
|
274
|
+
if (!storage.acquireConsumerLock(this.consumerId)) {
|
|
275
|
+
console.error('[Consumer] Failed to re-acquire lock');
|
|
276
|
+
this.stop();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
console.log(`[Consumer] Re-acquired lock (${this.consumerId.slice(0, 8)})`);
|
|
280
|
+
}
|
|
281
|
+
// Calculate available slots
|
|
282
|
+
const availableSlots = this.maxConcurrency - this.activeJobs.size;
|
|
283
|
+
// Dequeue batch of jobs atomically (ensures all queued items are seen in one transaction)
|
|
284
|
+
if (availableSlots > 0) {
|
|
285
|
+
const executions = storage.dequeueBatch(availableSlots, this.consumerId);
|
|
286
|
+
for (const execution of executions) {
|
|
287
|
+
// Track this job
|
|
288
|
+
this.activeJobs.add(execution.id);
|
|
289
|
+
// Log execution start
|
|
290
|
+
this.logExecutionStart(execution);
|
|
291
|
+
// Process in background (fire and forget with completion tracking)
|
|
292
|
+
this.processExecutionAsync(execution);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Periodic orphan detection - check for dead consumers and fail their executions
|
|
296
|
+
orphanCheckCounter++;
|
|
297
|
+
if (orphanCheckCounter >= ORPHAN_CHECK_INTERVAL_CYCLES) {
|
|
298
|
+
orphanCheckCounter = 0;
|
|
299
|
+
const orphaned = storage.cleanupStaleConsumers(STALE_TIMEOUT_MS);
|
|
300
|
+
if (orphaned > 0) {
|
|
301
|
+
console.log(`[Consumer] Detected ${orphaned} orphaned executions from dead consumers`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Periodic cleanup (every 10 poll cycles when idle)
|
|
305
|
+
cleanupCounter++;
|
|
306
|
+
if (cleanupCounter >= 10 && this.activeJobs.size === 0) {
|
|
307
|
+
cleanupCounter = 0;
|
|
308
|
+
const cleaned = storage.cleanupOldExecutions(100);
|
|
309
|
+
if (cleaned > 0) {
|
|
310
|
+
console.log(`[Consumer] Cleaned up ${cleaned} old executions`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
console.error('[Consumer] Poll error:', error);
|
|
316
|
+
// Try to recover - re-acquire lock if lost
|
|
317
|
+
// eslint-disable-next-line no-await-in-loop -- Must wait for recovery before continuing
|
|
318
|
+
storage = await this.tryRecoverLock(storage);
|
|
319
|
+
}
|
|
320
|
+
// eslint-disable-next-line no-await-in-loop
|
|
321
|
+
await sleep(this.pollInterval);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Process a single execution
|
|
326
|
+
*/
|
|
327
|
+
async processExecution(execution) {
|
|
328
|
+
const storage = getAgentStorageSync();
|
|
329
|
+
try {
|
|
330
|
+
switch (execution.type) {
|
|
331
|
+
case 'curate': {
|
|
332
|
+
await this.executeCurate(execution);
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case 'query': {
|
|
336
|
+
// Query should not be in queue (runs sync)
|
|
337
|
+
console.warn(`[Consumer] Query execution ${execution.id} found in queue, marking failed`);
|
|
338
|
+
storage.updateExecutionStatus(execution.id, 'failed', undefined, 'Query should not be queued');
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
default: {
|
|
342
|
+
storage.updateExecutionStatus(execution.id, 'failed', undefined, `Unknown execution type: ${execution.type}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
348
|
+
console.error(`[Consumer] Execution ${execution.id} failed:`, errorMessage);
|
|
349
|
+
storage.updateExecutionStatus(execution.id, 'failed', undefined, errorMessage);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Process execution asynchronously (fire and forget with tracking)
|
|
354
|
+
*/
|
|
355
|
+
processExecutionAsync(execution) {
|
|
356
|
+
const storage = getAgentStorageSync();
|
|
357
|
+
this.processExecution(execution)
|
|
358
|
+
.then(() => {
|
|
359
|
+
// Log completion
|
|
360
|
+
const updated = storage.getExecution(execution.id);
|
|
361
|
+
if (updated?.status === 'completed') {
|
|
362
|
+
const resultPreview = (updated.result ?? '').slice(0, 100).replaceAll('\n', ' ');
|
|
363
|
+
console.log(`[Consumer] ✓ Done (${this.activeJobs.size - 1}/${this.maxConcurrency}): "${resultPreview}${(updated.result?.length ?? 0) > 100 ? '...' : ''}"`);
|
|
364
|
+
}
|
|
365
|
+
})
|
|
366
|
+
.catch((error) => {
|
|
367
|
+
console.error(`[Consumer] ✗ Failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
368
|
+
})
|
|
369
|
+
.finally(() => {
|
|
370
|
+
// Remove from active jobs
|
|
371
|
+
this.activeJobs.delete(execution.id);
|
|
372
|
+
// Log queue status
|
|
373
|
+
const queued = storage.getQueuedExecutions();
|
|
374
|
+
if (queued.length > 0 || this.activeJobs.size > 0) {
|
|
375
|
+
console.log(`[Consumer] Status: ${this.activeJobs.size} running, ${queued.length} queued`);
|
|
376
|
+
}
|
|
377
|
+
// Immediately try to pick up more jobs (don't wait for next poll cycle)
|
|
378
|
+
if (this.running && queued.length > 0) {
|
|
379
|
+
this.tryPickupJobs();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Setup tool call tracking via event listeners
|
|
385
|
+
*/
|
|
386
|
+
setupToolCallTracking(agent, executionId) {
|
|
387
|
+
if (!agent.agentEventBus) {
|
|
388
|
+
console.warn('[Consumer] Agent event bus not available, tool call tracking disabled');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const storage = getAgentStorageSync();
|
|
392
|
+
const eventBus = agent.agentEventBus;
|
|
393
|
+
const toolCallMap = new Map(); // callId -> dbToolCallId
|
|
394
|
+
eventBus.on('llmservice:toolCall', (payload) => {
|
|
395
|
+
try {
|
|
396
|
+
if (!payload.callId)
|
|
397
|
+
return;
|
|
398
|
+
const toolCallId = storage.addToolCall(executionId, {
|
|
399
|
+
args: payload.args,
|
|
400
|
+
name: payload.toolName,
|
|
401
|
+
});
|
|
402
|
+
toolCallMap.set(payload.callId, toolCallId);
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
console.error('[Consumer] Failed to add tool call:', error);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
eventBus.on('llmservice:toolResult', (payload) => {
|
|
409
|
+
try {
|
|
410
|
+
if (!payload.callId)
|
|
411
|
+
return;
|
|
412
|
+
const toolCallId = toolCallMap.get(payload.callId);
|
|
413
|
+
if (toolCallId) {
|
|
414
|
+
// Format result: if error, wrap in {error: "..."} object
|
|
415
|
+
let resultStr;
|
|
416
|
+
if (payload.success) {
|
|
417
|
+
resultStr = typeof payload.result === 'string' ? payload.result : JSON.stringify(payload.result);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Error case: store as {error: "message"} format
|
|
421
|
+
const errorMsg = payload.error ?? (typeof payload.result === 'string' ? payload.result : 'Unknown error');
|
|
422
|
+
resultStr = JSON.stringify({ error: errorMsg });
|
|
423
|
+
}
|
|
424
|
+
const metrics = payload.success ? calculateMetrics(payload.result) : undefined;
|
|
425
|
+
storage.updateToolCall(toolCallId, payload.success ? 'completed' : 'failed', {
|
|
426
|
+
charsCount: metrics?.charsCount,
|
|
427
|
+
linesCount: metrics?.linesCount,
|
|
428
|
+
result: resultStr,
|
|
429
|
+
resultSummary: payload.success ? createResultSummary(payload.result) : undefined,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
console.error('[Consumer] Failed to update tool call:', error);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Try to pick up queued jobs (called after job completion)
|
|
440
|
+
*/
|
|
441
|
+
tryPickupJobs() {
|
|
442
|
+
const storage = getAgentStorageSync();
|
|
443
|
+
const availableSlots = this.maxConcurrency - this.activeJobs.size;
|
|
444
|
+
if (availableSlots <= 0)
|
|
445
|
+
return;
|
|
446
|
+
// Dequeue batch atomically
|
|
447
|
+
const executions = storage.dequeueBatch(availableSlots, this.consumerId);
|
|
448
|
+
for (const execution of executions) {
|
|
449
|
+
this.activeJobs.add(execution.id);
|
|
450
|
+
try {
|
|
451
|
+
const input = JSON.parse(execution.input);
|
|
452
|
+
const content = input.content ?? '';
|
|
453
|
+
const preview = content.slice(0, 100).replaceAll('\n', ' ');
|
|
454
|
+
console.log(`[Consumer] Starting (${this.activeJobs.size}/${this.maxConcurrency}): "${preview}${content.length > 100 ? '...' : ''}"`);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
console.log(`[Consumer] Starting (${this.activeJobs.size}/${this.maxConcurrency}): ${execution.id}`);
|
|
458
|
+
}
|
|
459
|
+
this.processExecutionAsync(execution);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Try to recover lock after error (extracted to reduce nesting depth)
|
|
464
|
+
*/
|
|
465
|
+
async tryRecoverLock(currentStorage) {
|
|
466
|
+
try {
|
|
467
|
+
let storage = getAgentStorageSync();
|
|
468
|
+
if (!storage.hasConsumerLock(this.consumerId)) {
|
|
469
|
+
if (storage.isDbFileChanged()) {
|
|
470
|
+
await storage.reconnect();
|
|
471
|
+
storage = getAgentStorageSync();
|
|
472
|
+
}
|
|
473
|
+
storage.acquireConsumerLock(this.consumerId);
|
|
474
|
+
console.log('[Consumer] Recovered from error, re-acquired lock');
|
|
475
|
+
}
|
|
476
|
+
return storage;
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Ignore recovery errors, will retry on next poll
|
|
480
|
+
return currentStorage;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// ==================== FACTORY & SINGLETON ====================
|
|
485
|
+
let consumerInstance = null;
|
|
486
|
+
/**
|
|
487
|
+
* Create a new ExecutionConsumer instance
|
|
488
|
+
*/
|
|
489
|
+
export function createExecutionConsumer(options) {
|
|
490
|
+
return new ExecutionConsumer(options);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Try to start a consumer (singleton pattern)
|
|
494
|
+
* Returns the consumer instance if started, null if already running
|
|
495
|
+
*/
|
|
496
|
+
export async function tryStartConsumer(options) {
|
|
497
|
+
// If we already have a running consumer, return null
|
|
498
|
+
if (consumerInstance?.isRunning()) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
const consumer = createExecutionConsumer(options);
|
|
502
|
+
const started = await consumer.start();
|
|
503
|
+
if (started) {
|
|
504
|
+
consumerInstance = consumer;
|
|
505
|
+
return consumer;
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Get the current consumer instance (if running)
|
|
511
|
+
*/
|
|
512
|
+
export function getConsumer() {
|
|
513
|
+
return consumerInstance?.isRunning() ? consumerInstance : null;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Stop the current consumer
|
|
517
|
+
*/
|
|
518
|
+
export function stopConsumer() {
|
|
519
|
+
if (consumerInstance) {
|
|
520
|
+
consumerInstance.stop();
|
|
521
|
+
consumerInstance = null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer Module - Public API for queue processing and UI monitoring
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* - ConsumerService: Singleton background worker (start once in main)
|
|
6
|
+
* - QueuePollingService: UI subscribes here for real-time updates
|
|
7
|
+
* - Both communicate via AgentStorage (SQLite DB)
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Main process - start consumer singleton
|
|
12
|
+
* import { getConsumerService } from 'byterover-cli/dist/infra/cipher/consumer'
|
|
13
|
+
* const consumer = getConsumerService({ concurrency: 5 })
|
|
14
|
+
* await consumer.start()
|
|
15
|
+
*
|
|
16
|
+
* // UI components - subscribe to polling service
|
|
17
|
+
* import { getQueuePollingService } from 'byterover-cli/dist/infra/cipher/consumer'
|
|
18
|
+
* const poller = getQueuePollingService({ pollInterval: 500 })
|
|
19
|
+
* poller.on('snapshot', (snapshot) => renderUI(snapshot))
|
|
20
|
+
* poller.on('execution:completed', (exec) => showNotification(exec))
|
|
21
|
+
* await poller.start()
|
|
22
|
+
*
|
|
23
|
+
* // Cleanup
|
|
24
|
+
* consumer.dispose()
|
|
25
|
+
* poller.stop()
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export { isConsumerRunning, isConsumerRunningSync } from './consumer-lock.js';
|
|
29
|
+
export { ConsumerService, disposeConsumerService, getConsumerService } from './consumer-service.js';
|
|
30
|
+
export type { ConsumerServiceOptions } from './consumer-service.js';
|
|
31
|
+
export { createExecutionConsumer, ExecutionConsumer, getConsumer, stopConsumer, tryStartConsumer, } from './execution-consumer.js';
|
|
32
|
+
export { getQueuePollingService, QueuePollingService, stopQueuePollingService } from './queue-polling-service.js';
|
|
33
|
+
export type { ExecutionWithToolCalls, QueueSnapshot, QueueStats } from './queue-polling-service.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer Module - Public API for queue processing and UI monitoring
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* - ConsumerService: Singleton background worker (start once in main)
|
|
6
|
+
* - QueuePollingService: UI subscribes here for real-time updates
|
|
7
|
+
* - Both communicate via AgentStorage (SQLite DB)
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Main process - start consumer singleton
|
|
12
|
+
* import { getConsumerService } from 'byterover-cli/dist/infra/cipher/consumer'
|
|
13
|
+
* const consumer = getConsumerService({ concurrency: 5 })
|
|
14
|
+
* await consumer.start()
|
|
15
|
+
*
|
|
16
|
+
* // UI components - subscribe to polling service
|
|
17
|
+
* import { getQueuePollingService } from 'byterover-cli/dist/infra/cipher/consumer'
|
|
18
|
+
* const poller = getQueuePollingService({ pollInterval: 500 })
|
|
19
|
+
* poller.on('snapshot', (snapshot) => renderUI(snapshot))
|
|
20
|
+
* poller.on('execution:completed', (exec) => showNotification(exec))
|
|
21
|
+
* await poller.start()
|
|
22
|
+
*
|
|
23
|
+
* // Cleanup
|
|
24
|
+
* consumer.dispose()
|
|
25
|
+
* poller.stop()
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
// ==================== LOW-LEVEL API (for advanced usage) ====================
|
|
29
|
+
export { isConsumerRunning, isConsumerRunningSync } from './consumer-lock.js';
|
|
30
|
+
// ==================== HIGH-LEVEL API (for UI/REPL) ====================
|
|
31
|
+
export { ConsumerService, disposeConsumerService, getConsumerService } from './consumer-service.js';
|
|
32
|
+
export { createExecutionConsumer, ExecutionConsumer, getConsumer, stopConsumer, tryStartConsumer, } from './execution-consumer.js';
|
|
33
|
+
export { getQueuePollingService, QueuePollingService, stopQueuePollingService } from './queue-polling-service.js';
|