@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/dist/config.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
2
|
-
import * as path from
|
|
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,
|
|
23
|
-
const apiKey = requireEnv(env,
|
|
24
|
-
const orgId = requireEnv(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');
|
|
25
25
|
const claudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os.homedir());
|
|
26
|
-
const stateDir = resolvePath(env.
|
|
27
|
-
const workspaceDir = resolvePath(env.
|
|
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() ||
|
|
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() ||
|
|
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() ===
|
|
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(/\/$/,
|
|
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(
|
|
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(
|
|
62
|
-
return path.join(stateDir,
|
|
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(
|
|
66
|
-
return path.join(stateDir,
|
|
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(
|
|
71
|
-
return path.join(stateDir,
|
|
70
|
+
const fileName = Buffer.from(sessionKey).toString('base64url');
|
|
71
|
+
return path.join(stateDir, 'step-ids', `${fileName}.txt`);
|
|
72
72
|
}
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { CleanupForkOpts, DispatchAdapter, DispatchOpts, ForkOpts, RuntimeEvent } from
|
|
2
|
-
import type { ClaudeAgentConfig } from
|
|
3
|
-
import { ClaudeSessionManager } from
|
|
4
|
-
type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig,
|
|
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[
|
|
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,14 +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;
|
|
39
|
+
resetProcesses(): void;
|
|
38
40
|
shutdown(): Promise<void>;
|
|
39
|
-
private
|
|
41
|
+
private readonly steerTurnTimeoutMs;
|
|
40
42
|
/**
|
|
41
43
|
* Consume a steer turn whose message was already written to stdin via
|
|
42
44
|
* enqueueDuringDispatch. Skip the stdin write — only read parser output.
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
2
|
-
import * as path from
|
|
3
|
-
import { randomUUID } from
|
|
4
|
-
import { execSync, spawn } from
|
|
5
|
-
import { appendPreparedLocalAttachmentRefs, pinLocalAttachmentPaths, } from
|
|
6
|
-
import { parseClaudeStreamJson } from
|
|
7
|
-
const IS_WIN32 = process.platform ===
|
|
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:
|
|
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 ?
|
|
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() {
|
|
58
|
-
|
|
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,
|
|
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 }) {
|
|
@@ -139,15 +156,21 @@ export class ClaudeCodeAdapter {
|
|
|
139
156
|
}
|
|
140
157
|
this.opts.sessionManager.cleanupFork(fork.sessionKey);
|
|
141
158
|
}
|
|
142
|
-
|
|
143
|
-
this.shuttingDown = true;
|
|
159
|
+
resetProcesses() {
|
|
144
160
|
for (const [sessionKey, state] of this.processes) {
|
|
145
161
|
this.killProcess(sessionKey, state);
|
|
146
162
|
}
|
|
147
163
|
this.processes.clear();
|
|
164
|
+
this.pendingInjections.clear();
|
|
165
|
+
}
|
|
166
|
+
async shutdown() {
|
|
167
|
+
this.shuttingDown = true;
|
|
168
|
+
this.resetProcesses();
|
|
148
169
|
await this.opts.sessionManager.shutdownAll();
|
|
149
170
|
}
|
|
150
|
-
|
|
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;
|
|
151
174
|
/**
|
|
152
175
|
* Consume a steer turn whose message was already written to stdin via
|
|
153
176
|
* enqueueDuringDispatch. Skip the stdin write — only read parser output.
|
|
@@ -161,15 +184,26 @@ export class ClaudeCodeAdapter {
|
|
|
161
184
|
async *consumeSteerTurn(sessionKey, log) {
|
|
162
185
|
const state = this.processes.get(sessionKey);
|
|
163
186
|
if (!state || state.done) {
|
|
164
|
-
throw new Error(
|
|
187
|
+
throw new Error('process dead during steer consumption');
|
|
165
188
|
}
|
|
166
189
|
const groupKey = randomUUID();
|
|
167
|
-
|
|
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;
|
|
168
202
|
const firstRead = await Promise.race([
|
|
169
|
-
parserNext.then((r) => ({ kind:
|
|
170
|
-
new Promise((resolve) => setTimeout(() => resolve({ kind:
|
|
203
|
+
parserNext.then((r) => ({ kind: 'value', result: r })),
|
|
204
|
+
new Promise((resolve) => setTimeout(() => resolve({ kind: 'timeout' }), this.steerTurnTimeoutMs)),
|
|
171
205
|
]);
|
|
172
|
-
if (firstRead.kind ===
|
|
206
|
+
if (firstRead.kind === 'timeout') {
|
|
173
207
|
state.steerReadPending = parserNext;
|
|
174
208
|
log?.info?.(`steer turn timeout — steer was incorporated into previous turn`);
|
|
175
209
|
return;
|
|
@@ -180,25 +214,29 @@ export class ClaudeCodeAdapter {
|
|
|
180
214
|
if (next.done) {
|
|
181
215
|
state.done = true;
|
|
182
216
|
this.processes.delete(sessionKey);
|
|
183
|
-
throw new Error(
|
|
217
|
+
throw new Error('process exited while consuming steer turn');
|
|
184
218
|
}
|
|
185
219
|
const parsed = next.value;
|
|
186
|
-
if (parsed.type ===
|
|
220
|
+
if (parsed.type === 'session_id') {
|
|
187
221
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
188
|
-
yield {
|
|
222
|
+
yield {
|
|
223
|
+
type: 'runtime_session',
|
|
224
|
+
runtimeSessionId: parsed.sessionId,
|
|
225
|
+
runtimeLaneKey: sessionKey,
|
|
226
|
+
};
|
|
189
227
|
}
|
|
190
|
-
else if (parsed.type ===
|
|
228
|
+
else if (parsed.type === 'turn_end') {
|
|
191
229
|
if (state.needsRestart)
|
|
192
230
|
this.killProcess(sessionKey, state);
|
|
193
231
|
return;
|
|
194
232
|
}
|
|
195
|
-
else if (parsed.type ===
|
|
233
|
+
else if (parsed.type === 'error') {
|
|
196
234
|
yield parsed;
|
|
197
235
|
}
|
|
198
|
-
else if (parsed.type ===
|
|
236
|
+
else if (parsed.type === 'text') {
|
|
199
237
|
yield { ...parsed, project: false, groupKey };
|
|
200
238
|
}
|
|
201
|
-
else if (parsed.type ===
|
|
239
|
+
else if (parsed.type === 'runtime_session') {
|
|
202
240
|
yield parsed;
|
|
203
241
|
}
|
|
204
242
|
else {
|
|
@@ -213,7 +251,7 @@ export class ClaudeCodeAdapter {
|
|
|
213
251
|
state = this.ensureProcess(sessionKey, log);
|
|
214
252
|
}
|
|
215
253
|
catch (err) {
|
|
216
|
-
yield { type:
|
|
254
|
+
yield { type: 'error', message: `Claude spawn failed: ${String(err)}` };
|
|
217
255
|
return;
|
|
218
256
|
}
|
|
219
257
|
const groupKey = randomUUID();
|
|
@@ -222,7 +260,7 @@ export class ClaudeCodeAdapter {
|
|
|
222
260
|
this.writeUserMessage(state.handle, promptBody);
|
|
223
261
|
}
|
|
224
262
|
catch (err) {
|
|
225
|
-
yield { type:
|
|
263
|
+
yield { type: 'error', message: `Claude stdin write failed: ${String(err)}` };
|
|
226
264
|
this.killProcess(sessionKey, state);
|
|
227
265
|
return;
|
|
228
266
|
}
|
|
@@ -231,23 +269,27 @@ export class ClaudeCodeAdapter {
|
|
|
231
269
|
state.steerReadPending = undefined;
|
|
232
270
|
if (!orphaned.done) {
|
|
233
271
|
const parsed = orphaned.value;
|
|
234
|
-
if (parsed.type ===
|
|
272
|
+
if (parsed.type === 'session_id') {
|
|
235
273
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
236
|
-
yield {
|
|
274
|
+
yield {
|
|
275
|
+
type: 'runtime_session',
|
|
276
|
+
runtimeSessionId: parsed.sessionId,
|
|
277
|
+
runtimeLaneKey: sessionKey,
|
|
278
|
+
};
|
|
237
279
|
}
|
|
238
|
-
else if (parsed.type ===
|
|
280
|
+
else if (parsed.type === 'turn_end') {
|
|
239
281
|
if (state.needsRestart)
|
|
240
282
|
this.killProcess(sessionKey, state);
|
|
241
283
|
return;
|
|
242
284
|
}
|
|
243
|
-
else if (parsed.type ===
|
|
285
|
+
else if (parsed.type === 'error') {
|
|
244
286
|
sawError = true;
|
|
245
287
|
yield parsed;
|
|
246
288
|
}
|
|
247
|
-
else if (parsed.type ===
|
|
289
|
+
else if (parsed.type === 'text') {
|
|
248
290
|
yield { ...parsed, project: false, groupKey };
|
|
249
291
|
}
|
|
250
|
-
else if (parsed.type ===
|
|
292
|
+
else if (parsed.type === 'runtime_session') {
|
|
251
293
|
yield parsed;
|
|
252
294
|
}
|
|
253
295
|
else {
|
|
@@ -260,46 +302,46 @@ export class ClaudeCodeAdapter {
|
|
|
260
302
|
if (next.done) {
|
|
261
303
|
state.done = true;
|
|
262
304
|
this.processes.delete(sessionKey);
|
|
263
|
-
const detail = state.handle.stderrChunks.join(
|
|
305
|
+
const detail = state.handle.stderrChunks.join('').trim();
|
|
264
306
|
const exit = await state.handle.exitPromise.catch(() => ({ code: null, signal: null }));
|
|
265
307
|
if (detail) {
|
|
266
308
|
log?.warn?.(`subprocess stderr: ${detail}`);
|
|
267
309
|
}
|
|
268
310
|
if (!sawError) {
|
|
269
311
|
yield {
|
|
270
|
-
type:
|
|
271
|
-
message: detail
|
|
272
|
-
|
|
312
|
+
type: 'error',
|
|
313
|
+
message: detail ||
|
|
314
|
+
`Claude exited with code ${exit.code ?? 'unknown'}${exit.signal ? ` (${exit.signal})` : ''}`,
|
|
273
315
|
};
|
|
274
316
|
}
|
|
275
317
|
return;
|
|
276
318
|
}
|
|
277
319
|
const parsed = next.value;
|
|
278
|
-
if (parsed.type ===
|
|
320
|
+
if (parsed.type === 'session_id') {
|
|
279
321
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
280
322
|
yield {
|
|
281
|
-
type:
|
|
323
|
+
type: 'runtime_session',
|
|
282
324
|
runtimeSessionId: parsed.sessionId,
|
|
283
325
|
runtimeLaneKey: sessionKey,
|
|
284
326
|
};
|
|
285
327
|
continue;
|
|
286
328
|
}
|
|
287
|
-
if (parsed.type ===
|
|
329
|
+
if (parsed.type === 'turn_end') {
|
|
288
330
|
if (state.needsRestart) {
|
|
289
331
|
this.killProcess(sessionKey, state);
|
|
290
332
|
}
|
|
291
333
|
return;
|
|
292
334
|
}
|
|
293
|
-
if (parsed.type ===
|
|
335
|
+
if (parsed.type === 'error') {
|
|
294
336
|
sawError = true;
|
|
295
337
|
yield parsed;
|
|
296
338
|
continue;
|
|
297
339
|
}
|
|
298
|
-
if (parsed.type ===
|
|
340
|
+
if (parsed.type === 'text') {
|
|
299
341
|
yield { ...parsed, project: false, groupKey };
|
|
300
342
|
continue;
|
|
301
343
|
}
|
|
302
|
-
if (parsed.type ===
|
|
344
|
+
if (parsed.type === 'runtime_session') {
|
|
303
345
|
yield parsed;
|
|
304
346
|
continue;
|
|
305
347
|
}
|
|
@@ -308,7 +350,7 @@ export class ClaudeCodeAdapter {
|
|
|
308
350
|
}
|
|
309
351
|
ensureProcess(sessionKey, log) {
|
|
310
352
|
if (this.shuttingDown) {
|
|
311
|
-
throw new Error(
|
|
353
|
+
throw new Error('adapter shutting down, refusing new process');
|
|
312
354
|
}
|
|
313
355
|
const existing = this.processes.get(sessionKey);
|
|
314
356
|
if (existing && !existing.done) {
|
|
@@ -333,31 +375,31 @@ export class ClaudeCodeAdapter {
|
|
|
333
375
|
spawnProcess(sessionKey, log) {
|
|
334
376
|
const args = this.buildArgs(sessionKey);
|
|
335
377
|
const env = buildSpawnEnv(process.env, this.opts.claudeHome, this.buildPlaceholderContext(sessionKey), { allowApiKey: this.opts.allowApiKey, effortLevel: this._effortLevel });
|
|
336
|
-
log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model ||
|
|
378
|
+
log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`);
|
|
337
379
|
const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.claudeBin) : this.opts.claudeBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
|
|
338
380
|
cwd: this.opts.workspaceDir,
|
|
339
381
|
env,
|
|
340
|
-
stdio: [
|
|
382
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
341
383
|
shell: IS_WIN32,
|
|
342
384
|
});
|
|
343
385
|
if (!proc.stdout || !proc.stderr || !proc.stdin) {
|
|
344
|
-
throw new Error(
|
|
386
|
+
throw new Error('Claude subprocess did not provide stdio pipes');
|
|
345
387
|
}
|
|
346
388
|
const stderrChunks = [];
|
|
347
|
-
proc.stderr.on(
|
|
389
|
+
proc.stderr.on('data', (chunk) => {
|
|
348
390
|
stderrChunks.push(chunk.toString());
|
|
349
391
|
});
|
|
350
|
-
proc.stdin.on(
|
|
392
|
+
proc.stdin.on('error', () => {
|
|
351
393
|
proc.stdin.destroy();
|
|
352
394
|
if (proc.exitCode === null && proc.signalCode === null) {
|
|
353
395
|
if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
|
|
354
|
-
proc.kill(
|
|
396
|
+
proc.kill('SIGTERM');
|
|
355
397
|
}
|
|
356
398
|
}
|
|
357
399
|
});
|
|
358
400
|
const exitPromise = new Promise((resolve) => {
|
|
359
|
-
proc.once(
|
|
360
|
-
proc.once(
|
|
401
|
+
proc.once('close', (code, signal) => resolve({ code, signal }));
|
|
402
|
+
proc.once('error', () => resolve({ code: null, signal: null }));
|
|
361
403
|
});
|
|
362
404
|
return { proc, exitPromise, stderrChunks };
|
|
363
405
|
}
|
|
@@ -370,70 +412,74 @@ export class ClaudeCodeAdapter {
|
|
|
370
412
|
try {
|
|
371
413
|
state.handle.proc.stdin.end();
|
|
372
414
|
}
|
|
373
|
-
catch {
|
|
415
|
+
catch {
|
|
416
|
+
/* best-effort */
|
|
417
|
+
}
|
|
374
418
|
if (state.handle.proc.exitCode === null && state.handle.proc.signalCode === null) {
|
|
375
419
|
try {
|
|
376
420
|
if (!IS_WIN32 || !state.handle.proc.pid || !killWin32Tree(state.handle.proc.pid)) {
|
|
377
|
-
state.handle.proc.kill(
|
|
421
|
+
state.handle.proc.kill('SIGTERM');
|
|
378
422
|
}
|
|
379
423
|
}
|
|
380
|
-
catch {
|
|
424
|
+
catch {
|
|
425
|
+
/* best-effort */
|
|
426
|
+
}
|
|
381
427
|
}
|
|
382
428
|
}
|
|
383
429
|
writeUserMessage(handle, text) {
|
|
384
430
|
const payload = JSON.stringify({
|
|
385
|
-
type:
|
|
431
|
+
type: 'user',
|
|
386
432
|
message: {
|
|
387
|
-
role:
|
|
388
|
-
content: [{ type:
|
|
433
|
+
role: 'user',
|
|
434
|
+
content: [{ type: 'text', text }],
|
|
389
435
|
},
|
|
390
436
|
});
|
|
391
437
|
handle.proc.stdin.write(`${payload}\n`);
|
|
392
438
|
}
|
|
393
439
|
buildArgs(sessionKey) {
|
|
394
440
|
const args = [
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
441
|
+
'--verbose',
|
|
442
|
+
'--input-format',
|
|
443
|
+
'stream-json',
|
|
444
|
+
'--output-format',
|
|
445
|
+
'stream-json',
|
|
446
|
+
'--permission-mode',
|
|
401
447
|
this.opts.permissionMode,
|
|
402
448
|
];
|
|
403
449
|
if (this._model) {
|
|
404
|
-
args.push(
|
|
450
|
+
args.push('--model', this._model);
|
|
405
451
|
}
|
|
406
452
|
if (this.opts.allowedTools.length > 0) {
|
|
407
|
-
args.push(
|
|
453
|
+
args.push('--allowedTools', this.opts.allowedTools.join(','));
|
|
408
454
|
}
|
|
409
455
|
if (this.opts.disallowedTools.length > 0) {
|
|
410
|
-
args.push(
|
|
456
|
+
args.push('--disallowedTools', this.opts.disallowedTools.join(','));
|
|
411
457
|
}
|
|
412
|
-
args.push(
|
|
458
|
+
args.push('--append-system-prompt-file', path.join(this.opts.workspaceDir, '.parall', 'system-prompt.md'));
|
|
413
459
|
if (this.opts.appendSystemPrompt) {
|
|
414
|
-
args.push(
|
|
460
|
+
args.push('--append-system-prompt', this.opts.appendSystemPrompt);
|
|
415
461
|
}
|
|
416
462
|
if (this.opts.additionalDirs.length > 0) {
|
|
417
|
-
args.push(
|
|
463
|
+
args.push('--add-dir', ...this.opts.additionalDirs);
|
|
418
464
|
}
|
|
419
465
|
args.push(...this.opts.sessionManager.getResumeArgs(sessionKey));
|
|
420
466
|
return args;
|
|
421
467
|
}
|
|
422
468
|
buildPlaceholderContext(sessionKey) {
|
|
423
469
|
return {
|
|
424
|
-
accountId:
|
|
470
|
+
accountId: '',
|
|
425
471
|
apiUrl: this.opts.apiUrl,
|
|
426
472
|
apiKey: this.opts.apiKey,
|
|
427
473
|
orgId: this.opts.orgId,
|
|
428
|
-
agentUserId:
|
|
429
|
-
runtimeType:
|
|
430
|
-
runtimeKey:
|
|
431
|
-
sessionId:
|
|
432
|
-
chatId:
|
|
433
|
-
triggerMessageId:
|
|
474
|
+
agentUserId: '',
|
|
475
|
+
runtimeType: '',
|
|
476
|
+
runtimeKey: '',
|
|
477
|
+
sessionId: '',
|
|
478
|
+
chatId: '',
|
|
479
|
+
triggerMessageId: '',
|
|
434
480
|
noReply: false,
|
|
435
|
-
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ??
|
|
436
|
-
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ??
|
|
481
|
+
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? '',
|
|
482
|
+
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? '',
|
|
437
483
|
client: undefined,
|
|
438
484
|
};
|
|
439
485
|
}
|