@parall/claude-agent 1.31.0 → 1.32.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAsBF,wBAAgB,wBAAwB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,iBAAiB,CA4BhG;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlG;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,iDAAiD;AACjD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGrF"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAsBF,wBAAgB,wBAAwB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,iBAAiB,CAgChG;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAMR;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,iDAAiD;AACjD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGrF"}
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
- import * as os from "node:os";
2
- import * as path from "node:path";
1
+ import * as os from 'node:os';
2
+ import * as path from 'node:path';
3
3
  function requireEnv(env, name) {
4
4
  const value = env[name]?.trim();
5
5
  if (!value) {
@@ -19,12 +19,12 @@ function resolvePath(value) {
19
19
  return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
20
20
  }
21
21
  export function resolveClaudeAgentConfig(env = process.env) {
22
- const apiUrl = requireEnv(env, "PRLL_API_URL");
23
- const apiKey = requireEnv(env, "PRLL_API_KEY");
24
- const orgId = requireEnv(env, "PRLL_ORG_ID");
22
+ const apiUrl = requireEnv(env, 'PRLL_API_URL');
23
+ const apiKey = requireEnv(env, 'PRLL_API_KEY');
24
+ const orgId = requireEnv(env, 'PRLL_ORG_ID');
25
25
  const claudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os.homedir());
26
- const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim() || path.join(claudeHome, ".parall-agent"));
27
- const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path.join(stateDir, "workspace"));
26
+ const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim() || path.join(claudeHome, '.parall-agent'));
27
+ const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path.join(stateDir, 'workspace'));
28
28
  const additionalDirs = parseList(env.PRLL_CLAUDE_ADD_DIRS).map(resolvePath);
29
29
  return {
30
30
  apiUrl,
@@ -32,41 +32,41 @@ export function resolveClaudeAgentConfig(env = process.env) {
32
32
  orgId,
33
33
  wsUrl: env.PRLL_WS_URL?.trim() || undefined,
34
34
  swimlaneName: env.PRLL_SWIMLANE_NAME?.trim() || undefined,
35
- claudeBin: env.PRLL_CLAUDE_BIN?.trim() || "claude",
35
+ claudeBin: env.PRLL_CLAUDE_BIN?.trim() || 'claude',
36
36
  claudeHome,
37
37
  stateDir,
38
38
  workspaceDir,
39
39
  model: env.PRLL_CLAUDE_MODEL?.trim() || undefined,
40
- permissionMode: env.PRLL_CLAUDE_PERMISSION_MODE?.trim() || "bypassPermissions",
40
+ permissionMode: env.PRLL_CLAUDE_PERMISSION_MODE?.trim() || 'bypassPermissions',
41
41
  allowedTools: parseList(env.PRLL_CLAUDE_ALLOWED_TOOLS),
42
42
  disallowedTools: parseList(env.PRLL_CLAUDE_DISALLOWED_TOOLS),
43
43
  additionalDirs,
44
44
  appendSystemPrompt: env.PRLL_CLAUDE_APPEND_SYSTEM_PROMPT?.trim() || undefined,
45
45
  runtimeKey: env.PRLL_CLAUDE_RUNTIME_KEY?.trim() || undefined,
46
- allowApiKey: env.PRLL_CLAUDE_ALLOW_API_KEY?.trim() === "1",
46
+ allowApiKey: env.PRLL_CLAUDE_ALLOW_API_KEY?.trim() === '1',
47
47
  };
48
48
  }
49
49
  export function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
50
- const base = explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
50
+ const base = explicitWsUrl || `${apiUrl.replace(/\/$/, '').replace(/^http/, 'ws')}/ws`;
51
51
  if (!swimlaneName)
52
52
  return base;
53
53
  const url = new URL(base);
54
- url.searchParams.set("swimlane", swimlaneName);
54
+ url.searchParams.set('swimlane', swimlaneName);
55
55
  return url.toString();
56
56
  }
57
57
  export function buildClaudeRuntimeKey(agentUserId) {
58
58
  return `agent:main:claude:${agentUserId}:orchestrator`;
59
59
  }
60
60
  export function sessionStateFilePathForRuntime(stateDir, runtimeKey) {
61
- const fileName = Buffer.from(runtimeKey).toString("base64url");
62
- return path.join(stateDir, "sessions", `${fileName}.json`);
61
+ const fileName = Buffer.from(runtimeKey).toString('base64url');
62
+ return path.join(stateDir, 'sessions', `${fileName}.json`);
63
63
  }
