kumo-cli 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -127
- package/dist/auth/credentialsStore.js +1 -1
- package/dist/auth/credentialsStore.js.map +1 -1
- package/dist/claude/transcriptParser.js +110 -11
- package/dist/claude/transcriptParser.js.map +1 -1
- package/dist/core/config.js +1 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/logger.js +6 -18
- package/dist/core/logger.js.map +1 -1
- package/dist/engine/claudeArgs.js +102 -0
- package/dist/engine/claudeArgs.js.map +1 -0
- package/dist/engine/claudeBinary.js +85 -0
- package/dist/engine/claudeBinary.js.map +1 -0
- package/dist/engine/claudeConfig.js +113 -0
- package/dist/engine/claudeConfig.js.map +1 -0
- package/dist/engine/controlChannel.js +63 -0
- package/dist/engine/controlChannel.js.map +1 -0
- package/dist/engine/liveTranscript.js +101 -0
- package/dist/engine/liveTranscript.js.map +1 -0
- package/dist/engine/ndjson.js +43 -0
- package/dist/engine/ndjson.js.map +1 -0
- package/dist/engine/permissions.js +98 -0
- package/dist/engine/permissions.js.map +1 -0
- package/dist/engine/sessionManager.js +429 -0
- package/dist/engine/sessionManager.js.map +1 -0
- package/dist/engine/settingsCommands.js +204 -0
- package/dist/engine/settingsCommands.js.map +1 -0
- package/dist/engine/streamSession.js +509 -0
- package/dist/engine/streamSession.js.map +1 -0
- package/dist/engine/taskTracker.js +110 -0
- package/dist/engine/taskTracker.js.map +1 -0
- package/dist/handlers/promptImages.js +8 -39
- package/dist/handlers/promptImages.js.map +1 -1
- package/dist/handlers/runtimeHandlers.js +195 -27
- package/dist/handlers/runtimeHandlers.js.map +1 -1
- package/dist/handlers/sessionHandlers.js +41 -70
- package/dist/handlers/sessionHandlers.js.map +1 -1
- package/dist/index.js +35 -40
- package/dist/index.js.map +1 -1
- package/dist/services/cliRuntime.js +37 -30
- package/dist/services/cliRuntime.js.map +1 -1
- package/dist/services/effortService.js +13 -60
- package/dist/services/effortService.js.map +1 -1
- package/dist/services/forkService.js +120 -0
- package/dist/services/forkService.js.map +1 -0
- package/dist/services/modelService.js +37 -84
- package/dist/services/modelService.js.map +1 -1
- package/dist/services/sessionListService.js +7 -9
- package/dist/services/sessionListService.js.map +1 -1
- package/dist/services/sessionService.js +77 -57
- package/dist/services/sessionService.js.map +1 -1
- package/dist/services/tunnelService.js +4 -5
- package/dist/services/tunnelService.js.map +1 -1
- package/dist/transport/wsClient.js +0 -4
- package/dist/transport/wsClient.js.map +1 -1
- package/dist/utils/ignorePaths.js +3 -3
- package/dist/utils/ignorePaths.js.map +1 -1
- package/package.json +62 -69
- package/dist/ansiCodes.js +0 -107
- package/dist/ansiCodes.js.map +0 -1
- package/dist/auth/credentialsStore.test.js +0 -60
- package/dist/auth/credentialsStore.test.js.map +0 -1
- package/dist/claude/fileHistory.js +0 -79
- package/dist/claude/fileHistory.js.map +0 -1
- package/dist/claude/generateHookSettings.js +0 -23
- package/dist/claude/generateHookSettings.js.map +0 -1
- package/dist/claude/hookServer.js +0 -33
- package/dist/claude/hookServer.js.map +0 -1
- package/dist/claude/sessionScanner.js +0 -162
- package/dist/claude/sessionScanner.js.map +0 -1
- package/dist/claude/sessionScanner.test.js +0 -24
- package/dist/claude/sessionScanner.test.js.map +0 -1
- package/dist/claude/transcriptParser.test.js +0 -78
- package/dist/claude/transcriptParser.test.js.map +0 -1
- package/dist/commands/cliArgs.test.js +0 -25
- package/dist/commands/cliArgs.test.js.map +0 -1
- package/dist/config.js +0 -40
- package/dist/config.js.map +0 -1
- package/dist/credentialsStore.js +0 -57
- package/dist/credentialsStore.js.map +0 -1
- package/dist/generateHookSettings.js +0 -30
- package/dist/generateHookSettings.js.map +0 -1
- package/dist/handlers/fileHandlerPayloads.test.js +0 -55
- package/dist/handlers/fileHandlerPayloads.test.js.map +0 -1
- package/dist/handlers/promptImages.test.js +0 -52
- package/dist/handlers/promptImages.test.js.map +0 -1
- package/dist/hookServer.js +0 -32
- package/dist/hookServer.js.map +0 -1
- package/dist/ignorePaths.js +0 -68
- package/dist/ignorePaths.js.map +0 -1
- package/dist/loadDotEnv.js +0 -79
- package/dist/loadDotEnv.js.map +0 -1
- package/dist/logger.js +0 -29
- package/dist/logger.js.map +0 -1
- package/dist/portForwardManager.js +0 -97
- package/dist/portForwardManager.js.map +0 -1
- package/dist/projectHistoryReader.js +0 -85
- package/dist/projectHistoryReader.js.map +0 -1
- package/dist/pty/ansiCodes.js +0 -14
- package/dist/pty/ansiCodes.js.map +0 -1
- package/dist/pty/notificationDetector.js +0 -29
- package/dist/pty/notificationDetector.js.map +0 -1
- package/dist/pty/notificationDetector.test.js +0 -24
- package/dist/pty/notificationDetector.test.js.map +0 -1
- package/dist/pty/ptyMenuParser.js +0 -91
- package/dist/pty/ptyMenuParser.js.map +0 -1
- package/dist/pty/ptyMenuParser.test.js +0 -26
- package/dist/pty/ptyMenuParser.test.js.map +0 -1
- package/dist/pty/ptySnapshotRenderer.js +0 -71
- package/dist/pty/ptySnapshotRenderer.js.map +0 -1
- package/dist/pty/selectionMenuDetector.js +0 -49
- package/dist/pty/selectionMenuDetector.js.map +0 -1
- package/dist/pty/selectionMenuDetector.test.js +0 -62
- package/dist/pty/selectionMenuDetector.test.js.map +0 -1
- package/dist/pty/selectionMenuMeta.js +0 -54
- package/dist/pty/selectionMenuMeta.js.map +0 -1
- package/dist/pty/selectionMenuMeta.test.js +0 -26
- package/dist/pty/selectionMenuMeta.test.js.map +0 -1
- package/dist/pty/selectionMenuParser.js +0 -83
- package/dist/pty/selectionMenuParser.js.map +0 -1
- package/dist/pty/selectionMenuParser.test.js +0 -30
- package/dist/pty/selectionMenuParser.test.js.map +0 -1
- package/dist/pty/spawn.js +0 -104
- package/dist/pty/spawn.js.map +0 -1
- package/dist/pty/spawn.test.js +0 -17
- package/dist/pty/spawn.test.js.map +0 -1
- package/dist/pty/statusSessionDetector.js +0 -28
- package/dist/pty/statusSessionDetector.js.map +0 -1
- package/dist/pty/statusSessionDetector.test.js +0 -23
- package/dist/pty/statusSessionDetector.test.js.map +0 -1
- package/dist/pty/textInputDetector.js +0 -60
- package/dist/pty/textInputDetector.js.map +0 -1
- package/dist/pty/textInputDetector.test.js +0 -32
- package/dist/pty/textInputDetector.test.js.map +0 -1
- package/dist/ptyMenuParser.js +0 -470
- package/dist/ptyMenuParser.js.map +0 -1
- package/dist/ptySnapshotRenderer.js +0 -78
- package/dist/ptySnapshotRenderer.js.map +0 -1
- package/dist/services/checkpointService.js +0 -113
- package/dist/services/checkpointService.js.map +0 -1
- package/dist/services/checkpointService.test.js +0 -61
- package/dist/services/checkpointService.test.js.map +0 -1
- package/dist/services/claudeMenuBridge.js +0 -116
- package/dist/services/claudeMenuBridge.js.map +0 -1
- package/dist/services/claudeMenuBridge.test.js +0 -90
- package/dist/services/claudeMenuBridge.test.js.map +0 -1
- package/dist/services/claudeService.js +0 -146
- package/dist/services/claudeService.js.map +0 -1
- package/dist/services/claudeService.test.js +0 -39
- package/dist/services/claudeService.test.js.map +0 -1
- package/dist/services/cliRuntime.test.js +0 -55
- package/dist/services/cliRuntime.test.js.map +0 -1
- package/dist/services/modelAdapter.test.js +0 -47
- package/dist/services/modelAdapter.test.js.map +0 -1
- package/dist/services/modelArgs.test.js +0 -64
- package/dist/services/modelArgs.test.js.map +0 -1
- package/dist/services/modelBaseUrl.test.js +0 -32
- package/dist/services/modelBaseUrl.test.js.map +0 -1
- package/dist/services/modelService.test.js +0 -136
- package/dist/services/modelService.test.js.map +0 -1
- package/dist/services/notificationDedupe.js +0 -23
- package/dist/services/notificationDedupe.js.map +0 -1
- package/dist/services/notificationDedupe.test.js +0 -20
- package/dist/services/notificationDedupe.test.js.map +0 -1
- package/dist/services/sessionListService.test.js +0 -22
- package/dist/services/sessionListService.test.js.map +0 -1
- package/dist/services/sessionRollbackService.js +0 -195
- package/dist/services/sessionRollbackService.js.map +0 -1
- package/dist/services/sessionRollbackService.test.js +0 -142
- package/dist/services/sessionRollbackService.test.js.map +0 -1
- package/dist/services/tunnelHelpers.test.js +0 -42
- package/dist/services/tunnelHelpers.test.js.map +0 -1
- package/dist/services/tunnelService.test.js +0 -91
- package/dist/services/tunnelService.test.js.map +0 -1
- package/dist/sessionScanner.js +0 -271
- package/dist/sessionScanner.js.map +0 -1
- package/dist/snapshotScanner.js +0 -154
- package/dist/snapshotScanner.js.map +0 -1
- package/dist/spawn.js +0 -79
- package/dist/spawn.js.map +0 -1
- package/dist/terminalManager.js +0 -73
- package/dist/terminalManager.js.map +0 -1
- package/dist/transport/directWsServer.js +0 -135
- package/dist/transport/directWsServer.js.map +0 -1
- package/dist/transport/webrtcTransport.js +0 -145
- package/dist/transport/webrtcTransport.js.map +0 -1
- package/dist/transport/webrtcTransport.test.js +0 -104
- package/dist/transport/webrtcTransport.test.js.map +0 -1
- package/dist/transport/webrtcTypes.js +0 -29
- package/dist/transport/webrtcTypes.js.map +0 -1
- package/dist/transport/webrtcTypes.test.js +0 -20
- package/dist/transport/webrtcTypes.test.js.map +0 -1
- package/dist/utils/ptyBuffer.js +0 -46
- package/dist/utils/ptyBuffer.js.map +0 -1
- package/scripts/claude_launcher.cjs +0 -71
- package/scripts/run-tests.mjs +0 -46
- package/scripts/session_hook_forwarder.cjs +0 -44
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import xtermHeadless from '@xterm/headless';
|
|
3
|
-
const { Terminal } = xtermHeadless;
|
|
4
|
-
export class PtySnapshotRenderer {
|
|
5
|
-
terminal;
|
|
6
|
-
cols;
|
|
7
|
-
rows;
|
|
8
|
-
dirty = false;
|
|
9
|
-
lastSentHash = '';
|
|
10
|
-
maxLines;
|
|
11
|
-
constructor(cols = 120, rows = 30, maxLines = 400) {
|
|
12
|
-
this.cols = cols;
|
|
13
|
-
this.rows = rows;
|
|
14
|
-
this.maxLines = maxLines;
|
|
15
|
-
this.terminal = this.createTerminal();
|
|
16
|
-
}
|
|
17
|
-
feed(data) {
|
|
18
|
-
this.terminal.write(data);
|
|
19
|
-
this.dirty = true;
|
|
20
|
-
}
|
|
21
|
-
resize(cols, rows) {
|
|
22
|
-
if (cols === this.cols && rows === this.rows)
|
|
23
|
-
return;
|
|
24
|
-
this.cols = cols;
|
|
25
|
-
this.rows = rows;
|
|
26
|
-
this.terminal.resize(cols, rows);
|
|
27
|
-
this.dirty = true;
|
|
28
|
-
}
|
|
29
|
-
reset(cols = this.cols, rows = this.rows) {
|
|
30
|
-
this.cols = cols;
|
|
31
|
-
this.rows = rows;
|
|
32
|
-
this.terminal.dispose();
|
|
33
|
-
this.terminal = this.createTerminal();
|
|
34
|
-
this.dirty = false;
|
|
35
|
-
this.lastSentHash = '';
|
|
36
|
-
}
|
|
37
|
-
flushSnapshot() {
|
|
38
|
-
if (!this.dirty)
|
|
39
|
-
return null;
|
|
40
|
-
this.dirty = false;
|
|
41
|
-
const snapshot = this.buildSnapshot();
|
|
42
|
-
if (snapshot.hash === this.lastSentHash)
|
|
43
|
-
return null;
|
|
44
|
-
this.lastSentHash = snapshot.hash;
|
|
45
|
-
return snapshot;
|
|
46
|
-
}
|
|
47
|
-
createTerminal() {
|
|
48
|
-
return new Terminal({
|
|
49
|
-
cols: this.cols,
|
|
50
|
-
rows: this.rows,
|
|
51
|
-
scrollback: Math.max(this.maxLines * 4, 1000),
|
|
52
|
-
allowProposedApi: true,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
buildSnapshot() {
|
|
56
|
-
const buffer = this.terminal.buffer.active;
|
|
57
|
-
const start = Math.max(0, buffer.length - this.maxLines);
|
|
58
|
-
const lines = [];
|
|
59
|
-
for (let row = start; row < buffer.length; row++) {
|
|
60
|
-
const line = buffer.getLine(row);
|
|
61
|
-
lines.push(line?.translateToString(true) ?? '');
|
|
62
|
-
}
|
|
63
|
-
while (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
64
|
-
lines.pop();
|
|
65
|
-
}
|
|
66
|
-
const text = lines.join('\n').replace(/\r\n/g, '\n');
|
|
67
|
-
const hash = createHash('sha1').update(text).digest('hex').slice(0, 16);
|
|
68
|
-
return {
|
|
69
|
-
text,
|
|
70
|
-
hash,
|
|
71
|
-
rows: this.rows,
|
|
72
|
-
cols: this.cols,
|
|
73
|
-
cursorRow: buffer.cursorY,
|
|
74
|
-
cursorCol: buffer.cursorX,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
//# sourceMappingURL=ptySnapshotRenderer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ptySnapshotRenderer.js","sourceRoot":"","sources":["../src/ptySnapshotRenderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { readSessionHistory } from './sessionService.js';
|
|
4
|
-
import { safePath } from '../utils/safePath.js';
|
|
5
|
-
export function buildRollbackResult(sessionId, targetMessageId, history = readSessionHistory(sessionId)) {
|
|
6
|
-
const targetIndex = history.findIndex((m) => m.id === targetMessageId);
|
|
7
|
-
if (targetIndex < 0)
|
|
8
|
-
throw new Error('rollback target message not found');
|
|
9
|
-
const target = history[targetIndex];
|
|
10
|
-
if (target.role !== 'user')
|
|
11
|
-
throw new Error('rollback target must be a user message');
|
|
12
|
-
const cutoff = findTurnEndIndex(history, targetIndex);
|
|
13
|
-
return {
|
|
14
|
-
sessionId,
|
|
15
|
-
targetMessageId,
|
|
16
|
-
restoredPrompt: target.content,
|
|
17
|
-
messages: history.slice(0, cutoff + 1),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export function rollbackChatTurnOnDisk(sessionId, targetMessageId, history = readSessionHistory(sessionId)) {
|
|
21
|
-
const plan = buildRollbackPlan(sessionId, targetMessageId, history);
|
|
22
|
-
for (const operation of plan.operations)
|
|
23
|
-
operation();
|
|
24
|
-
return plan.result;
|
|
25
|
-
}
|
|
26
|
-
function buildRollbackPlan(sessionId, targetMessageId, history) {
|
|
27
|
-
const targetIndex = history.findIndex((m) => m.id === targetMessageId);
|
|
28
|
-
if (targetIndex < 0)
|
|
29
|
-
throw new Error('rollback target message not found');
|
|
30
|
-
const target = history[targetIndex];
|
|
31
|
-
if (target.role !== 'user')
|
|
32
|
-
throw new Error('rollback target must be a user message');
|
|
33
|
-
const cutoff = findTurnEndIndex(history, targetIndex);
|
|
34
|
-
const laterMessages = history.slice(cutoff + 1);
|
|
35
|
-
const operations = [];
|
|
36
|
-
const restoredFiles = new Set();
|
|
37
|
-
for (let i = laterMessages.length - 1; i >= 0; i--) {
|
|
38
|
-
const message = laterMessages[i];
|
|
39
|
-
for (const tool of [...(message.toolUses ?? [])].reverse()) {
|
|
40
|
-
addToolRollbackOperation(tool, operations, restoredFiles);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
result: {
|
|
45
|
-
sessionId,
|
|
46
|
-
targetMessageId,
|
|
47
|
-
restoredPrompt: target.content,
|
|
48
|
-
messages: history.slice(0, cutoff + 1),
|
|
49
|
-
restoredFiles: [...restoredFiles],
|
|
50
|
-
},
|
|
51
|
-
operations,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function findTurnEndIndex(history, userIndex) {
|
|
55
|
-
let end = history.length - 1;
|
|
56
|
-
for (let i = userIndex + 1; i < history.length; i++) {
|
|
57
|
-
if (history[i].role === 'user') {
|
|
58
|
-
end = i - 1;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return end;
|
|
63
|
-
}
|
|
64
|
-
function addToolRollbackOperation(tool, operations, restoredFiles) {
|
|
65
|
-
const filePath = getToolPath(tool);
|
|
66
|
-
if (!filePath)
|
|
67
|
-
return;
|
|
68
|
-
switch (tool.toolName) {
|
|
69
|
-
case 'Write': {
|
|
70
|
-
restoredFiles.add(filePath);
|
|
71
|
-
const oldContent = tool.input['old_content'];
|
|
72
|
-
if (typeof oldContent === 'string')
|
|
73
|
-
operations.push(() => writeFile(filePath, oldContent));
|
|
74
|
-
else
|
|
75
|
-
operations.push(() => removePath(filePath));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
case 'Edit':
|
|
79
|
-
case 'SearchReplace': {
|
|
80
|
-
const oldStr = tool.input['old_str'] ?? tool.input['old_string'];
|
|
81
|
-
const newStr = tool.input['new_str'] ?? tool.input['new_string'];
|
|
82
|
-
if (typeof oldStr !== 'string' || typeof newStr !== 'string')
|
|
83
|
-
return;
|
|
84
|
-
restoredFiles.add(filePath);
|
|
85
|
-
operations.push(() => revertTextReplacement(filePath, oldStr, newStr));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function getToolPath(tool) {
|
|
91
|
-
const value = tool.input['file_path'] ?? tool.input['path'];
|
|
92
|
-
return typeof value === 'string' ? value : '';
|
|
93
|
-
}
|
|
94
|
-
function writeFile(filePath, content) {
|
|
95
|
-
const resolved = safePath(filePath);
|
|
96
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
97
|
-
fs.writeFileSync(resolved, content, 'utf-8');
|
|
98
|
-
}
|
|
99
|
-
function removePath(targetPath) {
|
|
100
|
-
const resolved = safePath(targetPath);
|
|
101
|
-
if (fs.existsSync(resolved))
|
|
102
|
-
fs.rmSync(resolved, { recursive: true, force: false });
|
|
103
|
-
}
|
|
104
|
-
function revertTextReplacement(filePath, oldStr, newStr) {
|
|
105
|
-
const resolved = safePath(filePath);
|
|
106
|
-
const content = fs.readFileSync(resolved, 'utf-8');
|
|
107
|
-
const index = content.indexOf(newStr);
|
|
108
|
-
if (index < 0)
|
|
109
|
-
throw new Error(`rollback replacement not found for ${filePath}`);
|
|
110
|
-
const next = `${content.slice(0, index)}${oldStr}${content.slice(index + newStr.length)}`;
|
|
111
|
-
fs.writeFileSync(resolved, next, 'utf-8');
|
|
112
|
-
}
|
|
113
|
-
//# sourceMappingURL=checkpointService.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"checkpointService.js","sourceRoot":"","sources":["../../src/services/checkpointService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { buildRollbackResult, rollbackChatTurnOnDisk } from './checkpointService.js';
|
|
7
|
-
test('buildRollbackResult truncates history to selected user message', () => {
|
|
8
|
-
const result = buildRollbackResult('sess-1', 'u1', [
|
|
9
|
-
{ id: 'u1', role: 'user', content: 'first' },
|
|
10
|
-
{ id: 'a1', role: 'assistant', content: 'reply' },
|
|
11
|
-
{ id: 'u2', role: 'user', content: 'second' },
|
|
12
|
-
{ id: 'a2', role: 'assistant', content: 'reply2' },
|
|
13
|
-
]);
|
|
14
|
-
assert.equal(result.sessionId, 'sess-1');
|
|
15
|
-
assert.equal(result.restoredPrompt, 'first');
|
|
16
|
-
assert.deepEqual(result.messages.map((m) => m.id), ['u1', 'a1']);
|
|
17
|
-
});
|
|
18
|
-
test('rollbackChatTurnOnDisk restores file changes from later turns', () => {
|
|
19
|
-
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'kumo-rollback-'));
|
|
20
|
-
const prevCwd = process.cwd();
|
|
21
|
-
process.chdir(tempRoot);
|
|
22
|
-
try {
|
|
23
|
-
fs.mkdirSync(path.join(tempRoot, 'lib'), { recursive: true });
|
|
24
|
-
fs.writeFileSync(path.join(tempRoot, 'lib', 'demo.txt'), 'after second', 'utf-8');
|
|
25
|
-
const result = rollbackChatTurnOnDisk('sess-1', 'u1', [
|
|
26
|
-
{ id: 'u1', role: 'user', content: 'first' },
|
|
27
|
-
{
|
|
28
|
-
id: 'a1',
|
|
29
|
-
role: 'assistant',
|
|
30
|
-
content: 'reply1',
|
|
31
|
-
toolUses: [
|
|
32
|
-
{ toolName: 'Write', input: { file_path: 'lib/demo.txt', content: 'first version' } },
|
|
33
|
-
],
|
|
34
|
-
},
|
|
35
|
-
{ id: 'u2', role: 'user', content: 'second' },
|
|
36
|
-
{
|
|
37
|
-
id: 'a2',
|
|
38
|
-
role: 'assistant',
|
|
39
|
-
content: 'reply2',
|
|
40
|
-
toolUses: [
|
|
41
|
-
{
|
|
42
|
-
toolName: 'Write',
|
|
43
|
-
input: {
|
|
44
|
-
file_path: 'lib/demo.txt',
|
|
45
|
-
content: 'after second',
|
|
46
|
-
old_content: 'first version',
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
]);
|
|
52
|
-
assert.equal(fs.readFileSync(path.join(tempRoot, 'lib', 'demo.txt'), 'utf-8'), 'first version');
|
|
53
|
-
assert.deepEqual(result.restoredFiles, ['lib/demo.txt']);
|
|
54
|
-
assert.deepEqual(result.messages.map((m) => m.id), ['u1', 'a1']);
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
process.chdir(prevCwd);
|
|
58
|
-
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
//# sourceMappingURL=checkpointService.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"checkpointService.test.js","sourceRoot":"","sources":["../../src/services/checkpointService.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { log } from '../core/logger.js';
|
|
2
|
-
import { NotificationDedupe } from './notificationDedupe.js';
|
|
3
|
-
const LOGIN_WARNING_MESSAGE = 'Not logged in · Run /login';
|
|
4
|
-
// translate parsed pty elements into ws messages and handle safe auto-accept flows
|
|
5
|
-
export class ClaudeMenuBridge {
|
|
6
|
-
send;
|
|
7
|
-
getEffectiveEffort;
|
|
8
|
-
getWorkingDir;
|
|
9
|
-
autoAcceptedApiKeyDialog = false;
|
|
10
|
-
menuIdCounter = 0;
|
|
11
|
-
notificationDedupe = new NotificationDedupe();
|
|
12
|
-
constructor(send, getEffectiveEffort, getWorkingDir) {
|
|
13
|
-
this.send = send;
|
|
14
|
-
this.getEffectiveEffort = getEffectiveEffort;
|
|
15
|
-
this.getWorkingDir = getWorkingDir;
|
|
16
|
-
}
|
|
17
|
-
// reset transient state whenever a fresh claude process is spawned
|
|
18
|
-
reset() {
|
|
19
|
-
this.autoAcceptedApiKeyDialog = false;
|
|
20
|
-
}
|
|
21
|
-
// handle a parser event and emit ws payloads or synthetic process input
|
|
22
|
-
handleElement(element, process) {
|
|
23
|
-
switch (element.type) {
|
|
24
|
-
case 'selection':
|
|
25
|
-
this.handleSelection(element, process);
|
|
26
|
-
break;
|
|
27
|
-
case 'input':
|
|
28
|
-
log(`[parser] input: "${element.label}" = "${element.value}"`);
|
|
29
|
-
this.send({
|
|
30
|
-
type: 'text_input',
|
|
31
|
-
label: element.label,
|
|
32
|
-
value: element.value,
|
|
33
|
-
placeholder: element.placeholder,
|
|
34
|
-
cursorPosition: element.cursorPosition,
|
|
35
|
-
});
|
|
36
|
-
break;
|
|
37
|
-
case 'notification':
|
|
38
|
-
this.handleNotification(element);
|
|
39
|
-
break;
|
|
40
|
-
case 'status': {
|
|
41
|
-
const effort = element.effort ?? this.getEffectiveEffort();
|
|
42
|
-
log(`[parser] status: model=${element.model}, effort=${effort}, logged=${element.loggedIn}`);
|
|
43
|
-
this.send({ type: 'cli_status', loggedIn: element.loggedIn, effort, workingDir: element.workingDir });
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
case 'session':
|
|
47
|
-
log(`[parser] session: ${element.sessionId}`);
|
|
48
|
-
this.send({ type: 'session_info', sessionId: element.sessionId, resumeCommand: element.resumeCommand });
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// auto-accept known safe dialogs or forward the menu to the app
|
|
53
|
-
handleSelection(element, process) {
|
|
54
|
-
if (element.options.length === 0) {
|
|
55
|
-
log('[parser] ignoring empty menu');
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (element.title.includes('Bypass Permissions')) {
|
|
59
|
-
if (this.tryAcceptYes(element, process, 'auto-accepted bypass permissions dialog'))
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (!this.autoAcceptedApiKeyDialog &&
|
|
63
|
-
(element.title.includes('Detected a custom API key') || element.title.includes('Do you want to use this API key'))) {
|
|
64
|
-
if (this.tryAcceptYes(element, process, 'auto-accepted custom api key dialog', true)) {
|
|
65
|
-
this.autoAcceptedApiKeyDialog = true;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
this.menuIdCounter += 1;
|
|
70
|
-
const id = `menu-${this.menuIdCounter}`;
|
|
71
|
-
log(`[parser] menu detected: "${element.title}" (${element.options.length} opts, selected: ${element.selectedIndex})`);
|
|
72
|
-
log(`[parser] options: ${JSON.stringify(element.options)}`);
|
|
73
|
-
this.send({
|
|
74
|
-
type: 'prompt_options',
|
|
75
|
-
id,
|
|
76
|
-
title: element.title,
|
|
77
|
-
description: element.description,
|
|
78
|
-
options: element.options,
|
|
79
|
-
selectedIndex: element.selectedIndex,
|
|
80
|
-
hint: element.hint,
|
|
81
|
-
footer: element.footer,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
// forward deduped notifications and update cli status for login warnings
|
|
85
|
-
handleNotification(element) {
|
|
86
|
-
log(`[parser] notification: [${element.level}] ${element.message}`);
|
|
87
|
-
if (this.notificationDedupe.shouldSend(element.level, element.message)) {
|
|
88
|
-
this.send({ type: 'notification', level: element.level, message: element.message });
|
|
89
|
-
}
|
|
90
|
-
if (element.level === 'warning' && element.message.trim().toLowerCase() === LOGIN_WARNING_MESSAGE.toLowerCase()) {
|
|
91
|
-
this.send({
|
|
92
|
-
type: 'cli_status',
|
|
93
|
-
loggedIn: false,
|
|
94
|
-
effort: this.getEffectiveEffort(),
|
|
95
|
-
workingDir: this.getWorkingDir(),
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// try to auto-press "yes" inside a selection dialog
|
|
100
|
-
tryAcceptYes(element, process, logMessage, exact = false) {
|
|
101
|
-
const yesIndex = element.options.findIndex((option) => (exact
|
|
102
|
-
? option.label.trim().toLowerCase() === 'yes'
|
|
103
|
-
: option.label.trim().toLowerCase().includes('yes')));
|
|
104
|
-
if (yesIndex < 0 || !process)
|
|
105
|
-
return false;
|
|
106
|
-
const delta = yesIndex - element.selectedIndex;
|
|
107
|
-
const key = delta < 0 ? '\x1b[A' : '\x1b[B';
|
|
108
|
-
for (let index = 0; index < Math.abs(delta); index += 1) {
|
|
109
|
-
process.write(key);
|
|
110
|
-
}
|
|
111
|
-
process.write('\r');
|
|
112
|
-
log(`[parser] ${logMessage}`);
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
//# sourceMappingURL=claudeMenuBridge.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"claudeMenuBridge.js","sourceRoot":"","sources":["../../src/services/claudeMenuBridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { ClaudeMenuBridge } from './claudeMenuBridge.js';
|
|
4
|
-
function createBridge(sent) {
|
|
5
|
-
return new ClaudeMenuBridge((data) => sent.push(data), () => 'high', () => 'E:\\Data\\new_journey\\kumo\\cli');
|
|
6
|
-
}
|
|
7
|
-
function createProcessRecorder() {
|
|
8
|
-
const writes = [];
|
|
9
|
-
return {
|
|
10
|
-
process: {
|
|
11
|
-
write(data) {
|
|
12
|
-
writes.push(data);
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
writes,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
test('auto accepts bypass permissions dialog without sending prompt options', () => {
|
|
19
|
-
const sent = [];
|
|
20
|
-
const bridge = createBridge(sent);
|
|
21
|
-
const recorder = createProcessRecorder();
|
|
22
|
-
bridge.handleElement({
|
|
23
|
-
type: 'selection',
|
|
24
|
-
title: 'Bypass Permissions',
|
|
25
|
-
options: [
|
|
26
|
-
{ index: 0, label: 'Yes', isSelected: false },
|
|
27
|
-
{ index: 1, label: 'No', isSelected: true },
|
|
28
|
-
],
|
|
29
|
-
selectedIndex: 1,
|
|
30
|
-
}, recorder.process);
|
|
31
|
-
assert.deepEqual(recorder.writes, ['\x1b[A', '\r']);
|
|
32
|
-
assert.equal(sent.length, 0);
|
|
33
|
-
});
|
|
34
|
-
test('auto accepts custom api key dialog only once', () => {
|
|
35
|
-
const sent = [];
|
|
36
|
-
const bridge = createBridge(sent);
|
|
37
|
-
const recorder = createProcessRecorder();
|
|
38
|
-
const element = {
|
|
39
|
-
type: 'selection',
|
|
40
|
-
title: 'Detected a custom API key',
|
|
41
|
-
options: [
|
|
42
|
-
{ index: 0, label: 'Yes', isSelected: true },
|
|
43
|
-
{ index: 1, label: 'No', isSelected: false },
|
|
44
|
-
],
|
|
45
|
-
selectedIndex: 0,
|
|
46
|
-
};
|
|
47
|
-
bridge.handleElement(element, recorder.process);
|
|
48
|
-
bridge.handleElement(element, recorder.process);
|
|
49
|
-
assert.deepEqual(recorder.writes, ['\r']);
|
|
50
|
-
assert.equal(sent.length, 1);
|
|
51
|
-
assert.equal(sent[0]?.type, 'prompt_options');
|
|
52
|
-
});
|
|
53
|
-
test('login warning sends both notification and logged-out status', () => {
|
|
54
|
-
const sent = [];
|
|
55
|
-
const bridge = createBridge(sent);
|
|
56
|
-
bridge.handleElement({
|
|
57
|
-
type: 'notification',
|
|
58
|
-
level: 'warning',
|
|
59
|
-
message: 'Not logged in · Run /login',
|
|
60
|
-
}, null);
|
|
61
|
-
assert.deepEqual(sent, [
|
|
62
|
-
{ type: 'notification', level: 'warning', message: 'Not logged in · Run /login' },
|
|
63
|
-
{ type: 'cli_status', loggedIn: false, effort: 'high', workingDir: 'E:\\Data\\new_journey\\kumo\\cli' },
|
|
64
|
-
]);
|
|
65
|
-
});
|
|
66
|
-
test('selection prompts receive stable incremental ids', () => {
|
|
67
|
-
const sent = [];
|
|
68
|
-
const bridge = createBridge(sent);
|
|
69
|
-
bridge.handleElement({
|
|
70
|
-
type: 'selection',
|
|
71
|
-
title: 'Select model',
|
|
72
|
-
options: [
|
|
73
|
-
{ index: 0, label: 'Sonnet', isSelected: true },
|
|
74
|
-
{ index: 1, label: 'Opus', isSelected: false },
|
|
75
|
-
],
|
|
76
|
-
selectedIndex: 0,
|
|
77
|
-
}, null);
|
|
78
|
-
bridge.handleElement({
|
|
79
|
-
type: 'selection',
|
|
80
|
-
title: 'Select effort',
|
|
81
|
-
options: [
|
|
82
|
-
{ index: 0, label: 'High', isSelected: true },
|
|
83
|
-
{ index: 1, label: 'Max', isSelected: false },
|
|
84
|
-
],
|
|
85
|
-
selectedIndex: 0,
|
|
86
|
-
}, null);
|
|
87
|
-
assert.equal(sent[0]?.id, 'menu-1');
|
|
88
|
-
assert.equal(sent[1]?.id, 'menu-2');
|
|
89
|
-
});
|
|
90
|
-
//# sourceMappingURL=claudeMenuBridge.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"claudeMenuBridge.test.js","sourceRoot":"","sources":["../../src/services/claudeMenuBridge.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,SAAS,YAAY,CAAC,IAAmB;IACvC,OAAO,IAAI,gBAAgB,CACzB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAmB,CAAC,EACxC,GAAG,EAAE,CAAC,MAAM,EACZ,GAAG,EAAE,CAAC,kCAAkC,CACzC,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB;IAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,OAAO;QACL,OAAO,EAAE;YACP,KAAK,CAAC,IAAY;gBAChB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;SACiB;QACpB,MAAM;KACP,CAAC;AACJ,CAAC;AAED,IAAI,CAAC,uEAAuE,EAAE,GAAG,EAAE;IACjF,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IAEzC,MAAM,CAAC,aAAa,CAAC;QACnB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE;YAC7C,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;SAC5C;QACD,aAAa,EAAE,CAAC;KACjB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAErB,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;IACxD,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,WAAoB;QAC1B,KAAK,EAAE,2BAA2B;QAClC,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;YAC5C,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE;SAC7C;QACD,aAAa,EAAE,CAAC;KACjB,CAAC;IAEF,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEhD,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;IACvE,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAElC,MAAM,CAAC,aAAa,CAAC;QACnB,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,4BAA4B;KACtC,EAAE,IAAI,CAAC,CAAC;IAET,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QACrB,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,4BAA4B,EAAE;QACjF,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,kCAAkC,EAAE;KACxG,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAC5D,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAElC,MAAM,CAAC,aAAa,CAAC;QACnB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,cAAc;QACrB,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE;YAC/C,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE;SAC/C;QACD,aAAa,EAAE,CAAC;KACjB,EAAE,IAAI,CAAC,CAAC;IAET,MAAM,CAAC,aAAa,CAAC;QACnB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,eAAe;QACtB,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;YAC7C,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE;SAC9C;QACD,aAAa,EAAE,CAAC;KACjB,EAAE,IAAI,CAAC,CAAC;IAET,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IACpC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC"}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { spawnClaude, writeToProcess } from '../pty/spawn.js';
|
|
2
|
-
import { PtyMenuParser } from '../pty/ptyMenuParser.js';
|
|
3
|
-
import { PtySnapshotRenderer } from '../pty/ptySnapshotRenderer.js';
|
|
4
|
-
import { PtyBuffer } from '../utils/ptyBuffer.js';
|
|
5
|
-
import { log, debugWrite } from '../core/logger.js';
|
|
6
|
-
import { ClaudeMenuBridge } from './claudeMenuBridge.js';
|
|
7
|
-
// manage the lifecycle of a claude pty process plus its snapshot/parser
|
|
8
|
-
export class ClaudeService {
|
|
9
|
-
send;
|
|
10
|
-
model;
|
|
11
|
-
effort;
|
|
12
|
-
getDeviceToken;
|
|
13
|
-
process = null;
|
|
14
|
-
cleanup = null;
|
|
15
|
-
isRespawning = false;
|
|
16
|
-
pendingSelectNudgeTimer = null;
|
|
17
|
-
menuParser;
|
|
18
|
-
menuBridge;
|
|
19
|
-
snapshotRenderer;
|
|
20
|
-
buffer;
|
|
21
|
-
lastWsSendAt = 0;
|
|
22
|
-
args = [];
|
|
23
|
-
settingsPath = '';
|
|
24
|
-
onExit = null;
|
|
25
|
-
constructor(send, model, effort, getDeviceToken) {
|
|
26
|
-
this.send = send;
|
|
27
|
-
this.model = model;
|
|
28
|
-
this.effort = effort;
|
|
29
|
-
this.getDeviceToken = getDeviceToken;
|
|
30
|
-
this.snapshotRenderer = new PtySnapshotRenderer(process.stdout.columns ?? 120, process.stdout.rows ?? 30);
|
|
31
|
-
this.buffer = new PtyBuffer(this.snapshotRenderer);
|
|
32
|
-
this.menuBridge = new ClaudeMenuBridge(this.send, () => this.effort.getEffective(), () => process.cwd());
|
|
33
|
-
this.menuParser = new PtyMenuParser((el) => this.handleMenuElement(el));
|
|
34
|
-
}
|
|
35
|
-
// expose a wrapper so wsClient can stamp the last-send timestamp here
|
|
36
|
-
notifySent() { this.lastWsSendAt = Date.now(); }
|
|
37
|
-
getProcess() { return this.process; }
|
|
38
|
-
resetSnapshot() {
|
|
39
|
-
this.snapshotRenderer.reset(process.stdout.columns ?? 120, process.stdout.rows ?? 30);
|
|
40
|
-
}
|
|
41
|
-
resizeSnapshot() {
|
|
42
|
-
this.snapshotRenderer.resize(process.stdout.columns ?? 120, process.stdout.rows ?? 30);
|
|
43
|
-
}
|
|
44
|
-
resetMenu() { this.menuParser.reset(); }
|
|
45
|
-
flushBuffer() { this.buffer.flush(); }
|
|
46
|
-
// dispatch a typed prompt into the running claude process
|
|
47
|
-
dispatchPrompt(text) {
|
|
48
|
-
if (!this.process)
|
|
49
|
-
return;
|
|
50
|
-
if (text.trim() === '/login')
|
|
51
|
-
this.menuParser.reset();
|
|
52
|
-
writeToProcess(this.process, text);
|
|
53
|
-
}
|
|
54
|
-
// send a single key/option select (no Enter), then nudge if no echo soon
|
|
55
|
-
selectOption(value) {
|
|
56
|
-
if (!this.process) {
|
|
57
|
-
log(`[select_option] no claude process!`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
log(`[select_option] sending: "${value}" (without Enter)`);
|
|
61
|
-
this.menuParser.reset();
|
|
62
|
-
this.process.write(value);
|
|
63
|
-
if (this.pendingSelectNudgeTimer)
|
|
64
|
-
clearTimeout(this.pendingSelectNudgeTimer);
|
|
65
|
-
const baseline = this.lastWsSendAt;
|
|
66
|
-
this.pendingSelectNudgeTimer = setTimeout(() => {
|
|
67
|
-
this.pendingSelectNudgeTimer = null;
|
|
68
|
-
if (!this.process)
|
|
69
|
-
return;
|
|
70
|
-
if (this.lastWsSendAt === baseline)
|
|
71
|
-
this.nudgeResize();
|
|
72
|
-
}, 1000);
|
|
73
|
-
}
|
|
74
|
-
// submit a textual input by clearing existing chars then writing+enter
|
|
75
|
-
submitInput(value) {
|
|
76
|
-
if (!this.process)
|
|
77
|
-
return;
|
|
78
|
-
this.menuParser.reset();
|
|
79
|
-
this.process.write('\x15');
|
|
80
|
-
this.process.write(value);
|
|
81
|
-
this.process.write('\r');
|
|
82
|
-
}
|
|
83
|
-
// forward a synthetic key press (only esc currently supported)
|
|
84
|
-
sendKey(key) {
|
|
85
|
-
if (!this.process || key !== 'esc')
|
|
86
|
-
return;
|
|
87
|
-
this.menuParser.reset();
|
|
88
|
-
this.process.write('\x1b');
|
|
89
|
-
}
|
|
90
|
-
// toggle pty size by 1 col then back to force claude to redraw
|
|
91
|
-
nudgeResize() {
|
|
92
|
-
if (!this.process)
|
|
93
|
-
return;
|
|
94
|
-
const cols = process.stdout.columns ?? 120;
|
|
95
|
-
const rows = process.stdout.rows ?? 30;
|
|
96
|
-
try {
|
|
97
|
-
this.process.resize(cols + 1, rows);
|
|
98
|
-
setTimeout(() => { try {
|
|
99
|
-
this.process?.resize(cols, rows);
|
|
100
|
-
}
|
|
101
|
-
catch { } }, 5);
|
|
102
|
-
}
|
|
103
|
-
catch { }
|
|
104
|
-
}
|
|
105
|
-
// (re)spawn claude with current args; refreshes default model just-in-time
|
|
106
|
-
async spawn() {
|
|
107
|
-
log(`[spawn] spawning claude, args=${JSON.stringify(this.args)}, cwd=${process.cwd()}`);
|
|
108
|
-
if (this.process) {
|
|
109
|
-
this.isRespawning = true;
|
|
110
|
-
try {
|
|
111
|
-
this.process.kill();
|
|
112
|
-
}
|
|
113
|
-
catch { }
|
|
114
|
-
this.process = null;
|
|
115
|
-
}
|
|
116
|
-
this.cleanup?.();
|
|
117
|
-
this.cleanup = null;
|
|
118
|
-
this.menuBridge.reset();
|
|
119
|
-
this.resetSnapshot();
|
|
120
|
-
this.args = await this.model.ensureBeforeSpawn(this.args, this.getDeviceToken());
|
|
121
|
-
this.args = this.effort.ensureBeforeSpawn(this.args);
|
|
122
|
-
const env = await this.model.buildEnv();
|
|
123
|
-
const { pty, cleanup } = spawnClaude([...this.args, '--settings', this.settingsPath], (active) => this.send({ type: 'thinking', active }), (data) => { debugWrite(data); this.menuParser.feed(data); this.buffer.feed(data); }, (cols, rows) => this.menuParser.resize(cols, rows), (p) => { this.process = p; }, undefined, env);
|
|
124
|
-
this.cleanup = cleanup;
|
|
125
|
-
this.send({
|
|
126
|
-
type: 'cli_status',
|
|
127
|
-
effort: this.effort.getEffective(),
|
|
128
|
-
workingDir: process.cwd(),
|
|
129
|
-
});
|
|
130
|
-
pty.onExit(({ exitCode }) => {
|
|
131
|
-
log(`[spawn] claude exited, exitCode=${exitCode}, isRespawning=${this.isRespawning}`);
|
|
132
|
-
this.flushBuffer();
|
|
133
|
-
if (this.isRespawning) {
|
|
134
|
-
this.isRespawning = false;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
this.onExit?.(exitCode);
|
|
138
|
-
});
|
|
139
|
-
process.on('SIGINT', () => { pty.kill(); });
|
|
140
|
-
}
|
|
141
|
-
// handle a parsed pty menu element and translate into ws message
|
|
142
|
-
handleMenuElement(element) {
|
|
143
|
-
this.menuBridge.handleElement(element, this.process);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
//# sourceMappingURL=claudeService.js.map
|