@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/src/dispatch.ts CHANGED
@@ -1,11 +1,11 @@
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";
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 "@parall/agent-core/internal/attachment-input";
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 "@parall/agent-core";
17
- import type { ClaudeAgentConfig } from "./config.js";
18
- import { parseClaudeStreamJson, type ClaudeParsedEvent } from "./output-parser.js";
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
- | "additionalDirs"
27
- | "allowApiKey"
28
- | "allowedTools"
29
- | "appendSystemPrompt"
30
- | "claudeBin"
31
- | "claudeHome"
32
- | "disallowedTools"
33
- | "model"
34
- | "permissionMode"
35
- | "workspaceDir"
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 === "win32";
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: "ignore" });
52
+ execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: 'ignore' });
56
53
  return true;
57
- } catch { return false; }
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["context"],
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 ? "1" : "",
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 { return this._model; }
117
- get currentEffort(): string | undefined { return this._effortLevel; }
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({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent> {
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(this.opts.claudeHome, ".claude", "projects", projectSlug, `${sessionId}.jsonl`);
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
- async shutdown(): Promise<void> {
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
- private static readonly STEER_TURN_TIMEOUT_MS = 10_000;
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("process dead during steer consumption");
264
+ throw new Error('process dead during steer consumption');
234
265
  }
235
266
 
236
267
  const groupKey = randomUUID();
237
268
 
238
- const parserNext = state.parser.next();
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: "value" as const, result: r })),
241
- new Promise<{ kind: "timeout" }>((resolve) =>
242
- setTimeout(() => resolve({ kind: "timeout" }), ClaudeCodeAdapter.STEER_TURN_TIMEOUT_MS),
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 === "timeout") {
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("process exited while consuming steer turn");
300
+ throw new Error('process exited while consuming steer turn');
259
301
  }
260
302
  const parsed = next.value;
261
303
 
262
- if (parsed.type === "session_id") {
304
+ if (parsed.type === 'session_id') {
263
305
  this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
264
- yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
265
- } else if (parsed.type === "turn_end") {
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 === "error") {
314
+ } else if (parsed.type === 'error') {
269
315
  yield parsed;
270
- } else if (parsed.type === "text") {
316
+ } else if (parsed.type === 'text') {
271
317
  yield { ...parsed, project: false, groupKey };
272
- } else if (parsed.type === "runtime_session") {
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: "error", message: `Claude spawn failed: ${String(err)}` };
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: "error", message: `Claude stdin write failed: ${String(err)}` };
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 === "session_id") {
355
+ if (parsed.type === 'session_id') {
310
356
  this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
311
- yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
312
- } else if (parsed.type === "turn_end") {
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 === "error") {
365
+ } else if (parsed.type === 'error') {
316
366
  sawError = true;
317
367
  yield parsed;
318
- } else if (parsed.type === "text") {
368
+ } else if (parsed.type === 'text') {
319
369
  yield { ...parsed, project: false, groupKey };
320
- } else if (parsed.type === "runtime_session") {
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("").trim();
335
- const exit = await state.handle.exitPromise.catch(() => ({ code: null, signal: null } as const));
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: "error",
342
- message: detail
343
- || `Claude exited with code ${exit.code ?? "unknown"}${exit.signal ? ` (${exit.signal})` : ""}`,
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 === "session_id") {
404
+ if (parsed.type === 'session_id') {
352
405
  this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
353
406
  yield {
354
- type: "runtime_session",
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 === "turn_end") {
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 === "error") {
421
+ if (parsed.type === 'error') {
369
422
  sawError = true;
370
423
  yield parsed;
371
424
  continue;
372
425
  }
373
426
 
374
- if (parsed.type === "text") {
427
+ if (parsed.type === 'text') {
375
428
  yield { ...parsed, project: false, groupKey };
376
429
  continue;
377
430
  }
378
431
 
379
- if (parsed.type === "runtime_session") {
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("adapter shutting down, refusing new process");
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 || "default"}, effort=${this._effortLevel || "default"}, mode=${this.opts.permissionMode})`,
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
- cwd: this.opts.workspaceDir,
430
- env,
431
- stdio: ["pipe", "pipe", "pipe"],
432
- shell: IS_WIN32,
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("Claude subprocess did not provide stdio pipes");
491
+ throw new Error('Claude subprocess did not provide stdio pipes');
437
492
  }
438
493
 
439
494
  const stderrChunks: string[] = [];
440
- proc.stderr.on("data", (chunk: Buffer) => {
495
+ proc.stderr.on('data', (chunk: Buffer) => {
441
496
  stderrChunks.push(chunk.toString());
442
497
  });
443
- proc.stdin.on("error", () => {
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("SIGTERM");
502
+ proc.kill('SIGTERM');
448
503
  }
449
504
  }
450
505
  });
451
506
 
452
- const exitPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve) => {
453
- proc.once("close", (code, signal) => resolve({ code, signal }));
454
- proc.once("error", () => resolve({ code: null, signal: null }));
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 { /* best-effort */ }
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("SIGTERM");
531
+ state.handle.proc.kill('SIGTERM');
473
532
  }
474
- } catch { /* best-effort */ }
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: "user",
541
+ type: 'user',
481
542
  message: {
482
- role: "user",
483
- content: [{ type: "text", text }],
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
- "--verbose",
492
- "--input-format",
493
- "stream-json",
494
- "--output-format",
495
- "stream-json",
496
- "--permission-mode",
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("--model", this._model);
562
+ args.push('--model', this._model);
502
563
  }
503
564
 
504
565
  if (this.opts.allowedTools.length > 0) {
505
- args.push("--allowedTools", this.opts.allowedTools.join(","));
566
+ args.push('--allowedTools', this.opts.allowedTools.join(','));
506
567
  }
507
568
 
508
569
  if (this.opts.disallowedTools.length > 0) {
509
- args.push("--disallowedTools", this.opts.disallowedTools.join(","));
570
+ args.push('--disallowedTools', this.opts.disallowedTools.join(','));
510
571
  }
511
572
 
512
573
  args.push(
513
- "--append-system-prompt-file",
514
- path.join(this.opts.workspaceDir, ".parall", "system-prompt.md"),
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("--append-system-prompt", this.opts.appendSystemPrompt);
579
+ args.push('--append-system-prompt', this.opts.appendSystemPrompt);
519
580
  }
520
581
 
521
582
  if (this.opts.additionalDirs.length > 0) {
522
- args.push("--add-dir", ...this.opts.additionalDirs);
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["context"] {
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["context"]["client"],
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
  }