@adhdev/daemon-core 0.8.29 → 0.8.31
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/agent-stream/manager.d.ts +1 -0
- package/dist/agent-stream/provider-adapter.d.ts +1 -0
- package/dist/agent-stream/types.d.ts +3 -0
- package/dist/boot/daemon-lifecycle.d.ts +2 -1
- package/dist/cdp/manager.d.ts +2 -0
- package/dist/cli-adapter-types.d.ts +34 -5
- package/dist/cli-adapters/provider-cli-adapter.d.ts +4 -158
- package/dist/cli-adapters/provider-cli-config.d.ts +30 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +42 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +29 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +158 -0
- package/dist/commands/handler.d.ts +4 -3
- package/dist/config/config.d.ts +4 -3
- package/dist/index.js +1033 -621
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1035 -624
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +1 -0
- package/dist/providers/approval-utils.d.ts +7 -0
- package/dist/providers/cli-provider-instance.d.ts +2 -0
- package/dist/providers/contracts.d.ts +12 -1
- package/dist/providers/ide-provider-instance.d.ts +1 -0
- package/dist/providers/provider-loader.d.ts +3 -0
- package/dist/status/reporter.d.ts +2 -3
- package/dist/status/snapshot.d.ts +2 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +3 -1
- package/src/agent-stream/manager.ts +8 -2
- package/src/agent-stream/poller.ts +57 -6
- package/src/agent-stream/provider-adapter.ts +11 -7
- package/src/agent-stream/types.ts +3 -0
- package/src/boot/daemon-lifecycle.ts +7 -6
- package/src/cdp/initializer.ts +2 -2
- package/src/cdp/manager.ts +5 -0
- package/src/cdp/setup.ts +1 -1
- package/src/cli-adapter-types.ts +37 -5
- package/src/cli-adapters/provider-cli-adapter.ts +212 -795
- package/src/cli-adapters/provider-cli-config.ts +66 -0
- package/src/cli-adapters/provider-cli-parse.ts +202 -0
- package/src/cli-adapters/provider-cli-runtime.ts +142 -0
- package/src/cli-adapters/provider-cli-shared.ts +439 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +1 -1
- package/src/commands/cdp-commands.ts +6 -1
- package/src/commands/chat-commands.ts +45 -29
- package/src/commands/cli-manager.ts +28 -9
- package/src/commands/handler.ts +14 -10
- package/src/commands/router.ts +23 -10
- package/src/commands/stream-commands.ts +11 -5
- package/src/config/config.ts +4 -10
- package/src/daemon/dev-auto-implement.ts +22 -18
- package/src/daemon/dev-cli-debug.ts +59 -16
- package/src/daemon/dev-server.ts +67 -43
- package/src/providers/acp-provider-instance.ts +18 -3
- package/src/providers/approval-utils.ts +66 -0
- package/src/providers/cli-provider-instance.ts +32 -6
- package/src/providers/contracts.d.ts +1 -0
- package/src/providers/contracts.ts +15 -2
- package/src/providers/extension-provider-instance.ts +1 -1
- package/src/providers/ide-provider-instance.ts +67 -41
- package/src/providers/provider-loader.ts +110 -55
- package/src/providers/version-archive.ts +23 -5
- package/src/status/reporter.ts +18 -14
- package/src/status/snapshot.ts +5 -4
|
@@ -15,128 +15,74 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import * as os from 'os';
|
|
18
|
-
import * as path from 'path';
|
|
19
|
-
import { execSync } from 'child_process';
|
|
20
18
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
21
19
|
import { LOG } from '../logging/logger.js';
|
|
22
20
|
import { TerminalScreen } from './terminal-screen.js';
|
|
23
|
-
import type { ProviderResumeCapability } from '../providers/contracts.js';
|
|
24
21
|
import {
|
|
25
22
|
NodePtyTransportFactory,
|
|
26
23
|
type PtyRuntimeMetadata,
|
|
27
24
|
type PtyRuntimeTransport,
|
|
28
25
|
type PtyTransportFactory,
|
|
29
26
|
} from './pty-transport.js';
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
27
|
+
import {
|
|
28
|
+
buildCliScreenSnapshot,
|
|
29
|
+
compactPromptText,
|
|
30
|
+
estimatePromptDisplayLines,
|
|
31
|
+
extractPromptRetrySnippet,
|
|
32
|
+
getLastUserPromptText,
|
|
33
|
+
listCliScriptNames,
|
|
34
|
+
looksLikeConfirmOnlyLabel,
|
|
35
|
+
normalizePromptText,
|
|
36
|
+
normalizeScreenSnapshot,
|
|
37
|
+
promptLikelyVisible,
|
|
38
|
+
sanitizeTerminalText,
|
|
39
|
+
trimPromptEchoPrefix,
|
|
40
|
+
type CliChatMessage,
|
|
41
|
+
type CliProviderModule,
|
|
42
|
+
type CliScriptInput,
|
|
43
|
+
type CliScripts,
|
|
44
|
+
type CliSessionStatus,
|
|
45
|
+
type CliTraceEntry,
|
|
46
|
+
} from './provider-cli-shared.js';
|
|
47
|
+
import {
|
|
48
|
+
buildCliParseInput,
|
|
49
|
+
buildCliTraceParseSnapshot,
|
|
50
|
+
hydrateCliParsedMessages,
|
|
51
|
+
normalizeCliParsedMessages,
|
|
52
|
+
summarizeCliTraceMessages,
|
|
53
|
+
summarizeCliTraceText,
|
|
54
|
+
type TurnParseScope,
|
|
55
|
+
} from './provider-cli-parse.js';
|
|
56
|
+
import {
|
|
57
|
+
resolveCliAdapterConfig,
|
|
58
|
+
type ProviderResolutionMeta,
|
|
59
|
+
} from './provider-cli-config.js';
|
|
60
|
+
import {
|
|
61
|
+
buildCliLoginShellRetry,
|
|
62
|
+
getCliSpawnErrorHint,
|
|
63
|
+
resolveCliSpawnPlan,
|
|
64
|
+
respondToCliTerminalQueries,
|
|
65
|
+
} from './provider-cli-runtime.js';
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
normalizeCliProviderForRuntime,
|
|
69
|
+
type CliApprovalInput,
|
|
70
|
+
type CliChatMessage,
|
|
71
|
+
type CliProviderModule,
|
|
72
|
+
type CliScreenLine,
|
|
73
|
+
type CliScreenSnapshot,
|
|
74
|
+
type CliScriptInput,
|
|
75
|
+
type CliScripts,
|
|
76
|
+
type CliSessionStatus,
|
|
77
|
+
type CliStatusInput,
|
|
78
|
+
type CliTraceEntry,
|
|
79
|
+
} from './provider-cli-shared.js';
|
|
45
80
|
|
|
46
81
|
type SeedCliChatMessage = Omit<Partial<CliChatMessage>, 'role'> & {
|
|
47
82
|
role?: string;
|
|
48
83
|
content?: string;
|
|
49
84
|
};
|
|
50
85
|
|
|
51
|
-
export interface CliSessionStatus {
|
|
52
|
-
status: 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
|
|
53
|
-
messages: CliChatMessage[];
|
|
54
|
-
workingDir: string;
|
|
55
|
-
activeModal: { message: string; buttons: string[] } | null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* CLI Script Functions.
|
|
60
|
-
* Unlike IDE scripts (which return JS code strings for CDP evaluate),
|
|
61
|
-
* CLI scripts are Node.js functions that receive PTY buffer data and return structured results.
|
|
62
|
-
*/
|
|
63
|
-
export interface CliScripts {
|
|
64
|
-
/** Full PTY buffer → ReadChatResult (messages, status, activeModal) */
|
|
65
|
-
parseOutput?: (input: CliScriptInput) => any;
|
|
66
|
-
/** Lightweight status detection (high-frequency polling) → AgentStatus string */
|
|
67
|
-
detectStatus?: (input: CliStatusInput) => string | null;
|
|
68
|
-
/** Parse approval modal from PTY output → ModalInfo | null */
|
|
69
|
-
parseApproval?: (input: CliApprovalInput) => { message: string; buttons: string[] } | null;
|
|
70
|
-
/** Produce a cli-specific prompt from a dashboard action payload */
|
|
71
|
-
resolveAction?: (data: any) => string;
|
|
72
|
-
/** Custom scripts */
|
|
73
|
-
[name: string]: ((input: any) => any) | undefined;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface CliScreenLine {
|
|
77
|
-
index: number;
|
|
78
|
-
fromTop: number;
|
|
79
|
-
fromBottom: number;
|
|
80
|
-
text: string;
|
|
81
|
-
trimmed: string;
|
|
82
|
-
isEmpty: boolean;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface CliScreenSnapshot {
|
|
86
|
-
text: string;
|
|
87
|
-
lineCount: number;
|
|
88
|
-
lines: CliScreenLine[];
|
|
89
|
-
nonEmptyLines: CliScreenLine[];
|
|
90
|
-
firstNonEmptyLineIndex: number;
|
|
91
|
-
lastNonEmptyLineIndex: number;
|
|
92
|
-
firstNonEmptyLine: CliScreenLine | null;
|
|
93
|
-
lastNonEmptyLine: CliScreenLine | null;
|
|
94
|
-
promptLineIndex: number;
|
|
95
|
-
promptLine: CliScreenLine | null;
|
|
96
|
-
linesAbovePrompt: CliScreenLine[];
|
|
97
|
-
linesBelowPrompt: CliScreenLine[];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface CliScriptInput {
|
|
101
|
-
buffer: string; // Full ANSI-stripped accumulated PTY output
|
|
102
|
-
rawBuffer: string; // Raw PTY output (with ANSI)
|
|
103
|
-
recentBuffer: string; // Recent 1000 chars (ANSI-stripped)
|
|
104
|
-
screenText: string; // Current visible screen snapshot
|
|
105
|
-
screen: CliScreenSnapshot;
|
|
106
|
-
bufferScreen: CliScreenSnapshot;
|
|
107
|
-
recentScreen: CliScreenSnapshot;
|
|
108
|
-
messages: CliChatMessage[]; // Previously parsed messages
|
|
109
|
-
partialResponse: string; // Current partial response being generated
|
|
110
|
-
promptText?: string; // Current turn prompt when available
|
|
111
|
-
settings?: Record<string, any>;
|
|
112
|
-
args?: Record<string, any>;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface CliStatusInput {
|
|
116
|
-
tail: string;
|
|
117
|
-
screenText?: string;
|
|
118
|
-
rawBuffer?: string;
|
|
119
|
-
screen: CliScreenSnapshot;
|
|
120
|
-
tailScreen: CliScreenSnapshot;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface CliApprovalInput {
|
|
124
|
-
buffer: string;
|
|
125
|
-
screenText?: string;
|
|
126
|
-
rawBuffer?: string;
|
|
127
|
-
tail: string;
|
|
128
|
-
screen: CliScreenSnapshot;
|
|
129
|
-
bufferScreen: CliScreenSnapshot;
|
|
130
|
-
tailScreen: CliScreenSnapshot;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
interface TurnParseScope {
|
|
134
|
-
prompt: string;
|
|
135
|
-
startedAt: number;
|
|
136
|
-
bufferStart: number;
|
|
137
|
-
rawBufferStart: number;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
86
|
interface IdleFinishCandidate {
|
|
141
87
|
armedAt: number;
|
|
142
88
|
lastOutputAt: number;
|
|
@@ -145,369 +91,6 @@ interface IdleFinishCandidate {
|
|
|
145
91
|
assistantLength: number;
|
|
146
92
|
}
|
|
147
93
|
|
|
148
|
-
export interface CliTraceEntry {
|
|
149
|
-
id: number;
|
|
150
|
-
at: number;
|
|
151
|
-
type: string;
|
|
152
|
-
status: CliSessionStatus['status'];
|
|
153
|
-
isWaitingForResponse: boolean;
|
|
154
|
-
activeModal: { message: string; buttons: string[] } | null;
|
|
155
|
-
payload: Record<string, any>;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export interface CliProviderModule {
|
|
159
|
-
type: string;
|
|
160
|
-
name: string;
|
|
161
|
-
category: 'cli';
|
|
162
|
-
binary: string;
|
|
163
|
-
sendDelayMs?: number;
|
|
164
|
-
sendKey?: string;
|
|
165
|
-
submitStrategy?: 'wait_for_echo' | 'immediate';
|
|
166
|
-
spawn: {
|
|
167
|
-
command: string;
|
|
168
|
-
args: string[];
|
|
169
|
-
shell: boolean;
|
|
170
|
-
env: Record<string, string>;
|
|
171
|
-
};
|
|
172
|
-
timeouts?: {
|
|
173
|
-
/** PTY output batch transmit interval (default 50ms) */
|
|
174
|
-
ptyFlush?: number;
|
|
175
|
-
/** Wait for startup dialog auto-proceed (default 300ms) */
|
|
176
|
-
dialogAccept?: number;
|
|
177
|
-
/** Approval detect cooldown (default 2000ms) */
|
|
178
|
-
approvalCooldown?: number;
|
|
179
|
-
/** Check for completion on no-response during generating (default 6000ms) */
|
|
180
|
-
generatingIdle?: number;
|
|
181
|
-
/** Check for completion on no-response (default 5000ms) */
|
|
182
|
-
idleFinish?: number;
|
|
183
|
-
/** Max response wait (default 300000ms = 5min) */
|
|
184
|
-
maxResponse?: number;
|
|
185
|
-
/** shutdown after kill wait (default 1000ms) */
|
|
186
|
-
shutdownGrace?: number;
|
|
187
|
-
/** Output settle debounce before evaluating status (default 300ms) */
|
|
188
|
-
outputSettle?: number;
|
|
189
|
-
};
|
|
190
|
-
resume?: ProviderResumeCapability;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ─── Utility Functions ──────────────────────────────
|
|
194
|
-
|
|
195
|
-
function stripAnsi(str: string): string {
|
|
196
|
-
// eslint-disable-next-line no-control-regex
|
|
197
|
-
return str
|
|
198
|
-
// OSC sequences (title bar etc) — strip before generic ESC removal so payload cannot leak.
|
|
199
|
-
.replace(/\x1B\][^\x07]*\x07/g, '')
|
|
200
|
-
.replace(/\x1B\][\s\S]*?\x1B\\/g, '')
|
|
201
|
-
// DCS / APC / PM / SOS control strings terminated by ST or BEL.
|
|
202
|
-
.replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, '')
|
|
203
|
-
// Cursor movement sequences → space (prevents word concatenation)
|
|
204
|
-
.replace(/\x1B\[\d*[A-HJKSTfG]/g, ' ')
|
|
205
|
-
// SGR and other CSI sequences → remove
|
|
206
|
-
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '')
|
|
207
|
-
// Collapse multiple spaces
|
|
208
|
-
.replace(/ +/g, ' ');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function stripTerminalNoise(str: string): string {
|
|
212
|
-
return String(str || '')
|
|
213
|
-
// Remove remaining C0/C1 control chars except newlines/tabs.
|
|
214
|
-
// eslint-disable-next-line no-control-regex
|
|
215
|
-
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, '')
|
|
216
|
-
// Drop common terminal negotiation/report fragments that can remain after ANSI stripping.
|
|
217
|
-
.replace(/(^|[\s([])(?:\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, '$1')
|
|
218
|
-
.replace(/(^|[\s([])(?:\[\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, '$1')
|
|
219
|
-
.replace(/(^|[\s([])(?:\d{1,4};\?)(?=$|[\s)\]])/g, '$1')
|
|
220
|
-
// Drop common leftover DCS/OSC payload fragments when a control string was split across PTY chunks.
|
|
221
|
-
.replace(/(^|[\s([])(?:\d+\$r[0-9;\" ]*[A-Za-z]?)(?=$|[\s)\]])/g, '$1')
|
|
222
|
-
.replace(/(^|[\s([])(?:>\|[A-Za-z0-9_.:-]+(?:\([^)]*\))?)(?=$|[\s)\]])/g, '$1')
|
|
223
|
-
.replace(/(^|[\s([])(?:[A-Z]\d(?:\s+[A-Z]\d)+)(?=$|[\s)\]])/g, '$1')
|
|
224
|
-
.replace(/(^|[\s([])(?:\d+;[^\s)\]]+)(?=$|[\s)\]])/g, '$1')
|
|
225
|
-
.replace(/\r+/g, '\n')
|
|
226
|
-
.replace(/[ \t]+\n/g, '\n')
|
|
227
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
228
|
-
.replace(/ {2,}/g, ' ');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function sanitizeTerminalText(str: string): string {
|
|
232
|
-
return stripTerminalNoise(stripAnsi(str));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function splitCliScreenLines(text: string): string[] {
|
|
236
|
-
return String(text || '')
|
|
237
|
-
.replace(/\u0007/g, '')
|
|
238
|
-
.replace(/\r\n/g, '\n')
|
|
239
|
-
.replace(/\r/g, '\n')
|
|
240
|
-
.split('\n')
|
|
241
|
-
.map((line) => line.replace(/\s+$/, ''));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function isPromptLikeCliLine(line: string): boolean {
|
|
245
|
-
const trimmed = String(line || '').trim();
|
|
246
|
-
if (!trimmed) return false;
|
|
247
|
-
return /^[❯›>]\s*(?:$|\S.*)$/.test(trimmed);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function buildCliScreenSnapshot(text: string): CliScreenSnapshot {
|
|
251
|
-
const normalizedText = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
252
|
-
const rawLines = splitCliScreenLines(normalizedText);
|
|
253
|
-
const lines = rawLines.map((line, index, arr) => {
|
|
254
|
-
const trimmed = String(line || '').trim();
|
|
255
|
-
return {
|
|
256
|
-
index,
|
|
257
|
-
fromTop: index,
|
|
258
|
-
fromBottom: arr.length - index - 1,
|
|
259
|
-
text: line,
|
|
260
|
-
trimmed,
|
|
261
|
-
isEmpty: trimmed.length === 0,
|
|
262
|
-
};
|
|
263
|
-
});
|
|
264
|
-
const nonEmptyLines = lines.filter((line) => !line.isEmpty);
|
|
265
|
-
const firstNonEmptyLine = nonEmptyLines[0] ?? null;
|
|
266
|
-
const lastNonEmptyLine = nonEmptyLines[nonEmptyLines.length - 1] ?? null;
|
|
267
|
-
let promptLineIndex = -1;
|
|
268
|
-
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
269
|
-
if (isPromptLikeCliLine(lines[i].text)) {
|
|
270
|
-
promptLineIndex = i;
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
text: normalizedText,
|
|
276
|
-
lineCount: lines.length,
|
|
277
|
-
lines,
|
|
278
|
-
nonEmptyLines,
|
|
279
|
-
firstNonEmptyLineIndex: firstNonEmptyLine?.index ?? -1,
|
|
280
|
-
lastNonEmptyLineIndex: lastNonEmptyLine?.index ?? -1,
|
|
281
|
-
firstNonEmptyLine,
|
|
282
|
-
lastNonEmptyLine,
|
|
283
|
-
promptLineIndex,
|
|
284
|
-
promptLine: promptLineIndex >= 0 ? lines[promptLineIndex] : null,
|
|
285
|
-
linesAbovePrompt: promptLineIndex >= 0 ? lines.slice(0, promptLineIndex) : [...lines],
|
|
286
|
-
linesBelowPrompt: promptLineIndex >= 0 ? lines.slice(promptLineIndex + 1) : [],
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Re-export sanitizeSpawnEnv under the local alias for backward compat within this file
|
|
291
|
-
const buildCliSpawnEnv = sanitizeSpawnEnv;
|
|
292
|
-
|
|
293
|
-
function computeTerminalQueryTail(buffer: string): string {
|
|
294
|
-
const prefixes = ['\x1b[6n', '\x1b[?6n'];
|
|
295
|
-
const maxLength = prefixes.reduce((n, value) => Math.max(n, value.length), 0) - 1;
|
|
296
|
-
const start = Math.max(0, buffer.length - maxLength);
|
|
297
|
-
for (let i = start; i < buffer.length; i++) {
|
|
298
|
-
const suffix = buffer.slice(i);
|
|
299
|
-
if (prefixes.some((pattern) => suffix.length < pattern.length && pattern.startsWith(suffix))) {
|
|
300
|
-
return suffix;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return '';
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function findBinary(name: string): string {
|
|
307
|
-
const trimmed = String(name || '').trim();
|
|
308
|
-
if (!trimmed) return trimmed;
|
|
309
|
-
const expanded = trimmed.startsWith('~')
|
|
310
|
-
? path.join(os.homedir(), trimmed.slice(1))
|
|
311
|
-
: trimmed;
|
|
312
|
-
if (path.isAbsolute(expanded) || expanded.includes('/') || expanded.includes('\\')) {
|
|
313
|
-
return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);
|
|
314
|
-
}
|
|
315
|
-
const isWin = os.platform() === 'win32';
|
|
316
|
-
try {
|
|
317
|
-
const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
|
|
318
|
-
return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim().split('\n')[0].trim();
|
|
319
|
-
} catch {
|
|
320
|
-
return isWin ? `${trimmed}.cmd` : trimmed;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/** True if file starts with a UTF-8 BOM then #!, or plain #!. */
|
|
325
|
-
function isScriptBinary(binaryPath: string): boolean {
|
|
326
|
-
if (!path.isAbsolute(binaryPath)) return false;
|
|
327
|
-
try {
|
|
328
|
-
const fs = require('fs');
|
|
329
|
-
const resolved = fs.realpathSync(binaryPath);
|
|
330
|
-
const head = Buffer.alloc(8);
|
|
331
|
-
const fd = fs.openSync(resolved, 'r');
|
|
332
|
-
fs.readSync(fd, head, 0, 8, 0);
|
|
333
|
-
fs.closeSync(fd);
|
|
334
|
-
let i = 0;
|
|
335
|
-
if (head[0] === 0xef && head[1] === 0xbb && head[2] === 0xbf) i = 3;
|
|
336
|
-
return head[i] === 0x23 && head[i + 1] === 0x21; // '#!'
|
|
337
|
-
} catch {
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** True only for Mach-O / ELF — npm shims and shell scripts return false. */
|
|
343
|
-
function looksLikeMachOOrElf(filePath: string): boolean {
|
|
344
|
-
if (!path.isAbsolute(filePath)) return false;
|
|
345
|
-
try {
|
|
346
|
-
const fs = require('fs');
|
|
347
|
-
const resolved = fs.realpathSync(filePath);
|
|
348
|
-
const buf = Buffer.alloc(8);
|
|
349
|
-
const fd = fs.openSync(resolved, 'r');
|
|
350
|
-
fs.readSync(fd, buf, 0, 8, 0);
|
|
351
|
-
fs.closeSync(fd);
|
|
352
|
-
let i = 0;
|
|
353
|
-
if (buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) i = 3;
|
|
354
|
-
const b = buf.subarray(i);
|
|
355
|
-
if (b.length < 4) return false;
|
|
356
|
-
// ELF
|
|
357
|
-
if (b[0] === 0x7f && b[1] === 0x45 && b[2] === 0x4c && b[3] === 0x46) return true;
|
|
358
|
-
const le = b.readUInt32LE(0);
|
|
359
|
-
const be = b.readUInt32BE(0);
|
|
360
|
-
const magics = [0xfeedface, 0xfeedfacf, 0xcafebabe, 0xbebafeca];
|
|
361
|
-
return magics.some(m => m === le || m === be);
|
|
362
|
-
} catch {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function shSingleQuote(arg: string): string {
|
|
368
|
-
if (/^[a-zA-Z0-9@%_+=:,./-]+$/.test(arg)) return arg;
|
|
369
|
-
if (os.platform() === 'win32') {
|
|
370
|
-
return `"${arg.replace(/"/g, '""')}"`;
|
|
371
|
-
}
|
|
372
|
-
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function estimatePromptDisplayLines(text: string, cols = 80): number {
|
|
376
|
-
const normalized = String(text || '').replace(/\r/g, '');
|
|
377
|
-
if (!normalized) return 1;
|
|
378
|
-
return normalized
|
|
379
|
-
.split('\n')
|
|
380
|
-
.reduce((sum, line) => sum + Math.max(1, Math.ceil(Math.max(1, line.length) / cols)), 0);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function extractPromptRetrySnippet(text: string): string {
|
|
384
|
-
const lines = String(text || '')
|
|
385
|
-
.replace(/\r/g, '')
|
|
386
|
-
.split('\n')
|
|
387
|
-
.map(line => line.trim())
|
|
388
|
-
.filter(Boolean);
|
|
389
|
-
const candidate = lines[lines.length - 1] || lines[0] || '';
|
|
390
|
-
return candidate.slice(-120);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function normalizePromptText(text: string): string {
|
|
394
|
-
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function compactPromptText(text: string): string {
|
|
398
|
-
return String(text || '').replace(/\s+/g, '').trim();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function promptLikelyVisible(screenText: string, promptSnippet: string): boolean {
|
|
402
|
-
const snippet = normalizePromptText(promptSnippet);
|
|
403
|
-
if (!snippet) return false;
|
|
404
|
-
|
|
405
|
-
const normalizedScreen = normalizePromptText(screenText);
|
|
406
|
-
if (normalizedScreen.includes(snippet)) return true;
|
|
407
|
-
|
|
408
|
-
const compactScreen = compactPromptText(screenText);
|
|
409
|
-
const compactSnippet = compactPromptText(promptSnippet);
|
|
410
|
-
if (compactSnippet && compactScreen.includes(compactSnippet)) return true;
|
|
411
|
-
|
|
412
|
-
const tokens = snippet
|
|
413
|
-
.split(/[^A-Za-z0-9_.:/-]+/)
|
|
414
|
-
.map(token => token.trim())
|
|
415
|
-
.filter(token => token.length >= 4);
|
|
416
|
-
if (tokens.length === 0) return false;
|
|
417
|
-
|
|
418
|
-
const required = Math.min(tokens.length, 3);
|
|
419
|
-
const matched = tokens.filter(token =>
|
|
420
|
-
normalizedScreen.includes(token) || compactScreen.includes(compactPromptText(token)),
|
|
421
|
-
).length;
|
|
422
|
-
return matched >= required;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function normalizeScreenSnapshot(text: string): string {
|
|
426
|
-
return sanitizeTerminalText(String(text || ''))
|
|
427
|
-
.replace(/\s+/g, ' ')
|
|
428
|
-
.trim();
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function normalizeComparableMessageContent(text: string): string {
|
|
432
|
-
return String(text || '')
|
|
433
|
-
.replace(/\s+/g, ' ')
|
|
434
|
-
.trim();
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function trimPromptEchoPrefix(text: string, promptText?: string | null): string {
|
|
438
|
-
const prompt = normalizeComparableMessageContent(String(promptText || ''));
|
|
439
|
-
if (!prompt) return String(text || '');
|
|
440
|
-
|
|
441
|
-
const lines = String(text || '').split(/\r\n|\n|\r/g);
|
|
442
|
-
let dropCount = 0;
|
|
443
|
-
for (let index = 0; index < Math.min(lines.length, 6); index += 1) {
|
|
444
|
-
const fragment = normalizeComparableMessageContent(lines[index].replace(/^[.…]+\s*/, ''));
|
|
445
|
-
if (!fragment) {
|
|
446
|
-
if (dropCount === index) dropCount = index + 1;
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
const fragmentWordCount = fragment ? fragment.split(/\s+/).filter(Boolean).length : 0;
|
|
450
|
-
const canBePromptEcho = fragment.length >= 16 || fragmentWordCount >= 4;
|
|
451
|
-
if (canBePromptEcho && prompt.includes(fragment)) {
|
|
452
|
-
dropCount = index + 1;
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
break;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return lines.slice(dropCount).join('\n').trim();
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function getLastUserPromptText(messages: Array<{ role?: string; content?: string }> | null | undefined): string {
|
|
462
|
-
const items = Array.isArray(messages) ? messages : [];
|
|
463
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
464
|
-
const message = items[index];
|
|
465
|
-
if (message?.role === 'user' && typeof message.content === 'string' && message.content.trim()) {
|
|
466
|
-
return message.content;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return '';
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function looksLikeConfirmOnlyLabel(label: string): boolean {
|
|
473
|
-
return /^(?:continue|confirm|ok|yes|trust|proceed|enter)$/i.test(String(label || '').trim());
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Normalize provider.json for auto-implement approval detection.
|
|
478
|
-
* Kept for backward compat with dev-server auto-impl pipeline only.
|
|
479
|
-
*/
|
|
480
|
-
function parsePatternEntry(x: unknown): RegExp | null {
|
|
481
|
-
if (x instanceof RegExp) return x;
|
|
482
|
-
if (x && typeof x === 'object' && typeof (x as { source?: string }).source === 'string') {
|
|
483
|
-
try {
|
|
484
|
-
const s = x as { source: string; flags?: string };
|
|
485
|
-
return new RegExp(s.source, s.flags || '');
|
|
486
|
-
} catch {
|
|
487
|
-
return null;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function coercePatternArray(raw: unknown): RegExp[] {
|
|
494
|
-
if (!Array.isArray(raw)) return [];
|
|
495
|
-
return raw.map(parsePatternEntry).filter((r): r is RegExp => r != null);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Normalize raw provider JSON for auto-implement approval patterns.
|
|
500
|
-
* Used by dev-server only — ProviderCliAdapter itself uses scripts.
|
|
501
|
-
*/
|
|
502
|
-
export function normalizeCliProviderForRuntime(raw: any): { patterns: { approval: RegExp[] } } {
|
|
503
|
-
const patterns = raw?.patterns || {};
|
|
504
|
-
return {
|
|
505
|
-
patterns: {
|
|
506
|
-
approval: coercePatternArray(patterns.approval),
|
|
507
|
-
},
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
|
|
511
94
|
// ─── Adapter ────────────────────────────────────────
|
|
512
95
|
|
|
513
96
|
export class ProviderCliAdapter implements CliAdapter {
|
|
@@ -598,7 +181,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
598
181
|
private traceSeq = 0;
|
|
599
182
|
private traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
600
183
|
private static readonly MAX_TRACE_ENTRIES = 250;
|
|
601
|
-
private readonly providerResolutionMeta:
|
|
184
|
+
private readonly providerResolutionMeta: ProviderResolutionMeta;
|
|
602
185
|
private static readonly IDLE_FINISH_CONFIRM_MS = 2000;
|
|
603
186
|
private static readonly STATUS_ACTIVITY_HOLD_MS = 2000;
|
|
604
187
|
private static readonly FINISH_RETRY_DELAY_MS = 300;
|
|
@@ -609,117 +192,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
609
192
|
this.structuredMessages = [...this.committedMessages];
|
|
610
193
|
}
|
|
611
194
|
|
|
612
|
-
private hydrateParsedMessages(parsedMessages: any[], scope?: TurnParseScope | null): any[] {
|
|
613
|
-
const referenceMessages = [...this.committedMessages];
|
|
614
|
-
const usedReferenceIndexes = new Set<number>();
|
|
615
|
-
const now = Date.now();
|
|
616
|
-
|
|
617
|
-
const findReferenceTimestamp = (role: 'user' | 'assistant', content: string, parsedIndex: number): number | undefined => {
|
|
618
|
-
const normalizedContent = normalizeComparableMessageContent(content);
|
|
619
|
-
if (!normalizedContent) return undefined;
|
|
620
|
-
|
|
621
|
-
const sameIndex = referenceMessages[parsedIndex];
|
|
622
|
-
if (
|
|
623
|
-
sameIndex
|
|
624
|
-
&& !usedReferenceIndexes.has(parsedIndex)
|
|
625
|
-
&& sameIndex.role === role
|
|
626
|
-
&& normalizeComparableMessageContent(sameIndex.content) === normalizedContent
|
|
627
|
-
&& typeof sameIndex.timestamp === 'number'
|
|
628
|
-
&& Number.isFinite(sameIndex.timestamp)
|
|
629
|
-
) {
|
|
630
|
-
usedReferenceIndexes.add(parsedIndex);
|
|
631
|
-
return sameIndex.timestamp;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
for (let i = 0; i < referenceMessages.length; i++) {
|
|
635
|
-
if (usedReferenceIndexes.has(i)) continue;
|
|
636
|
-
const candidate = referenceMessages[i];
|
|
637
|
-
if (!candidate || candidate.role !== role) continue;
|
|
638
|
-
const candidateContent = normalizeComparableMessageContent(candidate.content);
|
|
639
|
-
if (!candidateContent) continue;
|
|
640
|
-
const exactMatch = candidateContent === normalizedContent;
|
|
641
|
-
const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
|
|
642
|
-
if (!exactMatch && !fuzzyMatch) continue;
|
|
643
|
-
if (typeof candidate.timestamp === 'number' && Number.isFinite(candidate.timestamp)) {
|
|
644
|
-
usedReferenceIndexes.add(i);
|
|
645
|
-
return candidate.timestamp;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
return undefined;
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
return parsedMessages
|
|
653
|
-
.filter((message) => message && (message.role === 'user' || message.role === 'assistant'))
|
|
654
|
-
.map((message, index) => {
|
|
655
|
-
const role = message.role as 'user' | 'assistant';
|
|
656
|
-
const content = typeof message.content === 'string' ? message.content : String(message.content || '');
|
|
657
|
-
const parsedTimestamp = typeof message.timestamp === 'number' && Number.isFinite(message.timestamp)
|
|
658
|
-
? message.timestamp
|
|
659
|
-
: undefined;
|
|
660
|
-
const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
|
|
661
|
-
const fallbackTimestamp = role === 'user'
|
|
662
|
-
? (scope?.startedAt || now)
|
|
663
|
-
: (this.lastOutputAt || scope?.startedAt || now);
|
|
664
|
-
const timestamp = referenceTimestamp ?? fallbackTimestamp;
|
|
665
|
-
return {
|
|
666
|
-
...message,
|
|
667
|
-
role,
|
|
668
|
-
content,
|
|
669
|
-
timestamp,
|
|
670
|
-
receivedAt: typeof message.receivedAt === 'number' && Number.isFinite(message.receivedAt)
|
|
671
|
-
? message.receivedAt
|
|
672
|
-
: timestamp,
|
|
673
|
-
};
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
private normalizeParsedMessages(parsedMessages: any[], scope?: TurnParseScope | null): CliChatMessage[] {
|
|
678
|
-
return this.hydrateParsedMessages(parsedMessages, scope).map((message) => ({
|
|
679
|
-
role: message.role,
|
|
680
|
-
content: message.content,
|
|
681
|
-
timestamp: message.timestamp,
|
|
682
|
-
receivedAt: message.receivedAt,
|
|
683
|
-
kind: message.kind,
|
|
684
|
-
id: message.id,
|
|
685
|
-
index: message.index,
|
|
686
|
-
meta: message.meta,
|
|
687
|
-
senderName: message.senderName,
|
|
688
|
-
}));
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
private sliceFromOffset(text: string, start: number): string {
|
|
692
|
-
if (!text) return '';
|
|
693
|
-
if (!Number.isFinite(start) || start <= 0) return text;
|
|
694
|
-
if (start >= text.length) return '';
|
|
695
|
-
return text.slice(start);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
private buildParseInput(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): CliScriptInput {
|
|
699
|
-
const buffer = scope
|
|
700
|
-
? (this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer)
|
|
701
|
-
: this.accumulatedBuffer;
|
|
702
|
-
const rawBuffer = scope
|
|
703
|
-
? (this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer)
|
|
704
|
-
: this.accumulatedRawBuffer;
|
|
705
|
-
const screenText = this.terminalScreen.getText();
|
|
706
|
-
const recentBuffer = buffer.slice(-1000) || this.recentOutputBuffer;
|
|
707
|
-
|
|
708
|
-
return {
|
|
709
|
-
buffer,
|
|
710
|
-
rawBuffer,
|
|
711
|
-
recentBuffer,
|
|
712
|
-
screenText,
|
|
713
|
-
screen: buildCliScreenSnapshot(screenText),
|
|
714
|
-
bufferScreen: buildCliScreenSnapshot(buffer),
|
|
715
|
-
recentScreen: buildCliScreenSnapshot(recentBuffer),
|
|
716
|
-
messages: [...baseMessages],
|
|
717
|
-
partialResponse,
|
|
718
|
-
promptText: scope?.prompt || '',
|
|
719
|
-
settings: { ...this.runtimeSettings },
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
|
|
723
195
|
private setStatus(status: CliSessionStatus['status'], trigger?: string): void {
|
|
724
196
|
const prev = this.currentStatus;
|
|
725
197
|
if (prev === status) return;
|
|
@@ -754,7 +226,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
754
226
|
this.recordTrace('idle_candidate_armed', {
|
|
755
227
|
confirmMs: ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
|
|
756
228
|
candidate: this.idleFinishCandidate,
|
|
757
|
-
...
|
|
229
|
+
...buildCliTraceParseSnapshot({
|
|
230
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
231
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
232
|
+
responseBuffer: this.responseBuffer,
|
|
233
|
+
partialResponse: this.responseBuffer,
|
|
234
|
+
scope: this.currentTurnScope,
|
|
235
|
+
}),
|
|
758
236
|
});
|
|
759
237
|
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
760
238
|
this.settleTimer = setTimeout(() => {
|
|
@@ -764,36 +242,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
764
242
|
}, ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS);
|
|
765
243
|
}
|
|
766
244
|
|
|
767
|
-
private summarizeTraceText(text: string, max = 800): string {
|
|
768
|
-
const value = sanitizeTerminalText(String(text || ''));
|
|
769
|
-
if (value.length <= max) return value;
|
|
770
|
-
return `…${value.slice(-max)}`;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
private summarizeTraceMessages(messages: CliChatMessage[], limit = 3): { role: string; content: string; timestamp?: number }[] {
|
|
774
|
-
return messages.slice(-limit).map((message) => ({
|
|
775
|
-
role: message.role,
|
|
776
|
-
content: this.summarizeTraceText(message.content, 240),
|
|
777
|
-
timestamp: message.timestamp,
|
|
778
|
-
}));
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
private buildTraceParseSnapshot(scope?: TurnParseScope | null, partialResponse = ''): Record<string, any> {
|
|
782
|
-
const scopedBuffer = scope
|
|
783
|
-
? (this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer)
|
|
784
|
-
: this.accumulatedBuffer;
|
|
785
|
-
const scopedRawBuffer = scope
|
|
786
|
-
? (this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer)
|
|
787
|
-
: this.accumulatedRawBuffer;
|
|
788
|
-
return {
|
|
789
|
-
currentTurnScope: scope || null,
|
|
790
|
-
responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
|
|
791
|
-
partialResponse: this.summarizeTraceText(partialResponse || this.responseBuffer, 1200),
|
|
792
|
-
turnBuffer: this.summarizeTraceText(scopedBuffer, 1600),
|
|
793
|
-
turnRawPreview: this.summarizeTraceText(scopedRawBuffer, 1600),
|
|
794
|
-
turnSanitizedRawPreview: this.summarizeTraceText(sanitizeTerminalText(scopedRawBuffer), 1600),
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
245
|
|
|
798
246
|
private recordTrace(type: string, payload: Record<string, any> = {}): void {
|
|
799
247
|
const entry: CliTraceEntry = {
|
|
@@ -847,40 +295,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
847
295
|
? workingDir.replace(/^~/, os.homedir())
|
|
848
296
|
: workingDir;
|
|
849
297
|
|
|
850
|
-
const
|
|
851
|
-
this.timeouts =
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
maxResponse: t.maxResponse ?? 300000,
|
|
858
|
-
shutdownGrace: t.shutdownGrace ?? 1000,
|
|
859
|
-
outputSettle: t.outputSettle ?? 300,
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
const rawKeys = (provider as any).approvalKeys;
|
|
863
|
-
this.approvalKeys = (rawKeys && typeof rawKeys === 'object') ? rawKeys : {};
|
|
864
|
-
this.sendDelayMs = typeof (provider as any).sendDelayMs === 'number' ? Math.max(0, (provider as any).sendDelayMs) : 0;
|
|
865
|
-
this.sendKey = typeof (provider as any).sendKey === 'string' && (provider as any).sendKey.length > 0
|
|
866
|
-
? (provider as any).sendKey
|
|
867
|
-
: '\r';
|
|
868
|
-
this.submitStrategy = (provider as any).submitStrategy === 'immediate' ? 'immediate' : 'wait_for_echo';
|
|
869
|
-
this.providerResolutionMeta = {
|
|
870
|
-
type: provider.type,
|
|
871
|
-
name: provider.name,
|
|
872
|
-
resolvedVersion: (provider as any)._resolvedVersion || null,
|
|
873
|
-
resolvedOs: (provider as any)._resolvedOs || null,
|
|
874
|
-
providerDir: (provider as any)._resolvedProviderDir || null,
|
|
875
|
-
scriptDir: (provider as any)._resolvedScriptDir || null,
|
|
876
|
-
scriptsPath: (provider as any)._resolvedScriptsPath || null,
|
|
877
|
-
scriptsSource: (provider as any)._resolvedScriptsSource || null,
|
|
878
|
-
versionWarning: (provider as any)._versionWarning || null,
|
|
879
|
-
};
|
|
298
|
+
const resolvedConfig = resolveCliAdapterConfig(provider);
|
|
299
|
+
this.timeouts = resolvedConfig.timeouts;
|
|
300
|
+
this.approvalKeys = resolvedConfig.approvalKeys;
|
|
301
|
+
this.sendDelayMs = resolvedConfig.sendDelayMs;
|
|
302
|
+
this.sendKey = resolvedConfig.sendKey;
|
|
303
|
+
this.submitStrategy = resolvedConfig.submitStrategy;
|
|
304
|
+
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
880
305
|
|
|
881
306
|
// Scripts are required — loaded by ProviderLoader via compatibility array
|
|
882
|
-
this.cliScripts =
|
|
883
|
-
const scriptNames =
|
|
307
|
+
this.cliScripts = provider.scripts || {};
|
|
308
|
+
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
884
309
|
if (scriptNames.length > 0) {
|
|
885
310
|
LOG.info('CLI', `[${this.cliType}] CLI scripts: [${scriptNames.join(', ')}]`);
|
|
886
311
|
LOG.info(
|
|
@@ -895,7 +320,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
895
320
|
/** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
|
|
896
321
|
setCliScripts(scripts: CliScripts): void {
|
|
897
322
|
this.cliScripts = scripts;
|
|
898
|
-
const scriptNames =
|
|
323
|
+
const scriptNames = listCliScriptNames(scripts);
|
|
899
324
|
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
900
325
|
}
|
|
901
326
|
|
|
@@ -935,99 +360,44 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
935
360
|
async spawn(): Promise<void> {
|
|
936
361
|
if (this.ptyProcess) return;
|
|
937
362
|
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
:
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
const allArgs = [...spawnConfig.args, ...this.extraArgs];
|
|
363
|
+
const spawnPlan = resolveCliSpawnPlan({
|
|
364
|
+
provider: this.provider,
|
|
365
|
+
runtimeSettings: this.runtimeSettings,
|
|
366
|
+
workingDir: this.workingDir,
|
|
367
|
+
extraArgs: this.extraArgs,
|
|
368
|
+
});
|
|
945
369
|
|
|
946
370
|
LOG.info('CLI', `[${this.cliType}] Spawning in ${this.workingDir}`);
|
|
947
371
|
this.resetTraceSession();
|
|
948
|
-
|
|
949
|
-
let shellCmd: string;
|
|
950
|
-
let shellArgs: string[];
|
|
951
|
-
const useShellUnix = !isWin && (
|
|
952
|
-
!!spawnConfig.shell
|
|
953
|
-
|| !path.isAbsolute(binaryPath)
|
|
954
|
-
|| isScriptBinary(binaryPath)
|
|
955
|
-
|| !looksLikeMachOOrElf(binaryPath)
|
|
956
|
-
);
|
|
957
|
-
// On Windows, .cmd/.bat shims cannot be spawned directly — must go through cmd.exe
|
|
958
|
-
const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
|
|
959
|
-
const useShellWin = !!spawnConfig.shell
|
|
960
|
-
|| isCmdShim
|
|
961
|
-
|| !path.isAbsolute(binaryPath)
|
|
962
|
-
|| isScriptBinary(binaryPath);
|
|
963
|
-
const useShell = isWin ? useShellWin : useShellUnix;
|
|
964
|
-
|
|
965
|
-
if (useShell) {
|
|
966
|
-
if (!spawnConfig.shell && !isWin) {
|
|
967
|
-
LOG.info('CLI', `[${this.cliType}] Using login shell (script shim or non-native binary)`);
|
|
968
|
-
}
|
|
969
|
-
if (isCmdShim) {
|
|
970
|
-
LOG.info('CLI', `[${this.cliType}] Using cmd.exe shell for .cmd/.bat shim: ${binaryPath}`);
|
|
971
|
-
} else if (isWin) {
|
|
972
|
-
LOG.info('CLI', `[${this.cliType}] Using cmd.exe shell on Windows: ${binaryPath}`);
|
|
973
|
-
}
|
|
974
|
-
shellCmd = isWin ? 'cmd.exe' : (process.env.SHELL || '/bin/zsh');
|
|
975
|
-
if (isWin) {
|
|
976
|
-
// On Windows, pass binaryPath and args as separate items so node-pty's
|
|
977
|
-
// argvToCommandLine quotes each one individually. Joining them into a
|
|
978
|
-
// single pre-quoted string causes cmd.exe to receive \"path\" (backslash-
|
|
979
|
-
// escaped quotes) which it does not recognise as a valid executable name.
|
|
980
|
-
shellArgs = ['/c', binaryPath, ...allArgs];
|
|
981
|
-
} else {
|
|
982
|
-
const fullCmd = [binaryPath, ...allArgs].map(shSingleQuote).join(' ');
|
|
983
|
-
shellArgs = ['-l', '-c', fullCmd];
|
|
984
|
-
}
|
|
985
|
-
} else {
|
|
986
|
-
if (isWin && spawnConfig.shell) {
|
|
987
|
-
LOG.info('CLI', `[${this.cliType}] Spawning Windows binary directly without cmd.exe: ${binaryPath}`);
|
|
988
|
-
}
|
|
989
|
-
shellCmd = binaryPath;
|
|
990
|
-
shellArgs = allArgs;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
const ptyOpts = {
|
|
994
|
-
cols: 80,
|
|
995
|
-
rows: 24,
|
|
996
|
-
cwd: this.workingDir,
|
|
997
|
-
env: buildCliSpawnEnv(process.env, spawnConfig.env),
|
|
998
|
-
};
|
|
999
372
|
this.recordTrace('spawn', {
|
|
1000
|
-
shellCommand: shellCmd,
|
|
1001
|
-
shellArgs,
|
|
1002
|
-
cwd:
|
|
1003
|
-
cols:
|
|
1004
|
-
rows:
|
|
373
|
+
shellCommand: spawnPlan.shellCmd,
|
|
374
|
+
shellArgs: spawnPlan.shellArgs,
|
|
375
|
+
cwd: spawnPlan.ptyOptions.cwd,
|
|
376
|
+
cols: spawnPlan.ptyOptions.cols,
|
|
377
|
+
rows: spawnPlan.ptyOptions.rows,
|
|
1005
378
|
providerResolution: this.providerResolutionMeta,
|
|
1006
379
|
});
|
|
1007
380
|
|
|
1008
381
|
try {
|
|
1009
|
-
this.ptyProcess = this.transportFactory.spawn(
|
|
382
|
+
this.ptyProcess = this.transportFactory.spawn(
|
|
383
|
+
spawnPlan.shellCmd,
|
|
384
|
+
spawnPlan.shellArgs,
|
|
385
|
+
spawnPlan.ptyOptions,
|
|
386
|
+
);
|
|
1010
387
|
} catch (err: any) {
|
|
1011
388
|
const msg = err?.message || String(err);
|
|
1012
|
-
if (!isWin && !useShell && /posix_spawn|spawn/i.test(msg)) {
|
|
389
|
+
if (!spawnPlan.isWin && !spawnPlan.useShell && /posix_spawn|spawn/i.test(msg)) {
|
|
1013
390
|
LOG.warn('CLI', `[${this.cliType}] Direct spawn failed (${msg}), retrying via login shell`);
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
391
|
+
const retryPlan = buildCliLoginShellRetry(spawnPlan);
|
|
392
|
+
this.ptyProcess = this.transportFactory.spawn(
|
|
393
|
+
retryPlan.shellCmd,
|
|
394
|
+
retryPlan.shellArgs,
|
|
395
|
+
spawnPlan.ptyOptions,
|
|
396
|
+
);
|
|
1018
397
|
} else {
|
|
1019
|
-
|
|
1020
|
-
if (
|
|
1021
|
-
|
|
1022
|
-
? ' (working directory does not exist or is not a directory)'
|
|
1023
|
-
: /error code 740|elevation/i.test(msg)
|
|
1024
|
-
? ' (requires administrator privileges)'
|
|
1025
|
-
: /error code 2|ENOENT|not found/i.test(msg)
|
|
1026
|
-
? ` (executable not found: ${shellCmd})`
|
|
1027
|
-
: '';
|
|
1028
|
-
if (hint) {
|
|
1029
|
-
throw new Error(`Failed to spawn CLI${hint}: ${msg}`);
|
|
1030
|
-
}
|
|
398
|
+
const hint = getCliSpawnErrorHint(msg, spawnPlan.shellCmd, spawnPlan.isWin);
|
|
399
|
+
if (hint) {
|
|
400
|
+
throw new Error(`Failed to spawn CLI${hint}: ${msg}`);
|
|
1031
401
|
}
|
|
1032
402
|
throw err;
|
|
1033
403
|
}
|
|
@@ -1037,7 +407,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1037
407
|
if (Date.now() < this.resizeSuppressUntil) return;
|
|
1038
408
|
|
|
1039
409
|
if (!this.ptyProcess?.terminalQueriesHandled) {
|
|
1040
|
-
this.
|
|
410
|
+
this.pendingTerminalQueryTail = respondToCliTerminalQueries({
|
|
411
|
+
ptyProcess: this.ptyProcess,
|
|
412
|
+
pendingTail: this.pendingTerminalQueryTail,
|
|
413
|
+
data,
|
|
414
|
+
terminalScreen: this.terminalScreen,
|
|
415
|
+
});
|
|
1041
416
|
}
|
|
1042
417
|
|
|
1043
418
|
this.pendingOutputParseBuffer += data;
|
|
@@ -1116,9 +491,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1116
491
|
this.recordTrace('output', {
|
|
1117
492
|
rawLength: rawData.length,
|
|
1118
493
|
cleanLength: cleanData.length,
|
|
1119
|
-
rawPreview:
|
|
1120
|
-
cleanPreview:
|
|
1121
|
-
screenText:
|
|
494
|
+
rawPreview: summarizeCliTraceText(rawData, 300),
|
|
495
|
+
cleanPreview: summarizeCliTraceText(cleanData, 300),
|
|
496
|
+
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 1200),
|
|
1122
497
|
});
|
|
1123
498
|
|
|
1124
499
|
if (this.startupParseGate) {
|
|
@@ -1411,7 +786,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1411
786
|
loggedWait = true;
|
|
1412
787
|
LOG.info(
|
|
1413
788
|
'CLI',
|
|
1414
|
-
`[${this.cliType}] Waiting for interactive prompt: hasPrompt=${hasPrompt} stableMs=${stableMs} recentOutputMs=${recentlyOutput} status=${status} startup=${startupLikelyActive} screen=${JSON.stringify(
|
|
789
|
+
`[${this.cliType}] Waiting for interactive prompt: hasPrompt=${hasPrompt} stableMs=${stableMs} recentOutputMs=${recentlyOutput} status=${status} startup=${startupLikelyActive} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)}`
|
|
1415
790
|
);
|
|
1416
791
|
}
|
|
1417
792
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
@@ -1420,7 +795,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1420
795
|
const finalScreenText = this.terminalScreen.getText() || '';
|
|
1421
796
|
LOG.warn(
|
|
1422
797
|
'CLI',
|
|
1423
|
-
`[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(
|
|
798
|
+
`[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(summarizeCliTraceText(finalScreenText, 240)).slice(0, 280)}`
|
|
1424
799
|
);
|
|
1425
800
|
}
|
|
1426
801
|
|
|
@@ -1453,24 +828,34 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1453
828
|
this.currentTurnScope,
|
|
1454
829
|
);
|
|
1455
830
|
const parsedMessages = Array.isArray(parsedTranscript?.messages)
|
|
1456
|
-
?
|
|
831
|
+
? normalizeCliParsedMessages(parsedTranscript.messages, {
|
|
832
|
+
committedMessages: this.committedMessages,
|
|
833
|
+
scope: this.currentTurnScope,
|
|
834
|
+
lastOutputAt: this.lastOutputAt,
|
|
835
|
+
})
|
|
1457
836
|
: [];
|
|
1458
837
|
const lastParsedAssistant = [...parsedMessages].reverse().find((message) => message.role === 'assistant');
|
|
1459
838
|
this.recordTrace('settled', {
|
|
1460
|
-
tail:
|
|
1461
|
-
screenText:
|
|
839
|
+
tail: summarizeCliTraceText(tail, 500),
|
|
840
|
+
screenText: summarizeCliTraceText(screenText, 1200),
|
|
1462
841
|
detectStatus: scriptStatus,
|
|
1463
842
|
parsedStatus: parsedTranscript?.status || null,
|
|
1464
843
|
parsedMessageCount: parsedMessages.length,
|
|
1465
|
-
parsedLastAssistant: lastParsedAssistant ?
|
|
844
|
+
parsedLastAssistant: lastParsedAssistant ? summarizeCliTraceText(lastParsedAssistant.content, 280) : '',
|
|
1466
845
|
parsedActiveModal: parsedTranscript?.activeModal ?? null,
|
|
1467
846
|
approval: modal,
|
|
1468
|
-
...
|
|
847
|
+
...buildCliTraceParseSnapshot({
|
|
848
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
849
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
850
|
+
responseBuffer: this.responseBuffer,
|
|
851
|
+
partialResponse: this.responseBuffer,
|
|
852
|
+
scope: this.currentTurnScope,
|
|
853
|
+
}),
|
|
1469
854
|
});
|
|
1470
855
|
if (this.currentTurnScope && !lastParsedAssistant) {
|
|
1471
856
|
LOG.info(
|
|
1472
857
|
'CLI',
|
|
1473
|
-
`[${this.cliType}] Settled without assistant: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(
|
|
858
|
+
`[${this.cliType}] Settled without assistant: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'}`
|
|
1474
859
|
);
|
|
1475
860
|
}
|
|
1476
861
|
if (!scriptStatus) return;
|
|
@@ -1537,7 +922,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1537
922
|
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
1538
923
|
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1539
924
|
holdMs: ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS,
|
|
1540
|
-
...
|
|
925
|
+
...buildCliTraceParseSnapshot({
|
|
926
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
927
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
928
|
+
responseBuffer: this.responseBuffer,
|
|
929
|
+
partialResponse: this.responseBuffer,
|
|
930
|
+
scope: this.currentTurnScope,
|
|
931
|
+
}),
|
|
1541
932
|
});
|
|
1542
933
|
this.onStatusChange?.();
|
|
1543
934
|
return;
|
|
@@ -1654,7 +1045,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1654
1045
|
canFinishImmediately,
|
|
1655
1046
|
submitPendingUntil: this.submitPendingUntil,
|
|
1656
1047
|
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
1657
|
-
...
|
|
1048
|
+
...buildCliTraceParseSnapshot({
|
|
1049
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1050
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1051
|
+
responseBuffer: this.responseBuffer,
|
|
1052
|
+
partialResponse: this.responseBuffer,
|
|
1053
|
+
scope: this.currentTurnScope,
|
|
1054
|
+
}),
|
|
1658
1055
|
});
|
|
1659
1056
|
|
|
1660
1057
|
if (canFinishImmediately) {
|
|
@@ -1693,7 +1090,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1693
1090
|
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
1694
1091
|
this.clearIdleFinishCandidate('finish_response_enter');
|
|
1695
1092
|
this.recordTrace('finish_response', {
|
|
1696
|
-
...
|
|
1093
|
+
...buildCliTraceParseSnapshot({
|
|
1094
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1095
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1096
|
+
responseBuffer: this.responseBuffer,
|
|
1097
|
+
partialResponse: this.responseBuffer,
|
|
1098
|
+
scope: this.currentTurnScope,
|
|
1099
|
+
}),
|
|
1697
1100
|
});
|
|
1698
1101
|
const commitResult = this.commitCurrentTranscript();
|
|
1699
1102
|
if (this.shouldRetryFinishResponse(commitResult)) {
|
|
@@ -1701,8 +1104,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1701
1104
|
this.recordTrace('finish_response_retry', {
|
|
1702
1105
|
retryCount: this.finishRetryCount,
|
|
1703
1106
|
retryDelayMs: ProviderCliAdapter.FINISH_RETRY_DELAY_MS,
|
|
1704
|
-
assistantContent:
|
|
1705
|
-
...
|
|
1107
|
+
assistantContent: summarizeCliTraceText(commitResult.assistantContent, 220),
|
|
1108
|
+
...buildCliTraceParseSnapshot({
|
|
1109
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1110
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1111
|
+
responseBuffer: this.responseBuffer,
|
|
1112
|
+
partialResponse: this.responseBuffer,
|
|
1113
|
+
scope: this.currentTurnScope,
|
|
1114
|
+
}),
|
|
1706
1115
|
});
|
|
1707
1116
|
if (this.finishRetryTimer) clearTimeout(this.finishRetryTimer);
|
|
1708
1117
|
this.finishRetryTimer = setTimeout(() => {
|
|
@@ -1738,7 +1147,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1738
1147
|
this.currentTurnScope,
|
|
1739
1148
|
);
|
|
1740
1149
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1741
|
-
this.committedMessages =
|
|
1150
|
+
this.committedMessages = normalizeCliParsedMessages(parsed.messages, {
|
|
1151
|
+
committedMessages: this.committedMessages,
|
|
1152
|
+
scope: this.currentTurnScope,
|
|
1153
|
+
lastOutputAt: this.lastOutputAt,
|
|
1154
|
+
});
|
|
1742
1155
|
const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
|
|
1743
1156
|
if (promptForTrim) {
|
|
1744
1157
|
const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === 'assistant');
|
|
@@ -1751,14 +1164,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1751
1164
|
this.recordTrace('commit_transcript', {
|
|
1752
1165
|
parsedStatus: parsed.status || null,
|
|
1753
1166
|
messageCount: this.committedMessages.length,
|
|
1754
|
-
lastAssistant: lastAssistant ?
|
|
1755
|
-
messages:
|
|
1756
|
-
...
|
|
1167
|
+
lastAssistant: lastAssistant ? summarizeCliTraceText(lastAssistant.content, 320) : '',
|
|
1168
|
+
messages: summarizeCliTraceMessages(this.committedMessages),
|
|
1169
|
+
...buildCliTraceParseSnapshot({
|
|
1170
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1171
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1172
|
+
responseBuffer: this.responseBuffer,
|
|
1173
|
+
partialResponse: this.responseBuffer,
|
|
1174
|
+
scope: this.currentTurnScope,
|
|
1175
|
+
}),
|
|
1757
1176
|
});
|
|
1758
1177
|
if (!lastAssistant && this.currentTurnScope) {
|
|
1759
1178
|
LOG.warn(
|
|
1760
1179
|
'CLI',
|
|
1761
|
-
`[${this.cliType}] Commit without assistant turn: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(
|
|
1180
|
+
`[${this.cliType}] Commit without assistant turn: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'}`
|
|
1762
1181
|
);
|
|
1763
1182
|
}
|
|
1764
1183
|
return {
|
|
@@ -1863,14 +1282,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1863
1282
|
const hydratedMessages = shouldPreferCommittedMessages
|
|
1864
1283
|
? this.committedMessages.map((message, index) => ({
|
|
1865
1284
|
...message,
|
|
1866
|
-
id:
|
|
1867
|
-
index: typeof
|
|
1868
|
-
kind:
|
|
1869
|
-
receivedAt: typeof
|
|
1870
|
-
?
|
|
1285
|
+
id: message.id || `msg_${index}`,
|
|
1286
|
+
index: typeof message.index === 'number' ? message.index : index,
|
|
1287
|
+
kind: message.kind || 'standard',
|
|
1288
|
+
receivedAt: typeof message.receivedAt === 'number'
|
|
1289
|
+
? message.receivedAt
|
|
1871
1290
|
: message.timestamp,
|
|
1872
1291
|
}))
|
|
1873
|
-
:
|
|
1292
|
+
: hydrateCliParsedMessages(parsed.messages, {
|
|
1293
|
+
committedMessages: this.committedMessages,
|
|
1294
|
+
scope: this.currentTurnScope,
|
|
1295
|
+
lastOutputAt: this.lastOutputAt,
|
|
1296
|
+
});
|
|
1874
1297
|
return {
|
|
1875
1298
|
id: parsed.id || 'cli_session',
|
|
1876
1299
|
status: parsed.status || this.currentStatus,
|
|
@@ -1903,11 +1326,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1903
1326
|
if (typeof fn !== 'function') {
|
|
1904
1327
|
throw new Error(`CLI script '${scriptName}' not available`);
|
|
1905
1328
|
}
|
|
1906
|
-
const input =
|
|
1907
|
-
this.
|
|
1908
|
-
this.
|
|
1909
|
-
this.
|
|
1910
|
-
|
|
1329
|
+
const input = buildCliParseInput({
|
|
1330
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1331
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1332
|
+
recentOutputBuffer: this.recentOutputBuffer,
|
|
1333
|
+
terminalScreenText: this.terminalScreen.getText(),
|
|
1334
|
+
baseMessages: this.committedMessages,
|
|
1335
|
+
partialResponse: this.responseBuffer,
|
|
1336
|
+
scope: this.currentTurnScope,
|
|
1337
|
+
runtimeSettings: this.runtimeSettings,
|
|
1338
|
+
});
|
|
1911
1339
|
return await Promise.resolve(fn({
|
|
1912
1340
|
...input,
|
|
1913
1341
|
args: args && typeof args === 'object' ? { ...args } : {},
|
|
@@ -1917,7 +1345,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1917
1345
|
private parseCurrentTranscript(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): any {
|
|
1918
1346
|
if (!this.cliScripts?.parseOutput) return null;
|
|
1919
1347
|
try {
|
|
1920
|
-
const input =
|
|
1348
|
+
const input = buildCliParseInput({
|
|
1349
|
+
accumulatedBuffer: this.accumulatedBuffer,
|
|
1350
|
+
accumulatedRawBuffer: this.accumulatedRawBuffer,
|
|
1351
|
+
recentOutputBuffer: this.recentOutputBuffer,
|
|
1352
|
+
terminalScreenText: this.terminalScreen.getText(),
|
|
1353
|
+
baseMessages,
|
|
1354
|
+
partialResponse,
|
|
1355
|
+
scope,
|
|
1356
|
+
runtimeSettings: this.runtimeSettings,
|
|
1357
|
+
});
|
|
1921
1358
|
const parsed = this.cliScripts.parseOutput(input);
|
|
1922
1359
|
const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === 'string' ? parsed.status : null, input.recentBuffer, input.screenText);
|
|
1923
1360
|
if (parsed && refinedStatus && parsed.status !== refinedStatus) {
|
|
@@ -2005,7 +1442,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2005
1442
|
rawBufferStart: this.accumulatedRawBuffer.length,
|
|
2006
1443
|
};
|
|
2007
1444
|
this.recordTrace('send_message', {
|
|
2008
|
-
text:
|
|
1445
|
+
text: summarizeCliTraceText(text, 500),
|
|
2009
1446
|
estimatedLines: estimatePromptDisplayLines(text),
|
|
2010
1447
|
turnScope: this.currentTurnScope,
|
|
2011
1448
|
});
|
|
@@ -2040,7 +1477,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2040
1477
|
this.recordTrace('submit_write', {
|
|
2041
1478
|
mode: 'submit_key',
|
|
2042
1479
|
sendKey: this.sendKey,
|
|
2043
|
-
screenText:
|
|
1480
|
+
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500),
|
|
2044
1481
|
});
|
|
2045
1482
|
this.ptyProcess.write(this.sendKey);
|
|
2046
1483
|
const retrySubmitIfStuck = (attempt: number) => {
|
|
@@ -2057,7 +1494,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2057
1494
|
mode: 'submit_retry',
|
|
2058
1495
|
attempt,
|
|
2059
1496
|
sendKey: this.sendKey,
|
|
2060
|
-
screenText:
|
|
1497
|
+
screenText: summarizeCliTraceText(screenText, 500),
|
|
2061
1498
|
});
|
|
2062
1499
|
this.ptyProcess.write(this.sendKey);
|
|
2063
1500
|
if (attempt >= 3) {
|
|
@@ -2074,9 +1511,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2074
1511
|
this.submitPendingUntil = 0;
|
|
2075
1512
|
this.recordTrace('submit_write', {
|
|
2076
1513
|
mode: 'immediate',
|
|
2077
|
-
text:
|
|
1514
|
+
text: summarizeCliTraceText(text, 500),
|
|
2078
1515
|
sendKey: this.sendKey,
|
|
2079
|
-
screenText:
|
|
1516
|
+
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500),
|
|
2080
1517
|
});
|
|
2081
1518
|
this.ptyProcess.write(text + this.sendKey);
|
|
2082
1519
|
this.submitRetryTimer = setTimeout(() => {
|
|
@@ -2092,7 +1529,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2092
1529
|
mode: 'immediate_retry',
|
|
2093
1530
|
attempt: 1,
|
|
2094
1531
|
sendKey: this.sendKey,
|
|
2095
|
-
screenText:
|
|
1532
|
+
screenText: summarizeCliTraceText(screenText, 500),
|
|
2096
1533
|
});
|
|
2097
1534
|
this.ptyProcess.write(this.sendKey);
|
|
2098
1535
|
this.submitRetryUsed = true;
|
|
@@ -2107,9 +1544,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2107
1544
|
this.ptyProcess.write(text);
|
|
2108
1545
|
this.recordTrace('submit_write', {
|
|
2109
1546
|
mode: 'type_then_submit',
|
|
2110
|
-
text:
|
|
1547
|
+
text: summarizeCliTraceText(text, 500),
|
|
2111
1548
|
sendKey: this.sendKey,
|
|
2112
|
-
screenText:
|
|
1549
|
+
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500),
|
|
2113
1550
|
});
|
|
2114
1551
|
const submitStartedAt = Date.now();
|
|
2115
1552
|
let lastNormalizedScreen = '';
|
|
@@ -2394,7 +1831,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2394
1831
|
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
2395
1832
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
2396
1833
|
hasCliScripts: this.hasCliScripts(),
|
|
2397
|
-
scriptNames:
|
|
1834
|
+
scriptNames: listCliScriptNames(this.cliScripts),
|
|
2398
1835
|
traceSessionId: this.traceSessionId,
|
|
2399
1836
|
traceEntryCount: this.traceEntries.length,
|
|
2400
1837
|
statusHistory: this.statusHistory.slice(-30),
|
|
@@ -2412,37 +1849,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2412
1849
|
providerResolution: this.providerResolutionMeta,
|
|
2413
1850
|
entryCount: this.traceEntries.length,
|
|
2414
1851
|
entries: this.traceEntries.slice(-cappedLimit),
|
|
2415
|
-
screenText:
|
|
2416
|
-
recentOutputBuffer:
|
|
2417
|
-
responseBuffer:
|
|
1852
|
+
screenText: summarizeCliTraceText(this.terminalScreen.getText(), 4000),
|
|
1853
|
+
recentOutputBuffer: summarizeCliTraceText(this.recentOutputBuffer, 1000),
|
|
1854
|
+
responseBuffer: summarizeCliTraceText(this.responseBuffer, 1200),
|
|
2418
1855
|
status: this.currentStatus,
|
|
2419
1856
|
activeModal: this.activeModal,
|
|
2420
1857
|
currentTurnScope: this.currentTurnScope,
|
|
2421
|
-
messages:
|
|
1858
|
+
messages: summarizeCliTraceMessages(this.committedMessages, 5),
|
|
2422
1859
|
};
|
|
2423
1860
|
}
|
|
2424
1861
|
|
|
2425
|
-
getProviderResolutionMeta():
|
|
1862
|
+
getProviderResolutionMeta(): ProviderResolutionMeta {
|
|
2426
1863
|
return { ...this.providerResolutionMeta };
|
|
2427
1864
|
}
|
|
2428
|
-
|
|
2429
|
-
private respondToTerminalQueries(data: string): void {
|
|
2430
|
-
if (!this.ptyProcess || !data) return;
|
|
2431
|
-
|
|
2432
|
-
const combined = this.pendingTerminalQueryTail + data;
|
|
2433
|
-
const regex = /\x1b\[(\?)?6n/g;
|
|
2434
|
-
let match: RegExpExecArray | null;
|
|
2435
|
-
|
|
2436
|
-
while ((match = regex.exec(combined)) !== null) {
|
|
2437
|
-
const cursor = this.terminalScreen.getCursorPosition();
|
|
2438
|
-
const row = Math.max(1, (cursor.row | 0) + 1);
|
|
2439
|
-
const col = Math.max(1, (cursor.col | 0) + 1);
|
|
2440
|
-
const response = match[1]
|
|
2441
|
-
? `\x1b[?${row};${col}R`
|
|
2442
|
-
: `\x1b[${row};${col}R`;
|
|
2443
|
-
this.ptyProcess.write(response);
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
|
-
this.pendingTerminalQueryTail = computeTerminalQueryTail(combined);
|
|
2447
|
-
}
|
|
2448
1865
|
}
|