@parall/claude-agent 1.57.2 → 1.58.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/dispatch.d.ts +6 -7
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +12 -56
- package/dist/index.js +12 -5
- package/dist/spawn-env.d.ts +9 -0
- package/dist/spawn-env.d.ts.map +1 -0
- package/dist/spawn-env.js +61 -0
- package/package.json +4 -4
- package/src/dispatch.ts +26 -76
- package/src/index.ts +27 -15
- package/src/spawn-env.ts +77 -0
package/dist/dispatch.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { CleanupForkOpts, DispatchAdapter, DispatchInputLifecycle, DispatchOpts, ForkOpts, RuntimeEvent } from '@parall/agent-core';
|
|
2
2
|
import type { ClaudeAgentConfig } from './config.js';
|
|
3
3
|
import type { ClaudeSessionManager } from './session-manager.js';
|
|
4
|
+
import { buildSpawnEnv } from './spawn-env.js';
|
|
5
|
+
export { buildSpawnEnv };
|
|
4
6
|
type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig, 'additionalDirs' | 'allowApiKey' | 'allowedTools' | 'appendSystemPrompt' | 'claudeBin' | 'claudeHome' | 'disallowedTools' | 'model' | 'permissionMode' | 'workspaceDir'> & {
|
|
5
7
|
sessionManager: ClaudeSessionManager;
|
|
6
8
|
apiUrl: string;
|
|
7
9
|
apiKey: string;
|
|
8
10
|
orgId: string;
|
|
11
|
+
/** Catalog context size for the selected model. */
|
|
12
|
+
contextWindow?: number;
|
|
9
13
|
contextFilePathForSession?: (sessionKey: string) => string;
|
|
10
14
|
/** @deprecated Use contextFilePathForSession. */
|
|
11
15
|
stepIdFilePathForSession?: (sessionKey: string) => string;
|
|
@@ -27,12 +31,6 @@ type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig, 'additionalDirs' | 'allo
|
|
|
27
31
|
*/
|
|
28
32
|
capabilityBinDir?: string;
|
|
29
33
|
};
|
|
30
|
-
export declare function buildSpawnEnv(parentEnv: NodeJS.ProcessEnv, claudeHome: string, context: DispatchOpts['context'], opts: {
|
|
31
|
-
allowApiKey: boolean;
|
|
32
|
-
effortLevel?: string;
|
|
33
|
-
wikiMountRoot?: string;
|
|
34
|
-
capabilityBinDir?: string;
|
|
35
|
-
}): NodeJS.ProcessEnv;
|
|
36
34
|
export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
37
35
|
private readonly opts;
|
|
38
36
|
readonly inputLifecycleMode: "explicit";
|
|
@@ -42,12 +40,14 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
42
40
|
private shuttingDown;
|
|
43
41
|
private _model;
|
|
44
42
|
private _effortLevel;
|
|
43
|
+
private _contextWindow;
|
|
45
44
|
constructor(opts: ClaudeCodeAdapterOptions);
|
|
46
45
|
get currentModel(): string | undefined;
|
|
47
46
|
get currentEffort(): string | undefined;
|
|
48
47
|
updateConfig(config: {
|
|
49
48
|
model?: string | null;
|
|
50
49
|
effort?: string | null;
|
|
50
|
+
contextWindow?: number | null;
|
|
51
51
|
}): void;
|
|
52
52
|
requestProcessRestart(): void;
|
|
53
53
|
enqueueDuringDispatch(sessionKey: string, body: string, inputLifecycle?: DispatchInputLifecycle): boolean;
|
|
@@ -85,5 +85,4 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
85
85
|
private buildArgs;
|
|
86
86
|
private buildPlaceholderContext;
|
|
87
87
|
}
|
|
88
|
-
export {};
|
|
89
88
|
//# sourceMappingURL=dispatch.d.ts.map
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
|
package/dist/dispatch.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
1
3
|
import * as fs from 'node:fs';
|
|
2
4
|
import * as path from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { execSync, spawn } from 'node:child_process';
|
|
5
5
|
import { appendPreparedLocalAttachmentRefs, pinLocalAttachmentPaths, } from '@parall/agent-core/internal/attachment-input';
|
|
6
6
|
import { ClaudeInputRegistry } from './input-lifecycle.js';
|
|
7
7
|
import { parseClaudeStreamJson, } from './output-parser.js';
|
|
8
|
+
import { buildSpawnEnv } from './spawn-env.js';
|
|
8
9
|
import { classifyClaudeTurn } from './turn-outcome.js';
|
|
10
|
+
export { buildSpawnEnv };
|
|
9
11
|
const IS_WIN32 = process.platform === 'win32';
|
|
10
12
|
const CAPABILITY_PROBE_TIMEOUT_MS = 15_000;
|
|
11
13
|
class MissingClaudeLifecycleCapabilityError extends Error {
|
|
@@ -24,58 +26,6 @@ function killWin32Tree(pid) {
|
|
|
24
26
|
return false;
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
|
-
export function buildSpawnEnv(parentEnv, claudeHome, context, opts) {
|
|
28
|
-
const env = { ...parentEnv };
|
|
29
|
-
if (!opts.allowApiKey) {
|
|
30
|
-
delete env.ANTHROPIC_API_KEY;
|
|
31
|
-
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
32
|
-
}
|
|
33
|
-
const result = {
|
|
34
|
-
...env,
|
|
35
|
-
HOME: claudeHome,
|
|
36
|
-
PRLL_API_URL: context.apiUrl,
|
|
37
|
-
PRLL_API_KEY: context.apiKey,
|
|
38
|
-
PRLL_ORG_ID: context.orgId,
|
|
39
|
-
PRLL_SESSION_ID: context.sessionId ?? '',
|
|
40
|
-
PRLL_CHAT_ID: context.chatId ?? '',
|
|
41
|
-
PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? '',
|
|
42
|
-
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
43
|
-
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
44
|
-
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
45
|
-
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
46
|
-
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
47
|
-
// CLI keys into it by send target to derive dispatch effect keys.
|
|
48
|
-
PRLL_CONTEXT_DIR: context.contextDirPath ?? '',
|
|
49
|
-
};
|
|
50
|
-
// Pin the wiki workspace to the agent workspace (same convention as the
|
|
51
|
-
// OpenClaw runtime). Without this, `parall wiki` resolves mounts relative
|
|
52
|
-
// to the subprocess cwd — a `cd` into any subdirectory would silently
|
|
53
|
-
// split the agent across two wiki workspaces. Operator-set env wins.
|
|
54
|
-
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
55
|
-
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
56
|
-
}
|
|
57
|
-
if (opts.effortLevel) {
|
|
58
|
-
result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
|
|
59
|
-
}
|
|
60
|
-
// Channel-capability shims resolve ahead of any globally-installed CLI of
|
|
61
|
-
// the same name (the shim owns auth; the real binary is found further down
|
|
62
|
-
// the PATH by the shim itself). On Windows env keys are case-insensitive and
|
|
63
|
-
// the host key is usually `Path`; after the plain-object spread a literal
|
|
64
|
-
// `PATH` write would SPLIT the key (undefined which wins in the child), so
|
|
65
|
-
// reuse the parent's casing. On POSIX keys are case-SENSITIVE — `PATH` is
|
|
66
|
-
// the real one and a stray `Path` is unrelated, so match exactly there or we
|
|
67
|
-
// could prepend to the wrong variable and silently bypass the shim.
|
|
68
|
-
if (opts.capabilityBinDir) {
|
|
69
|
-
const pathKey = process.platform === 'win32'
|
|
70
|
-
? (Object.keys(result).find((k) => k.toUpperCase() === 'PATH') ?? 'PATH')
|
|
71
|
-
: 'PATH';
|
|
72
|
-
const existing = result[pathKey];
|
|
73
|
-
result[pathKey] = existing
|
|
74
|
-
? `${opts.capabilityBinDir}${path.delimiter}${existing}`
|
|
75
|
-
: opts.capabilityBinDir;
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
79
29
|
export class ClaudeCodeAdapter {
|
|
80
30
|
opts;
|
|
81
31
|
inputLifecycleMode = 'explicit';
|
|
@@ -85,9 +35,11 @@ export class ClaudeCodeAdapter {
|
|
|
85
35
|
shuttingDown = false;
|
|
86
36
|
_model;
|
|
87
37
|
_effortLevel;
|
|
38
|
+
_contextWindow;
|
|
88
39
|
constructor(opts) {
|
|
89
40
|
this.opts = opts;
|
|
90
41
|
this._model = opts.model;
|
|
42
|
+
this._contextWindow = opts.contextWindow;
|
|
91
43
|
}
|
|
92
44
|
get currentModel() {
|
|
93
45
|
return this._model;
|
|
@@ -98,11 +50,14 @@ export class ClaudeCodeAdapter {
|
|
|
98
50
|
updateConfig(config) {
|
|
99
51
|
const modelChanged = config.model !== undefined && config.model !== this._model;
|
|
100
52
|
const effortChanged = config.effort !== undefined && config.effort !== this._effortLevel;
|
|
53
|
+
const contextWindowChanged = config.contextWindow !== undefined && config.contextWindow !== this._contextWindow;
|
|
101
54
|
if (modelChanged)
|
|
102
55
|
this._model = config.model ?? undefined;
|
|
103
56
|
if (effortChanged)
|
|
104
57
|
this._effortLevel = config.effort ?? undefined;
|
|
105
|
-
if (
|
|
58
|
+
if (contextWindowChanged)
|
|
59
|
+
this._contextWindow = config.contextWindow ?? undefined;
|
|
60
|
+
if (modelChanged || effortChanged || contextWindowChanged) {
|
|
106
61
|
this.requestProcessRestart();
|
|
107
62
|
}
|
|
108
63
|
}
|
|
@@ -557,11 +512,12 @@ export class ClaudeCodeAdapter {
|
|
|
557
512
|
const args = this.buildArgs(sessionKey, resume);
|
|
558
513
|
const env = buildSpawnEnv(process.env, this.opts.claudeHome, this.buildPlaceholderContext(sessionKey), {
|
|
559
514
|
allowApiKey: this.opts.allowApiKey,
|
|
515
|
+
contextWindow: this._contextWindow,
|
|
560
516
|
effortLevel: this._effortLevel,
|
|
561
517
|
wikiMountRoot: this.opts.workspaceDir,
|
|
562
518
|
capabilityBinDir: this.opts.capabilityBinDir,
|
|
563
519
|
});
|
|
564
|
-
log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`);
|
|
520
|
+
log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, context=${this._contextWindow || 'cli-default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`);
|
|
565
521
|
const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.claudeBin) : this.opts.claudeBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
|
|
566
522
|
cwd: this.opts.workspaceDir,
|
|
567
523
|
env,
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as os from 'node:os';
|
|
3
|
-
import {
|
|
3
|
+
import { capabilityBinDir, childLogger, clearAllProviderCreds, configureHttpKeepAlive, createLogger, createOtelLogger, createPlatformConfigManager, deriveModelIsPin, identityFromMe, initAgentTelemetry, llmSource, materializeChannelCapabilities, ParallAgentGateway, parseDispatchDeadlineMs, parseForkDeadlineMs, parseProviderConfig, parseShutdownDeadlineMs, resolveRuntimeContextWindow, resolveRuntimeModel, } from '@parall/agent-core';
|
|
4
4
|
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
5
5
|
import { buildClaudeRuntimeKey, contextFilePathForSession, dispatchContextDirPath, resolveClaudeAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from './config.js';
|
|
6
6
|
import { ClaudeCodeAdapter } from './dispatch.js';
|
|
@@ -22,10 +22,10 @@ async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
|
22
22
|
}
|
|
23
23
|
function resolveProviderEnv() {
|
|
24
24
|
const pc = parseProviderConfig(process.env);
|
|
25
|
+
const source = llmSource(pc);
|
|
25
26
|
if (!pc)
|
|
26
|
-
return;
|
|
27
|
+
return source;
|
|
27
28
|
clearAllProviderCreds(process.env);
|
|
28
|
-
const source = llmSource(pc);
|
|
29
29
|
if (source === 'parall') {
|
|
30
30
|
process.env.ANTHROPIC_AUTH_TOKEN = process.env.PRLL_API_KEY;
|
|
31
31
|
process.env.ANTHROPIC_BASE_URL = `${process.env.PRLL_API_URL}/api/llm`;
|
|
@@ -39,6 +39,7 @@ function resolveProviderEnv() {
|
|
|
39
39
|
if (pc.anthropic_base_url)
|
|
40
40
|
process.env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
|
|
41
41
|
}
|
|
42
|
+
return source;
|
|
42
43
|
}
|
|
43
44
|
async function main() {
|
|
44
45
|
// Before any fetch: long-lived HTTP connections for every bridge→api call.
|
|
@@ -46,7 +47,7 @@ async function main() {
|
|
|
46
47
|
const telemetry = await initAgentTelemetry('parall-claude-agent', 'claude-code');
|
|
47
48
|
activeLog = createOtelLogger('agent', 'claude-agent');
|
|
48
49
|
try {
|
|
49
|
-
resolveProviderEnv();
|
|
50
|
+
const activeLLMSource = resolveProviderEnv();
|
|
50
51
|
const config = resolveClaudeAgentConfig(process.env);
|
|
51
52
|
if (config.allowApiKey && (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN)) {
|
|
52
53
|
// Surface the billing path: default is OAuth (Claude.ai subscription); this
|
|
@@ -111,17 +112,21 @@ async function main() {
|
|
|
111
112
|
// server only when pinned, so we no longer gate it on a local flag.
|
|
112
113
|
const platformModelOverride = deriveModelIsPin(platformDefaults, me.agent_profile);
|
|
113
114
|
const resolvedModel = resolveRuntimeModel(platformModelOverride, platformDefaults.model, config.model);
|
|
115
|
+
const resolvedContextWindow = resolveRuntimeContextWindow(resolvedModel, platformDefaults, activeLLMSource);
|
|
114
116
|
const resolvedEffort = platformDefaults.thinkingEffort ??
|
|
115
117
|
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined);
|
|
116
118
|
if (platformDefaults.model)
|
|
117
119
|
agentLog.info(`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? 'pin/override' : 'floor'})`);
|
|
118
120
|
if (platformDefaults.thinkingEffort)
|
|
119
121
|
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
|
|
122
|
+
if (resolvedContextWindow)
|
|
123
|
+
agentLog.info(`platform config: context_window=${resolvedContextWindow}`);
|
|
120
124
|
const adapter = new ClaudeCodeAdapter({
|
|
121
125
|
claudeBin: config.claudeBin,
|
|
122
126
|
claudeHome: config.claudeHome,
|
|
123
127
|
workspaceDir: config.workspaceDir,
|
|
124
128
|
model: resolvedModel,
|
|
129
|
+
contextWindow: resolvedContextWindow,
|
|
125
130
|
permissionMode: config.permissionMode,
|
|
126
131
|
allowedTools: config.allowedTools,
|
|
127
132
|
disallowedTools: config.disallowedTools,
|
|
@@ -170,8 +175,10 @@ async function main() {
|
|
|
170
175
|
}
|
|
171
176
|
const isPin = deriveModelIsPin(updated, lastKnownAgentProfile);
|
|
172
177
|
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
178
|
+
const model = resolveRuntimeModel(isPin, updated.model, config.model) ?? null;
|
|
173
179
|
adapter.updateConfig({
|
|
174
|
-
model
|
|
180
|
+
model,
|
|
181
|
+
contextWindow: resolveRuntimeContextWindow(model, updated, activeLLMSource) ?? null,
|
|
175
182
|
effort: updated.thinkingEffort ?? localEffort,
|
|
176
183
|
});
|
|
177
184
|
// Channel capabilities heat-update with the config: re-materialize the
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DispatchOpts } from '@parall/agent-core';
|
|
2
|
+
export declare function buildSpawnEnv(parentEnv: NodeJS.ProcessEnv, claudeHome: string, context: DispatchOpts['context'], opts: {
|
|
3
|
+
allowApiKey: boolean;
|
|
4
|
+
contextWindow?: number;
|
|
5
|
+
effortLevel?: string;
|
|
6
|
+
wikiMountRoot?: string;
|
|
7
|
+
capabilityBinDir?: string;
|
|
8
|
+
}): NodeJS.ProcessEnv;
|
|
9
|
+
//# sourceMappingURL=spawn-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-env.d.ts","sourceRoot":"","sources":["../src/spawn-env.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IACJ,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GACA,MAAM,CAAC,UAAU,CA8DnB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
export function buildSpawnEnv(parentEnv, claudeHome, context, opts) {
|
|
3
|
+
const env = { ...parentEnv };
|
|
4
|
+
if (!opts.allowApiKey) {
|
|
5
|
+
delete env.ANTHROPIC_API_KEY;
|
|
6
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
7
|
+
}
|
|
8
|
+
const result = {
|
|
9
|
+
...env,
|
|
10
|
+
HOME: claudeHome,
|
|
11
|
+
PRLL_API_URL: context.apiUrl,
|
|
12
|
+
PRLL_API_KEY: context.apiKey,
|
|
13
|
+
PRLL_ORG_ID: context.orgId,
|
|
14
|
+
PRLL_SESSION_ID: context.sessionId ?? '',
|
|
15
|
+
PRLL_CHAT_ID: context.chatId ?? '',
|
|
16
|
+
PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? '',
|
|
17
|
+
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
18
|
+
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
19
|
+
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
20
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
21
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
22
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
23
|
+
PRLL_CONTEXT_DIR: context.contextDirPath ?? '',
|
|
24
|
+
};
|
|
25
|
+
// Pin the wiki workspace to the agent workspace (same convention as the
|
|
26
|
+
// OpenClaw runtime). Without this, `parall wiki` resolves mounts relative
|
|
27
|
+
// to the subprocess cwd — a `cd` into any subdirectory would silently
|
|
28
|
+
// split the agent across two wiki workspaces. Operator-set env wins.
|
|
29
|
+
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
30
|
+
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
31
|
+
}
|
|
32
|
+
if (opts.effortLevel) {
|
|
33
|
+
result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
|
|
34
|
+
}
|
|
35
|
+
if (typeof opts.contextWindow === 'number' &&
|
|
36
|
+
Number.isSafeInteger(opts.contextWindow) &&
|
|
37
|
+
opts.contextWindow > 0) {
|
|
38
|
+
// Claude Code otherwise assumes 200k for an unrecognized model ID. Its
|
|
39
|
+
// own auto-compaction policy reserves the output buffer before this
|
|
40
|
+
// declared boundary; Parall supplies the model fact, not a second ratio.
|
|
41
|
+
result.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(opts.contextWindow);
|
|
42
|
+
}
|
|
43
|
+
// Channel-capability shims resolve ahead of any globally-installed CLI of
|
|
44
|
+
// the same name (the shim owns auth; the real binary is found further down
|
|
45
|
+
// the PATH by the shim itself). On Windows env keys are case-insensitive and
|
|
46
|
+
// the host key is usually `Path`; after the plain-object spread a literal
|
|
47
|
+
// `PATH` write would SPLIT the key (undefined which wins in the child), so
|
|
48
|
+
// reuse the parent's casing. On POSIX keys are case-SENSITIVE — `PATH` is
|
|
49
|
+
// the real one and a stray `Path` is unrelated, so match exactly there or we
|
|
50
|
+
// could prepend to the wrong variable and silently bypass the shim.
|
|
51
|
+
if (opts.capabilityBinDir) {
|
|
52
|
+
const pathKey = process.platform === 'win32'
|
|
53
|
+
? (Object.keys(result).find((k) => k.toUpperCase() === 'PATH') ?? 'PATH')
|
|
54
|
+
: 'PATH';
|
|
55
|
+
const existing = result[pathKey];
|
|
56
|
+
result[pathKey] = existing
|
|
57
|
+
? `${opts.capabilityBinDir}${path.delimiter}${existing}`
|
|
58
|
+
: opts.capabilityBinDir;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/claude-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.0",
|
|
4
4
|
"description": "Claude Code bridge runtime for self-hosted Parall agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@parall/agent-core": "1.
|
|
29
|
-
"@parall/
|
|
30
|
-
"@parall/
|
|
28
|
+
"@parall/agent-core": "1.58.0",
|
|
29
|
+
"@parall/cli": "1.58.0",
|
|
30
|
+
"@parall/sdk": "1.58.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|
package/src/dispatch.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
1
3
|
import * as fs from 'node:fs';
|
|
2
4
|
import * as path from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { execSync, spawn } from 'node:child_process';
|
|
5
|
-
import {
|
|
6
|
-
appendPreparedLocalAttachmentRefs,
|
|
7
|
-
pinLocalAttachmentPaths,
|
|
8
|
-
} from '@parall/agent-core/internal/attachment-input';
|
|
9
5
|
import type {
|
|
10
6
|
CleanupForkOpts,
|
|
11
7
|
DispatchAdapter,
|
|
@@ -15,16 +11,23 @@ import type {
|
|
|
15
11
|
GatewayLogger,
|
|
16
12
|
RuntimeEvent,
|
|
17
13
|
} from '@parall/agent-core';
|
|
14
|
+
import {
|
|
15
|
+
appendPreparedLocalAttachmentRefs,
|
|
16
|
+
pinLocalAttachmentPaths,
|
|
17
|
+
} from '@parall/agent-core/internal/attachment-input';
|
|
18
18
|
import type { ClaudeAgentConfig } from './config.js';
|
|
19
|
-
import {
|
|
19
|
+
import { type ClaudeInputDelivery, ClaudeInputRegistry } from './input-lifecycle.js';
|
|
20
20
|
import {
|
|
21
|
-
parseClaudeStreamJson,
|
|
22
21
|
type ClaudeParsedEvent,
|
|
23
22
|
type ClaudeResultMeta,
|
|
23
|
+
parseClaudeStreamJson,
|
|
24
24
|
} from './output-parser.js';
|
|
25
25
|
import type { ClaudeProcessHandle, ClaudeSessionManager } from './session-manager.js';
|
|
26
|
+
import { buildSpawnEnv } from './spawn-env.js';
|
|
26
27
|
import { classifyClaudeTurn } from './turn-outcome.js';
|
|
27
28
|
|
|
29
|
+
export { buildSpawnEnv };
|
|
30
|
+
|
|
28
31
|
type ClaudeCodeAdapterOptions = Pick<
|
|
29
32
|
ClaudeAgentConfig,
|
|
30
33
|
| 'additionalDirs'
|
|
@@ -42,6 +45,8 @@ type ClaudeCodeAdapterOptions = Pick<
|
|
|
42
45
|
apiUrl: string;
|
|
43
46
|
apiKey: string;
|
|
44
47
|
orgId: string;
|
|
48
|
+
/** Catalog context size for the selected model. */
|
|
49
|
+
contextWindow?: number;
|
|
45
50
|
contextFilePathForSession?: (sessionKey: string) => string;
|
|
46
51
|
/** @deprecated Use contextFilePathForSession. */
|
|
47
52
|
stepIdFilePathForSession?: (sessionKey: string) => string;
|
|
@@ -83,70 +88,6 @@ function killWin32Tree(pid: number): boolean {
|
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
export function buildSpawnEnv(
|
|
87
|
-
parentEnv: NodeJS.ProcessEnv,
|
|
88
|
-
claudeHome: string,
|
|
89
|
-
context: DispatchOpts['context'],
|
|
90
|
-
opts: {
|
|
91
|
-
allowApiKey: boolean;
|
|
92
|
-
effortLevel?: string;
|
|
93
|
-
wikiMountRoot?: string;
|
|
94
|
-
capabilityBinDir?: string;
|
|
95
|
-
},
|
|
96
|
-
): NodeJS.ProcessEnv {
|
|
97
|
-
const env: NodeJS.ProcessEnv = { ...parentEnv };
|
|
98
|
-
if (!opts.allowApiKey) {
|
|
99
|
-
delete env.ANTHROPIC_API_KEY;
|
|
100
|
-
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
101
|
-
}
|
|
102
|
-
const result: NodeJS.ProcessEnv = {
|
|
103
|
-
...env,
|
|
104
|
-
HOME: claudeHome,
|
|
105
|
-
PRLL_API_URL: context.apiUrl,
|
|
106
|
-
PRLL_API_KEY: context.apiKey,
|
|
107
|
-
PRLL_ORG_ID: context.orgId,
|
|
108
|
-
PRLL_SESSION_ID: context.sessionId ?? '',
|
|
109
|
-
PRLL_CHAT_ID: context.chatId ?? '',
|
|
110
|
-
PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? '',
|
|
111
|
-
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
112
|
-
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
113
|
-
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
114
|
-
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
115
|
-
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
116
|
-
// CLI keys into it by send target to derive dispatch effect keys.
|
|
117
|
-
PRLL_CONTEXT_DIR: context.contextDirPath ?? '',
|
|
118
|
-
};
|
|
119
|
-
// Pin the wiki workspace to the agent workspace (same convention as the
|
|
120
|
-
// OpenClaw runtime). Without this, `parall wiki` resolves mounts relative
|
|
121
|
-
// to the subprocess cwd — a `cd` into any subdirectory would silently
|
|
122
|
-
// split the agent across two wiki workspaces. Operator-set env wins.
|
|
123
|
-
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
124
|
-
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
125
|
-
}
|
|
126
|
-
if (opts.effortLevel) {
|
|
127
|
-
result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
|
|
128
|
-
}
|
|
129
|
-
// Channel-capability shims resolve ahead of any globally-installed CLI of
|
|
130
|
-
// the same name (the shim owns auth; the real binary is found further down
|
|
131
|
-
// the PATH by the shim itself). On Windows env keys are case-insensitive and
|
|
132
|
-
// the host key is usually `Path`; after the plain-object spread a literal
|
|
133
|
-
// `PATH` write would SPLIT the key (undefined which wins in the child), so
|
|
134
|
-
// reuse the parent's casing. On POSIX keys are case-SENSITIVE — `PATH` is
|
|
135
|
-
// the real one and a stray `Path` is unrelated, so match exactly there or we
|
|
136
|
-
// could prepend to the wrong variable and silently bypass the shim.
|
|
137
|
-
if (opts.capabilityBinDir) {
|
|
138
|
-
const pathKey =
|
|
139
|
-
process.platform === 'win32'
|
|
140
|
-
? (Object.keys(result).find((k) => k.toUpperCase() === 'PATH') ?? 'PATH')
|
|
141
|
-
: 'PATH';
|
|
142
|
-
const existing = result[pathKey];
|
|
143
|
-
result[pathKey] = existing
|
|
144
|
-
? `${opts.capabilityBinDir}${path.delimiter}${existing}`
|
|
145
|
-
: opts.capabilityBinDir;
|
|
146
|
-
}
|
|
147
|
-
return result;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
91
|
/**
|
|
151
92
|
* Per-sessionKey long-lived process state. The process stays alive across
|
|
152
93
|
* dispatches; each dispatch writes one NDJSON user message to stdin and
|
|
@@ -171,9 +112,11 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
171
112
|
private shuttingDown = false;
|
|
172
113
|
private _model: string | undefined;
|
|
173
114
|
private _effortLevel: string | undefined;
|
|
115
|
+
private _contextWindow: number | undefined;
|
|
174
116
|
|
|
175
117
|
constructor(private readonly opts: ClaudeCodeAdapterOptions) {
|
|
176
118
|
this._model = opts.model;
|
|
119
|
+
this._contextWindow = opts.contextWindow;
|
|
177
120
|
}
|
|
178
121
|
|
|
179
122
|
get currentModel(): string | undefined {
|
|
@@ -182,13 +125,19 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
182
125
|
get currentEffort(): string | undefined {
|
|
183
126
|
return this._effortLevel;
|
|
184
127
|
}
|
|
185
|
-
|
|
186
|
-
|
|
128
|
+
updateConfig(config: {
|
|
129
|
+
model?: string | null;
|
|
130
|
+
effort?: string | null;
|
|
131
|
+
contextWindow?: number | null;
|
|
132
|
+
}): void {
|
|
187
133
|
const modelChanged = config.model !== undefined && config.model !== this._model;
|
|
188
134
|
const effortChanged = config.effort !== undefined && config.effort !== this._effortLevel;
|
|
135
|
+
const contextWindowChanged =
|
|
136
|
+
config.contextWindow !== undefined && config.contextWindow !== this._contextWindow;
|
|
189
137
|
if (modelChanged) this._model = config.model ?? undefined;
|
|
190
138
|
if (effortChanged) this._effortLevel = config.effort ?? undefined;
|
|
191
|
-
if (
|
|
139
|
+
if (contextWindowChanged) this._contextWindow = config.contextWindow ?? undefined;
|
|
140
|
+
if (modelChanged || effortChanged || contextWindowChanged) {
|
|
192
141
|
this.requestProcessRestart();
|
|
193
142
|
}
|
|
194
143
|
}
|
|
@@ -714,6 +663,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
714
663
|
this.buildPlaceholderContext(sessionKey),
|
|
715
664
|
{
|
|
716
665
|
allowApiKey: this.opts.allowApiKey,
|
|
666
|
+
contextWindow: this._contextWindow,
|
|
717
667
|
effortLevel: this._effortLevel,
|
|
718
668
|
wikiMountRoot: this.opts.workspaceDir,
|
|
719
669
|
capabilityBinDir: this.opts.capabilityBinDir,
|
|
@@ -721,7 +671,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
721
671
|
);
|
|
722
672
|
|
|
723
673
|
log?.info(
|
|
724
|
-
`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`,
|
|
674
|
+
`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, context=${this._contextWindow || 'cli-default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`,
|
|
725
675
|
);
|
|
726
676
|
|
|
727
677
|
const proc = spawn(
|
package/src/index.ts
CHANGED
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import {
|
|
5
|
-
ParallAgentGateway,
|
|
6
5
|
capabilityBinDir,
|
|
6
|
+
childLogger,
|
|
7
|
+
clearAllProviderCreds,
|
|
7
8
|
configureHttpKeepAlive,
|
|
8
|
-
createPlatformConfigManager,
|
|
9
9
|
createLogger,
|
|
10
10
|
createOtelLogger,
|
|
11
|
-
|
|
11
|
+
createPlatformConfigManager,
|
|
12
12
|
deriveModelIsPin,
|
|
13
|
+
identityFromMe,
|
|
14
|
+
initAgentTelemetry,
|
|
15
|
+
llmSource,
|
|
13
16
|
materializeChannelCapabilities,
|
|
14
|
-
|
|
15
|
-
parseShutdownDeadlineMs,
|
|
16
|
-
parseForkDeadlineMs,
|
|
17
|
+
ParallAgentGateway,
|
|
17
18
|
parseDispatchDeadlineMs,
|
|
19
|
+
parseForkDeadlineMs,
|
|
18
20
|
parseProviderConfig,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
initAgentTelemetry,
|
|
21
|
+
parseShutdownDeadlineMs,
|
|
22
|
+
resolveRuntimeContextWindow,
|
|
23
|
+
resolveRuntimeModel,
|
|
23
24
|
} from '@parall/agent-core';
|
|
24
25
|
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
25
26
|
import {
|
|
@@ -52,11 +53,11 @@ async function getAgentMeWithLegacyFallback(client: ParallClient, orgId: string)
|
|
|
52
53
|
|
|
53
54
|
type AgentMe = Awaited<ReturnType<typeof getAgentMeWithLegacyFallback>>;
|
|
54
55
|
|
|
55
|
-
function resolveProviderEnv():
|
|
56
|
+
function resolveProviderEnv(): string {
|
|
56
57
|
const pc = parseProviderConfig(process.env);
|
|
57
|
-
if (!pc) return;
|
|
58
|
-
clearAllProviderCreds(process.env);
|
|
59
58
|
const source = llmSource(pc);
|
|
59
|
+
if (!pc) return source;
|
|
60
|
+
clearAllProviderCreds(process.env);
|
|
60
61
|
if (source === 'parall') {
|
|
61
62
|
process.env.ANTHROPIC_AUTH_TOKEN = process.env.PRLL_API_KEY;
|
|
62
63
|
process.env.ANTHROPIC_BASE_URL = `${process.env.PRLL_API_URL}/api/llm`;
|
|
@@ -68,6 +69,7 @@ function resolveProviderEnv(): void {
|
|
|
68
69
|
}
|
|
69
70
|
if (pc.anthropic_base_url) process.env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
|
|
70
71
|
}
|
|
72
|
+
return source;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
async function main() {
|
|
@@ -76,7 +78,7 @@ async function main() {
|
|
|
76
78
|
const telemetry = await initAgentTelemetry('parall-claude-agent', 'claude-code');
|
|
77
79
|
activeLog = createOtelLogger('agent', 'claude-agent');
|
|
78
80
|
try {
|
|
79
|
-
resolveProviderEnv();
|
|
81
|
+
const activeLLMSource = resolveProviderEnv();
|
|
80
82
|
const config = resolveClaudeAgentConfig(process.env);
|
|
81
83
|
|
|
82
84
|
if (config.allowApiKey && (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN)) {
|
|
@@ -152,6 +154,11 @@ async function main() {
|
|
|
152
154
|
platformDefaults.model,
|
|
153
155
|
config.model,
|
|
154
156
|
);
|
|
157
|
+
const resolvedContextWindow = resolveRuntimeContextWindow(
|
|
158
|
+
resolvedModel,
|
|
159
|
+
platformDefaults,
|
|
160
|
+
activeLLMSource,
|
|
161
|
+
);
|
|
155
162
|
const resolvedEffort =
|
|
156
163
|
platformDefaults.thinkingEffort ??
|
|
157
164
|
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined);
|
|
@@ -161,12 +168,15 @@ async function main() {
|
|
|
161
168
|
);
|
|
162
169
|
if (platformDefaults.thinkingEffort)
|
|
163
170
|
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
|
|
171
|
+
if (resolvedContextWindow)
|
|
172
|
+
agentLog.info(`platform config: context_window=${resolvedContextWindow}`);
|
|
164
173
|
|
|
165
174
|
const adapter = new ClaudeCodeAdapter({
|
|
166
175
|
claudeBin: config.claudeBin,
|
|
167
176
|
claudeHome: config.claudeHome,
|
|
168
177
|
workspaceDir: config.workspaceDir,
|
|
169
178
|
model: resolvedModel,
|
|
179
|
+
contextWindow: resolvedContextWindow,
|
|
170
180
|
permissionMode: config.permissionMode,
|
|
171
181
|
allowedTools: config.allowedTools,
|
|
172
182
|
disallowedTools: config.disallowedTools,
|
|
@@ -219,8 +229,10 @@ async function main() {
|
|
|
219
229
|
}
|
|
220
230
|
const isPin = deriveModelIsPin(updated, lastKnownAgentProfile);
|
|
221
231
|
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
232
|
+
const model = resolveRuntimeModel(isPin, updated.model, config.model) ?? null;
|
|
222
233
|
adapter.updateConfig({
|
|
223
|
-
model
|
|
234
|
+
model,
|
|
235
|
+
contextWindow: resolveRuntimeContextWindow(model, updated, activeLLMSource) ?? null,
|
|
224
236
|
effort: updated.thinkingEffort ?? localEffort,
|
|
225
237
|
});
|
|
226
238
|
// Channel capabilities heat-update with the config: re-materialize the
|
package/src/spawn-env.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import type { DispatchOpts } from '@parall/agent-core';
|
|
3
|
+
|
|
4
|
+
export function buildSpawnEnv(
|
|
5
|
+
parentEnv: NodeJS.ProcessEnv,
|
|
6
|
+
claudeHome: string,
|
|
7
|
+
context: DispatchOpts['context'],
|
|
8
|
+
opts: {
|
|
9
|
+
allowApiKey: boolean;
|
|
10
|
+
contextWindow?: number;
|
|
11
|
+
effortLevel?: string;
|
|
12
|
+
wikiMountRoot?: string;
|
|
13
|
+
capabilityBinDir?: string;
|
|
14
|
+
},
|
|
15
|
+
): NodeJS.ProcessEnv {
|
|
16
|
+
const env: NodeJS.ProcessEnv = { ...parentEnv };
|
|
17
|
+
if (!opts.allowApiKey) {
|
|
18
|
+
delete env.ANTHROPIC_API_KEY;
|
|
19
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
20
|
+
}
|
|
21
|
+
const result: NodeJS.ProcessEnv = {
|
|
22
|
+
...env,
|
|
23
|
+
HOME: claudeHome,
|
|
24
|
+
PRLL_API_URL: context.apiUrl,
|
|
25
|
+
PRLL_API_KEY: context.apiKey,
|
|
26
|
+
PRLL_ORG_ID: context.orgId,
|
|
27
|
+
PRLL_SESSION_ID: context.sessionId ?? '',
|
|
28
|
+
PRLL_CHAT_ID: context.chatId ?? '',
|
|
29
|
+
PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? '',
|
|
30
|
+
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
31
|
+
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
32
|
+
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
33
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
34
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
35
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
36
|
+
PRLL_CONTEXT_DIR: context.contextDirPath ?? '',
|
|
37
|
+
};
|
|
38
|
+
// Pin the wiki workspace to the agent workspace (same convention as the
|
|
39
|
+
// OpenClaw runtime). Without this, `parall wiki` resolves mounts relative
|
|
40
|
+
// to the subprocess cwd — a `cd` into any subdirectory would silently
|
|
41
|
+
// split the agent across two wiki workspaces. Operator-set env wins.
|
|
42
|
+
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
43
|
+
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
44
|
+
}
|
|
45
|
+
if (opts.effortLevel) {
|
|
46
|
+
result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
|
|
47
|
+
}
|
|
48
|
+
if (
|
|
49
|
+
typeof opts.contextWindow === 'number' &&
|
|
50
|
+
Number.isSafeInteger(opts.contextWindow) &&
|
|
51
|
+
opts.contextWindow > 0
|
|
52
|
+
) {
|
|
53
|
+
// Claude Code otherwise assumes 200k for an unrecognized model ID. Its
|
|
54
|
+
// own auto-compaction policy reserves the output buffer before this
|
|
55
|
+
// declared boundary; Parall supplies the model fact, not a second ratio.
|
|
56
|
+
result.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(opts.contextWindow);
|
|
57
|
+
}
|
|
58
|
+
// Channel-capability shims resolve ahead of any globally-installed CLI of
|
|
59
|
+
// the same name (the shim owns auth; the real binary is found further down
|
|
60
|
+
// the PATH by the shim itself). On Windows env keys are case-insensitive and
|
|
61
|
+
// the host key is usually `Path`; after the plain-object spread a literal
|
|
62
|
+
// `PATH` write would SPLIT the key (undefined which wins in the child), so
|
|
63
|
+
// reuse the parent's casing. On POSIX keys are case-SENSITIVE — `PATH` is
|
|
64
|
+
// the real one and a stray `Path` is unrelated, so match exactly there or we
|
|
65
|
+
// could prepend to the wrong variable and silently bypass the shim.
|
|
66
|
+
if (opts.capabilityBinDir) {
|
|
67
|
+
const pathKey =
|
|
68
|
+
process.platform === 'win32'
|
|
69
|
+
? (Object.keys(result).find((k) => k.toUpperCase() === 'PATH') ?? 'PATH')
|
|
70
|
+
: 'PATH';
|
|
71
|
+
const existing = result[pathKey];
|
|
72
|
+
result[pathKey] = existing
|
|
73
|
+
? `${opts.capabilityBinDir}${path.delimiter}${existing}`
|
|
74
|
+
: opts.capabilityBinDir;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|