ccmanager 3.5.0 → 3.5.2
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/dist/cli.js +3 -2
- package/dist/components/App.d.ts +1 -0
- package/dist/components/App.js +17 -39
- package/dist/components/App.test.js +12 -44
- package/dist/components/Configuration.js +10 -15
- package/dist/components/ConfigureCommand.js +18 -106
- package/dist/components/ConfigureCustomCommand.js +2 -17
- package/dist/components/ConfigureOther.js +5 -23
- package/dist/components/ConfigureOther.test.js +7 -31
- package/dist/components/ConfigureShortcuts.js +4 -31
- package/dist/components/ConfigureStatusHooks.js +5 -44
- package/dist/components/ConfigureStatusHooks.test.js +7 -31
- package/dist/components/ConfigureTimeout.js +2 -14
- package/dist/components/ConfigureWorktree.js +4 -47
- package/dist/components/ConfigureWorktreeHooks.js +5 -37
- package/dist/components/ConfigureWorktreeHooks.test.js +8 -33
- package/dist/components/Confirmation.js +9 -21
- package/dist/components/CustomCommandSummary.js +2 -5
- package/dist/components/DeleteConfirmation.js +10 -47
- package/dist/components/DeleteWorktree.js +14 -42
- package/dist/components/DeleteWorktree.test.js +6 -6
- package/dist/components/LoadingSpinner.js +3 -6
- package/dist/components/LoadingSpinner.test.js +22 -22
- package/dist/components/Menu.d.ts +1 -0
- package/dist/components/Menu.js +10 -42
- package/dist/components/Menu.recent-projects.test.js +8 -8
- package/dist/components/Menu.test.js +10 -10
- package/dist/components/MergeWorktree.js +16 -88
- package/dist/components/MergeWorktree.test.js +5 -5
- package/dist/components/NewWorktree.js +25 -105
- package/dist/components/NewWorktree.test.js +8 -8
- package/dist/components/PresetSelector.js +3 -9
- package/dist/components/ProjectList.js +9 -38
- package/dist/components/ProjectList.recent-projects.test.js +7 -7
- package/dist/components/ProjectList.test.js +37 -37
- package/dist/components/RemoteBranchSelector.js +2 -21
- package/dist/components/RemoteBranchSelector.test.js +8 -8
- package/dist/components/TextInputWrapper.d.ts +5 -0
- package/dist/components/TextInputWrapper.js +138 -11
- package/dist/constants/statusIcons.d.ts +2 -1
- package/dist/constants/statusIcons.js +13 -4
- package/dist/constants/statusIcons.test.js +41 -11
- package/dist/contexts/ConfigEditorContext.d.ts +1 -1
- package/dist/contexts/ConfigEditorContext.js +3 -2
- package/dist/services/autoApprovalVerifier.js +1 -8
- package/dist/services/bunTerminal.js +41 -136
- package/dist/services/config/configEditor.js +2 -12
- package/dist/services/config/globalConfigManager.js +4 -24
- package/dist/services/config/projectConfigManager.js +3 -18
- package/dist/services/globalSessionOrchestrator.js +3 -12
- package/dist/services/globalSessionOrchestrator.test.js +1 -8
- package/dist/services/projectManager.js +13 -68
- package/dist/services/sessionManager.d.ts +1 -1
- package/dist/services/sessionManager.effect.test.js +9 -37
- package/dist/services/sessionManager.js +12 -28
- package/dist/services/sessionManager.test.js +48 -40
- package/dist/services/shortcutManager.js +7 -13
- package/dist/services/stateDetector/base.d.ts +1 -1
- package/dist/services/stateDetector/claude.d.ts +1 -1
- package/dist/services/stateDetector/claude.js +11 -4
- package/dist/services/stateDetector/claude.test.js +47 -24
- package/dist/services/stateDetector/cline.d.ts +1 -1
- package/dist/services/stateDetector/cline.js +1 -1
- package/dist/services/stateDetector/codex.d.ts +1 -1
- package/dist/services/stateDetector/codex.js +1 -1
- package/dist/services/stateDetector/cursor.d.ts +1 -1
- package/dist/services/stateDetector/cursor.js +1 -1
- package/dist/services/stateDetector/gemini.d.ts +1 -1
- package/dist/services/stateDetector/gemini.js +1 -1
- package/dist/services/stateDetector/github-copilot.d.ts +1 -1
- package/dist/services/stateDetector/github-copilot.js +1 -1
- package/dist/services/stateDetector/opencode.d.ts +1 -1
- package/dist/services/stateDetector/opencode.js +1 -1
- package/dist/services/stateDetector/types.d.ts +1 -1
- package/dist/services/worktreeConfigManager.js +3 -8
- package/dist/services/worktreeService.js +2 -12
- package/dist/types/index.js +4 -12
- package/dist/utils/logger.js +12 -33
- package/dist/utils/mutex.d.ts +1 -1
- package/dist/utils/mutex.js +4 -19
- package/dist/utils/worktreeUtils.js +4 -4
- package/package.json +12 -12
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { getStatusDisplay, STATUS_ICONS, STATUS_LABELS, STATUS_TAGS, } from './statusIcons.js';
|
|
2
|
+
import { getStatusDisplay, getBackgroundTaskTag, STATUS_ICONS, STATUS_LABELS, STATUS_TAGS, } from './statusIcons.js';
|
|
3
3
|
describe('getStatusDisplay', () => {
|
|
4
4
|
it('should return busy display for busy state', () => {
|
|
5
5
|
const result = getStatusDisplay('busy');
|
|
@@ -18,25 +18,55 @@ describe('getStatusDisplay', () => {
|
|
|
18
18
|
expect(result).toBe(`${STATUS_ICONS.IDLE} ${STATUS_LABELS.IDLE}`);
|
|
19
19
|
});
|
|
20
20
|
describe('background task indicator', () => {
|
|
21
|
-
it('should append [BG] badge when idle and
|
|
22
|
-
const result = getStatusDisplay('idle',
|
|
21
|
+
it('should append [BG] badge when idle and backgroundTaskCount is 1', () => {
|
|
22
|
+
const result = getStatusDisplay('idle', 1);
|
|
23
23
|
expect(result).toBe(`${STATUS_ICONS.IDLE} ${STATUS_LABELS.IDLE} ${STATUS_TAGS.BACKGROUND_TASK}`);
|
|
24
24
|
});
|
|
25
|
-
it('should not append [BG] badge when idle and
|
|
26
|
-
const result = getStatusDisplay('idle',
|
|
25
|
+
it('should not append [BG] badge when idle and backgroundTaskCount is 0', () => {
|
|
26
|
+
const result = getStatusDisplay('idle', 0);
|
|
27
27
|
expect(result).toBe(`${STATUS_ICONS.IDLE} ${STATUS_LABELS.IDLE}`);
|
|
28
28
|
});
|
|
29
|
-
it('should append [BG] badge when busy and
|
|
30
|
-
const result = getStatusDisplay('busy',
|
|
29
|
+
it('should append [BG] badge when busy and backgroundTaskCount is 1', () => {
|
|
30
|
+
const result = getStatusDisplay('busy', 1);
|
|
31
31
|
expect(result).toBe(`${STATUS_ICONS.BUSY} ${STATUS_LABELS.BUSY} ${STATUS_TAGS.BACKGROUND_TASK}`);
|
|
32
32
|
});
|
|
33
|
-
it('should append [BG] badge when waiting_input and
|
|
34
|
-
const result = getStatusDisplay('waiting_input',
|
|
33
|
+
it('should append [BG] badge when waiting_input and backgroundTaskCount is 1', () => {
|
|
34
|
+
const result = getStatusDisplay('waiting_input', 1);
|
|
35
35
|
expect(result).toBe(`${STATUS_ICONS.WAITING} ${STATUS_LABELS.WAITING} ${STATUS_TAGS.BACKGROUND_TASK}`);
|
|
36
36
|
});
|
|
37
|
-
it('should append [BG] badge when pending_auto_approval and
|
|
38
|
-
const result = getStatusDisplay('pending_auto_approval',
|
|
37
|
+
it('should append [BG] badge when pending_auto_approval and backgroundTaskCount is 1', () => {
|
|
38
|
+
const result = getStatusDisplay('pending_auto_approval', 1);
|
|
39
39
|
expect(result).toBe(`${STATUS_ICONS.WAITING} ${STATUS_LABELS.PENDING_AUTO_APPROVAL} ${STATUS_TAGS.BACKGROUND_TASK}`);
|
|
40
40
|
});
|
|
41
|
+
it('should append [BG:2] badge when backgroundTaskCount is 2', () => {
|
|
42
|
+
const result = getStatusDisplay('idle', 2);
|
|
43
|
+
expect(result).toBe(`${STATUS_ICONS.IDLE} ${STATUS_LABELS.IDLE} \x1b[2m[BG:2]\x1b[0m`);
|
|
44
|
+
});
|
|
45
|
+
it('should append [BG:5] badge when backgroundTaskCount is 5', () => {
|
|
46
|
+
const result = getStatusDisplay('busy', 5);
|
|
47
|
+
expect(result).toBe(`${STATUS_ICONS.BUSY} ${STATUS_LABELS.BUSY} \x1b[2m[BG:5]\x1b[0m`);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('getBackgroundTaskTag', () => {
|
|
52
|
+
it('should return empty string when count is 0', () => {
|
|
53
|
+
const result = getBackgroundTaskTag(0);
|
|
54
|
+
expect(result).toBe('');
|
|
55
|
+
});
|
|
56
|
+
it('should return empty string when count is negative', () => {
|
|
57
|
+
expect(getBackgroundTaskTag(-1)).toBe('');
|
|
58
|
+
expect(getBackgroundTaskTag(-100)).toBe('');
|
|
59
|
+
});
|
|
60
|
+
it('should return [BG] when count is 1', () => {
|
|
61
|
+
const result = getBackgroundTaskTag(1);
|
|
62
|
+
expect(result).toBe(STATUS_TAGS.BACKGROUND_TASK);
|
|
63
|
+
});
|
|
64
|
+
it('should return [BG:2] when count is 2', () => {
|
|
65
|
+
const result = getBackgroundTaskTag(2);
|
|
66
|
+
expect(result).toBe('\x1b[2m[BG:2]\x1b[0m');
|
|
67
|
+
});
|
|
68
|
+
it('should return [BG:10] when count is 10', () => {
|
|
69
|
+
const result = getBackgroundTaskTag(10);
|
|
70
|
+
expect(result).toBe('\x1b[2m[BG:10]\x1b[0m');
|
|
41
71
|
});
|
|
42
72
|
});
|
|
@@ -12,7 +12,7 @@ interface ConfigEditorProviderProps {
|
|
|
12
12
|
* Uses singleton config editors to ensure config changes are
|
|
13
13
|
* immediately visible to all components.
|
|
14
14
|
*/
|
|
15
|
-
export declare function ConfigEditorProvider({ scope, children, }: ConfigEditorProviderProps):
|
|
15
|
+
export declare function ConfigEditorProvider({ scope, children, }: ConfigEditorProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
16
16
|
/**
|
|
17
17
|
* Hook to access ConfigEditor from context.
|
|
18
18
|
* Must be used within a ConfigEditorProvider.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
2
3
|
import { ConfigEditor } from '../services/config/configEditor.js';
|
|
3
4
|
const ConfigEditorContext = createContext(null);
|
|
4
5
|
/**
|
|
@@ -9,7 +10,7 @@ const ConfigEditorContext = createContext(null);
|
|
|
9
10
|
*/
|
|
10
11
|
export function ConfigEditorProvider({ scope, children, }) {
|
|
11
12
|
const configEditor = useMemo(() => new ConfigEditor(scope), [scope]);
|
|
12
|
-
return (
|
|
13
|
+
return (_jsx(ConfigEditorContext.Provider, { value: configEditor, children: children }));
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Hook to access ConfigEditor from context.
|
|
@@ -44,14 +44,7 @@ const buildPrompt = (terminalOutput) => PROMPT_TEMPLATE.replace(PLACEHOLDER.term
|
|
|
44
44
|
* user permission is required before proceeding
|
|
45
45
|
*/
|
|
46
46
|
export class AutoApprovalVerifier {
|
|
47
|
-
|
|
48
|
-
Object.defineProperty(this, "model", {
|
|
49
|
-
enumerable: true,
|
|
50
|
-
configurable: true,
|
|
51
|
-
writable: true,
|
|
52
|
-
value: 'haiku'
|
|
53
|
-
});
|
|
54
|
-
}
|
|
47
|
+
model = 'haiku';
|
|
55
48
|
createExecOptions(signal) {
|
|
56
49
|
return {
|
|
57
50
|
encoding: 'utf8',
|
|
@@ -9,118 +9,26 @@
|
|
|
9
9
|
* BunTerminal class that wraps Bun's built-in Terminal API.
|
|
10
10
|
*/
|
|
11
11
|
class BunTerminal {
|
|
12
|
+
_pid = -1;
|
|
13
|
+
_cols;
|
|
14
|
+
_rows;
|
|
15
|
+
_process;
|
|
16
|
+
_closed = false;
|
|
17
|
+
_dataListeners = [];
|
|
18
|
+
_exitListeners = [];
|
|
19
|
+
_subprocess = null;
|
|
20
|
+
_terminal = null;
|
|
21
|
+
_decoder = new globalThis.TextDecoder('utf-8');
|
|
22
|
+
// Buffering to combine fragmented data chunks from the same event loop
|
|
23
|
+
_dataBuffer = '';
|
|
24
|
+
_flushTimer = null;
|
|
25
|
+
_syncOutputMode = false;
|
|
26
|
+
// Synchronized output escape sequences (used by Ink and other TUI frameworks)
|
|
27
|
+
static SYNC_OUTPUT_START = '\x1b[?2026h';
|
|
28
|
+
static SYNC_OUTPUT_END = '\x1b[?2026l';
|
|
29
|
+
static FLUSH_DELAY_MS = 8; // ~2 frames at 60fps for batching
|
|
30
|
+
static SYNC_TIMEOUT_MS = 100; // Timeout for sync mode
|
|
12
31
|
constructor(file, args, options) {
|
|
13
|
-
Object.defineProperty(this, "_pid", {
|
|
14
|
-
enumerable: true,
|
|
15
|
-
configurable: true,
|
|
16
|
-
writable: true,
|
|
17
|
-
value: -1
|
|
18
|
-
});
|
|
19
|
-
Object.defineProperty(this, "_cols", {
|
|
20
|
-
enumerable: true,
|
|
21
|
-
configurable: true,
|
|
22
|
-
writable: true,
|
|
23
|
-
value: void 0
|
|
24
|
-
});
|
|
25
|
-
Object.defineProperty(this, "_rows", {
|
|
26
|
-
enumerable: true,
|
|
27
|
-
configurable: true,
|
|
28
|
-
writable: true,
|
|
29
|
-
value: void 0
|
|
30
|
-
});
|
|
31
|
-
Object.defineProperty(this, "_process", {
|
|
32
|
-
enumerable: true,
|
|
33
|
-
configurable: true,
|
|
34
|
-
writable: true,
|
|
35
|
-
value: void 0
|
|
36
|
-
});
|
|
37
|
-
Object.defineProperty(this, "_closed", {
|
|
38
|
-
enumerable: true,
|
|
39
|
-
configurable: true,
|
|
40
|
-
writable: true,
|
|
41
|
-
value: false
|
|
42
|
-
});
|
|
43
|
-
Object.defineProperty(this, "_dataListeners", {
|
|
44
|
-
enumerable: true,
|
|
45
|
-
configurable: true,
|
|
46
|
-
writable: true,
|
|
47
|
-
value: []
|
|
48
|
-
});
|
|
49
|
-
Object.defineProperty(this, "_exitListeners", {
|
|
50
|
-
enumerable: true,
|
|
51
|
-
configurable: true,
|
|
52
|
-
writable: true,
|
|
53
|
-
value: []
|
|
54
|
-
});
|
|
55
|
-
Object.defineProperty(this, "_subprocess", {
|
|
56
|
-
enumerable: true,
|
|
57
|
-
configurable: true,
|
|
58
|
-
writable: true,
|
|
59
|
-
value: null
|
|
60
|
-
});
|
|
61
|
-
Object.defineProperty(this, "_terminal", {
|
|
62
|
-
enumerable: true,
|
|
63
|
-
configurable: true,
|
|
64
|
-
writable: true,
|
|
65
|
-
value: null
|
|
66
|
-
});
|
|
67
|
-
Object.defineProperty(this, "_decoder", {
|
|
68
|
-
enumerable: true,
|
|
69
|
-
configurable: true,
|
|
70
|
-
writable: true,
|
|
71
|
-
value: new globalThis.TextDecoder('utf-8')
|
|
72
|
-
});
|
|
73
|
-
// Buffering to combine fragmented data chunks from the same event loop
|
|
74
|
-
Object.defineProperty(this, "_dataBuffer", {
|
|
75
|
-
enumerable: true,
|
|
76
|
-
configurable: true,
|
|
77
|
-
writable: true,
|
|
78
|
-
value: ''
|
|
79
|
-
});
|
|
80
|
-
Object.defineProperty(this, "_flushTimer", {
|
|
81
|
-
enumerable: true,
|
|
82
|
-
configurable: true,
|
|
83
|
-
writable: true,
|
|
84
|
-
value: null
|
|
85
|
-
});
|
|
86
|
-
Object.defineProperty(this, "_syncOutputMode", {
|
|
87
|
-
enumerable: true,
|
|
88
|
-
configurable: true,
|
|
89
|
-
writable: true,
|
|
90
|
-
value: false
|
|
91
|
-
});
|
|
92
|
-
Object.defineProperty(this, "onData", {
|
|
93
|
-
enumerable: true,
|
|
94
|
-
configurable: true,
|
|
95
|
-
writable: true,
|
|
96
|
-
value: (listener) => {
|
|
97
|
-
this._dataListeners.push(listener);
|
|
98
|
-
return {
|
|
99
|
-
dispose: () => {
|
|
100
|
-
const index = this._dataListeners.indexOf(listener);
|
|
101
|
-
if (index !== -1) {
|
|
102
|
-
this._dataListeners.splice(index, 1);
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
Object.defineProperty(this, "onExit", {
|
|
109
|
-
enumerable: true,
|
|
110
|
-
configurable: true,
|
|
111
|
-
writable: true,
|
|
112
|
-
value: (listener) => {
|
|
113
|
-
this._exitListeners.push(listener);
|
|
114
|
-
return {
|
|
115
|
-
dispose: () => {
|
|
116
|
-
const index = this._exitListeners.indexOf(listener);
|
|
117
|
-
if (index !== -1) {
|
|
118
|
-
this._exitListeners.splice(index, 1);
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
32
|
this._cols = options.cols ?? 80;
|
|
125
33
|
this._rows = options.rows ?? 24;
|
|
126
34
|
this._process = file;
|
|
@@ -278,6 +186,28 @@ class BunTerminal {
|
|
|
278
186
|
get process() {
|
|
279
187
|
return this._process;
|
|
280
188
|
}
|
|
189
|
+
onData = (listener) => {
|
|
190
|
+
this._dataListeners.push(listener);
|
|
191
|
+
return {
|
|
192
|
+
dispose: () => {
|
|
193
|
+
const index = this._dataListeners.indexOf(listener);
|
|
194
|
+
if (index !== -1) {
|
|
195
|
+
this._dataListeners.splice(index, 1);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
onExit = (listener) => {
|
|
201
|
+
this._exitListeners.push(listener);
|
|
202
|
+
return {
|
|
203
|
+
dispose: () => {
|
|
204
|
+
const index = this._exitListeners.indexOf(listener);
|
|
205
|
+
if (index !== -1) {
|
|
206
|
+
this._exitListeners.splice(index, 1);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
};
|
|
281
211
|
write(data) {
|
|
282
212
|
if (this._closed || !this._terminal) {
|
|
283
213
|
return;
|
|
@@ -317,31 +247,6 @@ class BunTerminal {
|
|
|
317
247
|
}
|
|
318
248
|
}
|
|
319
249
|
}
|
|
320
|
-
// Synchronized output escape sequences (used by Ink and other TUI frameworks)
|
|
321
|
-
Object.defineProperty(BunTerminal, "SYNC_OUTPUT_START", {
|
|
322
|
-
enumerable: true,
|
|
323
|
-
configurable: true,
|
|
324
|
-
writable: true,
|
|
325
|
-
value: '\x1b[?2026h'
|
|
326
|
-
});
|
|
327
|
-
Object.defineProperty(BunTerminal, "SYNC_OUTPUT_END", {
|
|
328
|
-
enumerable: true,
|
|
329
|
-
configurable: true,
|
|
330
|
-
writable: true,
|
|
331
|
-
value: '\x1b[?2026l'
|
|
332
|
-
});
|
|
333
|
-
Object.defineProperty(BunTerminal, "FLUSH_DELAY_MS", {
|
|
334
|
-
enumerable: true,
|
|
335
|
-
configurable: true,
|
|
336
|
-
writable: true,
|
|
337
|
-
value: 8
|
|
338
|
-
}); // ~2 frames at 60fps for batching
|
|
339
|
-
Object.defineProperty(BunTerminal, "SYNC_TIMEOUT_MS", {
|
|
340
|
-
enumerable: true,
|
|
341
|
-
configurable: true,
|
|
342
|
-
writable: true,
|
|
343
|
-
value: 100
|
|
344
|
-
}); // Timeout for sync mode
|
|
345
250
|
/**
|
|
346
251
|
* Spawn a new PTY process using Bun's built-in Terminal API.
|
|
347
252
|
*
|
|
@@ -12,19 +12,9 @@ import { projectConfigManager } from './projectConfigManager.js';
|
|
|
12
12
|
* immediately visible to all components (e.g., shortcutManager, configReader).
|
|
13
13
|
*/
|
|
14
14
|
export class ConfigEditor {
|
|
15
|
+
scope;
|
|
16
|
+
configEditor;
|
|
15
17
|
constructor(scope) {
|
|
16
|
-
Object.defineProperty(this, "scope", {
|
|
17
|
-
enumerable: true,
|
|
18
|
-
configurable: true,
|
|
19
|
-
writable: true,
|
|
20
|
-
value: void 0
|
|
21
|
-
});
|
|
22
|
-
Object.defineProperty(this, "configEditor", {
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
writable: true,
|
|
26
|
-
value: void 0
|
|
27
|
-
});
|
|
28
18
|
this.scope = scope;
|
|
29
19
|
this.configEditor =
|
|
30
20
|
scope === 'global' ? globalConfigManager : projectConfigManager;
|
|
@@ -8,31 +8,11 @@ import { join } from 'path';
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
9
9
|
import { DEFAULT_SHORTCUTS, } from '../../types/index.js';
|
|
10
10
|
class GlobalConfigManager {
|
|
11
|
+
configPath;
|
|
12
|
+
legacyShortcutsPath;
|
|
13
|
+
configDir;
|
|
14
|
+
config = {};
|
|
11
15
|
constructor() {
|
|
12
|
-
Object.defineProperty(this, "configPath", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
configurable: true,
|
|
15
|
-
writable: true,
|
|
16
|
-
value: void 0
|
|
17
|
-
});
|
|
18
|
-
Object.defineProperty(this, "legacyShortcutsPath", {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
configurable: true,
|
|
21
|
-
writable: true,
|
|
22
|
-
value: void 0
|
|
23
|
-
});
|
|
24
|
-
Object.defineProperty(this, "configDir", {
|
|
25
|
-
enumerable: true,
|
|
26
|
-
configurable: true,
|
|
27
|
-
writable: true,
|
|
28
|
-
value: void 0
|
|
29
|
-
});
|
|
30
|
-
Object.defineProperty(this, "config", {
|
|
31
|
-
enumerable: true,
|
|
32
|
-
configurable: true,
|
|
33
|
-
writable: true,
|
|
34
|
-
value: {}
|
|
35
|
-
});
|
|
36
16
|
// Determine config directory based on platform
|
|
37
17
|
const homeDir = homedir();
|
|
38
18
|
this.configDir =
|
|
@@ -14,25 +14,10 @@ const PROJECT_CONFIG_FILENAME = '.ccmanager.json';
|
|
|
14
14
|
* Implements IConfigEditor for consistent API with GlobalConfigManager.
|
|
15
15
|
*/
|
|
16
16
|
class ProjectConfigManager {
|
|
17
|
+
gitRoot;
|
|
18
|
+
configPath;
|
|
19
|
+
projectConfig = null;
|
|
17
20
|
constructor(cwd) {
|
|
18
|
-
Object.defineProperty(this, "gitRoot", {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
configurable: true,
|
|
21
|
-
writable: true,
|
|
22
|
-
value: void 0
|
|
23
|
-
});
|
|
24
|
-
Object.defineProperty(this, "configPath", {
|
|
25
|
-
enumerable: true,
|
|
26
|
-
configurable: true,
|
|
27
|
-
writable: true,
|
|
28
|
-
value: void 0
|
|
29
|
-
});
|
|
30
|
-
Object.defineProperty(this, "projectConfig", {
|
|
31
|
-
enumerable: true,
|
|
32
|
-
configurable: true,
|
|
33
|
-
writable: true,
|
|
34
|
-
value: null
|
|
35
|
-
});
|
|
36
21
|
// Use git repository root
|
|
37
22
|
this.gitRoot = getGitRepositoryRoot(cwd);
|
|
38
23
|
this.configPath = this.gitRoot
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { SessionManager } from './sessionManager.js';
|
|
2
2
|
class GlobalSessionOrchestrator {
|
|
3
|
+
static instance;
|
|
4
|
+
projectManagers = new Map();
|
|
5
|
+
globalManager;
|
|
3
6
|
constructor() {
|
|
4
|
-
Object.defineProperty(this, "projectManagers", {
|
|
5
|
-
enumerable: true,
|
|
6
|
-
configurable: true,
|
|
7
|
-
writable: true,
|
|
8
|
-
value: new Map()
|
|
9
|
-
});
|
|
10
|
-
Object.defineProperty(this, "globalManager", {
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
writable: true,
|
|
14
|
-
value: void 0
|
|
15
|
-
});
|
|
16
7
|
// Create a global session manager for single-project mode
|
|
17
8
|
this.globalManager = new SessionManager();
|
|
18
9
|
}
|
|
@@ -3,14 +3,7 @@ import { globalSessionOrchestrator } from './globalSessionOrchestrator.js';
|
|
|
3
3
|
// Mock SessionManager
|
|
4
4
|
vi.mock('./sessionManager.js', () => {
|
|
5
5
|
class MockSessionManager {
|
|
6
|
-
|
|
7
|
-
Object.defineProperty(this, "sessions", {
|
|
8
|
-
enumerable: true,
|
|
9
|
-
configurable: true,
|
|
10
|
-
writable: true,
|
|
11
|
-
value: new Map()
|
|
12
|
-
});
|
|
13
|
-
}
|
|
6
|
+
sessions = new Map();
|
|
14
7
|
getAllSessions() {
|
|
15
8
|
return Array.from(this.sessions.values());
|
|
16
9
|
}
|
|
@@ -7,68 +7,20 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
|
7
7
|
import { Effect } from 'effect';
|
|
8
8
|
import { FileSystemError, ConfigError } from '../types/errors.js';
|
|
9
9
|
export class ProjectManager {
|
|
10
|
+
currentMode;
|
|
11
|
+
currentProject;
|
|
12
|
+
projects = [];
|
|
13
|
+
worktreeServiceCache = new Map();
|
|
14
|
+
projectsDir;
|
|
15
|
+
// Multi-project discovery
|
|
16
|
+
projectCache = new Map();
|
|
17
|
+
discoveryWorkers = 4;
|
|
18
|
+
// Recent projects
|
|
19
|
+
static MAX_RECENT_PROJECTS = 5;
|
|
20
|
+
recentProjects = [];
|
|
21
|
+
dataPath;
|
|
22
|
+
configDir;
|
|
10
23
|
constructor() {
|
|
11
|
-
Object.defineProperty(this, "currentMode", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
configurable: true,
|
|
14
|
-
writable: true,
|
|
15
|
-
value: void 0
|
|
16
|
-
});
|
|
17
|
-
Object.defineProperty(this, "currentProject", {
|
|
18
|
-
enumerable: true,
|
|
19
|
-
configurable: true,
|
|
20
|
-
writable: true,
|
|
21
|
-
value: void 0
|
|
22
|
-
});
|
|
23
|
-
Object.defineProperty(this, "projects", {
|
|
24
|
-
enumerable: true,
|
|
25
|
-
configurable: true,
|
|
26
|
-
writable: true,
|
|
27
|
-
value: []
|
|
28
|
-
});
|
|
29
|
-
Object.defineProperty(this, "worktreeServiceCache", {
|
|
30
|
-
enumerable: true,
|
|
31
|
-
configurable: true,
|
|
32
|
-
writable: true,
|
|
33
|
-
value: new Map()
|
|
34
|
-
});
|
|
35
|
-
Object.defineProperty(this, "projectsDir", {
|
|
36
|
-
enumerable: true,
|
|
37
|
-
configurable: true,
|
|
38
|
-
writable: true,
|
|
39
|
-
value: void 0
|
|
40
|
-
});
|
|
41
|
-
// Multi-project discovery
|
|
42
|
-
Object.defineProperty(this, "projectCache", {
|
|
43
|
-
enumerable: true,
|
|
44
|
-
configurable: true,
|
|
45
|
-
writable: true,
|
|
46
|
-
value: new Map()
|
|
47
|
-
});
|
|
48
|
-
Object.defineProperty(this, "discoveryWorkers", {
|
|
49
|
-
enumerable: true,
|
|
50
|
-
configurable: true,
|
|
51
|
-
writable: true,
|
|
52
|
-
value: 4
|
|
53
|
-
});
|
|
54
|
-
Object.defineProperty(this, "recentProjects", {
|
|
55
|
-
enumerable: true,
|
|
56
|
-
configurable: true,
|
|
57
|
-
writable: true,
|
|
58
|
-
value: []
|
|
59
|
-
});
|
|
60
|
-
Object.defineProperty(this, "dataPath", {
|
|
61
|
-
enumerable: true,
|
|
62
|
-
configurable: true,
|
|
63
|
-
writable: true,
|
|
64
|
-
value: void 0
|
|
65
|
-
});
|
|
66
|
-
Object.defineProperty(this, "configDir", {
|
|
67
|
-
enumerable: true,
|
|
68
|
-
configurable: true,
|
|
69
|
-
writable: true,
|
|
70
|
-
value: void 0
|
|
71
|
-
});
|
|
72
24
|
// Initialize mode based on environment variables
|
|
73
25
|
const multiProjectRoot = process.env[ENV_VARS.MULTI_PROJECT_ROOT];
|
|
74
26
|
this.projectsDir = process.env[ENV_VARS.MULTI_PROJECT_ROOT];
|
|
@@ -554,13 +506,6 @@ export class ProjectManager {
|
|
|
554
506
|
})));
|
|
555
507
|
}
|
|
556
508
|
}
|
|
557
|
-
// Recent projects
|
|
558
|
-
Object.defineProperty(ProjectManager, "MAX_RECENT_PROJECTS", {
|
|
559
|
-
enumerable: true,
|
|
560
|
-
configurable: true,
|
|
561
|
-
writable: true,
|
|
562
|
-
value: 5
|
|
563
|
-
});
|
|
564
509
|
// Create singleton instance
|
|
565
510
|
let _instance = null;
|
|
566
511
|
export const projectManager = {
|
|
@@ -16,7 +16,7 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
|
|
|
16
16
|
private busyTimers;
|
|
17
17
|
private spawn;
|
|
18
18
|
detectTerminalState(session: Session): SessionState;
|
|
19
|
-
detectBackgroundTask(session: Session):
|
|
19
|
+
detectBackgroundTask(session: Session): number;
|
|
20
20
|
private getTerminalContent;
|
|
21
21
|
private handleAutoApproval;
|
|
22
22
|
private cancelAutoApprovalVerification;
|
|
@@ -42,43 +42,15 @@ vi.mock('@xterm/headless', () => ({
|
|
|
42
42
|
}));
|
|
43
43
|
// Create a mock IPty class
|
|
44
44
|
class MockPty extends EventEmitter {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
enumerable: true,
|
|
55
|
-
configurable: true,
|
|
56
|
-
writable: true,
|
|
57
|
-
value: vi.fn()
|
|
58
|
-
});
|
|
59
|
-
Object.defineProperty(this, "write", {
|
|
60
|
-
enumerable: true,
|
|
61
|
-
configurable: true,
|
|
62
|
-
writable: true,
|
|
63
|
-
value: vi.fn()
|
|
64
|
-
});
|
|
65
|
-
Object.defineProperty(this, "onData", {
|
|
66
|
-
enumerable: true,
|
|
67
|
-
configurable: true,
|
|
68
|
-
writable: true,
|
|
69
|
-
value: vi.fn((callback) => {
|
|
70
|
-
this.on('data', callback);
|
|
71
|
-
})
|
|
72
|
-
});
|
|
73
|
-
Object.defineProperty(this, "onExit", {
|
|
74
|
-
enumerable: true,
|
|
75
|
-
configurable: true,
|
|
76
|
-
writable: true,
|
|
77
|
-
value: vi.fn((callback) => {
|
|
78
|
-
this.on('exit', callback);
|
|
79
|
-
})
|
|
80
|
-
});
|
|
81
|
-
}
|
|
45
|
+
kill = vi.fn();
|
|
46
|
+
resize = vi.fn();
|
|
47
|
+
write = vi.fn();
|
|
48
|
+
onData = vi.fn((callback) => {
|
|
49
|
+
this.on('data', callback);
|
|
50
|
+
});
|
|
51
|
+
onExit = vi.fn((callback) => {
|
|
52
|
+
this.on('exit', callback);
|
|
53
|
+
});
|
|
82
54
|
}
|
|
83
55
|
describe('SessionManager Effect-based Operations', () => {
|
|
84
56
|
let sessionManager;
|
|
@@ -13,12 +13,15 @@ import { ProcessError, ConfigError } from '../types/errors.js';
|
|
|
13
13
|
import { autoApprovalVerifier } from './autoApprovalVerifier.js';
|
|
14
14
|
import { logger } from '../utils/logger.js';
|
|
15
15
|
import { Mutex, createInitialSessionStateData } from '../utils/mutex.js';
|
|
16
|
-
import {
|
|
16
|
+
import { getBackgroundTaskTag } from '../constants/statusIcons.js';
|
|
17
17
|
import { getTerminalScreenContent } from '../utils/screenCapture.js';
|
|
18
18
|
const { Terminal } = pkg;
|
|
19
19
|
const execAsync = promisify(exec);
|
|
20
20
|
const TERMINAL_CONTENT_MAX_LINES = 300;
|
|
21
21
|
export class SessionManager extends EventEmitter {
|
|
22
|
+
sessions;
|
|
23
|
+
waitingWithBottomBorder = new Map();
|
|
24
|
+
busyTimers = new Map();
|
|
22
25
|
async spawn(command, args, worktreePath) {
|
|
23
26
|
const spawnOptions = {
|
|
24
27
|
name: 'xterm-256color',
|
|
@@ -158,24 +161,6 @@ export class SessionManager extends EventEmitter {
|
|
|
158
161
|
}
|
|
159
162
|
constructor() {
|
|
160
163
|
super();
|
|
161
|
-
Object.defineProperty(this, "sessions", {
|
|
162
|
-
enumerable: true,
|
|
163
|
-
configurable: true,
|
|
164
|
-
writable: true,
|
|
165
|
-
value: void 0
|
|
166
|
-
});
|
|
167
|
-
Object.defineProperty(this, "waitingWithBottomBorder", {
|
|
168
|
-
enumerable: true,
|
|
169
|
-
configurable: true,
|
|
170
|
-
writable: true,
|
|
171
|
-
value: new Map()
|
|
172
|
-
});
|
|
173
|
-
Object.defineProperty(this, "busyTimers", {
|
|
174
|
-
enumerable: true,
|
|
175
|
-
configurable: true,
|
|
176
|
-
writable: true,
|
|
177
|
-
value: new Map()
|
|
178
|
-
});
|
|
179
164
|
this.sessions = new Map();
|
|
180
165
|
}
|
|
181
166
|
createSessionId() {
|
|
@@ -418,12 +403,12 @@ export class SessionManager extends EventEmitter {
|
|
|
418
403
|
!currentStateData.autoApprovalAbortController) {
|
|
419
404
|
this.handleAutoApproval(session);
|
|
420
405
|
}
|
|
421
|
-
// Detect and update background task
|
|
422
|
-
const
|
|
423
|
-
if (currentStateData.
|
|
406
|
+
// Detect and update background task count
|
|
407
|
+
const backgroundTaskCount = this.detectBackgroundTask(session);
|
|
408
|
+
if (currentStateData.backgroundTaskCount !== backgroundTaskCount) {
|
|
424
409
|
void session.stateMutex.update(data => ({
|
|
425
410
|
...data,
|
|
426
|
-
|
|
411
|
+
backgroundTaskCount,
|
|
427
412
|
}));
|
|
428
413
|
}
|
|
429
414
|
}, STATE_CHECK_INTERVAL_MS);
|
|
@@ -690,9 +675,7 @@ export class SessionManager extends EventEmitter {
|
|
|
690
675
|
counts.pending_auto_approval++;
|
|
691
676
|
break;
|
|
692
677
|
}
|
|
693
|
-
|
|
694
|
-
counts.backgroundTasks++;
|
|
695
|
-
}
|
|
678
|
+
counts.backgroundTasks += stateData.backgroundTaskCount;
|
|
696
679
|
});
|
|
697
680
|
return counts;
|
|
698
681
|
}
|
|
@@ -713,7 +696,8 @@ export class SessionManager extends EventEmitter {
|
|
|
713
696
|
if (parts.length === 0) {
|
|
714
697
|
return '';
|
|
715
698
|
}
|
|
716
|
-
const bgTag = counts.backgroundTasks
|
|
717
|
-
|
|
699
|
+
const bgTag = getBackgroundTaskTag(counts.backgroundTasks);
|
|
700
|
+
const bgSuffix = bgTag ? ` ${bgTag}` : '';
|
|
701
|
+
return ` (${parts.join(' / ')}${bgSuffix})`;
|
|
718
702
|
}
|
|
719
703
|
}
|