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
|
@@ -76,6 +76,11 @@ export class AuthenticatedHttpClient {
|
|
|
76
76
|
* Preserves error information while abstracting axios-specific details.
|
|
77
77
|
*/
|
|
78
78
|
handleError(error) {
|
|
79
|
+
if (isAxiosError(error) && error.response?.status === 401) {
|
|
80
|
+
// IMPORTANT: Do not handle 401 errors here - let callers handle errors (e.g., distinguish 401 from network errors)
|
|
81
|
+
return error;
|
|
82
|
+
}
|
|
83
|
+
// WARNING: isLLMServerError() matches any response with the standard ApiErrorResponse structure (IAM, Cogit, LLM services all use it)
|
|
79
84
|
if (this.isLLMServerError(error)) {
|
|
80
85
|
// Extract standardized API error message
|
|
81
86
|
return new Error(this.parseHttpError(error));
|
|
@@ -27,8 +27,9 @@ import { ProjectConfigStore } from '../config/file-config-store.js';
|
|
|
27
27
|
import { CurateExecutor } from '../core/executors/curate-executor.js';
|
|
28
28
|
import { QueryExecutor } from '../core/executors/query-executor.js';
|
|
29
29
|
import { createTaskProcessor } from '../core/task-processor.js';
|
|
30
|
-
import {
|
|
30
|
+
import { createTokenStore } from '../storage/token-store.js';
|
|
31
31
|
import { createTransportClient } from '../transport/transport-factory.js';
|
|
32
|
+
import { CURATE_MAX_CONCURRENT } from './constants.js';
|
|
32
33
|
import { TaskQueueManager } from './task-queue-manager.js';
|
|
33
34
|
// IPC types imported from ./ipc-types.ts
|
|
34
35
|
function sendToParent(message) {
|
|
@@ -62,6 +63,12 @@ let initializationError;
|
|
|
62
63
|
let isInitializing = false;
|
|
63
64
|
/** Guard: prevent double cleanup */
|
|
64
65
|
let isCleaningUp = false;
|
|
66
|
+
/** Parent process PID for heartbeat monitoring */
|
|
67
|
+
let parentPid;
|
|
68
|
+
/** Parent heartbeat running flag (for recursive setTimeout pattern) */
|
|
69
|
+
let parentHeartbeatRunning = false;
|
|
70
|
+
/** Parent heartbeat check interval in milliseconds */
|
|
71
|
+
const PARENT_HEARTBEAT_INTERVAL_MS = 2000;
|
|
65
72
|
let eventForwarders = [];
|
|
66
73
|
// ============================================================================
|
|
67
74
|
// Task Queue Manager (replaces inline queue logic)
|
|
@@ -69,13 +76,13 @@ let eventForwarders = [];
|
|
|
69
76
|
/**
|
|
70
77
|
* Task queue manager handles:
|
|
71
78
|
* - Separate queues for curate and query tasks
|
|
72
|
-
* - Concurrency limits (max
|
|
79
|
+
* - Concurrency limits (max 1 concurrent per type)
|
|
73
80
|
* - Task deduplication (same taskId can't be queued twice)
|
|
74
81
|
* - Cancel tasks from queue before processing
|
|
75
82
|
* - FIFO processing order
|
|
76
83
|
*/
|
|
77
84
|
const taskQueueManager = new TaskQueueManager({
|
|
78
|
-
curate: { maxConcurrent:
|
|
85
|
+
curate: { maxConcurrent: CURATE_MAX_CONCURRENT },
|
|
79
86
|
onExecutorError(taskId, error) {
|
|
80
87
|
agentLog(`Executor error for task ${taskId}: ${error}`);
|
|
81
88
|
},
|
|
@@ -104,7 +111,6 @@ function cleanupAgentEventForwarding() {
|
|
|
104
111
|
return;
|
|
105
112
|
}
|
|
106
113
|
// Get the old agent's event bus (if still available)
|
|
107
|
-
// Cast to CipherAgent to access agentEventBus property
|
|
108
114
|
const eventBus = cipherAgent?.agentEventBus;
|
|
109
115
|
if (eventBus) {
|
|
110
116
|
for (const { event, handler } of eventForwarders) {
|
|
@@ -310,7 +316,7 @@ async function tryInitializeAgent(forceReinit = false) {
|
|
|
310
316
|
taskProcessor = undefined;
|
|
311
317
|
isAgentInitialized = false;
|
|
312
318
|
}
|
|
313
|
-
const tokenStore =
|
|
319
|
+
const tokenStore = createTokenStore();
|
|
314
320
|
const configStore = new ProjectConfigStore();
|
|
315
321
|
const authToken = await tokenStore.load();
|
|
316
322
|
const brvConfig = await configStore.read();
|
|
@@ -320,6 +326,12 @@ async function tryInitializeAgent(forceReinit = false) {
|
|
|
320
326
|
agentLog('Cannot initialize - no auth token');
|
|
321
327
|
return false;
|
|
322
328
|
}
|
|
329
|
+
// Check if token is expired - fail early with clear message instead of 401 later
|
|
330
|
+
if (authToken.isExpired()) {
|
|
331
|
+
initializationError = new NotAuthenticatedError();
|
|
332
|
+
agentLog('Cannot initialize - token expired (please run /login to re-authenticate)');
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
323
335
|
// Create Executors
|
|
324
336
|
const curateExecutor = new CurateExecutor();
|
|
325
337
|
const queryExecutor = new QueryExecutor();
|
|
@@ -370,6 +382,13 @@ async function tryInitializeAgent(forceReinit = false) {
|
|
|
370
382
|
}
|
|
371
383
|
return true;
|
|
372
384
|
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
// Catch errors and return false instead of throwing
|
|
387
|
+
// This allows lazy init to retry when tasks arrive
|
|
388
|
+
initializationError = error instanceof Error ? error : new Error(String(error));
|
|
389
|
+
agentLog(`Agent initialization failed: ${error}`);
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
373
392
|
finally {
|
|
374
393
|
isInitializing = false;
|
|
375
394
|
}
|
|
@@ -378,7 +397,7 @@ async function tryInitializeAgent(forceReinit = false) {
|
|
|
378
397
|
* Handle task:execute from Transport.
|
|
379
398
|
*/
|
|
380
399
|
async function handleTaskExecute(data) {
|
|
381
|
-
const { content, files, taskId, type } = data;
|
|
400
|
+
const { clientCwd, content, files, taskId, type } = data;
|
|
382
401
|
agentLog(`Processing task: ${taskId} (type=${type})`);
|
|
383
402
|
// If not initialized, try to initialize now (lazy init for post-onboarding)
|
|
384
403
|
if (!isAgentInitialized) {
|
|
@@ -409,6 +428,7 @@ async function handleTaskExecute(data) {
|
|
|
409
428
|
// File validation is handled by UseCase (business logic belongs there)
|
|
410
429
|
// Note: taskId is passed to UseCase → CipherAgent → ChatSession, which adds it to all events
|
|
411
430
|
const result = await taskProcessor.process({
|
|
431
|
+
clientCwd,
|
|
412
432
|
content,
|
|
413
433
|
files,
|
|
414
434
|
taskId,
|
|
@@ -519,6 +539,71 @@ async function startAgent() {
|
|
|
519
539
|
});
|
|
520
540
|
agentLog('Ready to process tasks');
|
|
521
541
|
}
|
|
542
|
+
// ============================================================================
|
|
543
|
+
// Parent Heartbeat Monitoring
|
|
544
|
+
// ============================================================================
|
|
545
|
+
/**
|
|
546
|
+
* Setup parent process heartbeat monitoring.
|
|
547
|
+
*
|
|
548
|
+
* Why this is needed:
|
|
549
|
+
* - When main process receives SIGKILL, it dies immediately
|
|
550
|
+
* - SIGKILL cannot be caught, so no cleanup happens
|
|
551
|
+
* - IPC 'disconnect' event may not fire
|
|
552
|
+
* - Child processes become orphans (PPID = 1)
|
|
553
|
+
*
|
|
554
|
+
* This function periodically checks if parent is still alive.
|
|
555
|
+
* If parent dies, child self-terminates to prevent zombie processes.
|
|
556
|
+
*/
|
|
557
|
+
function setupParentHeartbeat() {
|
|
558
|
+
// Already running - don't start another
|
|
559
|
+
if (parentHeartbeatRunning)
|
|
560
|
+
return;
|
|
561
|
+
parentHeartbeatRunning = true;
|
|
562
|
+
parentPid = process.ppid;
|
|
563
|
+
/**
|
|
564
|
+
* Recursive setTimeout pattern - safer than setInterval:
|
|
565
|
+
* - No callback overlap possible
|
|
566
|
+
* - Clean cancellation (just set flag = false)
|
|
567
|
+
* - No orphan timers
|
|
568
|
+
*/
|
|
569
|
+
const checkParent = () => {
|
|
570
|
+
// Stopped - don't schedule next check
|
|
571
|
+
if (!parentHeartbeatRunning || !parentPid)
|
|
572
|
+
return;
|
|
573
|
+
// Check if parent is still alive using signal 0
|
|
574
|
+
// Signal 0 doesn't send any signal, just checks if process exists
|
|
575
|
+
try {
|
|
576
|
+
process.kill(parentPid, 0);
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// Parent is dead - self-terminate
|
|
580
|
+
agentLog(`Parent process (${parentPid}) died - shutting down to prevent zombie`);
|
|
581
|
+
parentHeartbeatRunning = false;
|
|
582
|
+
// Stop agent and exit
|
|
583
|
+
stopAgent()
|
|
584
|
+
.catch(() => { })
|
|
585
|
+
.finally(() => {
|
|
586
|
+
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
|
|
587
|
+
process.exit(0);
|
|
588
|
+
});
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
// Schedule next check (only if still running)
|
|
592
|
+
if (parentHeartbeatRunning) {
|
|
593
|
+
setTimeout(checkParent, PARENT_HEARTBEAT_INTERVAL_MS);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
// Start first check after delay
|
|
597
|
+
setTimeout(checkParent, PARENT_HEARTBEAT_INTERVAL_MS);
|
|
598
|
+
agentLog(`Parent heartbeat monitoring started (PPID: ${parentPid})`);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Stop the parent heartbeat monitoring.
|
|
602
|
+
* With recursive setTimeout, just set flag to false - next check won't schedule.
|
|
603
|
+
*/
|
|
604
|
+
function stopParentHeartbeat() {
|
|
605
|
+
parentHeartbeatRunning = false;
|
|
606
|
+
}
|
|
522
607
|
/**
|
|
523
608
|
* Stop Agent Process.
|
|
524
609
|
*/
|
|
@@ -530,6 +615,8 @@ async function stopAgent() {
|
|
|
530
615
|
}
|
|
531
616
|
isCleaningUp = true;
|
|
532
617
|
try {
|
|
618
|
+
// Stop parent heartbeat first
|
|
619
|
+
stopParentHeartbeat();
|
|
533
620
|
// Clear task queue
|
|
534
621
|
taskQueueManager.clear();
|
|
535
622
|
// Cleanup event forwarders before stopping agent
|
|
@@ -559,11 +646,16 @@ async function runWorker() {
|
|
|
559
646
|
try {
|
|
560
647
|
await startAgent();
|
|
561
648
|
sendToParent({ type: 'ready' });
|
|
649
|
+
// Start parent heartbeat monitoring after ready
|
|
650
|
+
// This ensures we self-terminate if parent dies (SIGKILL scenario)
|
|
651
|
+
setupParentHeartbeat();
|
|
562
652
|
}
|
|
563
653
|
catch (error) {
|
|
564
654
|
const message = error instanceof Error ? error.message : String(error);
|
|
565
655
|
agentLog(`Failed to start: ${message}`);
|
|
566
656
|
sendToParent({ error: message, type: 'error' });
|
|
657
|
+
// Cleanup before exit to release any acquired resources
|
|
658
|
+
await stopAgent().catch(() => { });
|
|
567
659
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
|
|
568
660
|
process.exit(1);
|
|
569
661
|
}
|
|
@@ -588,6 +680,19 @@ async function runWorker() {
|
|
|
588
680
|
process.once('SIGTERM', cleanup);
|
|
589
681
|
process.once('SIGINT', cleanup);
|
|
590
682
|
process.once('disconnect', cleanup);
|
|
683
|
+
// Global exception handlers - ensure cleanup on unexpected errors
|
|
684
|
+
process.on('uncaughtException', async (error) => {
|
|
685
|
+
agentLog(`Uncaught exception: ${error}`);
|
|
686
|
+
await stopAgent().catch(() => { });
|
|
687
|
+
// eslint-disable-next-line n/no-process-exit
|
|
688
|
+
process.exit(1);
|
|
689
|
+
});
|
|
690
|
+
process.on('unhandledRejection', async (reason) => {
|
|
691
|
+
agentLog(`Unhandled rejection: ${reason}`);
|
|
692
|
+
await stopAgent().catch(() => { });
|
|
693
|
+
// eslint-disable-next-line n/no-process-exit
|
|
694
|
+
process.exit(1);
|
|
695
|
+
});
|
|
591
696
|
}
|
|
592
697
|
// ============================================================================
|
|
593
698
|
// Run
|
|
@@ -597,6 +702,8 @@ try {
|
|
|
597
702
|
}
|
|
598
703
|
catch (error) {
|
|
599
704
|
agentLog(`Fatal error: ${error}`);
|
|
705
|
+
// Cleanup before exit to release any acquired resources
|
|
706
|
+
await stopAgent().catch(() => { });
|
|
600
707
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
|
|
601
708
|
process.exit(1);
|
|
602
709
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CURATE_MAX_CONCURRENT = 1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CURATE_MAX_CONCURRENT = 1;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* This class is extracted from agent-worker.ts to enable unit testing.
|
|
12
12
|
*/
|
|
13
|
+
import { CURATE_MAX_CONCURRENT } from './constants.js';
|
|
13
14
|
export class TaskQueueManager {
|
|
14
15
|
activeCurateTasks = 0;
|
|
15
16
|
activeQueryTasks = 0;
|
|
@@ -22,7 +23,7 @@ export class TaskQueueManager {
|
|
|
22
23
|
taskExecutor;
|
|
23
24
|
constructor(config) {
|
|
24
25
|
this.config = {
|
|
25
|
-
curate: { maxConcurrent: config?.curate?.maxConcurrent ??
|
|
26
|
+
curate: { maxConcurrent: config?.curate?.maxConcurrent ?? CURATE_MAX_CONCURRENT },
|
|
26
27
|
// Query tasks are unlimited (Infinity) - lightweight and fast
|
|
27
28
|
query: { maxConcurrent: config?.query?.maxConcurrent ?? Infinity },
|
|
28
29
|
};
|
|
@@ -152,6 +152,7 @@ export class TransportHandlers {
|
|
|
152
152
|
clientId,
|
|
153
153
|
content: data.content,
|
|
154
154
|
createdAt: Date.now(),
|
|
155
|
+
...(data.clientCwd ? { clientCwd: data.clientCwd } : {}),
|
|
155
156
|
...(data.files?.length ? { files: data.files } : {}),
|
|
156
157
|
taskId,
|
|
157
158
|
type: data.type,
|
|
@@ -161,6 +162,7 @@ export class TransportHandlers {
|
|
|
161
162
|
// Broadcast task:created to broadcast-room for TUI monitoring
|
|
162
163
|
this.transport.broadcastTo('broadcast-room', TransportTaskEventNames.CREATED, {
|
|
163
164
|
content: data.content,
|
|
165
|
+
...(data.clientCwd ? { clientCwd: data.clientCwd } : {}),
|
|
164
166
|
...(data.files?.length ? { files: data.files } : {}),
|
|
165
167
|
taskId,
|
|
166
168
|
type: data.type,
|
|
@@ -170,6 +172,7 @@ export class TransportHandlers {
|
|
|
170
172
|
const executeMsg = {
|
|
171
173
|
clientId,
|
|
172
174
|
content: data.content,
|
|
175
|
+
...(data.clientCwd ? { clientCwd: data.clientCwd } : {}),
|
|
173
176
|
...(data.files?.length ? { files: data.files } : {}),
|
|
174
177
|
taskId,
|
|
175
178
|
type: data.type,
|
|
@@ -214,6 +217,7 @@ export class TransportHandlers {
|
|
|
214
217
|
// so it's not available at task:started time. It's saved to DB instead.
|
|
215
218
|
this.transport.broadcastTo('broadcast-room', TransportTaskEventNames.STARTED, {
|
|
216
219
|
content: task.content,
|
|
220
|
+
...(task.clientCwd ? { clientCwd: task.clientCwd } : {}),
|
|
217
221
|
...(task.files?.length ? { files: task.files } : {}),
|
|
218
222
|
taskId,
|
|
219
223
|
type: task.type,
|
|
@@ -35,9 +35,13 @@ function sendToParent(message) {
|
|
|
35
35
|
let transportServer;
|
|
36
36
|
let transportHandlers;
|
|
37
37
|
let instancePollingInterval;
|
|
38
|
+
let parentHeartbeatRunning = false;
|
|
39
|
+
let parentPid;
|
|
38
40
|
const instanceManager = new FileInstanceManager();
|
|
39
41
|
/** Polling interval in milliseconds */
|
|
40
42
|
const INSTANCE_POLLING_INTERVAL_MS = 2000;
|
|
43
|
+
/** Parent heartbeat check interval in milliseconds */
|
|
44
|
+
const PARENT_HEARTBEAT_INTERVAL_MS = 2000;
|
|
41
45
|
/**
|
|
42
46
|
* Setup polling to detect instance.json deletion and recreate it.
|
|
43
47
|
*
|
|
@@ -79,6 +83,69 @@ function stopInstancePolling() {
|
|
|
79
83
|
instancePollingInterval = undefined;
|
|
80
84
|
}
|
|
81
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Setup parent process heartbeat monitoring.
|
|
88
|
+
*
|
|
89
|
+
* Why this is needed:
|
|
90
|
+
* - When main process receives SIGKILL, it dies immediately
|
|
91
|
+
* - SIGKILL cannot be caught, so no cleanup happens
|
|
92
|
+
* - IPC 'disconnect' event may not fire
|
|
93
|
+
* - Child processes become orphans (PPID = 1)
|
|
94
|
+
*
|
|
95
|
+
* This function periodically checks if parent is still alive.
|
|
96
|
+
* If parent dies, child self-terminates to prevent zombie processes.
|
|
97
|
+
*/
|
|
98
|
+
function setupParentHeartbeat() {
|
|
99
|
+
// Already running - don't start another
|
|
100
|
+
if (parentHeartbeatRunning)
|
|
101
|
+
return;
|
|
102
|
+
parentHeartbeatRunning = true;
|
|
103
|
+
parentPid = process.ppid;
|
|
104
|
+
/**
|
|
105
|
+
* Recursive setTimeout pattern - safer than setInterval:
|
|
106
|
+
* - No callback overlap possible
|
|
107
|
+
* - Clean cancellation (just set flag = false)
|
|
108
|
+
* - No orphan timers
|
|
109
|
+
*/
|
|
110
|
+
const checkParent = () => {
|
|
111
|
+
// Stopped - don't schedule next check
|
|
112
|
+
if (!parentHeartbeatRunning || !parentPid)
|
|
113
|
+
return;
|
|
114
|
+
// Check if parent is still alive using signal 0
|
|
115
|
+
// Signal 0 doesn't send any signal, just checks if process exists
|
|
116
|
+
try {
|
|
117
|
+
process.kill(parentPid, 0);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Parent is dead - self-terminate
|
|
121
|
+
transportLog(`Parent process (${parentPid}) died - shutting down to prevent zombie`);
|
|
122
|
+
parentHeartbeatRunning = false;
|
|
123
|
+
stopInstancePolling();
|
|
124
|
+
// Release instance lock and exit
|
|
125
|
+
stopTransport()
|
|
126
|
+
.catch(() => { })
|
|
127
|
+
.finally(() => {
|
|
128
|
+
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
|
|
129
|
+
process.exit(0);
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Schedule next check (only if still running)
|
|
134
|
+
if (parentHeartbeatRunning) {
|
|
135
|
+
setTimeout(checkParent, PARENT_HEARTBEAT_INTERVAL_MS);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
// Start first check after delay
|
|
139
|
+
setTimeout(checkParent, PARENT_HEARTBEAT_INTERVAL_MS);
|
|
140
|
+
transportLog(`Parent heartbeat monitoring started (PPID: ${parentPid})`);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stop the parent heartbeat monitoring.
|
|
144
|
+
* With recursive setTimeout, just set flag to false - next check won't schedule.
|
|
145
|
+
*/
|
|
146
|
+
function stopParentHeartbeat() {
|
|
147
|
+
parentHeartbeatRunning = false;
|
|
148
|
+
}
|
|
82
149
|
async function startTransport() {
|
|
83
150
|
// Create Socket.IO server
|
|
84
151
|
transportServer = createTransportServer();
|
|
@@ -103,7 +170,8 @@ async function startTransport() {
|
|
|
103
170
|
return port;
|
|
104
171
|
}
|
|
105
172
|
async function stopTransport() {
|
|
106
|
-
// Stop
|
|
173
|
+
// Stop heartbeat and polling first
|
|
174
|
+
stopParentHeartbeat();
|
|
107
175
|
stopInstancePolling();
|
|
108
176
|
// Release instance.json
|
|
109
177
|
const projectRoot = process.cwd();
|
|
@@ -125,11 +193,16 @@ async function runWorker() {
|
|
|
125
193
|
try {
|
|
126
194
|
const port = await startTransport();
|
|
127
195
|
sendToParent({ port, type: 'ready' });
|
|
196
|
+
// Start parent heartbeat monitoring after ready
|
|
197
|
+
// This ensures we self-terminate if parent dies (SIGKILL scenario)
|
|
198
|
+
setupParentHeartbeat();
|
|
128
199
|
}
|
|
129
200
|
catch (error) {
|
|
130
201
|
const message = error instanceof Error ? error.message : String(error);
|
|
131
202
|
transportLog(`Failed to start: ${message}`);
|
|
132
203
|
sendToParent({ error: message, type: 'error' });
|
|
204
|
+
// Cleanup before exit to release any acquired resources
|
|
205
|
+
await stopTransport().catch(() => { });
|
|
133
206
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
|
|
134
207
|
process.exit(1);
|
|
135
208
|
}
|
|
@@ -154,6 +227,19 @@ async function runWorker() {
|
|
|
154
227
|
process.once('SIGTERM', cleanup);
|
|
155
228
|
process.once('SIGINT', cleanup);
|
|
156
229
|
process.once('disconnect', cleanup);
|
|
230
|
+
// Global exception handlers - ensure cleanup on unexpected errors
|
|
231
|
+
process.on('uncaughtException', async (error) => {
|
|
232
|
+
transportLog(`Uncaught exception: ${error}`);
|
|
233
|
+
await stopTransport().catch(() => { });
|
|
234
|
+
// eslint-disable-next-line n/no-process-exit
|
|
235
|
+
process.exit(1);
|
|
236
|
+
});
|
|
237
|
+
process.on('unhandledRejection', async (reason) => {
|
|
238
|
+
transportLog(`Unhandled rejection: ${reason}`);
|
|
239
|
+
await stopTransport().catch(() => { });
|
|
240
|
+
// eslint-disable-next-line n/no-process-exit
|
|
241
|
+
process.exit(1);
|
|
242
|
+
});
|
|
157
243
|
}
|
|
158
244
|
// ============================================================================
|
|
159
245
|
// Run
|
|
@@ -163,6 +249,8 @@ try {
|
|
|
163
249
|
}
|
|
164
250
|
catch (error) {
|
|
165
251
|
transportLog(`Fatal error: ${error}`);
|
|
252
|
+
// Cleanup before exit to release any acquired resources
|
|
253
|
+
await stopTransport().catch(() => { });
|
|
166
254
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
|
|
167
255
|
process.exit(1);
|
|
168
256
|
}
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { isDevelopment } from '../../../config/environment.js';
|
|
3
3
|
import { CommandKind } from '../../../tui/types.js';
|
|
4
4
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
6
6
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
7
7
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
8
8
|
import { CurateUseCase } from '../../usecase/curate-use-case.js';
|
|
@@ -38,7 +38,7 @@ export const curateCommand = {
|
|
|
38
38
|
contextText = args || undefined;
|
|
39
39
|
}
|
|
40
40
|
const terminal = new ReplTerminal({ onMessage, onPrompt });
|
|
41
|
-
const tokenStore =
|
|
41
|
+
const tokenStore = createTokenStore();
|
|
42
42
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
43
43
|
const useCase = new CurateUseCase({
|
|
44
44
|
terminal,
|
|
@@ -3,7 +3,7 @@ import { FsFileService } from '../../file/fs-file-service.js';
|
|
|
3
3
|
import { LegacyRuleDetector } from '../../rule/legacy-rule-detector.js';
|
|
4
4
|
import { RuleTemplateService } from '../../rule/rule-template-service.js';
|
|
5
5
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
6
|
-
import {
|
|
6
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
7
7
|
import { FsTemplateLoader } from '../../template/fs-template-loader.js';
|
|
8
8
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
9
9
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
@@ -23,7 +23,7 @@ export const genRulesCommand = {
|
|
|
23
23
|
const templateLoader = new FsTemplateLoader(fileService);
|
|
24
24
|
const templateService = new RuleTemplateService(templateLoader);
|
|
25
25
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
26
|
-
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore:
|
|
26
|
+
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore: createTokenStore() });
|
|
27
27
|
// Create and run UseCase
|
|
28
28
|
const useCase = new GenerateRulesUseCase(fileService, new LegacyRuleDetector(), templateService, terminal, trackingService);
|
|
29
29
|
await useCase.run();
|
|
@@ -10,7 +10,7 @@ import { LegacyRuleDetector } from '../../rule/legacy-rule-detector.js';
|
|
|
10
10
|
import { RuleTemplateService } from '../../rule/rule-template-service.js';
|
|
11
11
|
import { HttpSpaceService } from '../../space/http-space-service.js';
|
|
12
12
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
13
|
-
import {
|
|
13
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
14
14
|
import { HttpTeamService } from '../../team/http-team-service.js';
|
|
15
15
|
import { FsTemplateLoader } from '../../template/fs-template-loader.js';
|
|
16
16
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
@@ -37,7 +37,7 @@ export const initCommand = {
|
|
|
37
37
|
const terminal = new ReplTerminal({ onMessage, onPrompt });
|
|
38
38
|
// Create services
|
|
39
39
|
const envConfig = getCurrentConfig();
|
|
40
|
-
const tokenStore =
|
|
40
|
+
const tokenStore = createTokenStore();
|
|
41
41
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
42
42
|
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore });
|
|
43
43
|
const fileService = new FsFileService();
|
|
@@ -6,7 +6,7 @@ import { OidcDiscoveryService } from '../../auth/oidc-discovery-service.js';
|
|
|
6
6
|
import { SystemBrowserLauncher } from '../../browser/system-browser-launcher.js';
|
|
7
7
|
import { CallbackHandler } from '../../http/callback-handler.js';
|
|
8
8
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
10
10
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
11
11
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
12
12
|
import { LoginUseCase } from '../../usecase/login-use-case.js';
|
|
@@ -23,7 +23,7 @@ export const loginCommand = {
|
|
|
23
23
|
const terminal = new ReplTerminal({ onMessage, onPrompt });
|
|
24
24
|
// Create services
|
|
25
25
|
const config = getCurrentConfig();
|
|
26
|
-
const tokenStore =
|
|
26
|
+
const tokenStore = createTokenStore();
|
|
27
27
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
28
28
|
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore });
|
|
29
29
|
const discoveryService = new OidcDiscoveryService();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CommandKind } from '../../../tui/types.js';
|
|
2
2
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
3
3
|
import { FileOnboardingPreferenceStore } from '../../storage/file-onboarding-preference-store.js';
|
|
4
|
-
import {
|
|
4
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
5
5
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
6
6
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
7
7
|
import { LogoutUseCase } from '../../usecase/logout-use-case.js';
|
|
@@ -26,7 +26,7 @@ export const logoutCommand = {
|
|
|
26
26
|
flags: logoutFlags,
|
|
27
27
|
strict: false,
|
|
28
28
|
});
|
|
29
|
-
const tokenStore =
|
|
29
|
+
const tokenStore = createTokenStore();
|
|
30
30
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
31
31
|
const useCase = new LogoutUseCase({
|
|
32
32
|
onboardingPreferenceStore: new FileOnboardingPreferenceStore(),
|
|
@@ -6,7 +6,7 @@ import { ProjectConfigStore } from '../../config/file-config-store.js';
|
|
|
6
6
|
import { FileContextTreeSnapshotService } from '../../context-tree/file-context-tree-snapshot-service.js';
|
|
7
7
|
import { FileContextTreeWriterService } from '../../context-tree/file-context-tree-writer-service.js';
|
|
8
8
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
10
10
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
11
11
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
12
12
|
import { PullUseCase } from '../../usecase/pull-use-case.js';
|
|
@@ -28,7 +28,7 @@ export const pullCommand = {
|
|
|
28
28
|
const terminal = new ReplTerminal({ onMessage, onPrompt });
|
|
29
29
|
const parsed = await parseReplArgs(args, { flags: pullFlags, strict: false });
|
|
30
30
|
const envConfig = getCurrentConfig();
|
|
31
|
-
const tokenStore =
|
|
31
|
+
const tokenStore = createTokenStore();
|
|
32
32
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
33
33
|
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore });
|
|
34
34
|
const contextTreeSnapshotService = new FileContextTreeSnapshotService();
|
|
@@ -6,7 +6,7 @@ import { ProjectConfigStore } from '../../config/file-config-store.js';
|
|
|
6
6
|
import { FileContextFileReader } from '../../context-tree/file-context-file-reader.js';
|
|
7
7
|
import { FileContextTreeSnapshotService } from '../../context-tree/file-context-tree-snapshot-service.js';
|
|
8
8
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
10
10
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
11
11
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
12
12
|
import { PushUseCase } from '../../usecase/push-use-case.js';
|
|
@@ -34,7 +34,7 @@ export const pushCommand = {
|
|
|
34
34
|
const terminal = new ReplTerminal({ onMessage, onPrompt });
|
|
35
35
|
const parsed = await parseReplArgs(args, { flags: pushFlags, strict: false });
|
|
36
36
|
const envConfig = getCurrentConfig();
|
|
37
|
-
const tokenStore =
|
|
37
|
+
const tokenStore = createTokenStore();
|
|
38
38
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
39
39
|
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore });
|
|
40
40
|
const useCase = new PushUseCase({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDevelopment } from '../../../config/environment.js';
|
|
2
2
|
import { CommandKind } from '../../../tui/types.js';
|
|
3
3
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
4
|
-
import {
|
|
4
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
5
5
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
6
6
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
7
7
|
import { QueryUseCase } from '../../usecase/query-use-case.js';
|
|
@@ -31,7 +31,7 @@ export const queryCommand = {
|
|
|
31
31
|
query = args;
|
|
32
32
|
}
|
|
33
33
|
const terminal = new ReplTerminal({ onMessage, onPrompt });
|
|
34
|
-
const tokenStore =
|
|
34
|
+
const tokenStore = createTokenStore();
|
|
35
35
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
36
36
|
const useCase = new QueryUseCase({
|
|
37
37
|
terminal,
|
|
@@ -2,7 +2,7 @@ import { getCurrentConfig } from '../../../../config/environment.js';
|
|
|
2
2
|
import { CommandKind } from '../../../../tui/types.js';
|
|
3
3
|
import { ProjectConfigStore } from '../../../config/file-config-store.js';
|
|
4
4
|
import { HttpSpaceService } from '../../../space/http-space-service.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createTokenStore } from '../../../storage/token-store.js';
|
|
6
6
|
import { ReplTerminal } from '../../../terminal/repl-terminal.js';
|
|
7
7
|
import { SpaceListUseCase } from '../../../usecase/space-list-use-case.js';
|
|
8
8
|
import { Flags, parseReplArgs, toCommandFlags } from '../arg-parser.js';
|
|
@@ -54,7 +54,7 @@ export const listCommand = {
|
|
|
54
54
|
projectConfigStore: new ProjectConfigStore(),
|
|
55
55
|
spaceService: new HttpSpaceService({ apiBaseUrl: envConfig.apiBaseUrl }),
|
|
56
56
|
terminal,
|
|
57
|
-
tokenStore:
|
|
57
|
+
tokenStore: createTokenStore(),
|
|
58
58
|
});
|
|
59
59
|
await useCase.run();
|
|
60
60
|
},
|
|
@@ -2,7 +2,7 @@ import { getCurrentConfig } from '../../../../config/environment.js';
|
|
|
2
2
|
import { CommandKind } from '../../../../tui/types.js';
|
|
3
3
|
import { ProjectConfigStore } from '../../../config/file-config-store.js';
|
|
4
4
|
import { HttpSpaceService } from '../../../space/http-space-service.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createTokenStore } from '../../../storage/token-store.js';
|
|
6
6
|
import { HttpTeamService } from '../../../team/http-team-service.js';
|
|
7
7
|
import { ReplTerminal } from '../../../terminal/repl-terminal.js';
|
|
8
8
|
import { SpaceSwitchUseCase } from '../../../usecase/space-switch-use-case.js';
|
|
@@ -21,7 +21,7 @@ export const switchCommand = {
|
|
|
21
21
|
spaceService: new HttpSpaceService({ apiBaseUrl: envConfig.apiBaseUrl }),
|
|
22
22
|
teamService: new HttpTeamService({ apiBaseUrl: envConfig.apiBaseUrl }),
|
|
23
23
|
terminal,
|
|
24
|
-
tokenStore:
|
|
24
|
+
tokenStore: createTokenStore(),
|
|
25
25
|
workspaceDetector: new WorkspaceDetectorService(),
|
|
26
26
|
});
|
|
27
27
|
await useCase.run();
|
|
@@ -3,7 +3,7 @@ import { ProjectConfigStore } from '../../config/file-config-store.js';
|
|
|
3
3
|
import { FileContextTreeService } from '../../context-tree/file-context-tree-service.js';
|
|
4
4
|
import { FileContextTreeSnapshotService } from '../../context-tree/file-context-tree-snapshot-service.js';
|
|
5
5
|
import { FileGlobalConfigStore } from "../../storage/file-global-config-store.js";
|
|
6
|
-
import {
|
|
6
|
+
import { createTokenStore } from '../../storage/token-store.js';
|
|
7
7
|
import { ReplTerminal } from '../../terminal/repl-terminal.js';
|
|
8
8
|
import { MixpanelTrackingService } from '../../tracking/mixpanel-tracking-service.js';
|
|
9
9
|
import { StatusUseCase } from '../../usecase/status-use-case.js';
|
|
@@ -15,7 +15,7 @@ export const statusCommand = {
|
|
|
15
15
|
return {
|
|
16
16
|
async execute(onMessage, onPrompt) {
|
|
17
17
|
const terminal = new ReplTerminal({ onMessage, onPrompt });
|
|
18
|
-
const tokenStore =
|
|
18
|
+
const tokenStore = createTokenStore();
|
|
19
19
|
const globalConfigStore = new FileGlobalConfigStore();
|
|
20
20
|
const trackingService = new MixpanelTrackingService({ globalConfigStore, tokenStore });
|
|
21
21
|
const useCase = new StatusUseCase({
|
|
@@ -2,7 +2,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { render } from 'ink';
|
|
3
3
|
import { App } from '../../tui/app.js';
|
|
4
4
|
import { AppProviders } from '../../tui/providers/app-providers.js';
|
|
5
|
-
import { stopQueuePollingService } from '../cipher/consumer/queue-polling-service.js';
|
|
6
5
|
import { connectTransportClient, disconnectTransportClient } from './transport-client-helper.js';
|
|
7
6
|
/** Broadcast client - joins broadcast-room to monitor all events */
|
|
8
7
|
let transportBroadcastClient = null;
|
|
@@ -30,6 +29,5 @@ export async function startRepl(options) {
|
|
|
30
29
|
// Cleanup
|
|
31
30
|
await disconnectTransportClient(transportBroadcastClient);
|
|
32
31
|
transportBroadcastClient = null;
|
|
33
|
-
stopQueuePollingService();
|
|
34
32
|
await trackingService.track('repl', { status: 'finished' });
|
|
35
33
|
}
|