byterover-cli 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -10
- package/dist/commands/curate.js +2 -2
- package/dist/commands/main.js +2 -2
- package/dist/commands/query.js +2 -2
- package/dist/commands/status.js +2 -2
- package/dist/config/context-tree-domains.d.ts +14 -2
- package/dist/config/context-tree-domains.js +22 -27
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +3 -0
- package/dist/core/domain/cipher/file-system/types.d.ts +2 -0
- package/dist/core/domain/entities/auth-token.js +6 -3
- package/dist/core/domain/entities/event.d.ts +1 -1
- package/dist/core/domain/entities/event.js +2 -1
- package/dist/core/domain/knowledge/relation-parser.d.ts +16 -1
- package/dist/core/domain/knowledge/relation-parser.js +19 -2
- package/dist/core/domain/transport/schemas.d.ts +17 -1
- package/dist/core/domain/transport/schemas.js +9 -1
- package/dist/core/interfaces/cipher/i-blob-storage.d.ts +6 -0
- package/dist/core/interfaces/cipher/index.d.ts +0 -1
- package/dist/core/interfaces/executor/i-curate-executor.d.ts +2 -0
- package/dist/infra/cipher/agent/cipher-agent.js +4 -0
- package/dist/infra/cipher/file-system/file-system-service.d.ts +4 -0
- package/dist/infra/cipher/file-system/file-system-service.js +5 -0
- package/dist/infra/cipher/system-prompt/contributors/context-tree-structure-contributor.js +4 -2
- package/dist/infra/cipher/tools/implementations/create-knowledge-topic-tool.js +24 -17
- package/dist/infra/cipher/tools/implementations/curate-tool.js +28 -33
- package/dist/infra/cipher/tools/implementations/read-file-tool.js +3 -12
- package/dist/infra/cipher/tools/implementations/spec-analyze-tool.js +18 -15
- package/dist/infra/cipher/tools/implementations/task-tool.js +53 -7
- package/dist/infra/context-tree/file-context-tree-service.js +4 -15
- package/dist/infra/core/executors/curate-executor.d.ts +2 -7
- package/dist/infra/core/executors/curate-executor.js +18 -53
- package/dist/infra/core/executors/query-executor.d.ts +1 -7
- package/dist/infra/core/executors/query-executor.js +10 -35
- package/dist/infra/core/task-processor.d.ts +2 -0
- package/dist/infra/core/task-processor.js +1 -0
- package/dist/infra/http/authenticated-http-client.js +5 -0
- package/dist/infra/process/agent-worker.js +113 -6
- package/dist/infra/process/constants.d.ts +1 -0
- package/dist/infra/process/constants.js +1 -0
- package/dist/infra/process/task-queue-manager.js +2 -1
- package/dist/infra/process/transport-handlers.js +4 -0
- package/dist/infra/process/transport-worker.js +89 -1
- package/dist/infra/repl/commands/curate-command.js +2 -2
- package/dist/infra/repl/commands/gen-rules-command.js +2 -2
- package/dist/infra/repl/commands/init-command.js +2 -2
- package/dist/infra/repl/commands/login-command.js +2 -2
- package/dist/infra/repl/commands/logout-command.js +2 -2
- package/dist/infra/repl/commands/pull-command.js +2 -2
- package/dist/infra/repl/commands/push-command.js +2 -2
- package/dist/infra/repl/commands/query-command.js +2 -2
- package/dist/infra/repl/commands/space/list-command.js +2 -2
- package/dist/infra/repl/commands/space/switch-command.js +2 -2
- package/dist/infra/repl/commands/status-command.js +2 -2
- package/dist/infra/repl/repl-startup.js +0 -2
- package/dist/infra/storage/file-token-store.d.ts +31 -0
- package/dist/infra/storage/file-token-store.js +98 -0
- package/dist/infra/storage/keychain-token-store.d.ts +4 -1
- package/dist/infra/storage/keychain-token-store.js +6 -4
- package/dist/infra/storage/token-store.d.ts +10 -0
- package/dist/infra/storage/token-store.js +14 -0
- package/dist/infra/usecase/curate-use-case.js +1 -1
- package/dist/infra/usecase/init-use-case.js +2 -4
- package/dist/infra/user/http-user-service.js +6 -11
- package/dist/resources/prompts/curate.yml +14 -5
- package/dist/resources/prompts/plan.yml +6 -0
- package/dist/tui/app.js +1 -1
- package/dist/tui/components/execution/log-item.js +2 -5
- package/dist/tui/components/header.d.ts +1 -1
- package/dist/tui/components/header.js +25 -4
- package/dist/tui/components/index.d.ts +5 -1
- package/dist/tui/components/index.js +3 -1
- package/dist/tui/components/init.d.ts +33 -0
- package/dist/tui/components/init.js +253 -0
- package/dist/tui/components/onboarding/index.d.ts +1 -0
- package/dist/tui/components/onboarding/index.js +1 -0
- package/dist/tui/components/onboarding/onboarding-flow.d.ts +2 -0
- package/dist/tui/components/onboarding/onboarding-flow.js +8 -229
- package/dist/tui/components/onboarding/onboarding-step.js +1 -1
- package/dist/tui/components/onboarding/welcome-box.d.ts +14 -0
- package/dist/tui/components/onboarding/welcome-box.js +23 -0
- package/dist/tui/components/status-badge.d.ts +22 -0
- package/dist/tui/components/status-badge.js +32 -0
- package/dist/tui/contexts/auth-context.js +2 -1
- package/dist/tui/contexts/index.d.ts +1 -0
- package/dist/tui/contexts/index.js +1 -0
- package/dist/tui/contexts/onboarding-context.d.ts +14 -0
- package/dist/tui/contexts/onboarding-context.js +17 -22
- package/dist/tui/contexts/status-context.d.ts +33 -0
- package/dist/tui/contexts/status-context.js +159 -0
- package/dist/tui/hooks/use-auth-polling.d.ts +4 -1
- package/dist/tui/hooks/use-auth-polling.js +21 -7
- package/dist/tui/hooks/use-tab-navigation.js +0 -2
- package/dist/tui/providers/app-providers.js +2 -2
- package/dist/tui/types/index.d.ts +2 -0
- package/dist/tui/types/index.js +2 -0
- package/dist/tui/types/status.d.ts +46 -0
- package/dist/tui/types/status.js +13 -0
- package/dist/tui/utils/index.d.ts +6 -0
- package/dist/tui/utils/index.js +6 -0
- package/dist/tui/utils/time.d.ts +10 -0
- package/dist/tui/utils/time.js +15 -0
- package/dist/tui/views/command-view.js +0 -2
- package/dist/tui/views/index.d.ts +1 -0
- package/dist/tui/views/index.js +1 -0
- package/dist/tui/views/init-view.d.ts +15 -0
- package/dist/tui/views/init-view.js +29 -0
- package/dist/tui/views/logs-view.js +22 -8
- package/dist/utils/environment-detector.d.ts +5 -0
- package/dist/utils/environment-detector.js +31 -0
- package/dist/utils/global-data-path.d.ts +11 -0
- package/dist/utils/global-data-path.js +32 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/core/interfaces/cipher/i-agent-storage.d.ts +0 -152
- package/dist/core/interfaces/cipher/i-agent-storage.js +0 -1
- package/dist/infra/cipher/consumer/consumer-lock.d.ts +0 -20
- package/dist/infra/cipher/consumer/consumer-lock.js +0 -41
- package/dist/infra/cipher/consumer/consumer-service.d.ts +0 -99
- package/dist/infra/cipher/consumer/consumer-service.js +0 -166
- package/dist/infra/cipher/consumer/execution-consumer.d.ts +0 -126
- package/dist/infra/cipher/consumer/execution-consumer.js +0 -561
- package/dist/infra/cipher/consumer/index.d.ts +0 -33
- package/dist/infra/cipher/consumer/index.js +0 -34
- package/dist/infra/cipher/consumer/queue-polling-service.d.ts +0 -120
- package/dist/infra/cipher/consumer/queue-polling-service.js +0 -249
- package/dist/infra/cipher/storage/agent-storage.d.ts +0 -246
- package/dist/infra/cipher/storage/agent-storage.js +0 -956
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Consumer Module - Public API for queue processing and UI monitoring
|
|
3
|
-
*
|
|
4
|
-
* Architecture (legacy):
|
|
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';
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// TODO(v0.5.0): Remove this entire module. Replaced by CoreProcess + TaskProcessor + Transport events.
|
|
2
|
-
/**
|
|
3
|
-
* Consumer Module - Public API for queue processing and UI monitoring
|
|
4
|
-
*
|
|
5
|
-
* Architecture (legacy):
|
|
6
|
-
* - ConsumerService: Singleton background worker (start once in main)
|
|
7
|
-
* - QueuePollingService: UI subscribes here for real-time updates
|
|
8
|
-
* - Both communicate via AgentStorage (SQLite DB)
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* ```typescript
|
|
12
|
-
* // Main process - start consumer singleton
|
|
13
|
-
* import { getConsumerService } from 'byterover-cli/dist/infra/cipher/consumer'
|
|
14
|
-
* const consumer = getConsumerService({ concurrency: 5 })
|
|
15
|
-
* await consumer.start()
|
|
16
|
-
*
|
|
17
|
-
* // UI components - subscribe to polling service
|
|
18
|
-
* import { getQueuePollingService } from 'byterover-cli/dist/infra/cipher/consumer'
|
|
19
|
-
* const poller = getQueuePollingService({ pollInterval: 500 })
|
|
20
|
-
* poller.on('snapshot', (snapshot) => renderUI(snapshot))
|
|
21
|
-
* poller.on('execution:completed', (exec) => showNotification(exec))
|
|
22
|
-
* await poller.start()
|
|
23
|
-
*
|
|
24
|
-
* // Cleanup
|
|
25
|
-
* consumer.dispose()
|
|
26
|
-
* poller.stop()
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
// ==================== LOW-LEVEL API (for advanced usage) ====================
|
|
30
|
-
export { isConsumerRunning, isConsumerRunningSync } from './consumer-lock.js';
|
|
31
|
-
// ==================== HIGH-LEVEL API (for UI/REPL) ====================
|
|
32
|
-
export { ConsumerService, disposeConsumerService, getConsumerService } from './consumer-service.js';
|
|
33
|
-
export { createExecutionConsumer, ExecutionConsumer, getConsumer, stopConsumer, tryStartConsumer, } from './execution-consumer.js';
|
|
34
|
-
export { getQueuePollingService, QueuePollingService, stopQueuePollingService } from './queue-polling-service.js';
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { Execution, ToolCall } from '../storage/agent-storage.js';
|
|
3
|
-
export interface QueueStats {
|
|
4
|
-
completed: number;
|
|
5
|
-
failed: number;
|
|
6
|
-
queued: number;
|
|
7
|
-
running: number;
|
|
8
|
-
total: number;
|
|
9
|
-
}
|
|
10
|
-
export interface ExecutionWithToolCalls {
|
|
11
|
-
execution: Execution;
|
|
12
|
-
toolCalls: ToolCall[];
|
|
13
|
-
}
|
|
14
|
-
export interface QueueSnapshot {
|
|
15
|
-
/** All executions for the current session (with tool calls) - ordered by created_at ASC */
|
|
16
|
-
sessionExecutions: ExecutionWithToolCalls[];
|
|
17
|
-
stats: QueueStats;
|
|
18
|
-
timestamp: number;
|
|
19
|
-
}
|
|
20
|
-
export type QueueEventType = 'error' | 'execution:completed' | 'execution:failed' | 'execution:started' | 'reconnected' | 'snapshot' | 'stats:updated' | 'stopped';
|
|
21
|
-
export interface QueueEvents {
|
|
22
|
-
error: (error: Error) => void;
|
|
23
|
-
'execution:completed': (execution: Execution) => void;
|
|
24
|
-
'execution:failed': (execution: Execution) => void;
|
|
25
|
-
'execution:started': (execution: Execution) => void;
|
|
26
|
-
reconnected: () => void;
|
|
27
|
-
snapshot: (snapshot: QueueSnapshot) => void;
|
|
28
|
-
'stats:updated': (stats: QueueStats) => void;
|
|
29
|
-
stopped: () => void;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* QueuePollingService - Singleton service that polls agent.db and emits events
|
|
33
|
-
*
|
|
34
|
-
* Architecture:
|
|
35
|
-
* - Polls database at configurable interval (default 500ms)
|
|
36
|
-
* - Compares snapshots to detect changes
|
|
37
|
-
* - Emits granular events for UI updates
|
|
38
|
-
* - Singleton pattern prevents memory leaks from multiple instances
|
|
39
|
-
*
|
|
40
|
-
* Events:
|
|
41
|
-
* - 'snapshot': Full queue snapshot (for initial render)
|
|
42
|
-
* - 'stats:updated': Queue statistics changed
|
|
43
|
-
* - 'execution:started': New execution started
|
|
44
|
-
* - 'execution:completed': Execution completed successfully
|
|
45
|
-
* - 'execution:failed': Execution failed
|
|
46
|
-
* - 'error': Polling error occurred
|
|
47
|
-
* - 'stopped': Service stopped
|
|
48
|
-
*/
|
|
49
|
-
export declare class QueuePollingService extends EventEmitter {
|
|
50
|
-
private consumerId?;
|
|
51
|
-
private initialized;
|
|
52
|
-
private lastSnapshot;
|
|
53
|
-
private pollInterval;
|
|
54
|
-
private pollTimer;
|
|
55
|
-
private running;
|
|
56
|
-
private seenExecutionIds;
|
|
57
|
-
constructor(options?: {
|
|
58
|
-
consumerId?: string;
|
|
59
|
-
pollInterval?: number;
|
|
60
|
-
});
|
|
61
|
-
/**
|
|
62
|
-
* Get current snapshot without polling
|
|
63
|
-
*/
|
|
64
|
-
getCurrentSnapshot(): null | QueueSnapshot;
|
|
65
|
-
/**
|
|
66
|
-
* Check if service is running
|
|
67
|
-
*/
|
|
68
|
-
isRunning(): boolean;
|
|
69
|
-
/**
|
|
70
|
-
* Set consumer ID for session-based execution history
|
|
71
|
-
* Takes effect on next poll cycle
|
|
72
|
-
*/
|
|
73
|
-
setConsumerId(consumerId: string | undefined): void;
|
|
74
|
-
/**
|
|
75
|
-
* Set poll interval (takes effect on next poll)
|
|
76
|
-
*/
|
|
77
|
-
setPollInterval(ms: number): void;
|
|
78
|
-
/**
|
|
79
|
-
* Start polling
|
|
80
|
-
*/
|
|
81
|
-
start(): Promise<void>;
|
|
82
|
-
/**
|
|
83
|
-
* Stop polling
|
|
84
|
-
*/
|
|
85
|
-
stop(): void;
|
|
86
|
-
/**
|
|
87
|
-
* Build current snapshot from database
|
|
88
|
-
*/
|
|
89
|
-
private buildSnapshot;
|
|
90
|
-
/**
|
|
91
|
-
* Detect changes and emit appropriate events
|
|
92
|
-
*/
|
|
93
|
-
private detectChangesAndEmit;
|
|
94
|
-
/**
|
|
95
|
-
* Single poll iteration
|
|
96
|
-
*/
|
|
97
|
-
private poll;
|
|
98
|
-
/**
|
|
99
|
-
* Schedule next poll
|
|
100
|
-
*/
|
|
101
|
-
private schedulePoll;
|
|
102
|
-
/**
|
|
103
|
-
* Compare two stats objects for equality
|
|
104
|
-
*/
|
|
105
|
-
private statsEqual;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Get singleton QueuePollingService instance
|
|
109
|
-
* @param options - Configuration options
|
|
110
|
-
* @param options.consumerId - Optional consumer identifier
|
|
111
|
-
* @param options.pollInterval - Optional poll interval in milliseconds
|
|
112
|
-
*/
|
|
113
|
-
export declare function getQueuePollingService(options?: {
|
|
114
|
-
consumerId?: string;
|
|
115
|
-
pollInterval?: number;
|
|
116
|
-
}): QueuePollingService;
|
|
117
|
-
/**
|
|
118
|
-
* Stop and clear singleton instance
|
|
119
|
-
*/
|
|
120
|
-
export declare function stopQueuePollingService(): void;
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
// TODO(v0.5.0): Remove this file. QueuePollingService is replaced by Transport events.
|
|
2
|
-
import { EventEmitter } from 'node:events';
|
|
3
|
-
import { closeAgentStorage, getAgentStorage, getAgentStorageSync } from '../storage/agent-storage.js';
|
|
4
|
-
// ==================== SERVICE ====================
|
|
5
|
-
/**
|
|
6
|
-
* QueuePollingService - Singleton service that polls agent.db and emits events
|
|
7
|
-
*
|
|
8
|
-
* Architecture:
|
|
9
|
-
* - Polls database at configurable interval (default 500ms)
|
|
10
|
-
* - Compares snapshots to detect changes
|
|
11
|
-
* - Emits granular events for UI updates
|
|
12
|
-
* - Singleton pattern prevents memory leaks from multiple instances
|
|
13
|
-
*
|
|
14
|
-
* Events:
|
|
15
|
-
* - 'snapshot': Full queue snapshot (for initial render)
|
|
16
|
-
* - 'stats:updated': Queue statistics changed
|
|
17
|
-
* - 'execution:started': New execution started
|
|
18
|
-
* - 'execution:completed': Execution completed successfully
|
|
19
|
-
* - 'execution:failed': Execution failed
|
|
20
|
-
* - 'error': Polling error occurred
|
|
21
|
-
* - 'stopped': Service stopped
|
|
22
|
-
*/
|
|
23
|
-
// eslint-disable-next-line unicorn/prefer-event-target -- EventEmitter better for Node.js typed events
|
|
24
|
-
export class QueuePollingService extends EventEmitter {
|
|
25
|
-
consumerId;
|
|
26
|
-
initialized = false;
|
|
27
|
-
lastSnapshot = null;
|
|
28
|
-
pollInterval;
|
|
29
|
-
pollTimer = null;
|
|
30
|
-
running = false;
|
|
31
|
-
seenExecutionIds = new Set();
|
|
32
|
-
constructor(options) {
|
|
33
|
-
super();
|
|
34
|
-
this.consumerId = options?.consumerId;
|
|
35
|
-
this.pollInterval = options?.pollInterval ?? 500;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Get current snapshot without polling
|
|
39
|
-
*/
|
|
40
|
-
getCurrentSnapshot() {
|
|
41
|
-
return this.lastSnapshot;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Check if service is running
|
|
45
|
-
*/
|
|
46
|
-
isRunning() {
|
|
47
|
-
return this.running;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Set consumer ID for session-based execution history
|
|
51
|
-
* Takes effect on next poll cycle
|
|
52
|
-
*/
|
|
53
|
-
setConsumerId(consumerId) {
|
|
54
|
-
this.consumerId = consumerId;
|
|
55
|
-
// Clear last snapshot to force fresh data with new consumer
|
|
56
|
-
this.lastSnapshot = null;
|
|
57
|
-
this.seenExecutionIds.clear();
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Set poll interval (takes effect on next poll)
|
|
61
|
-
*/
|
|
62
|
-
setPollInterval(ms) {
|
|
63
|
-
this.pollInterval = ms;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Start polling
|
|
67
|
-
*/
|
|
68
|
-
async start() {
|
|
69
|
-
if (this.running)
|
|
70
|
-
return;
|
|
71
|
-
try {
|
|
72
|
-
// Initialize storage (auto-detects .brv/blobs from cwd)
|
|
73
|
-
await getAgentStorage();
|
|
74
|
-
this.initialized = true;
|
|
75
|
-
this.running = true;
|
|
76
|
-
// Initial poll
|
|
77
|
-
await this.poll();
|
|
78
|
-
// Start poll loop
|
|
79
|
-
this.schedulePoll();
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
this.emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Stop polling
|
|
87
|
-
*/
|
|
88
|
-
stop() {
|
|
89
|
-
this.running = false;
|
|
90
|
-
if (this.pollTimer) {
|
|
91
|
-
clearTimeout(this.pollTimer);
|
|
92
|
-
this.pollTimer = null;
|
|
93
|
-
}
|
|
94
|
-
if (this.initialized) {
|
|
95
|
-
closeAgentStorage();
|
|
96
|
-
this.initialized = false;
|
|
97
|
-
}
|
|
98
|
-
this.emit('stopped');
|
|
99
|
-
}
|
|
100
|
-
// ==================== PRIVATE ====================
|
|
101
|
-
/**
|
|
102
|
-
* Build current snapshot from database
|
|
103
|
-
*/
|
|
104
|
-
buildSnapshot() {
|
|
105
|
-
const storage = getAgentStorageSync();
|
|
106
|
-
// Get session executions with tool calls (if consumerId is set)
|
|
107
|
-
let sessionExecutions = [];
|
|
108
|
-
if (this.consumerId) {
|
|
109
|
-
const sessionExecs = storage.getSessionExecutions(this.consumerId);
|
|
110
|
-
sessionExecutions = sessionExecs.map((exec) => ({
|
|
111
|
-
execution: exec,
|
|
112
|
-
toolCalls: storage.getToolCalls(exec.id),
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
// Get stats directly from DB (accurate counts)
|
|
116
|
-
const stats = storage.getStats();
|
|
117
|
-
return {
|
|
118
|
-
sessionExecutions,
|
|
119
|
-
stats,
|
|
120
|
-
timestamp: Date.now(),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Detect changes and emit appropriate events
|
|
125
|
-
*/
|
|
126
|
-
detectChangesAndEmit(oldSnapshot, newSnapshot) {
|
|
127
|
-
// Always emit snapshot for subscribers that want full state
|
|
128
|
-
this.emit('snapshot', newSnapshot);
|
|
129
|
-
// Detect stats changes
|
|
130
|
-
if (!oldSnapshot || !this.statsEqual(oldSnapshot.stats, newSnapshot.stats)) {
|
|
131
|
-
this.emit('stats:updated', newSnapshot.stats);
|
|
132
|
-
}
|
|
133
|
-
// Detect execution state changes
|
|
134
|
-
const executions = newSnapshot.sessionExecutions.map((e) => e.execution);
|
|
135
|
-
for (const exec of executions) {
|
|
136
|
-
const wasSeenBefore = this.seenExecutionIds.has(exec.id);
|
|
137
|
-
if (wasSeenBefore) {
|
|
138
|
-
// Check if status changed
|
|
139
|
-
const oldExecs = oldSnapshot?.sessionExecutions.map((e) => e.execution) ?? [];
|
|
140
|
-
const oldExec = oldExecs.find((e) => e.id === exec.id);
|
|
141
|
-
if (oldExec && oldExec.status !== exec.status) {
|
|
142
|
-
if (exec.status === 'completed') {
|
|
143
|
-
this.emit('execution:completed', exec);
|
|
144
|
-
}
|
|
145
|
-
else if (exec.status === 'failed') {
|
|
146
|
-
this.emit('execution:failed', exec);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
this.seenExecutionIds.add(exec.id);
|
|
152
|
-
if (exec.status === 'running') {
|
|
153
|
-
this.emit('execution:started', exec);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Limit seen IDs to prevent memory growth
|
|
158
|
-
if (this.seenExecutionIds.size > 1000) {
|
|
159
|
-
const idsToKeep = new Set(executions.map((e) => e.id));
|
|
160
|
-
this.seenExecutionIds = idsToKeep;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Single poll iteration
|
|
165
|
-
*/
|
|
166
|
-
async poll() {
|
|
167
|
-
if (!this.running || !this.initialized)
|
|
168
|
-
return;
|
|
169
|
-
try {
|
|
170
|
-
// Check if DB file was replaced (e.g., by brv init in another terminal)
|
|
171
|
-
const storage = getAgentStorageSync();
|
|
172
|
-
if (storage.isDbFileChanged()) {
|
|
173
|
-
// DB file was replaced - reconnect
|
|
174
|
-
await storage.reconnect();
|
|
175
|
-
// Clear seen IDs since DB was reset
|
|
176
|
-
this.seenExecutionIds.clear();
|
|
177
|
-
this.lastSnapshot = null;
|
|
178
|
-
this.emit('reconnected');
|
|
179
|
-
}
|
|
180
|
-
const newSnapshot = this.buildSnapshot();
|
|
181
|
-
this.detectChangesAndEmit(this.lastSnapshot, newSnapshot);
|
|
182
|
-
this.lastSnapshot = newSnapshot;
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
// If stop() was called during poll, silently exit - this is expected during shutdown
|
|
186
|
-
if (!this.running)
|
|
187
|
-
return;
|
|
188
|
-
// Try to recover from errors (connection lost, storage closed, etc.)
|
|
189
|
-
try {
|
|
190
|
-
// Use getAgentStorage() which auto-reinitializes if singleton was closed
|
|
191
|
-
const storage = await getAgentStorage();
|
|
192
|
-
await storage.reconnect();
|
|
193
|
-
this.seenExecutionIds.clear();
|
|
194
|
-
this.lastSnapshot = null;
|
|
195
|
-
this.emit('reconnected');
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
// Reconnect failed - only emit error if still running (not during intentional shutdown)
|
|
199
|
-
if (this.running) {
|
|
200
|
-
this.emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Schedule next poll
|
|
207
|
-
*/
|
|
208
|
-
schedulePoll() {
|
|
209
|
-
if (!this.running)
|
|
210
|
-
return;
|
|
211
|
-
this.pollTimer = setTimeout(async () => {
|
|
212
|
-
await this.poll();
|
|
213
|
-
this.schedulePoll();
|
|
214
|
-
}, this.pollInterval);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Compare two stats objects for equality
|
|
218
|
-
*/
|
|
219
|
-
statsEqual(a, b) {
|
|
220
|
-
return (a.queued === b.queued &&
|
|
221
|
-
a.running === b.running &&
|
|
222
|
-
a.completed === b.completed &&
|
|
223
|
-
a.failed === b.failed &&
|
|
224
|
-
a.total === b.total);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// ==================== SINGLETON ====================
|
|
228
|
-
let instance = null;
|
|
229
|
-
/**
|
|
230
|
-
* Get singleton QueuePollingService instance
|
|
231
|
-
* @param options - Configuration options
|
|
232
|
-
* @param options.consumerId - Optional consumer identifier
|
|
233
|
-
* @param options.pollInterval - Optional poll interval in milliseconds
|
|
234
|
-
*/
|
|
235
|
-
export function getQueuePollingService(options) {
|
|
236
|
-
if (!instance) {
|
|
237
|
-
instance = new QueuePollingService(options);
|
|
238
|
-
}
|
|
239
|
-
return instance;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Stop and clear singleton instance
|
|
243
|
-
*/
|
|
244
|
-
export function stopQueuePollingService() {
|
|
245
|
-
if (instance) {
|
|
246
|
-
instance.stop();
|
|
247
|
-
instance = null;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import type { Execution, ExecutionStatus, ExecutionType, ToolCall, ToolCallInfo, ToolCallStatus, ToolCallUpdateOptions } from '../../../core/domain/cipher/queue/types.js';
|
|
2
|
-
import type { IAgentStorage } from '../../../core/interfaces/cipher/i-agent-storage.js';
|
|
3
|
-
export type { ConsumerLock, Execution, ExecutionStatus, ExecutionType, ToolCall, ToolCallInfo, ToolCallStatus, ToolCallUpdateOptions, } from '../../../core/domain/cipher/queue/types.js';
|
|
4
|
-
/**
|
|
5
|
-
* AgentStorage - SQLite-based storage for execution queue and tool calls
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Single database file at .brv/blobs/agent.db
|
|
9
|
-
* - Job queue via executions.status = 'queued'
|
|
10
|
-
* - Tool call tracking for UI polling
|
|
11
|
-
* - Prepared statement caching (no memory leak)
|
|
12
|
-
* - WAL mode for concurrent read/write
|
|
13
|
-
* - Orphan cleanup on startup
|
|
14
|
-
* - Old execution cleanup (max 100)
|
|
15
|
-
*/
|
|
16
|
-
export declare class AgentStorage implements IAgentStorage {
|
|
17
|
-
initialized: boolean;
|
|
18
|
-
private db;
|
|
19
|
-
private dbFileInode;
|
|
20
|
-
private readonly dbPath;
|
|
21
|
-
private readonly inMemory;
|
|
22
|
-
private stmtAddToolCall;
|
|
23
|
-
private stmtCleanupOrphans;
|
|
24
|
-
private stmtCountCompletedFailed;
|
|
25
|
-
private stmtCreateExecution;
|
|
26
|
-
private stmtDeleteConsumerLock;
|
|
27
|
-
private stmtDeleteOldExecutions;
|
|
28
|
-
private stmtDequeueBatchSelect;
|
|
29
|
-
private stmtDequeueSelect;
|
|
30
|
-
private stmtFailQuery;
|
|
31
|
-
private stmtGetExecution;
|
|
32
|
-
private stmtGetExecutionsSince;
|
|
33
|
-
private stmtGetQueuedExecutions;
|
|
34
|
-
private stmtGetRecentExecutions;
|
|
35
|
-
private stmtGetRunningExecutions;
|
|
36
|
-
private stmtGetToolCalls;
|
|
37
|
-
private stmtOrphanFromConsumer;
|
|
38
|
-
private stmtOrphanMissingConsumer;
|
|
39
|
-
private stmtOrphanNullConsumer;
|
|
40
|
-
private stmtUpdateStatus;
|
|
41
|
-
private stmtUpdateToolCall;
|
|
42
|
-
private readonly storageDir;
|
|
43
|
-
constructor(config?: {
|
|
44
|
-
inMemory?: boolean;
|
|
45
|
-
storageDir?: string;
|
|
46
|
-
});
|
|
47
|
-
/**
|
|
48
|
-
* Acquire consumer lock (register this consumer)
|
|
49
|
-
* Only ONE consumer can run at a time - checks for any active consumer first
|
|
50
|
-
* @returns true if lock acquired, false if another consumer is already running
|
|
51
|
-
*/
|
|
52
|
-
acquireConsumerLock(consumerId: string): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Add a tool call record
|
|
55
|
-
* @returns tool call id
|
|
56
|
-
*/
|
|
57
|
-
addToolCall(executionId: string, info: ToolCallInfo): string;
|
|
58
|
-
/**
|
|
59
|
-
* Cleanup old executions, keep only maxKeep most recent completed/failed
|
|
60
|
-
*/
|
|
61
|
-
cleanupOldExecutions(maxKeep?: number): number;
|
|
62
|
-
/**
|
|
63
|
-
* Cleanup orphaned executions (status='running') from previous session crash
|
|
64
|
-
* Should be called on startup
|
|
65
|
-
*/
|
|
66
|
-
cleanupOrphanedExecutions(): number;
|
|
67
|
-
/**
|
|
68
|
-
* Cleanup stale consumers and orphan their executions
|
|
69
|
-
* A consumer is stale if its heartbeat is older than timeoutMs
|
|
70
|
-
* @param timeoutMs - heartbeat timeout (default 30 seconds)
|
|
71
|
-
* @returns number of orphaned executions
|
|
72
|
-
*/
|
|
73
|
-
cleanupStaleConsumers(timeoutMs?: number): number;
|
|
74
|
-
/**
|
|
75
|
-
* Close database connection
|
|
76
|
-
*/
|
|
77
|
-
close(): void;
|
|
78
|
-
/**
|
|
79
|
-
* Create a new execution
|
|
80
|
-
* @param type - 'curate' or 'query'
|
|
81
|
-
* @param input - content (curate) or query string (query)
|
|
82
|
-
* @returns execution id
|
|
83
|
-
*/
|
|
84
|
-
createExecution(type: ExecutionType, input: string): string;
|
|
85
|
-
/**
|
|
86
|
-
* Dequeue multiple executions at once (atomic batch SELECT + UPDATE)
|
|
87
|
-
* This is more efficient than calling dequeueExecution() multiple times
|
|
88
|
-
* and ensures all queued items are seen in a single transaction snapshot
|
|
89
|
-
* @param limit - max number of executions to dequeue
|
|
90
|
-
* @param consumerId - ID of the consumer claiming these executions
|
|
91
|
-
* @returns array of executions (may be empty if queue is empty)
|
|
92
|
-
*/
|
|
93
|
-
dequeueBatch(limit: number, consumerId?: string): Execution[];
|
|
94
|
-
/**
|
|
95
|
-
* Dequeue next queued execution (atomic SELECT + UPDATE)
|
|
96
|
-
* @param consumerId - ID of the consumer claiming this execution
|
|
97
|
-
* @returns execution or null if queue is empty
|
|
98
|
-
*/
|
|
99
|
-
dequeueExecution(consumerId?: string): Execution | null;
|
|
100
|
-
/**
|
|
101
|
-
* Get execution by id
|
|
102
|
-
*/
|
|
103
|
-
getExecution(id: string): Execution | null;
|
|
104
|
-
/**
|
|
105
|
-
* Get executions updated since timestamp (for incremental polling)
|
|
106
|
-
*/
|
|
107
|
-
getExecutionsSince(timestamp: number): Execution[];
|
|
108
|
-
/**
|
|
109
|
-
* Get execution with all its tool calls (for UI display)
|
|
110
|
-
*/
|
|
111
|
-
getExecutionWithToolCalls(id: string): null | {
|
|
112
|
-
execution: Execution;
|
|
113
|
-
toolCalls: ToolCall[];
|
|
114
|
-
};
|
|
115
|
-
/**
|
|
116
|
-
* Get all queued executions
|
|
117
|
-
*/
|
|
118
|
-
getQueuedExecutions(): Execution[];
|
|
119
|
-
/**
|
|
120
|
-
* Get recent executions (for UI display)
|
|
121
|
-
*/
|
|
122
|
-
getRecentExecutions(limit?: number): Execution[];
|
|
123
|
-
/**
|
|
124
|
-
* Get all running executions
|
|
125
|
-
*/
|
|
126
|
-
getRunningExecutions(): Execution[];
|
|
127
|
-
/**
|
|
128
|
-
* Get all executions belonging to a specific consumer session.
|
|
129
|
-
* Returns executions ordered by created_at ASC (oldest first, newest last).
|
|
130
|
-
* Includes:
|
|
131
|
-
* - Curate executions with matching consumer_id
|
|
132
|
-
* - Query executions created after consumer started (no consumer_id)
|
|
133
|
-
*/
|
|
134
|
-
getSessionExecutions(consumerId: string): Execution[];
|
|
135
|
-
/**
|
|
136
|
-
* Get queue statistics (queries DB directly for accurate counts)
|
|
137
|
-
*/
|
|
138
|
-
getStats(): {
|
|
139
|
-
completed: number;
|
|
140
|
-
failed: number;
|
|
141
|
-
queued: number;
|
|
142
|
-
running: number;
|
|
143
|
-
total: number;
|
|
144
|
-
};
|
|
145
|
-
/**
|
|
146
|
-
* Get all tool calls for an execution
|
|
147
|
-
*/
|
|
148
|
-
getToolCalls(executionId: string): ToolCall[];
|
|
149
|
-
/**
|
|
150
|
-
* Check if any consumer is currently active (has recent heartbeat)
|
|
151
|
-
* @param timeoutMs - heartbeat timeout (default 30 seconds)
|
|
152
|
-
*/
|
|
153
|
-
hasActiveConsumer(timeoutMs?: number): boolean;
|
|
154
|
-
/**
|
|
155
|
-
* Check if a specific consumer lock exists in the database
|
|
156
|
-
* Used by Consumer to verify its lock is still valid after DB reconnection
|
|
157
|
-
*/
|
|
158
|
-
hasConsumerLock(consumerId: string): boolean;
|
|
159
|
-
/**
|
|
160
|
-
* Initialize storage - create tables, enable WAL
|
|
161
|
-
* @param options - Initialization options
|
|
162
|
-
* @param options.cleanupOrphans - If true, cleanup orphaned executions (only Consumer should set this)
|
|
163
|
-
*/
|
|
164
|
-
initialize(options?: {
|
|
165
|
-
cleanupOrphans?: boolean;
|
|
166
|
-
}): Promise<void>;
|
|
167
|
-
/**
|
|
168
|
-
* Check if the DB file has been replaced (different inode)
|
|
169
|
-
* Returns true if DB needs reconnection
|
|
170
|
-
*/
|
|
171
|
-
isDbFileChanged(): boolean;
|
|
172
|
-
/**
|
|
173
|
-
* Reconnect to the database (close and reinitialize)
|
|
174
|
-
* Use when DB file has been replaced by another process (e.g., brv init)
|
|
175
|
-
*/
|
|
176
|
-
reconnect(): Promise<void>;
|
|
177
|
-
/**
|
|
178
|
-
* Release consumer lock (unregister this consumer)
|
|
179
|
-
*/
|
|
180
|
-
releaseConsumerLock(consumerId: string): void;
|
|
181
|
-
/**
|
|
182
|
-
* Update consumer heartbeat
|
|
183
|
-
*/
|
|
184
|
-
updateConsumerHeartbeat(consumerId: string): void;
|
|
185
|
-
/**
|
|
186
|
-
* Update execution status
|
|
187
|
-
*/
|
|
188
|
-
updateExecutionStatus(id: string, status: ExecutionStatus, result?: string, error?: string): void;
|
|
189
|
-
/**
|
|
190
|
-
* Update tool call status and result
|
|
191
|
-
*/
|
|
192
|
-
updateToolCall(id: string, status: ToolCallStatus, options?: ToolCallUpdateOptions): void;
|
|
193
|
-
/**
|
|
194
|
-
* Ensure storage has been initialized
|
|
195
|
-
*/
|
|
196
|
-
private ensureInitialized;
|
|
197
|
-
/**
|
|
198
|
-
* Get database instance with type safety (throws if not initialized)
|
|
199
|
-
* Use this instead of this.getDb() for proper type narrowing
|
|
200
|
-
*/
|
|
201
|
-
private getDb;
|
|
202
|
-
/**
|
|
203
|
-
* Convert database row to Execution object
|
|
204
|
-
*/
|
|
205
|
-
private rowToExecution;
|
|
206
|
-
/**
|
|
207
|
-
* Convert database row to ToolCall object
|
|
208
|
-
*/
|
|
209
|
-
private rowToToolCall;
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Get the singleton AgentStorage instance (auto-initializes if needed)
|
|
213
|
-
*
|
|
214
|
-
* This is the PRIMARY API - just call this and it handles everything.
|
|
215
|
-
* First call will initialize with provided config, subsequent calls return cached instance.
|
|
216
|
-
*
|
|
217
|
-
* @param config - Configuration options
|
|
218
|
-
* @param config.cleanupOrphans - Cleanup orphaned executions (only Consumer should set this)
|
|
219
|
-
* @param config.inMemory - Use in-memory database (for testing)
|
|
220
|
-
* @param config.storageDir - Directory for agent.db (default: .brv/blobs)
|
|
221
|
-
*/
|
|
222
|
-
export declare function getAgentStorage(config?: {
|
|
223
|
-
cleanupOrphans?: boolean;
|
|
224
|
-
inMemory?: boolean;
|
|
225
|
-
storageDir?: string;
|
|
226
|
-
}): Promise<AgentStorage>;
|
|
227
|
-
/**
|
|
228
|
-
* Get the singleton AgentStorage instance (sync version)
|
|
229
|
-
* THROWS if not initialized - use getAgentStorage() instead for auto-init
|
|
230
|
-
*
|
|
231
|
-
* Use this only when you KNOW storage is already initialized (e.g., in Consumer after start)
|
|
232
|
-
*/
|
|
233
|
-
export declare function getAgentStorageSync(): AgentStorage;
|
|
234
|
-
/**
|
|
235
|
-
* Initialize the singleton AgentStorage instance
|
|
236
|
-
* @deprecated Use getAgentStorage() directly - it auto-initializes
|
|
237
|
-
*/
|
|
238
|
-
export declare function initializeAgentStorage(config?: {
|
|
239
|
-
cleanupOrphans?: boolean;
|
|
240
|
-
inMemory?: boolean;
|
|
241
|
-
storageDir?: string;
|
|
242
|
-
}): Promise<AgentStorage>;
|
|
243
|
-
/**
|
|
244
|
-
* Close and clear the singleton AgentStorage instance
|
|
245
|
-
*/
|
|
246
|
-
export declare function closeAgentStorage(): void;
|