@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/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.PRLL_STATE_DIR?.trim() || path.join(claudeHome,
|
|
27
|
-
const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path.join(stateDir,
|
|
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,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
|
|
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.
|
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 }) {
|
|
@@ -151,7 +168,9 @@ export class ClaudeCodeAdapter {
|
|
|
151
168
|
this.resetProcesses();
|
|
152
169
|
await this.opts.sessionManager.shutdownAll();
|
|
153
170
|
}
|
|
154
|
-
|
|
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(
|
|
187
|
+
throw new Error('process dead during steer consumption');
|
|
169
188
|
}
|
|
170
189
|
const groupKey = randomUUID();
|
|
171
|
-
|
|
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:
|
|
174
|
-
new Promise((resolve) => setTimeout(() => resolve({ kind:
|
|
203
|
+
parserNext.then((r) => ({ kind: 'value', result: r })),
|
|
204
|
+
new Promise((resolve) => setTimeout(() => resolve({ kind: 'timeout' }), this.steerTurnTimeoutMs)),
|
|
175
205
|
]);
|
|
176
|
-
if (firstRead.kind ===
|
|
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(
|
|
217
|
+
throw new Error('process exited while consuming steer turn');
|
|
188
218
|
}
|
|
189
219
|
const parsed = next.value;
|
|
190
|
-
if (parsed.type ===
|
|
220
|
+
if (parsed.type === 'session_id') {
|
|
191
221
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
192
|
-
yield {
|
|
222
|
+
yield {
|
|
223
|
+
type: 'runtime_session',
|
|
224
|
+
runtimeSessionId: parsed.sessionId,
|
|
225
|
+
runtimeLaneKey: sessionKey,
|
|
226
|
+
};
|
|
193
227
|
}
|
|
194
|
-
else if (parsed.type ===
|
|
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 ===
|
|
233
|
+
else if (parsed.type === 'error') {
|
|
200
234
|
yield parsed;
|
|
201
235
|
}
|
|
202
|
-
else if (parsed.type ===
|
|
236
|
+
else if (parsed.type === 'text') {
|
|
203
237
|
yield { ...parsed, project: false, groupKey };
|
|
204
238
|
}
|
|
205
|
-
else if (parsed.type ===
|
|
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:
|
|
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:
|
|
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 ===
|
|
272
|
+
if (parsed.type === 'session_id') {
|
|
239
273
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
240
|
-
yield {
|
|
274
|
+
yield {
|
|
275
|
+
type: 'runtime_session',
|
|
276
|
+
runtimeSessionId: parsed.sessionId,
|
|
277
|
+
runtimeLaneKey: sessionKey,
|
|
278
|
+
};
|
|
241
279
|
}
|
|
242
|
-
else if (parsed.type ===
|
|
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 ===
|
|
285
|
+
else if (parsed.type === 'error') {
|
|
248
286
|
sawError = true;
|
|
249
287
|
yield parsed;
|
|
250
288
|
}
|
|
251
|
-
else if (parsed.type ===
|
|
289
|
+
else if (parsed.type === 'text') {
|
|
252
290
|
yield { ...parsed, project: false, groupKey };
|
|
253
291
|
}
|
|
254
|
-
else if (parsed.type ===
|
|
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(
|
|
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:
|
|
275
|
-
message: detail
|
|
276
|
-
|
|
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 ===
|
|
320
|
+
if (parsed.type === 'session_id') {
|
|
283
321
|
this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
|
|
284
322
|
yield {
|
|
285
|
-
type:
|
|
323
|
+
type: 'runtime_session',
|
|
286
324
|
runtimeSessionId: parsed.sessionId,
|
|
287
325
|
runtimeLaneKey: sessionKey,
|
|
288
326
|
};
|
|
289
327
|
continue;
|
|
290
328
|
}
|
|
291
|
-
if (parsed.type ===
|
|
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 ===
|
|
335
|
+
if (parsed.type === 'error') {
|
|
298
336
|
sawError = true;
|
|
299
337
|
yield parsed;
|
|
300
338
|
continue;
|
|
301
339
|
}
|
|
302
|
-
if (parsed.type ===
|
|
340
|
+
if (parsed.type === 'text') {
|
|
303
341
|
yield { ...parsed, project: false, groupKey };
|
|
304
342
|
continue;
|
|
305
343
|
}
|
|
306
|
-
if (parsed.type ===
|
|
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(
|
|
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 ||
|
|
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: [
|
|
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(
|
|
386
|
+
throw new Error('Claude subprocess did not provide stdio pipes');
|
|
349
387
|
}
|
|
350
388
|
const stderrChunks = [];
|
|
351
|
-
proc.stderr.on(
|
|
389
|
+
proc.stderr.on('data', (chunk) => {
|
|
352
390
|
stderrChunks.push(chunk.toString());
|
|
353
391
|
});
|
|
354
|
-
proc.stdin.on(
|
|
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(
|
|
396
|
+
proc.kill('SIGTERM');
|
|
359
397
|
}
|
|
360
398
|
}
|
|
361
399
|
});
|
|
362
400
|
const exitPromise = new Promise((resolve) => {
|
|
363
|
-
proc.once(
|
|
364
|
-
proc.once(
|
|
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 {
|
|
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(
|
|
421
|
+
state.handle.proc.kill('SIGTERM');
|
|
382
422
|
}
|
|
383
423
|
}
|
|
384
|
-
catch {
|
|
424
|
+
catch {
|
|
425
|
+
/* best-effort */
|
|
426
|
+
}
|
|
385
427
|
}
|
|
386
428
|
}
|
|
387
429
|
writeUserMessage(handle, text) {
|
|
388
430
|
const payload = JSON.stringify({
|
|
389
|
-
type:
|
|
431
|
+
type: 'user',
|
|
390
432
|
message: {
|
|
391
|
-
role:
|
|
392
|
-
content: [{ type:
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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(
|
|
450
|
+
args.push('--model', this._model);
|
|
409
451
|
}
|
|
410
452
|
if (this.opts.allowedTools.length > 0) {
|
|
411
|
-
args.push(
|
|
453
|
+
args.push('--allowedTools', this.opts.allowedTools.join(','));
|
|
412
454
|
}
|
|
413
455
|
if (this.opts.disallowedTools.length > 0) {
|
|
414
|
-
args.push(
|
|
456
|
+
args.push('--disallowedTools', this.opts.disallowedTools.join(','));
|
|
415
457
|
}
|
|
416
|
-
args.push(
|
|
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(
|
|
460
|
+
args.push('--append-system-prompt', this.opts.appendSystemPrompt);
|
|
419
461
|
}
|
|
420
462
|
if (this.opts.additionalDirs.length > 0) {
|
|
421
|
-
args.push(
|
|
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
|
}
|