@parall/claude-agent 1.30.0 → 1.32.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/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -18
- package/dist/dispatch.d.ts +9 -7
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +134 -88
- package/dist/index.js +185 -134
- package/dist/output-parser.d.ts +4 -4
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +41 -41
- package/dist/session-manager.d.ts +2 -2
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +12 -10
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.js +9 -9
- package/package.json +4 -4
- package/src/config.ts +27 -19
- package/src/dispatch.ts +182 -121
- package/src/index.ts +214 -142
- package/src/output-parser.ts +54 -49
- package/src/session-manager.ts +31 -28
- package/src/workspace.ts +10 -10
package/src/dispatch.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import { randomUUID } from
|
|
4
|
-
import { execSync, spawn } from
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { execSync, spawn } from 'node:child_process';
|
|
5
5
|
import {
|
|
6
6
|
appendPreparedLocalAttachmentRefs,
|
|
7
7
|
pinLocalAttachmentPaths,
|
|
8
|
-
} from
|
|
8
|
+
} from '@parall/agent-core/internal/attachment-input';
|
|
9
9
|
import type {
|
|
10
10
|
CleanupForkOpts,
|
|
11
11
|
DispatchAdapter,
|
|
@@ -13,26 +13,23 @@ import type {
|
|
|
13
13
|
ForkOpts,
|
|
14
14
|
GatewayLogger,
|
|
15
15
|
RuntimeEvent,
|
|
16
|
-
} from
|
|
17
|
-
import type { ClaudeAgentConfig } from
|
|
18
|
-
import { parseClaudeStreamJson, type ClaudeParsedEvent } from
|
|
19
|
-
import {
|
|
20
|
-
ClaudeSessionManager,
|
|
21
|
-
type ClaudeProcessHandle,
|
|
22
|
-
} from "./session-manager.js";
|
|
16
|
+
} from '@parall/agent-core';
|
|
17
|
+
import type { ClaudeAgentConfig } from './config.js';
|
|
18
|
+
import { parseClaudeStreamJson, type ClaudeParsedEvent } from './output-parser.js';
|
|
19
|
+
import { ClaudeSessionManager, type ClaudeProcessHandle } from './session-manager.js';
|
|
23
20
|
|
|
24
21
|
type ClaudeCodeAdapterOptions = Pick<
|
|
25
22
|
ClaudeAgentConfig,
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
23
|
+
| 'additionalDirs'
|
|
24
|
+
| 'allowApiKey'
|
|
25
|
+
| 'allowedTools'
|
|
26
|
+
| 'appendSystemPrompt'
|
|
27
|
+
| 'claudeBin'
|
|
28
|
+
| 'claudeHome'
|
|
29
|
+
| 'disallowedTools'
|
|
30
|
+
| 'model'
|
|
31
|
+
| 'permissionMode'
|
|
32
|
+
| 'workspaceDir'
|
|
36
33
|
> & {
|
|
37
34
|
sessionManager: ClaudeSessionManager;
|
|
38
35
|
apiUrl: string;
|
|
@@ -43,7 +40,7 @@ type ClaudeCodeAdapterOptions = Pick<
|
|
|
43
40
|
stepIdFilePathForSession?: (sessionKey: string) => string;
|
|
44
41
|
};
|
|
45
42
|
|
|
46
|
-
const IS_WIN32 = process.platform ===
|
|
43
|
+
const IS_WIN32 = process.platform === 'win32';
|
|
47
44
|
|
|
48
45
|
function quoteWin32Arg(arg: string): string {
|
|
49
46
|
if (!/[\s"&|^<>()]/.test(arg)) return arg;
|
|
@@ -52,15 +49,17 @@ function quoteWin32Arg(arg: string): string {
|
|
|
52
49
|
|
|
53
50
|
function killWin32Tree(pid: number): boolean {
|
|
54
51
|
try {
|
|
55
|
-
execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio:
|
|
52
|
+
execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: 'ignore' });
|
|
56
53
|
return true;
|
|
57
|
-
} catch {
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
export function buildSpawnEnv(
|
|
61
60
|
parentEnv: NodeJS.ProcessEnv,
|
|
62
61
|
claudeHome: string,
|
|
63
|
-
context: DispatchOpts[
|
|
62
|
+
context: DispatchOpts['context'],
|
|
64
63
|
opts: { allowApiKey: boolean; effortLevel?: string },
|
|
65
64
|
): NodeJS.ProcessEnv {
|
|
66
65
|
const env: NodeJS.ProcessEnv = { ...parentEnv };
|
|
@@ -74,12 +73,12 @@ export function buildSpawnEnv(
|
|
|
74
73
|
PRLL_API_URL: context.apiUrl,
|
|
75
74
|
PRLL_API_KEY: context.apiKey,
|
|
76
75
|
PRLL_ORG_ID: context.orgId,
|
|
77
|
-
PRLL_SESSION_ID: context.sessionId ??
|
|
78
|
-
PRLL_CHAT_ID: context.chatId ??
|
|
79
|
-
PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ??
|
|
80
|
-
PRLL_NO_REPLY: context.noReply ?
|
|
81
|
-
PRLL_CONTEXT_FILE: context.contextFilePath ??
|
|
82
|
-
PRLL_STEP_ID_FILE: context.stepIdFilePath ??
|
|
76
|
+
PRLL_SESSION_ID: context.sessionId ?? '',
|
|
77
|
+
PRLL_CHAT_ID: context.chatId ?? '',
|
|
78
|
+
PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? '',
|
|
79
|
+
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
80
|
+
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
81
|
+
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
83
82
|
};
|
|
84
83
|
if (opts.effortLevel) {
|
|
85
84
|
result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
|
|
@@ -113,8 +112,12 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
113
112
|
this._model = opts.model;
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
get currentModel(): string | undefined {
|
|
117
|
-
|
|
115
|
+
get currentModel(): string | undefined {
|
|
116
|
+
return this._model;
|
|
117
|
+
}
|
|
118
|
+
get currentEffort(): string | undefined {
|
|
119
|
+
return this._effortLevel;
|
|
120
|
+
}
|
|
118
121
|
|
|
119
122
|
updateConfig(config: { model?: string | null; effort?: string | null }): void {
|
|
120
123
|
const modelChanged = config.model !== undefined && config.model !== this._model;
|
|
@@ -142,11 +145,28 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
abortDispatch(sessionKey: string): void {
|
|
149
|
+
this.pendingInjections.delete(sessionKey);
|
|
150
|
+
const state = this.processes.get(sessionKey);
|
|
151
|
+
if (!state || state.done) return;
|
|
152
|
+
state.done = true;
|
|
153
|
+
try {
|
|
154
|
+
state.handle.proc.stdin.end();
|
|
155
|
+
} catch {
|
|
156
|
+
/* best-effort */
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
145
160
|
hasPendingInjections(sessionKey: string): boolean {
|
|
146
161
|
return (this.pendingInjections.get(sessionKey) ?? 0) > 0;
|
|
147
162
|
}
|
|
148
163
|
|
|
149
|
-
async *dispatch({
|
|
164
|
+
async *dispatch({
|
|
165
|
+
event,
|
|
166
|
+
bodyForAgent,
|
|
167
|
+
sessionKey,
|
|
168
|
+
context,
|
|
169
|
+
}: DispatchOpts): AsyncIterable<RuntimeEvent> {
|
|
150
170
|
const pending = this.pendingInjections.get(sessionKey) ?? 0;
|
|
151
171
|
if (pending > 0) {
|
|
152
172
|
this.pendingInjections.delete(sessionKey);
|
|
@@ -167,9 +187,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
167
187
|
promptBody = prepared.body;
|
|
168
188
|
releasePreparedAttachments = pinLocalAttachmentPaths(prepared.attachments.images);
|
|
169
189
|
} catch (err) {
|
|
170
|
-
context.log?.warn?.(
|
|
171
|
-
`failed to prepare local attachments: ${String(err)}`,
|
|
172
|
-
);
|
|
190
|
+
context.log?.warn?.(`failed to prepare local attachments: ${String(err)}`);
|
|
173
191
|
}
|
|
174
192
|
|
|
175
193
|
try {
|
|
@@ -186,8 +204,14 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
186
204
|
getSessionHistoryPath(sessionKey: string): string | undefined {
|
|
187
205
|
const sessionId = this.opts.sessionManager.getSessionId(sessionKey);
|
|
188
206
|
if (!sessionId) return undefined;
|
|
189
|
-
const projectSlug = this.opts.workspaceDir.replace(/[/.]/g,
|
|
190
|
-
const filePath = path.join(
|
|
207
|
+
const projectSlug = this.opts.workspaceDir.replace(/[/.]/g, '-');
|
|
208
|
+
const filePath = path.join(
|
|
209
|
+
this.opts.claudeHome,
|
|
210
|
+
'.claude',
|
|
211
|
+
'projects',
|
|
212
|
+
projectSlug,
|
|
213
|
+
`${sessionId}.jsonl`,
|
|
214
|
+
);
|
|
191
215
|
return fs.existsSync(filePath) ? filePath : undefined;
|
|
192
216
|
}
|
|
193
217
|
|
|
@@ -203,16 +227,23 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
203
227
|
this.opts.sessionManager.cleanupFork(fork.sessionKey);
|
|
204
228
|
}
|
|
205
229
|
|
|
206
|
-
|
|
207
|
-
this.shuttingDown = true;
|
|
230
|
+
resetProcesses(): void {
|
|
208
231
|
for (const [sessionKey, state] of this.processes) {
|
|
209
232
|
this.killProcess(sessionKey, state);
|
|
210
233
|
}
|
|
211
234
|
this.processes.clear();
|
|
235
|
+
this.pendingInjections.clear();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async shutdown(): Promise<void> {
|
|
239
|
+
this.shuttingDown = true;
|
|
240
|
+
this.resetProcesses();
|
|
212
241
|
await this.opts.sessionManager.shutdownAll();
|
|
213
242
|
}
|
|
214
243
|
|
|
215
|
-
|
|
244
|
+
// Steer-turn read timeout. Overridable via PRLL_STEER_TURN_TIMEOUT_MS (ms),
|
|
245
|
+
// chiefly so tests can exercise the timeout path without a 10s wait.
|
|
246
|
+
private readonly steerTurnTimeoutMs = Number(process.env.PRLL_STEER_TURN_TIMEOUT_MS) || 10_000;
|
|
216
247
|
|
|
217
248
|
/**
|
|
218
249
|
* Consume a steer turn whose message was already written to stdin via
|
|
@@ -230,20 +261,31 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
230
261
|
): AsyncGenerator<RuntimeEvent> {
|
|
231
262
|
const state = this.processes.get(sessionKey);
|
|
232
263
|
if (!state || state.done) {
|
|
233
|
-
throw new Error(
|
|
264
|
+
throw new Error('process dead during steer consumption');
|
|
234
265
|
}
|
|
235
266
|
|
|
236
267
|
const groupKey = randomUUID();
|
|
237
268
|
|
|
238
|
-
|
|
269
|
+
// Reuse a pull already issued by a prior steer timeout instead of issuing a
|
|
270
|
+
// fresh one. A burst of N steers all takes the pending-skip path, which calls
|
|
271
|
+
// this method N times in a loop. Issuing a new state.parser.next() on each
|
|
272
|
+
// call while a previous pull is still in flight queues multiple reads on the
|
|
273
|
+
// same async generator, but only one can be stashed in the single-slot
|
|
274
|
+
// state.steerReadPending — every earlier pull is then consumed-and-dropped,
|
|
275
|
+
// silently losing parser events. When a dropped event is a tool_call, its
|
|
276
|
+
// tool_result later orphans into "call_id does not match any tool_call".
|
|
277
|
+
// Threading the single in-flight pull through every call keeps at most one
|
|
278
|
+
// outstanding read and loses nothing.
|
|
279
|
+
const parserNext = state.steerReadPending ?? state.parser.next();
|
|
280
|
+
state.steerReadPending = undefined;
|
|
239
281
|
const firstRead = await Promise.race([
|
|
240
|
-
parserNext.then((r) => ({ kind:
|
|
241
|
-
new Promise<{ kind:
|
|
242
|
-
setTimeout(() => resolve({ kind:
|
|
282
|
+
parserNext.then((r) => ({ kind: 'value' as const, result: r })),
|
|
283
|
+
new Promise<{ kind: 'timeout' }>((resolve) =>
|
|
284
|
+
setTimeout(() => resolve({ kind: 'timeout' }), this.steerTurnTimeoutMs),
|
|
243
285
|
),
|
|
244
286
|
]);
|
|
245
287
|
|
|
246
|
-
if (firstRead.kind ===
|
|
288
|
+
if (firstRead.kind === 'timeout') {
|
|
247
289
|
state.steerReadPending = parserNext;
|
|
248
290
|
log?.info?.(`steer turn timeout — steer was incorporated into previous turn`);
|
|
249
291
|
return;
|
|
@@ -255,21 +297,25 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
255
297
|
if (next.done) {
|
|
256
298
|
state.done = true;
|
|
257
299
|
this.processes.delete(sessionKey);
|
|
258
|
-
throw new Error(
|
|
300
|
+
throw new Error('process exited while consuming steer turn');
|
|
259
301
|
}
|
|
260
302
|
const parsed = next.value;
|
|
261
303
|
|
|
262
|
-
if (parsed.type ===
|
|
304
|
+
if (parsed.type === 'session_id') {
|
|
263
305
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
264
|
-
yield {
|
|
265
|
-
|
|
306
|
+
yield {
|
|
307
|
+
type: 'runtime_session',
|
|
308
|
+
runtimeSessionId: parsed.sessionId,
|
|
309
|
+
runtimeLaneKey: sessionKey,
|
|
310
|
+
};
|
|
311
|
+
} else if (parsed.type === 'turn_end') {
|
|
266
312
|
if (state.needsRestart) this.killProcess(sessionKey, state);
|
|
267
313
|
return;
|
|
268
|
-
} else if (parsed.type ===
|
|
314
|
+
} else if (parsed.type === 'error') {
|
|
269
315
|
yield parsed;
|
|
270
|
-
} else if (parsed.type ===
|
|
316
|
+
} else if (parsed.type === 'text') {
|
|
271
317
|
yield { ...parsed, project: false, groupKey };
|
|
272
|
-
} else if (parsed.type ===
|
|
318
|
+
} else if (parsed.type === 'runtime_session') {
|
|
273
319
|
yield parsed;
|
|
274
320
|
} else {
|
|
275
321
|
yield { ...parsed, groupKey };
|
|
@@ -287,7 +333,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
287
333
|
try {
|
|
288
334
|
state = this.ensureProcess(sessionKey, log);
|
|
289
335
|
} catch (err) {
|
|
290
|
-
yield { type:
|
|
336
|
+
yield { type: 'error', message: `Claude spawn failed: ${String(err)}` };
|
|
291
337
|
return;
|
|
292
338
|
}
|
|
293
339
|
const groupKey = randomUUID();
|
|
@@ -296,7 +342,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
296
342
|
try {
|
|
297
343
|
this.writeUserMessage(state.handle, promptBody);
|
|
298
344
|
} catch (err) {
|
|
299
|
-
yield { type:
|
|
345
|
+
yield { type: 'error', message: `Claude stdin write failed: ${String(err)}` };
|
|
300
346
|
this.killProcess(sessionKey, state);
|
|
301
347
|
return;
|
|
302
348
|
}
|
|
@@ -306,18 +352,22 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
306
352
|
state.steerReadPending = undefined;
|
|
307
353
|
if (!orphaned.done) {
|
|
308
354
|
const parsed = orphaned.value;
|
|
309
|
-
if (parsed.type ===
|
|
355
|
+
if (parsed.type === 'session_id') {
|
|
310
356
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
311
|
-
yield {
|
|
312
|
-
|
|
357
|
+
yield {
|
|
358
|
+
type: 'runtime_session',
|
|
359
|
+
runtimeSessionId: parsed.sessionId,
|
|
360
|
+
runtimeLaneKey: sessionKey,
|
|
361
|
+
};
|
|
362
|
+
} else if (parsed.type === 'turn_end') {
|
|
313
363
|
if (state.needsRestart) this.killProcess(sessionKey, state);
|
|
314
364
|
return;
|
|
315
|
-
} else if (parsed.type ===
|
|
365
|
+
} else if (parsed.type === 'error') {
|
|
316
366
|
sawError = true;
|
|
317
367
|
yield parsed;
|
|
318
|
-
} else if (parsed.type ===
|
|
368
|
+
} else if (parsed.type === 'text') {
|
|
319
369
|
yield { ...parsed, project: false, groupKey };
|
|
320
|
-
} else if (parsed.type ===
|
|
370
|
+
} else if (parsed.type === 'runtime_session') {
|
|
321
371
|
yield parsed;
|
|
322
372
|
} else {
|
|
323
373
|
yield { ...parsed, groupKey };
|
|
@@ -331,16 +381,19 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
331
381
|
if (next.done) {
|
|
332
382
|
state.done = true;
|
|
333
383
|
this.processes.delete(sessionKey);
|
|
334
|
-
const detail = state.handle.stderrChunks.join(
|
|
335
|
-
const exit = await state.handle.exitPromise.catch(
|
|
384
|
+
const detail = state.handle.stderrChunks.join('').trim();
|
|
385
|
+
const exit = await state.handle.exitPromise.catch(
|
|
386
|
+
() => ({ code: null, signal: null }) as const,
|
|
387
|
+
);
|
|
336
388
|
if (detail) {
|
|
337
389
|
log?.warn?.(`subprocess stderr: ${detail}`);
|
|
338
390
|
}
|
|
339
391
|
if (!sawError) {
|
|
340
392
|
yield {
|
|
341
|
-
type:
|
|
342
|
-
message:
|
|
343
|
-
||
|
|
393
|
+
type: 'error',
|
|
394
|
+
message:
|
|
395
|
+
detail ||
|
|
396
|
+
`Claude exited with code ${exit.code ?? 'unknown'}${exit.signal ? ` (${exit.signal})` : ''}`,
|
|
344
397
|
};
|
|
345
398
|
}
|
|
346
399
|
return;
|
|
@@ -348,35 +401,35 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
348
401
|
|
|
349
402
|
const parsed = next.value;
|
|
350
403
|
|
|
351
|
-
if (parsed.type ===
|
|
404
|
+
if (parsed.type === 'session_id') {
|
|
352
405
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
353
406
|
yield {
|
|
354
|
-
type:
|
|
407
|
+
type: 'runtime_session',
|
|
355
408
|
runtimeSessionId: parsed.sessionId,
|
|
356
409
|
runtimeLaneKey: sessionKey,
|
|
357
410
|
};
|
|
358
411
|
continue;
|
|
359
412
|
}
|
|
360
413
|
|
|
361
|
-
if (parsed.type ===
|
|
414
|
+
if (parsed.type === 'turn_end') {
|
|
362
415
|
if (state.needsRestart) {
|
|
363
416
|
this.killProcess(sessionKey, state);
|
|
364
417
|
}
|
|
365
418
|
return;
|
|
366
419
|
}
|
|
367
420
|
|
|
368
|
-
if (parsed.type ===
|
|
421
|
+
if (parsed.type === 'error') {
|
|
369
422
|
sawError = true;
|
|
370
423
|
yield parsed;
|
|
371
424
|
continue;
|
|
372
425
|
}
|
|
373
426
|
|
|
374
|
-
if (parsed.type ===
|
|
427
|
+
if (parsed.type === 'text') {
|
|
375
428
|
yield { ...parsed, project: false, groupKey };
|
|
376
429
|
continue;
|
|
377
430
|
}
|
|
378
431
|
|
|
379
|
-
if (parsed.type ===
|
|
432
|
+
if (parsed.type === 'runtime_session') {
|
|
380
433
|
yield parsed;
|
|
381
434
|
continue;
|
|
382
435
|
}
|
|
@@ -387,7 +440,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
387
440
|
|
|
388
441
|
private ensureProcess(sessionKey: string, log: GatewayLogger | undefined): ProcessState {
|
|
389
442
|
if (this.shuttingDown) {
|
|
390
|
-
throw new Error(
|
|
443
|
+
throw new Error('adapter shutting down, refusing new process');
|
|
391
444
|
}
|
|
392
445
|
|
|
393
446
|
const existing = this.processes.get(sessionKey);
|
|
@@ -420,39 +473,43 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
420
473
|
);
|
|
421
474
|
|
|
422
475
|
log?.info(
|
|
423
|
-
`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model ||
|
|
476
|
+
`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`,
|
|
424
477
|
);
|
|
425
478
|
|
|
426
479
|
const proc = spawn(
|
|
427
480
|
IS_WIN32 ? quoteWin32Arg(this.opts.claudeBin) : this.opts.claudeBin,
|
|
428
|
-
IS_WIN32 ? args.map(quoteWin32Arg) : args,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
481
|
+
IS_WIN32 ? args.map(quoteWin32Arg) : args,
|
|
482
|
+
{
|
|
483
|
+
cwd: this.opts.workspaceDir,
|
|
484
|
+
env,
|
|
485
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
486
|
+
shell: IS_WIN32,
|
|
487
|
+
},
|
|
488
|
+
);
|
|
434
489
|
|
|
435
490
|
if (!proc.stdout || !proc.stderr || !proc.stdin) {
|
|
436
|
-
throw new Error(
|
|
491
|
+
throw new Error('Claude subprocess did not provide stdio pipes');
|
|
437
492
|
}
|
|
438
493
|
|
|
439
494
|
const stderrChunks: string[] = [];
|
|
440
|
-
proc.stderr.on(
|
|
495
|
+
proc.stderr.on('data', (chunk: Buffer) => {
|
|
441
496
|
stderrChunks.push(chunk.toString());
|
|
442
497
|
});
|
|
443
|
-
proc.stdin.on(
|
|
498
|
+
proc.stdin.on('error', () => {
|
|
444
499
|
proc.stdin.destroy();
|
|
445
500
|
if (proc.exitCode === null && proc.signalCode === null) {
|
|
446
501
|
if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
|
|
447
|
-
proc.kill(
|
|
502
|
+
proc.kill('SIGTERM');
|
|
448
503
|
}
|
|
449
504
|
}
|
|
450
505
|
});
|
|
451
506
|
|
|
452
|
-
const exitPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
507
|
+
const exitPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
|
|
508
|
+
(resolve) => {
|
|
509
|
+
proc.once('close', (code, signal) => resolve({ code, signal }));
|
|
510
|
+
proc.once('error', () => resolve({ code: null, signal: null }));
|
|
511
|
+
},
|
|
512
|
+
);
|
|
456
513
|
|
|
457
514
|
return { proc, exitPromise, stderrChunks };
|
|
458
515
|
}
|
|
@@ -465,22 +522,26 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
465
522
|
}
|
|
466
523
|
try {
|
|
467
524
|
state.handle.proc.stdin.end();
|
|
468
|
-
} catch {
|
|
525
|
+
} catch {
|
|
526
|
+
/* best-effort */
|
|
527
|
+
}
|
|
469
528
|
if (state.handle.proc.exitCode === null && state.handle.proc.signalCode === null) {
|
|
470
529
|
try {
|
|
471
530
|
if (!IS_WIN32 || !state.handle.proc.pid || !killWin32Tree(state.handle.proc.pid)) {
|
|
472
|
-
state.handle.proc.kill(
|
|
531
|
+
state.handle.proc.kill('SIGTERM');
|
|
473
532
|
}
|
|
474
|
-
} catch {
|
|
533
|
+
} catch {
|
|
534
|
+
/* best-effort */
|
|
535
|
+
}
|
|
475
536
|
}
|
|
476
537
|
}
|
|
477
538
|
|
|
478
539
|
private writeUserMessage(handle: ClaudeProcessHandle, text: string) {
|
|
479
540
|
const payload = JSON.stringify({
|
|
480
|
-
type:
|
|
541
|
+
type: 'user',
|
|
481
542
|
message: {
|
|
482
|
-
role:
|
|
483
|
-
content: [{ type:
|
|
543
|
+
role: 'user',
|
|
544
|
+
content: [{ type: 'text', text }],
|
|
484
545
|
},
|
|
485
546
|
});
|
|
486
547
|
handle.proc.stdin.write(`${payload}\n`);
|
|
@@ -488,60 +549,60 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
488
549
|
|
|
489
550
|
private buildArgs(sessionKey: string): string[] {
|
|
490
551
|
const args = [
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
552
|
+
'--verbose',
|
|
553
|
+
'--input-format',
|
|
554
|
+
'stream-json',
|
|
555
|
+
'--output-format',
|
|
556
|
+
'stream-json',
|
|
557
|
+
'--permission-mode',
|
|
497
558
|
this.opts.permissionMode,
|
|
498
559
|
];
|
|
499
560
|
|
|
500
561
|
if (this._model) {
|
|
501
|
-
args.push(
|
|
562
|
+
args.push('--model', this._model);
|
|
502
563
|
}
|
|
503
564
|
|
|
504
565
|
if (this.opts.allowedTools.length > 0) {
|
|
505
|
-
args.push(
|
|
566
|
+
args.push('--allowedTools', this.opts.allowedTools.join(','));
|
|
506
567
|
}
|
|
507
568
|
|
|
508
569
|
if (this.opts.disallowedTools.length > 0) {
|
|
509
|
-
args.push(
|
|
570
|
+
args.push('--disallowedTools', this.opts.disallowedTools.join(','));
|
|
510
571
|
}
|
|
511
572
|
|
|
512
573
|
args.push(
|
|
513
|
-
|
|
514
|
-
path.join(this.opts.workspaceDir,
|
|
574
|
+
'--append-system-prompt-file',
|
|
575
|
+
path.join(this.opts.workspaceDir, '.parall', 'system-prompt.md'),
|
|
515
576
|
);
|
|
516
577
|
|
|
517
578
|
if (this.opts.appendSystemPrompt) {
|
|
518
|
-
args.push(
|
|
579
|
+
args.push('--append-system-prompt', this.opts.appendSystemPrompt);
|
|
519
580
|
}
|
|
520
581
|
|
|
521
582
|
if (this.opts.additionalDirs.length > 0) {
|
|
522
|
-
args.push(
|
|
583
|
+
args.push('--add-dir', ...this.opts.additionalDirs);
|
|
523
584
|
}
|
|
524
585
|
|
|
525
586
|
args.push(...this.opts.sessionManager.getResumeArgs(sessionKey));
|
|
526
587
|
return args;
|
|
527
588
|
}
|
|
528
589
|
|
|
529
|
-
private buildPlaceholderContext(sessionKey: string): DispatchOpts[
|
|
590
|
+
private buildPlaceholderContext(sessionKey: string): DispatchOpts['context'] {
|
|
530
591
|
return {
|
|
531
|
-
accountId:
|
|
592
|
+
accountId: '',
|
|
532
593
|
apiUrl: this.opts.apiUrl,
|
|
533
594
|
apiKey: this.opts.apiKey,
|
|
534
595
|
orgId: this.opts.orgId,
|
|
535
|
-
agentUserId:
|
|
536
|
-
runtimeType:
|
|
537
|
-
runtimeKey:
|
|
538
|
-
sessionId:
|
|
539
|
-
chatId:
|
|
540
|
-
triggerMessageId:
|
|
596
|
+
agentUserId: '',
|
|
597
|
+
runtimeType: '',
|
|
598
|
+
runtimeKey: '',
|
|
599
|
+
sessionId: '',
|
|
600
|
+
chatId: '',
|
|
601
|
+
triggerMessageId: '',
|
|
541
602
|
noReply: false,
|
|
542
|
-
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ??
|
|
543
|
-
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ??
|
|
544
|
-
client: undefined as unknown as DispatchOpts[
|
|
603
|
+
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? '',
|
|
604
|
+
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? '',
|
|
605
|
+
client: undefined as unknown as DispatchOpts['context']['client'],
|
|
545
606
|
};
|
|
546
607
|
}
|
|
547
608
|
}
|