64
64
  export function contextFilePathForSession(stateDir, sessionKey) {
65
- const fileName = Buffer.from(sessionKey).toString("base64url");
66
- return path.join(stateDir, "dispatch-context", `${fileName}.json`);
65
+ const fileName = Buffer.from(sessionKey).toString('base64url');
66
+ return path.join(stateDir, 'dispatch-context', `${fileName}.json`);
67
67
  }
68
68
  /** @deprecated Use contextFilePathForSession. */
69
69
  export function stepIdFilePathForSession(stateDir, sessionKey) {
70
- const fileName = Buffer.from(sessionKey).toString("base64url");
71
- return path.join(stateDir, "step-ids", `${fileName}.txt`);
70
+ const fileName = Buffer.from(sessionKey).toString('base64url');
71
+ return path.join(stateDir, 'step-ids', `${fileName}.txt`);
72
72
  }
@@ -1,7 +1,7 @@
1
- import type { CleanupForkOpts, DispatchAdapter, DispatchOpts, ForkOpts, RuntimeEvent } from "@parall/agent-core";
2
- import type { ClaudeAgentConfig } from "./config.js";
3
- import { ClaudeSessionManager } from "./session-manager.js";
4
- type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig, "additionalDirs" | "allowApiKey" | "allowedTools" | "appendSystemPrompt" | "claudeBin" | "claudeHome" | "disallowedTools" | "model" | "permissionMode" | "workspaceDir"> & {
1
+ import type { CleanupForkOpts, DispatchAdapter, DispatchOpts, ForkOpts, RuntimeEvent } from '@parall/agent-core';
2
+ import type { ClaudeAgentConfig } from './config.js';
3
+ import { ClaudeSessionManager } from './session-manager.js';
4
+ type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig, 'additionalDirs' | 'allowApiKey' | 'allowedTools' | 'appendSystemPrompt' | 'claudeBin' | 'claudeHome' | 'disallowedTools' | 'model' | 'permissionMode' | 'workspaceDir'> & {
5
5
  sessionManager: ClaudeSessionManager;
6
6
  apiUrl: string;
7
7
  apiKey: string;
@@ -10,7 +10,7 @@ type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig, "additionalDirs" | "allo
10
10
  /** @deprecated Use contextFilePathForSession. */
11
11
  stepIdFilePathForSession?: (sessionKey: string) => string;
12
12
  };
13
- export declare function buildSpawnEnv(parentEnv: NodeJS.ProcessEnv, claudeHome: string, context: DispatchOpts["context"], opts: {
13
+ export declare function buildSpawnEnv(parentEnv: NodeJS.ProcessEnv, claudeHome: string, context: DispatchOpts['context'], opts: {
14
14
  allowApiKey: boolean;
15
15
  effortLevel?: string;
16
16
  }): NodeJS.ProcessEnv;
@@ -29,15 +29,16 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
29
29
  effort?: string | null;
30
30
  }): void;
31
31
  enqueueDuringDispatch(sessionKey: string, body: string): boolean;
32
+ abortDispatch(sessionKey: string): void;
32
33
  hasPendingInjections(sessionKey: string): boolean;
33
- dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent>;
34
+ dispatch({ event, bodyForAgent, sessionKey, context, }: DispatchOpts): AsyncIterable<RuntimeEvent>;
34
35
  getBranchPoint(_sessionKey: string): string | undefined;
35
36
  getSessionHistoryPath(sessionKey: string): string | undefined;
36
37
  forkSession({ sessionKey }: ForkOpts): import("@parall/agent-core").ForkSessionHandle | null;
37
38
  cleanupFork({ fork }: CleanupForkOpts): void;
38
39
  resetProcesses(): void;
39
40
  shutdown(): Promise<void>;
40
- private static readonly STEER_TURN_TIMEOUT_MS;
41
+ private readonly steerTurnTimeoutMs;
41
42
  /**
42
43
  * Consume a steer turn whose message was already written to stdin via
43
44
  * enqueueDuringDispatch. Skip the stdin write — only read parser output.
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EAER,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EACL,oBAAoB,EAErB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,wBAAwB,GAAG,IAAI,CAClC,iBAAiB,EACf,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,oBAAoB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3D,iDAAiD;IACjD,wBAAwB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3D,CAAC;AAgBF,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,CAAC,UAAU,CAuBnB;AAiBD,qBAAa,iBAAkB,YAAW,eAAe;IAO3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,YAAY,CAAqB;gBAEZ,IAAI,EAAE,wBAAwB;IAI3D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAAwB;IAC9D,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAA8B;IAErE,YAAY,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAY7E,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAchE,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI1C,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAiCxG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvD,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ7D,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ;IAIpC,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAQrC,cAAc,IAAI,IAAI;IAQhB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAU;IAEvD;;;;;;;;;OASG;YACY,gBAAgB;YAsDhB,OAAO;IA2GtB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,YAAY;IA+CpB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,uBAAuB;CAkBhC"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EAER,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,oBAAoB,EAA4B,MAAM,sBAAsB,CAAC;AAEtF,KAAK,wBAAwB,GAAG,IAAI,CAClC,iBAAiB,EACf,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,oBAAoB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3D,iDAAiD;IACjD,wBAAwB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3D,CAAC;AAkBF,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,CAAC,UAAU,CAuBnB;AAiBD,qBAAa,iBAAkB,YAAW,eAAe;IAO3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,YAAY,CAAqB;gBAEZ,IAAI,EAAE,wBAAwB;IAI3D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAErC;IACD,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED,YAAY,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAY7E,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAchE,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAYvC,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI1C,QAAQ,CAAC,EACd,KAAK,EACL,YAAY,EACZ,UAAU,EACV,OAAO,GACR,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IA+B7C,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvD,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAc7D,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ;IAIpC,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAQrC,cAAc,IAAI,IAAI;IAQhB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA4D;IAE/F;;;;;;;;;OASG;YACY,gBAAgB;YAqEhB,OAAO;IAkHtB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,YAAY;IAmDpB,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,uBAAuB;CAkBhC"}
package/dist/dispatch.js CHANGED
@@ -1,10 +1,10 @@
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
- import { appendPreparedLocalAttachmentRefs, pinLocalAttachmentPaths, } from "@parall/agent-core/internal/attachment-input";
6
- import { parseClaudeStreamJson } from "./output-parser.js";
7
- const IS_WIN32 = process.platform === "win32";
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
+ import { appendPreparedLocalAttachmentRefs, pinLocalAttachmentPaths, } from '@parall/agent-core/internal/attachment-input';
6
+ import { parseClaudeStreamJson } from './output-parser.js';
7
+ const IS_WIN32 = process.platform === 'win32';
8
8
  function quoteWin32Arg(arg) {
9
9
  if (!/[\s"&|^<>()]/.test(arg))
10
10
  return arg;
@@ -12,7 +12,7 @@ function quoteWin32Arg(arg) {
12
12
  }
13
13
  function killWin32Tree(pid) {
14
14
  try {
15
- execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: "ignore" });
15
+ execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: 'ignore' });
16
16
  return true;
17
17
  }
18
18
  catch {
@@ -31,12 +31,12 @@ export function buildSpawnEnv(parentEnv, claudeHome, context, opts) {
31
31
  PRLL_API_URL: context.apiUrl,
32
32
  PRLL_API_KEY: context.apiKey,
33
33
  PRLL_ORG_ID: context.orgId,
34
- PRLL_SESSION_ID: context.sessionId ?? "",
35
- PRLL_CHAT_ID: context.chatId ?? "",
36
- PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? "",
37
- PRLL_NO_REPLY: context.noReply ? "1" : "",
38
- PRLL_CONTEXT_FILE: context.contextFilePath ?? "",
39
- PRLL_STEP_ID_FILE: context.stepIdFilePath ?? "",
34
+ PRLL_SESSION_ID: context.sessionId ?? '',
35
+ PRLL_CHAT_ID: context.chatId ?? '',
36
+ PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? '',
37
+ PRLL_NO_REPLY: context.noReply ? '1' : '',
38
+ PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
39
+ PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
40
40
  };
41
41
  if (opts.effortLevel) {
42
42
  result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
@@ -54,8 +54,12 @@ export class ClaudeCodeAdapter {
54
54
  this.opts = opts;
55
55
  this._model = opts.model;
56
56
  }
57
- get currentModel() { return this._model; }
58
- get currentEffort() { return this._effortLevel; }
57
+ get currentModel() {
58
+ return this._model;
59
+ }
60
+ get currentEffort() {
61
+ return this._effortLevel;
62
+ }
59
63
  updateConfig(config) {
60
64
  const modelChanged = config.model !== undefined && config.model !== this._model;
61
65
  const effortChanged = config.effort !== undefined && config.effort !== this._effortLevel;
@@ -85,10 +89,23 @@ export class ClaudeCodeAdapter {
85
89
  return false;
86
90
  }
87
91
  }
92
+ abortDispatch(sessionKey) {
93
+ this.pendingInjections.delete(sessionKey);
94
+ const state = this.processes.get(sessionKey);
95
+ if (!state || state.done)
96
+ return;
97
+ state.done = true;
98
+ try {
99
+ state.handle.proc.stdin.end();
100
+ }
101
+ catch {
102
+ /* best-effort */
103
+ }
104
+ }
88
105
  hasPendingInjections(sessionKey) {
89
106
  return (this.pendingInjections.get(sessionKey) ?? 0) > 0;
90
107
  }
