@parall/claude-agent 1.31.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 +8 -7
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +128 -86
- package/dist/index.js +170 -144
- 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 +175 -119
- package/src/index.ts +200 -151
- 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
|
|
|
@@ -217,7 +241,9 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
217
241
|
await this.opts.sessionManager.shutdownAll();
|
|
218
242
|
}
|
|
219
243
|
|
|
220
|
-
|
|
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;
|
|
221
247
|
|
|
222
248
|
/**
|
|
223
249
|
* Consume a steer turn whose message was already written to stdin via
|
|
@@ -235,20 +261,31 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
235
261
|
): AsyncGenerator<RuntimeEvent> {
|
|
236
262
|
const state = this.processes.get(sessionKey);
|
|
237
263
|
if (!state || state.done) {
|
|
238
|
-
throw new Error(
|
|
264
|
+
throw new Error('process dead during steer consumption');
|
|
239
265
|
}
|
|
240
266
|
|
|
241
267
|
const groupKey = randomUUID();
|
|
242
268
|
|
|
243
|
-
|
|
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;
|
|
244
281
|
const firstRead = await Promise.race([
|
|
245
|
-
parserNext.then((r) => ({ kind:
|
|
246
|
-
new Promise<{ kind:
|
|
247
|
-
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),
|
|
248
285
|
),
|
|
249
286
|
]);
|
|
250
287
|
|
|
251
|
-
if (firstRead.kind ===
|
|
288
|
+
if (firstRead.kind === 'timeout') {
|
|
252
289
|
state.steerReadPending = parserNext;
|
|
253
290
|
log?.info?.(`steer turn timeout — steer was incorporated into previous turn`);
|
|
254
291
|
return;
|
|
@@ -260,21 +297,25 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
260
297
|
if (next.done) {
|
|
261
298
|
state.done = true;
|
|
262
299
|
this.processes.delete(sessionKey);
|
|
263
|
-
throw new Error(
|
|
300
|
+
throw new Error('process exited while consuming steer turn');
|
|
264
301
|
}
|
|
265
302
|
const parsed = next.value;
|
|
266
303
|
|
|
267
|
-
if (parsed.type ===
|
|
304
|
+
if (parsed.type === 'session_id') {
|
|
268
305
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
269
|
-
yield {
|
|
270
|
-
|
|
306
|
+
yield {
|
|
307
|
+
type: 'runtime_session',
|
|
308
|
+
runtimeSessionId: parsed.sessionId,
|
|
309
|
+
runtimeLaneKey: sessionKey,
|
|
310
|
+
};
|
|
311
|
+
} else if (parsed.type === 'turn_end') {
|
|
271
312
|
if (state.needsRestart) this.killProcess(sessionKey, state);
|
|
272
313
|
return;
|
|
273
|
-
} else if (parsed.type ===
|
|
314
|
+
} else if (parsed.type === 'error') {
|
|
274
315
|
yield parsed;
|
|
275
|
-
} else if (parsed.type ===
|
|
316
|
+
} else if (parsed.type === 'text') {
|
|
276
317
|
yield { ...parsed, project: false, groupKey };
|
|
277
|
-
} else if (parsed.type ===
|
|
318
|
+
} else if (parsed.type === 'runtime_session') {
|
|
278
319
|
yield parsed;
|
|
279
320
|
} else {
|
|
280
321
|
yield { ...parsed, groupKey };
|
|
@@ -292,7 +333,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
292
333
|
try {
|
|
293
334
|
state = this.ensureProcess(sessionKey, log);
|
|
294
335
|
} catch (err) {
|
|
295
|
-
yield { type:
|
|
336
|
+
yield { type: 'error', message: `Claude spawn failed: ${String(err)}` };
|
|
296
337
|
return;
|
|
297
338
|
}
|
|
298
339
|
const groupKey = randomUUID();
|
|
@@ -301,7 +342,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
301
342
|
try {
|
|
302
343
|
this.writeUserMessage(state.handle, promptBody);
|
|
303
344
|
} catch (err) {
|
|
304
|
-
yield { type:
|
|
345
|
+
yield { type: 'error', message: `Claude stdin write failed: ${String(err)}` };
|
|
305
346
|
this.killProcess(sessionKey, state);
|
|
306
347
|
return;
|
|
307
348
|
}
|
|
@@ -311,18 +352,22 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
311
352
|
state.steerReadPending = undefined;
|
|
312
353
|
if (!orphaned.done) {
|
|
313
354
|
const parsed = orphaned.value;
|
|
314
|
-
if (parsed.type ===
|
|
355
|
+
if (parsed.type === 'session_id') {
|
|
315
356
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
316
|
-
yield {
|
|
317
|
-
|
|
357
|
+
yield {
|
|
358
|
+
type: 'runtime_session',
|
|
359
|
+
runtimeSessionId: parsed.sessionId,
|
|
360
|
+
runtimeLaneKey: sessionKey,
|
|
361
|
+
};
|
|
362
|
+
} else if (parsed.type === 'turn_end') {
|
|
318
363
|
if (state.needsRestart) this.killProcess(sessionKey, state);
|
|
319
364
|
return;
|
|
320
|
-
} else if (parsed.type ===
|
|
365
|
+
} else if (parsed.type === 'error') {
|
|
321
366
|
sawError = true;
|
|
322
367
|
yield parsed;
|
|
323
|
-
} else if (parsed.type ===
|
|
368
|
+
} else if (parsed.type === 'text') {
|
|
324
369
|
yield { ...parsed, project: false, groupKey };
|
|
325
|
-
} else if (parsed.type ===
|
|
370
|
+
} else if (parsed.type === 'runtime_session') {
|
|
326
371
|
yield parsed;
|
|
327
372
|
} else {
|
|
328
373
|
yield { ...parsed, groupKey };
|
|
@@ -336,16 +381,19 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
336
381
|
if (next.done) {
|
|
337
382
|
state.done = true;
|
|
338
383
|
this.processes.delete(sessionKey);
|
|
339
|
-
const detail = state.handle.stderrChunks.join(
|
|
340
|
-
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
|
+
);
|
|
341
388
|
if (detail) {
|
|
342
389
|
log?.warn?.(`subprocess stderr: ${detail}`);
|
|
343
390
|
}
|
|
344
391
|
if (!sawError) {
|
|
345
392
|
yield {
|
|
346
|
-
type:
|
|
347
|
-
message:
|
|
348
|
-
||
|
|
393
|
+
type: 'error',
|
|
394
|
+
message:
|
|
395
|
+
detail ||
|
|
396
|
+
`Claude exited with code ${exit.code ?? 'unknown'}${exit.signal ? ` (${exit.signal})` : ''}`,
|
|
349
397
|
};
|
|
350
398
|
}
|
|
351
399
|
return;
|
|
@@ -353,35 +401,35 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
353
401
|
|
|
354
402
|
const parsed = next.value;
|
|
355
403
|
|
|
356
|
-
if (parsed.type ===
|
|
404
|
+
if (parsed.type === 'session_id') {
|
|
357
405
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
358
406
|
yield {
|
|
359
|
-
type:
|
|
407
|
+
type: 'runtime_session',
|
|
360
408
|
runtimeSessionId: parsed.sessionId,
|
|
361
409
|
runtimeLaneKey: sessionKey,
|
|
362
410
|
};
|
|
363
411
|
continue;
|
|
364
412
|
}
|
|
365
413
|
|
|
366
|
-
if (parsed.type ===
|
|
414
|
+
if (parsed.type === 'turn_end') {
|
|
367
415
|
if (state.needsRestart) {
|
|
368
416
|
this.killProcess(sessionKey, state);
|
|
369
417
|
}
|
|
370
418
|
return;
|
|
371
419
|
}
|
|
372
420
|
|
|
373
|
-
if (parsed.type ===
|
|
421
|
+
if (parsed.type === 'error') {
|
|
374
422
|
sawError = true;
|
|
375
423
|
yield parsed;
|
|
376
424
|
continue;
|
|
377
425
|
}
|
|
378
426
|
|
|
379
|
-
if (parsed.type ===
|
|
427
|
+
if (parsed.type === 'text') {
|
|
380
428
|
yield { ...parsed, project: false, groupKey };
|
|
381
429
|
continue;
|
|
382
430
|
}
|
|
383
431
|
|
|
384
|
-
if (parsed.type ===
|
|
432
|
+
if (parsed.type === 'runtime_session') {
|
|
385
433
|
yield parsed;
|
|
386
434
|
continue;
|
|
387
435
|
}
|
|
@@ -392,7 +440,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
392
440
|
|
|
393
441
|
private ensureProcess(sessionKey: string, log: GatewayLogger | undefined): ProcessState {
|
|
394
442
|
if (this.shuttingDown) {
|
|
395
|
-
throw new Error(
|
|
443
|
+
throw new Error('adapter shutting down, refusing new process');
|
|
396
444
|
}
|
|
397
445
|
|
|
398
446
|
const existing = this.processes.get(sessionKey);
|
|
@@ -425,39 +473,43 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
425
473
|
);
|
|
426
474
|
|
|
427
475
|
log?.info(
|
|
428
|
-
`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})`,
|
|
429
477
|
);
|
|
430
478
|
|
|
431
479
|
const proc = spawn(
|
|
432
480
|
IS_WIN32 ? quoteWin32Arg(this.opts.claudeBin) : this.opts.claudeBin,
|
|
433
|
-
IS_WIN32 ? args.map(quoteWin32Arg) : args,
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
+
);
|
|
439
489
|
|
|
440
490
|
if (!proc.stdout || !proc.stderr || !proc.stdin) {
|
|
441
|
-
throw new Error(
|
|
491
|
+
throw new Error('Claude subprocess did not provide stdio pipes');
|
|
442
492
|
}
|
|
443
493
|
|
|
444
494
|
const stderrChunks: string[] = [];
|
|
445
|
-
proc.stderr.on(
|
|
495
|
+
proc.stderr.on('data', (chunk: Buffer) => {
|
|
446
496
|
stderrChunks.push(chunk.toString());
|
|
447
497
|
});
|
|
448
|
-
proc.stdin.on(
|
|
498
|
+
proc.stdin.on('error', () => {
|
|
449
499
|
proc.stdin.destroy();
|
|
450
500
|
if (proc.exitCode === null && proc.signalCode === null) {
|
|
451
501
|
if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
|
|
452
|
-
proc.kill(
|
|
502
|
+
proc.kill('SIGTERM');
|
|
453
503
|
}
|
|
454
504
|
}
|
|
455
505
|
});
|
|
456
506
|
|
|
457
|
-
const exitPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
+
);
|
|
461
513
|
|
|
462
514
|
return { proc, exitPromise, stderrChunks };
|
|
463
515
|
}
|
|
@@ -470,22 +522,26 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
470
522
|
}
|
|
471
523
|
try {
|
|
472
524
|
state.handle.proc.stdin.end();
|
|
473
|
-
} catch {
|
|
525
|
+
} catch {
|
|
526
|
+
/* best-effort */
|
|
527
|
+
}
|
|
474
528
|
if (state.handle.proc.exitCode === null && state.handle.proc.signalCode === null) {
|
|
475
529
|
try {
|
|
476
530
|
if (!IS_WIN32 || !state.handle.proc.pid || !killWin32Tree(state.handle.proc.pid)) {
|
|
477
|
-
state.handle.proc.kill(
|
|
531
|
+
state.handle.proc.kill('SIGTERM');
|
|
478
532
|
}
|
|
479
|
-
} catch {
|
|
533
|
+
} catch {
|
|
534
|
+
/* best-effort */
|
|
535
|
+
}
|
|
480
536
|
}
|
|
481
537
|
}
|
|
482
538
|
|
|
483
539
|
private writeUserMessage(handle: ClaudeProcessHandle, text: string) {
|
|
484
540
|
const payload = JSON.stringify({
|
|
485
|
-
type:
|
|
541
|
+
type: 'user',
|
|
486
542
|
message: {
|
|
487
|
-
role:
|
|
488
|
-
content: [{ type:
|
|
543
|
+
role: 'user',
|
|
544
|
+
content: [{ type: 'text', text }],
|
|
489
545
|
},
|
|
490
546
|
});
|
|
491
547
|
handle.proc.stdin.write(`${payload}\n`);
|
|
@@ -493,60 +549,60 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
493
549
|
|
|
494
550
|
private buildArgs(sessionKey: string): string[] {
|
|
495
551
|
const args = [
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
552
|
+
'--verbose',
|
|
553
|
+
'--input-format',
|
|
554
|
+
'stream-json',
|
|
555
|
+
'--output-format',
|
|
556
|
+
'stream-json',
|
|
557
|
+
'--permission-mode',
|
|
502
558
|
this.opts.permissionMode,
|
|
503
559
|
];
|
|
504
560
|
|
|
505
561
|
if (this._model) {
|
|
506
|
-
args.push(
|
|
562
|
+
args.push('--model', this._model);
|
|
507
563
|
}
|
|
508
564
|
|
|
509
565
|
if (this.opts.allowedTools.length > 0) {
|
|
510
|
-
args.push(
|
|
566
|
+
args.push('--allowedTools', this.opts.allowedTools.join(','));
|
|
511
567
|
}
|
|
512
568
|
|
|
513
569
|
if (this.opts.disallowedTools.length > 0) {
|
|
514
|
-
args.push(
|
|
570
|
+
args.push('--disallowedTools', this.opts.disallowedTools.join(','));
|
|
515
571
|
}
|
|
516
572
|
|
|
517
573
|
args.push(
|
|
518
|
-
|
|
519
|
-
path.join(this.opts.workspaceDir,
|
|
574
|
+
'--append-system-prompt-file',
|
|
575
|
+
path.join(this.opts.workspaceDir, '.parall', 'system-prompt.md'),
|
|
520
576
|
);
|
|
521
577
|
|
|
522
578
|
if (this.opts.appendSystemPrompt) {
|
|
523
|
-
args.push(
|
|
579
|
+
args.push('--append-system-prompt', this.opts.appendSystemPrompt);
|
|
524
580
|
}
|
|
525
581
|
|
|
526
582
|
if (this.opts.additionalDirs.length > 0) {
|
|
527
|
-
args.push(
|
|
583
|
+
args.push('--add-dir', ...this.opts.additionalDirs);
|
|
528
584
|
}
|
|
529
585
|
|
|
530
586
|
args.push(...this.opts.sessionManager.getResumeArgs(sessionKey));
|
|
531
587
|
return args;
|
|
532
588
|
}
|
|
533
589
|
|
|
534
|
-
private buildPlaceholderContext(sessionKey: string): DispatchOpts[
|
|
590
|
+
private buildPlaceholderContext(sessionKey: string): DispatchOpts['context'] {
|
|
535
591
|
return {
|
|
536
|
-
accountId:
|
|
592
|
+
accountId: '',
|
|
537
593
|
apiUrl: this.opts.apiUrl,
|
|
538
594
|
apiKey: this.opts.apiKey,
|
|
539
595
|
orgId: this.opts.orgId,
|
|
540
|
-
agentUserId:
|
|
541
|
-
runtimeType:
|
|
542
|
-
runtimeKey:
|
|
543
|
-
sessionId:
|
|
544
|
-
chatId:
|
|
545
|
-
triggerMessageId:
|
|
596
|
+
agentUserId: '',
|
|
597
|
+
runtimeType: '',
|
|
598
|
+
runtimeKey: '',
|
|
599
|
+
sessionId: '',
|
|
600
|
+
chatId: '',
|
|
601
|
+
triggerMessageId: '',
|
|
546
602
|
noReply: false,
|
|
547
|
-
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ??
|
|
548
|
-
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ??
|
|
549
|
-
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'],
|
|
550
606
|
};
|
|
551
607
|
}
|
|
552
608
|
}
|