91
- async *dispatch({ event, bodyForAgent, sessionKey, context }) {
108
+ async *dispatch({ event, bodyForAgent, sessionKey, context, }) {
92
109
  const pending = this.pendingInjections.get(sessionKey) ?? 0;
93
110
  if (pending > 0) {
94
111
  this.pendingInjections.delete(sessionKey);
@@ -125,8 +142,8 @@ export class ClaudeCodeAdapter {
125
142
  const sessionId = this.opts.sessionManager.getSessionId(sessionKey);
126
143
  if (!sessionId)
127
144
  return undefined;
128
- const projectSlug = this.opts.workspaceDir.replace(/[/.]/g, "-");
129
- const filePath = path.join(this.opts.claudeHome, ".claude", "projects", projectSlug, `${sessionId}.jsonl`);
145
+ const projectSlug = this.opts.workspaceDir.replace(/[/.]/g, '-');
146
+ const filePath = path.join(this.opts.claudeHome, '.claude', 'projects', projectSlug, `${sessionId}.jsonl`);
130
147
  return fs.existsSync(filePath) ? filePath : undefined;
131
148
  }
132
149
  forkSession({ sessionKey }) {
@@ -151,7 +168,9 @@ export class ClaudeCodeAdapter {
151
168
  this.resetProcesses();
152
169
  await this.opts.sessionManager.shutdownAll();
153
170
  }
154
- static STEER_TURN_TIMEOUT_MS = 10_000;
171
+ // Steer-turn read timeout. Overridable via PRLL_STEER_TURN_TIMEOUT_MS (ms),
172
+ // chiefly so tests can exercise the timeout path without a 10s wait.
173
+ steerTurnTimeoutMs = Number(process.env.PRLL_STEER_TURN_TIMEOUT_MS) || 10_000;
155
174
  /**
156
175
  * Consume a steer turn whose message was already written to stdin via
157
176
  * enqueueDuringDispatch. Skip the stdin write — only read parser output.
@@ -165,15 +184,26 @@ export class ClaudeCodeAdapter {
165
184
  async *consumeSteerTurn(sessionKey, log) {
166
185
  const state = this.processes.get(sessionKey);
167
186
  if (!state || state.done) {
168
- throw new Error("process dead during steer consumption");
187
+ throw new Error('process dead during steer consumption');
169
188
  }
170
189
  const groupKey = randomUUID();
171
- const parserNext = state.parser.next();
190
+ // Reuse a pull already issued by a prior steer timeout instead of issuing a
191
+ // fresh one. A burst of N steers all takes the pending-skip path, which calls
192
+ // this method N times in a loop. Issuing a new state.parser.next() on each
193
+ // call while a previous pull is still in flight queues multiple reads on the
194
+ // same async generator, but only one can be stashed in the single-slot
195
+ // state.steerReadPending — every earlier pull is then consumed-and-dropped,
196
+ // silently losing parser events. When a dropped event is a tool_call, its
197
+ // tool_result later orphans into "call_id does not match any tool_call".
198
+ // Threading the single in-flight pull through every call keeps at most one
199
+ // outstanding read and loses nothing.
200
+ const parserNext = state.steerReadPending ?? state.parser.next();
201
+ state.steerReadPending = undefined;
172
202
  const firstRead = await Promise.race([
173
- parserNext.then((r) => ({ kind: "value", result: r })),
174
- new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), ClaudeCodeAdapter.STEER_TURN_TIMEOUT_MS)),
203
+ parserNext.then((r) => ({ kind: 'value', result: r })),
204
+ new Promise((resolve) => setTimeout(() => resolve({ kind: 'timeout' }), this.steerTurnTimeoutMs)),
175
205
  ]);
176
- if (firstRead.kind === "timeout") {
206
+ if (firstRead.kind === 'timeout') {
177
207
  state.steerReadPending = parserNext;
178
208
  log?.info?.(`steer turn timeout — steer was incorporated into previous turn`);
179
209
  return;
@@ -184,25 +214,29 @@ export class ClaudeCodeAdapter {
184
214
  if (next.done) {
185
215
  state.done = true;
186
216
  this.processes.delete(sessionKey);
187
- throw new Error("process exited while consuming steer turn");
217
+ throw new Error('process exited while consuming steer turn');
188
218
  }
189
219
  const parsed = next.value;
190
- if (parsed.type === "session_id") {
220
+ if (parsed.type === 'session_id') {
191
221
  this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
192
- yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
222
+ yield {
223
+ type: 'runtime_session',
224
+ runtimeSessionId: parsed.sessionId,
225
+ runtimeLaneKey: sessionKey,
226
+ };
193
227
  }
194
- else if (parsed.type === "turn_end") {
228
+ else if (parsed.type === 'turn_end') {
195
229
  if (state.needsRestart)
196
230
  this.killProcess(sessionKey, state);
197
231
  return;
198
232
  }
199
- else if (parsed.type === "error") {
233
+ else if (parsed.type === 'error') {
200
234
  yield parsed;
201
235
  }
202
- else if (parsed.type === "text") {
236
+ else if (parsed.type === 'text') {
203
237
  yield { ...parsed, project: false, groupKey };
204
238
  }
205
- else if (parsed.type === "runtime_session") {
239
+ else if (parsed.type === 'runtime_session') {
206
240
  yield parsed;
207
241
  }
208
242
  else {
@@ -217,7 +251,7 @@ export class ClaudeCodeAdapter {
217
251
  state = this.ensureProcess(sessionKey, log);
218
252
  }
219
253
  catch (err) {
220
- yield { type: "error", message: `Claude spawn failed: ${String(err)}` };
254
+ yield { type: 'error', message: `Claude spawn failed: ${String(err)}` };
221
255
  return;
222
256
  }
223
257
  const groupKey = randomUUID();
@@ -226,7 +260,7 @@ export class ClaudeCodeAdapter {
226
260
  this.writeUserMessage(state.handle, promptBody);
227
261
  }
228
262
  catch (err) {
229
- yield { type: "error", message: `Claude stdin write failed: ${String(err)}` };
263
+ yield { type: 'error', message: `Claude stdin write failed: ${String(err)}` };
230
264
  this.killProcess(sessionKey, state);
231
265
  return;
232
266
  }
@@ -235,23 +269,27 @@ export class ClaudeCodeAdapter {
235
269
  state.steerReadPending = undefined;
236
270
  if (!orphaned.done) {
237
271
  const parsed = orphaned.value;
238
- if (parsed.type === "session_id") {
272
+ if (parsed.type === 'session_id') {
239
273
  this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
240
- yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
274
+ yield {
275
+ type: 'runtime_session',
276
+ runtimeSessionId: parsed.sessionId,
277
+ runtimeLaneKey: sessionKey,
278
+ };
241
279
  }
242
- else if (parsed.type === "turn_end") {
280
+ else if (parsed.type === 'turn_end') {
243
281
  if (state.needsRestart)
244
282
  this.killProcess(sessionKey, state);
245
283
  return;
246
284
  }
247
- else if (parsed.type === "error") {
285
+ else if (parsed.type === 'error') {
248
286
  sawError = true;
249
287
  yield parsed;
250
288
  }
251
- else if (parsed.type === "text") {
289
+ else if (parsed.type === 'text') {
252
290
  yield { ...parsed, project: false, groupKey };
253
291
  }
254
- else if (parsed.type === "runtime_session") {
292
+ else if (parsed.type === 'runtime_session') {
255
293
  yield parsed;
256
294
  }
257
295
  else {
@@ -264,46 +302,46 @@ export class ClaudeCodeAdapter {
264
302
  if (next.done) {
265
303
  state.done = true;
266
304
  this.processes.delete(sessionKey);
267
- const detail = state.handle.stderrChunks.join("").trim();
305
+ const detail = state.handle.stderrChunks.join('').trim();
268
306
  const exit = await state.handle.exitPromise.catch(() => ({ code: null, signal: null }));
269
307
  if (detail) {
270
308
  log?.warn?.(`subprocess stderr: ${detail}`);
271
309
  }
272
310
  if (!sawError) {
273
311
  yield {
274
- type: "error",
275
- message: detail
276
- || `Claude exited with code ${exit.code ?? "unknown"}${exit.signal ? ` (${exit.signal})` : ""}`,
312
+ type: 'error',
313
+ message: detail ||
314
+ `Claude exited with code ${exit.code ?? 'unknown'}${exit.signal ? ` (${exit.signal})` : ''}`,
277
315
  };
278
316
  }
279
317
  return;
280
318
  }
281
319
  const parsed = next.value;
282
- if (parsed.type === "session_id") {
320
+ if (parsed.type === 'session_id') {
283
321
  this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
284
322
  yield {
285
- type: "runtime_session",
323
+ type: 'runtime_session',
286
324
  runtimeSessionId: parsed.sessionId,
287
325
  runtimeLaneKey: sessionKey,
288
326
  };
289
327
  continue;
290
328
  }
291
- if (parsed.type === "turn_end") {
329
+ if (parsed.type === 'turn_end') {
292
330
  if (state.needsRestart) {
293
331
  this.killProcess(sessionKey, state);
294
332
  }
295
333
  return;
296
334
  }
297
- if (parsed.type === "error") {
335
+ if (parsed.type === 'error') {
298
336
  sawError = true;
299
337
  yield parsed;
300
338
  continue;
301
339
  }
302
- if (parsed.type === "text") {
340
+ if (parsed.type === 'text') {
303
341
  yield { ...parsed, project: false, groupKey };
304
342
  continue;
305
343
  }
306
- if (parsed.type === "runtime_session") {
344
+ if (parsed.type === 'runtime_session') {
307
345
  yield parsed;
308
346
  continue;
309
347
  }
@@ -312,7 +350,7 @@ export class ClaudeCodeAdapter {
312
350
  }
313
351
  ensureProcess(sessionKey, log) {
314
352
  if (this.shuttingDown) {
315
- throw new Error("adapter shutting down, refusing new process");
353
+ throw new Error('adapter shutting down, refusing new process');
316
354
  }
317
355
  const existing = this.processes.get(sessionKey);
318
356
  if (existing && !existing.done) {
@@ -337,31 +375,31 @@ export class ClaudeCodeAdapter {
337
375
  spawnProcess(sessionKey, log) {
338
376
  const args = this.buildArgs(sessionKey);
339
377
  const env = buildSpawnEnv(process.env, this.opts.claudeHome, this.buildPlaceholderContext(sessionKey), { allowApiKey: this.opts.allowApiKey, effortLevel: this._effortLevel });
340
- log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || "default"}, effort=${this._effortLevel || "default"}, mode=${this.opts.permissionMode})`);
378
+ log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`);
341
379
  const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.claudeBin) : this.opts.claudeBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
342
380
  cwd: this.opts.workspaceDir,
343
381
  env,
344
- stdio: ["pipe", "pipe", "pipe"],
382
+ stdio: ['pipe', 'pipe', 'pipe'],
345
383
  shell: IS_WIN32,
346
384
  });
347
385
  if (!proc.stdout || !proc.stderr || !proc.stdin) {
348
- throw new Error("Claude subprocess did not provide stdio pipes");
386
+ throw new Error('Claude subprocess did not provide stdio pipes');
349
387
  }
350
388
  const stderrChunks = [];
351
- proc.stderr.on("data", (chunk) => {
389
+ proc.stderr.on('data', (chunk) => {
352
390
  stderrChunks.push(chunk.toString());
353
391
  });
354
- proc.stdin.on("error", () => {
392
+ proc.stdin.on('error', () => {
355
393
  proc.stdin.destroy();
356
394
  if (proc.exitCode === null && proc.signalCode === null) {
357
395
  if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
358
- proc.kill("SIGTERM");
396
+ proc.kill('SIGTERM');
359
397
  }
360
398
  }
361
399
  });
362
400
  const exitPromise = new Promise((resolve) => {
363
- proc.once("close", (code, signal) => resolve({ code, signal }));
364
- proc.once("error", () => resolve({ code: null, signal: null }));
401
+ proc.once('close', (code, signal) => resolve({ code, signal }));
402
+ proc.once('error', () => resolve({ code: null, signal: null }));
365
403
  });
366
404
  return { proc, exitPromise, stderrChunks };
367
405
  }
@@ -374,70 +412,74 @@ export class ClaudeCodeAdapter {
374
412
  try {
375
413
  state.handle.proc.stdin.end();
376
414
  }
377
- catch { /* best-effort */ }
415
+ catch {
416
+ /* best-effort */
417
+ }
378
418
  if (state.handle.proc.exitCode === null && state.handle.proc.signalCode === null) {
379
419
  try {
380
420
  if (!IS_WIN32 || !state.handle.proc.pid || !killWin32Tree(state.handle.proc.pid)) {
381
- state.handle.proc.kill("SIGTERM");
421
+ state.handle.proc.kill('SIGTERM');
382
422
  }
383
423
  }
384
- catch { /* best-effort */ }
424
+ catch {
425
+ /* best-effort */
426
+ }
385
427
  }
386
428
  }
387
429
  writeUserMessage(handle, text) {
388
430
  const payload = JSON.stringify({
389
- type: "user",
431
+ type: 'user',
390
432
  message: {
391
- role: "user",
392
- content: [{ type: "text", text }],
433
+ role: 'user',
434
+ content: [{ type: 'text', text }],
393
435
  },
394
436
  });
395
437
  handle.proc.stdin.write(`${payload}\n`);
396
438
  }
397
439
  buildArgs(sessionKey) {
398
440
  const args = [
399
- "--verbose",
400
- "--input-format",
401
- "stream-json",
402
- "--output-format",
403
- "stream-json",
404
- "--permission-mode",
441
+ '--verbose',
442
+ '--input-format',
443
+ 'stream-json',
444
+ '--output-format',
445
+ 'stream-json',
446
+ '--permission-mode',
405
447
  this.opts.permissionMode,
406
448
  ];
407
449
  if (this._model) {
408
- args.push("--model", this._model);
450
+ args.push('--model', this._model);
409
451
  }
410
452
  if (this.opts.allowedTools.length > 0) {
411
- args.push("--allowedTools", this.opts.allowedTools.join(","));
453
+ args.push('--allowedTools', this.opts.allowedTools.join(','));
412
454
  }
413
455
  if (this.opts.disallowedTools.length > 0) {
414
- args.push("--disallowedTools", this.opts.disallowedTools.join(","));
456
+ args.push('--disallowedTools', this.opts.disallowedTools.join(','));
415
457
  }
416
- args.push("--append-system-prompt-file", path.join(this.opts.workspaceDir, ".parall", "system-prompt.md"));
458
+ args.push('--append-system-prompt-file', path.join(this.opts.workspaceDir, '.parall', 'system-prompt.md'));
417
459
  if (this.opts.appendSystemPrompt) {
418
- args.push("--append-system-prompt", this.opts.appendSystemPrompt);
460
+ args.push('--append-system-prompt', this.opts.appendSystemPrompt);
419
461
  }
420
462
  if (this.opts.additionalDirs.length > 0) {
421
- args.push("--add-dir", ...this.opts.additionalDirs);
463
+ args.push('--add-dir', ...this.opts.additionalDirs);
422
464
  }
423
465
  args.push(...this.opts.sessionManager.getResumeArgs(sessionKey));
424
466
  return args;
425
467
  }
426
468
  buildPlaceholderContext(sessionKey) {
427
469
  return {
428
- accountId: "",
470
+ accountId: '',
429
471
  apiUrl: this.opts.apiUrl,
430
472
  apiKey: this.opts.apiKey,
431
473
  orgId: this.opts.orgId,
432
- agentUserId: "",
433
- runtimeType: "",
434
- runtimeKey: "",
435
- sessionId: "",
436
- chatId: "",
437
- triggerMessageId: "",
474
+ agentUserId: '',
475
+ runtimeType: '',
476
+ runtimeKey: '',
477
+ sessionId: '',
478
+ chatId: '',
479
+ triggerMessageId: '',
438
480
  noReply: false,
439
- contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? "",
440
- stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? "",
481
+ contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? '',
482
+ stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? '',
441
483
  client: undefined,
442
484
  };
443
485
  }