@parall/claude-agent 1.57.2 → 1.58.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatch.d.ts +10 -7
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +18 -56
- package/dist/index.js +17 -5
- package/dist/spawn-env.d.ts +10 -0
- package/dist/spawn-env.d.ts.map +1 -0
- package/dist/spawn-env.js +78 -0
- package/package.json +4 -4
- package/src/dispatch.ts +34 -76
- package/src/index.ts +36 -15
- package/src/spawn-env.ts +97 -0
package/dist/dispatch.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
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;
|
|
13
|
+
/** Catalog max output tokens for the selected model (input-budget subtrahend). */
|
|
14
|
+
maxTokens?: number;
|
|
9
15
|
contextFilePathForSession?: (sessionKey: string) => string;
|
|
10
16
|
/** @deprecated Use contextFilePathForSession. */
|
|
11
17
|
stepIdFilePathForSession?: (sessionKey: string) => string;
|
|
@@ -27,12 +33,6 @@ type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig, 'additionalDirs' | 'allo
|
|
|
27
33
|
*/
|
|
28
34
|
capabilityBinDir?: string;
|
|
29
35
|
};
|
|
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
36
|
export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
37
37
|
private readonly opts;
|
|
38
38
|
readonly inputLifecycleMode: "explicit";
|
|
@@ -42,12 +42,16 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
42
42
|
private shuttingDown;
|
|
43
43
|
private _model;
|
|
44
44
|
private _effortLevel;
|
|
45
|
+
private _contextWindow;
|
|
46
|
+
private _maxTokens;
|
|
45
47
|
constructor(opts: ClaudeCodeAdapterOptions);
|
|
46
48
|
get currentModel(): string | undefined;
|
|
47
49
|
get currentEffort(): string | undefined;
|
|
48
50
|
updateConfig(config: {
|
|
49
51
|
model?: string | null;
|
|
50
52
|
effort?: string | null;
|
|
53
|
+
contextWindow?: number | null;
|
|
54
|
+
maxTokens?: number | null;
|
|
51
55
|
}): void;
|
|
52
56
|
requestProcessRestart(): void;
|
|
53
57
|
enqueueDuringDispatch(sessionKey: string, body: string, inputLifecycle?: DispatchInputLifecycle): boolean;
|
|
@@ -85,5 +89,4 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
85
89
|
private buildArgs;
|
|
86
90
|
private buildPlaceholderContext;
|
|
87
91
|
}
|
|
88
|
-
export {};
|
|
89
92
|
//# 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,13 @@ export class ClaudeCodeAdapter {
|
|
|
85
35
|
shuttingDown = false;
|
|
86
36
|
_model;
|
|
87
37
|
_effortLevel;
|
|
38
|
+
_contextWindow;
|
|
39
|
+
_maxTokens;
|
|
88
40
|
constructor(opts) {
|
|
89
41
|
this.opts = opts;
|
|
90
42
|
this._model = opts.model;
|
|
43
|
+
this._contextWindow = opts.contextWindow;
|
|
44
|
+
this._maxTokens = opts.maxTokens;
|
|
91
45
|
}
|
|
92
46
|
get currentModel() {
|
|
93
47
|
return this._model;
|
|
@@ -98,11 +52,17 @@ export class ClaudeCodeAdapter {
|
|
|
98
52
|
updateConfig(config) {
|
|
99
53
|
const modelChanged = config.model !== undefined && config.model !== this._model;
|
|
100
54
|
const effortChanged = config.effort !== undefined && config.effort !== this._effortLevel;
|
|
55
|
+
const contextWindowChanged = config.contextWindow !== undefined && config.contextWindow !== this._contextWindow;
|
|
56
|
+
const maxTokensChanged = config.maxTokens !== undefined && config.maxTokens !== this._maxTokens;
|
|
101
57
|
if (modelChanged)
|
|
102
58
|
this._model = config.model ?? undefined;
|
|
103
59
|
if (effortChanged)
|
|
104
60
|
this._effortLevel = config.effort ?? undefined;
|
|
105
|
-
if (
|
|
61
|
+
if (contextWindowChanged)
|
|
62
|
+
this._contextWindow = config.contextWindow ?? undefined;
|
|
63
|
+
if (maxTokensChanged)
|
|
64
|
+
this._maxTokens = config.maxTokens ?? undefined;
|
|
65
|
+
if (modelChanged || effortChanged || contextWindowChanged || maxTokensChanged) {
|
|
106
66
|
this.requestProcessRestart();
|
|
107
67
|
}
|
|
108
68
|
}
|
|
@@ -557,11 +517,13 @@ export class ClaudeCodeAdapter {
|
|
|
557
517
|
const args = this.buildArgs(sessionKey, resume);
|
|
558
518
|
const env = buildSpawnEnv(process.env, this.opts.claudeHome, this.buildPlaceholderContext(sessionKey), {
|
|
559
519
|
allowApiKey: this.opts.allowApiKey,
|
|
520
|
+
contextWindow: this._contextWindow,
|
|
521
|
+
maxTokens: this._maxTokens,
|
|
560
522
|
effortLevel: this._effortLevel,
|
|
561
523
|
wikiMountRoot: this.opts.workspaceDir,
|
|
562
524
|
capabilityBinDir: this.opts.capabilityBinDir,
|
|
563
525
|
});
|
|
564
|
-
log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`);
|
|
526
|
+
log?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, context=${this._contextWindow || 'cli-default'}, maxOut=${this._maxTokens || 'cli-default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`);
|
|
565
527
|
const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.claudeBin) : this.opts.claudeBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
|
|
566
528
|
cwd: this.opts.workspaceDir,
|
|
567
529
|
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, resolveRuntimeMaxTokens, 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,25 @@ 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);
|
|
116
|
+
const resolvedMaxTokens = resolveRuntimeMaxTokens(resolvedModel, platformDefaults, activeLLMSource);
|
|
114
117
|
const resolvedEffort = platformDefaults.thinkingEffort ??
|
|
115
118
|
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined);
|
|
116
119
|
if (platformDefaults.model)
|
|
117
120
|
agentLog.info(`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? 'pin/override' : 'floor'})`);
|
|
118
121
|
if (platformDefaults.thinkingEffort)
|
|
119
122
|
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
|
|
123
|
+
if (resolvedContextWindow)
|
|
124
|
+
agentLog.info(`platform config: context_window=${resolvedContextWindow}`);
|
|
125
|
+
if (resolvedMaxTokens)
|
|
126
|
+
agentLog.info(`platform config: max_tokens=${resolvedMaxTokens}`);
|
|
120
127
|
const adapter = new ClaudeCodeAdapter({
|
|
121
128
|
claudeBin: config.claudeBin,
|
|
122
129
|
claudeHome: config.claudeHome,
|
|
123
130
|
workspaceDir: config.workspaceDir,
|
|
124
131
|
model: resolvedModel,
|
|
132
|
+
contextWindow: resolvedContextWindow,
|
|
133
|
+
maxTokens: resolvedMaxTokens,
|
|
125
134
|
permissionMode: config.permissionMode,
|
|
126
135
|
allowedTools: config.allowedTools,
|
|
127
136
|
disallowedTools: config.disallowedTools,
|
|
@@ -170,8 +179,11 @@ async function main() {
|
|
|
170
179
|
}
|
|
171
180
|
const isPin = deriveModelIsPin(updated, lastKnownAgentProfile);
|
|
172
181
|
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
182
|
+
const model = resolveRuntimeModel(isPin, updated.model, config.model) ?? null;
|
|
173
183
|
adapter.updateConfig({
|
|
174
|
-
model
|
|
184
|
+
model,
|
|
185
|
+
contextWindow: resolveRuntimeContextWindow(model, updated, activeLLMSource) ?? null,
|
|
186
|
+
maxTokens: resolveRuntimeMaxTokens(model, updated, activeLLMSource) ?? null,
|
|
175
187
|
effort: updated.thinkingEffort ?? localEffort,
|
|
176
188
|
});
|
|
177
189
|
// Channel capabilities heat-update with the config: re-materialize the
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
maxTokens?: number;
|
|
6
|
+
effortLevel?: string;
|
|
7
|
+
wikiMountRoot?: string;
|
|
8
|
+
capabilityBinDir?: string;
|
|
9
|
+
}): NodeJS.ProcessEnv;
|
|
10
|
+
//# 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,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GACA,MAAM,CAAC,UAAU,CAiFnB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
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.
|
|
39
|
+
//
|
|
40
|
+
// Declared as the model's INPUT budget (context_window - max_tokens; both
|
|
41
|
+
// raw catalog facts), not the full window. The upstream validates
|
|
42
|
+
// input + max_tokens <= context_window, while the CLI requests up to its
|
|
43
|
+
// own output default (32k) yet reserves at most 20k below this declared
|
|
44
|
+
// boundary before auto-compacting (2.1.251) — so a session declared the
|
|
45
|
+
// full window can grow into a band the provider already rejects but the
|
|
46
|
+
// CLI still considers legal: it retries forever, auto-compact never
|
|
47
|
+
// fires, and once past the boundary the compaction call itself is
|
|
48
|
+
// over-limit (the ParallSRE 08-31 lockup). Subtracting the catalog
|
|
49
|
+
// max_tokens keeps the compaction trigger strictly inside what the
|
|
50
|
+
// provider accepts.
|
|
51
|
+
let inputBudget = opts.contextWindow;
|
|
52
|
+
if (typeof opts.maxTokens === 'number' &&
|
|
53
|
+
Number.isSafeInteger(opts.maxTokens) &&
|
|
54
|
+
opts.maxTokens > 0 &&
|
|
55
|
+
opts.maxTokens < opts.contextWindow) {
|
|
56
|
+
inputBudget = opts.contextWindow - opts.maxTokens;
|
|
57
|
+
}
|
|
58
|
+
result.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(inputBudget);
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/claude-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.1",
|
|
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/
|
|
29
|
-
"@parall/
|
|
30
|
-
"@parall/
|
|
28
|
+
"@parall/cli": "1.58.1",
|
|
29
|
+
"@parall/agent-core": "1.58.1",
|
|
30
|
+
"@parall/sdk": "1.58.1"
|
|
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,10 @@ 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;
|
|
50
|
+
/** Catalog max output tokens for the selected model (input-budget subtrahend). */
|
|
51
|
+
maxTokens?: number;
|
|
45
52
|
contextFilePathForSession?: (sessionKey: string) => string;
|
|
46
53
|
/** @deprecated Use contextFilePathForSession. */
|
|
47
54
|
stepIdFilePathForSession?: (sessionKey: string) => string;
|
|
@@ -83,70 +90,6 @@ function killWin32Tree(pid: number): boolean {
|
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
|
|
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
93
|
/**
|
|
151
94
|
* Per-sessionKey long-lived process state. The process stays alive across
|
|
152
95
|
* dispatches; each dispatch writes one NDJSON user message to stdin and
|
|
@@ -171,9 +114,13 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
171
114
|
private shuttingDown = false;
|
|
172
115
|
private _model: string | undefined;
|
|
173
116
|
private _effortLevel: string | undefined;
|
|
117
|
+
private _contextWindow: number | undefined;
|
|
118
|
+
private _maxTokens: number | undefined;
|
|
174
119
|
|
|
175
120
|
constructor(private readonly opts: ClaudeCodeAdapterOptions) {
|
|
176
121
|
this._model = opts.model;
|
|
122
|
+
this._contextWindow = opts.contextWindow;
|
|
123
|
+
this._maxTokens = opts.maxTokens;
|
|
177
124
|
}
|
|
178
125
|
|
|
179
126
|
get currentModel(): string | undefined {
|
|
@@ -182,13 +129,22 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
182
129
|
get currentEffort(): string | undefined {
|
|
183
130
|
return this._effortLevel;
|
|
184
131
|
}
|
|
185
|
-
|
|
186
|
-
|
|
132
|
+
updateConfig(config: {
|
|
133
|
+
model?: string | null;
|
|
134
|
+
effort?: string | null;
|
|
135
|
+
contextWindow?: number | null;
|
|
136
|
+
maxTokens?: number | null;
|
|
137
|
+
}): void {
|
|
187
138
|
const modelChanged = config.model !== undefined && config.model !== this._model;
|
|
188
139
|
const effortChanged = config.effort !== undefined && config.effort !== this._effortLevel;
|
|
140
|
+
const contextWindowChanged =
|
|
141
|
+
config.contextWindow !== undefined && config.contextWindow !== this._contextWindow;
|
|
142
|
+
const maxTokensChanged = config.maxTokens !== undefined && config.maxTokens !== this._maxTokens;
|
|
189
143
|
if (modelChanged) this._model = config.model ?? undefined;
|
|
190
144
|
if (effortChanged) this._effortLevel = config.effort ?? undefined;
|
|
191
|
-
if (
|
|
145
|
+
if (contextWindowChanged) this._contextWindow = config.contextWindow ?? undefined;
|
|
146
|
+
if (maxTokensChanged) this._maxTokens = config.maxTokens ?? undefined;
|
|
147
|
+
if (modelChanged || effortChanged || contextWindowChanged || maxTokensChanged) {
|
|
192
148
|
this.requestProcessRestart();
|
|
193
149
|
}
|
|
194
150
|
}
|
|
@@ -714,6 +670,8 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
714
670
|
this.buildPlaceholderContext(sessionKey),
|
|
715
671
|
{
|
|
716
672
|
allowApiKey: this.opts.allowApiKey,
|
|
673
|
+
contextWindow: this._contextWindow,
|
|
674
|
+
maxTokens: this._maxTokens,
|
|
717
675
|
effortLevel: this._effortLevel,
|
|
718
676
|
wikiMountRoot: this.opts.workspaceDir,
|
|
719
677
|
capabilityBinDir: this.opts.capabilityBinDir,
|
|
@@ -721,7 +679,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
721
679
|
);
|
|
722
680
|
|
|
723
681
|
log?.info(
|
|
724
|
-
`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`,
|
|
682
|
+
`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || 'default'}, context=${this._contextWindow || 'cli-default'}, maxOut=${this._maxTokens || 'cli-default'}, effort=${this._effortLevel || 'default'}, mode=${this.opts.permissionMode})`,
|
|
725
683
|
);
|
|
726
684
|
|
|
727
685
|
const proc = spawn(
|
package/src/index.ts
CHANGED
|
@@ -2,24 +2,26 @@
|
|
|
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
|
-
|
|
21
|
+
parseShutdownDeadlineMs,
|
|
22
|
+
resolveRuntimeContextWindow,
|
|
23
|
+
resolveRuntimeMaxTokens,
|
|
24
|
+
resolveRuntimeModel,
|
|
23
25
|
} from '@parall/agent-core';
|
|
24
26
|
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
25
27
|
import {
|
|
@@ -52,11 +54,11 @@ async function getAgentMeWithLegacyFallback(client: ParallClient, orgId: string)
|
|
|
52
54
|
|
|
53
55
|
type AgentMe = Awaited<ReturnType<typeof getAgentMeWithLegacyFallback>>;
|
|
54
56
|
|
|
55
|
-
function resolveProviderEnv():
|
|
57
|
+
function resolveProviderEnv(): string {
|
|
56
58
|
const pc = parseProviderConfig(process.env);
|
|
57
|
-
if (!pc) return;
|
|
58
|
-
clearAllProviderCreds(process.env);
|
|
59
59
|
const source = llmSource(pc);
|
|
60
|
+
if (!pc) return source;
|
|
61
|
+
clearAllProviderCreds(process.env);
|
|
60
62
|
if (source === 'parall') {
|
|
61
63
|
process.env.ANTHROPIC_AUTH_TOKEN = process.env.PRLL_API_KEY;
|
|
62
64
|
process.env.ANTHROPIC_BASE_URL = `${process.env.PRLL_API_URL}/api/llm`;
|
|
@@ -68,6 +70,7 @@ function resolveProviderEnv(): void {
|
|
|
68
70
|
}
|
|
69
71
|
if (pc.anthropic_base_url) process.env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
|
|
70
72
|
}
|
|
73
|
+
return source;
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
async function main() {
|
|
@@ -76,7 +79,7 @@ async function main() {
|
|
|
76
79
|
const telemetry = await initAgentTelemetry('parall-claude-agent', 'claude-code');
|
|
77
80
|
activeLog = createOtelLogger('agent', 'claude-agent');
|
|
78
81
|
try {
|
|
79
|
-
resolveProviderEnv();
|
|
82
|
+
const activeLLMSource = resolveProviderEnv();
|
|
80
83
|
const config = resolveClaudeAgentConfig(process.env);
|
|
81
84
|
|
|
82
85
|
if (config.allowApiKey && (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN)) {
|
|
@@ -152,6 +155,16 @@ async function main() {
|
|
|
152
155
|
platformDefaults.model,
|
|
153
156
|
config.model,
|
|
154
157
|
);
|
|
158
|
+
const resolvedContextWindow = resolveRuntimeContextWindow(
|
|
159
|
+
resolvedModel,
|
|
160
|
+
platformDefaults,
|
|
161
|
+
activeLLMSource,
|
|
162
|
+
);
|
|
163
|
+
const resolvedMaxTokens = resolveRuntimeMaxTokens(
|
|
164
|
+
resolvedModel,
|
|
165
|
+
platformDefaults,
|
|
166
|
+
activeLLMSource,
|
|
167
|
+
);
|
|
155
168
|
const resolvedEffort =
|
|
156
169
|
platformDefaults.thinkingEffort ??
|
|
157
170
|
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined);
|
|
@@ -161,12 +174,17 @@ async function main() {
|
|
|
161
174
|
);
|
|
162
175
|
if (platformDefaults.thinkingEffort)
|
|
163
176
|
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
|
|
177
|
+
if (resolvedContextWindow)
|
|
178
|
+
agentLog.info(`platform config: context_window=${resolvedContextWindow}`);
|
|
179
|
+
if (resolvedMaxTokens) agentLog.info(`platform config: max_tokens=${resolvedMaxTokens}`);
|
|
164
180
|
|
|
165
181
|
const adapter = new ClaudeCodeAdapter({
|
|
166
182
|
claudeBin: config.claudeBin,
|
|
167
183
|
claudeHome: config.claudeHome,
|
|
168
184
|
workspaceDir: config.workspaceDir,
|
|
169
185
|
model: resolvedModel,
|
|
186
|
+
contextWindow: resolvedContextWindow,
|
|
187
|
+
maxTokens: resolvedMaxTokens,
|
|
170
188
|
permissionMode: config.permissionMode,
|
|
171
189
|
allowedTools: config.allowedTools,
|
|
172
190
|
disallowedTools: config.disallowedTools,
|
|
@@ -219,8 +237,11 @@ async function main() {
|
|
|
219
237
|
}
|
|
220
238
|
const isPin = deriveModelIsPin(updated, lastKnownAgentProfile);
|
|
221
239
|
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
240
|
+
const model = resolveRuntimeModel(isPin, updated.model, config.model) ?? null;
|
|
222
241
|
adapter.updateConfig({
|
|
223
|
-
model
|
|
242
|
+
model,
|
|
243
|
+
contextWindow: resolveRuntimeContextWindow(model, updated, activeLLMSource) ?? null,
|
|
244
|
+
maxTokens: resolveRuntimeMaxTokens(model, updated, activeLLMSource) ?? null,
|
|
224
245
|
effort: updated.thinkingEffort ?? localEffort,
|
|
225
246
|
});
|
|
226
247
|
// Channel capabilities heat-update with the config: re-materialize the
|
package/src/spawn-env.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
maxTokens?: number;
|
|
12
|
+
effortLevel?: string;
|
|
13
|
+
wikiMountRoot?: string;
|
|
14
|
+
capabilityBinDir?: string;
|
|
15
|
+
},
|
|
16
|
+
): NodeJS.ProcessEnv {
|
|
17
|
+
const env: NodeJS.ProcessEnv = { ...parentEnv };
|
|
18
|
+
if (!opts.allowApiKey) {
|
|
19
|
+
delete env.ANTHROPIC_API_KEY;
|
|
20
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
21
|
+
}
|
|
22
|
+
const result: NodeJS.ProcessEnv = {
|
|
23
|
+
...env,
|
|
24
|
+
HOME: claudeHome,
|
|
25
|
+
PRLL_API_URL: context.apiUrl,
|
|
26
|
+
PRLL_API_KEY: context.apiKey,
|
|
27
|
+
PRLL_ORG_ID: context.orgId,
|
|
28
|
+
PRLL_SESSION_ID: context.sessionId ?? '',
|
|
29
|
+
PRLL_CHAT_ID: context.chatId ?? '',
|
|
30
|
+
PRLL_TRIGGER_MESSAGE_ID: context.triggerMessageId ?? '',
|
|
31
|
+
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
32
|
+
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
33
|
+
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
34
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
35
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
36
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
37
|
+
PRLL_CONTEXT_DIR: context.contextDirPath ?? '',
|
|
38
|
+
};
|
|
39
|
+
// Pin the wiki workspace to the agent workspace (same convention as the
|
|
40
|
+
// OpenClaw runtime). Without this, `parall wiki` resolves mounts relative
|
|
41
|
+
// to the subprocess cwd — a `cd` into any subdirectory would silently
|
|
42
|
+
// split the agent across two wiki workspaces. Operator-set env wins.
|
|
43
|
+
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
44
|
+
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
45
|
+
}
|
|
46
|
+
if (opts.effortLevel) {
|
|
47
|
+
result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
|
|
48
|
+
}
|
|
49
|
+
if (
|
|
50
|
+
typeof opts.contextWindow === 'number' &&
|
|
51
|
+
Number.isSafeInteger(opts.contextWindow) &&
|
|
52
|
+
opts.contextWindow > 0
|
|
53
|
+
) {
|
|
54
|
+
// Claude Code otherwise assumes 200k for an unrecognized model ID.
|
|
55
|
+
//
|
|
56
|
+
// Declared as the model's INPUT budget (context_window - max_tokens; both
|
|
57
|
+
// raw catalog facts), not the full window. The upstream validates
|
|
58
|
+
// input + max_tokens <= context_window, while the CLI requests up to its
|
|
59
|
+
// own output default (32k) yet reserves at most 20k below this declared
|
|
60
|
+
// boundary before auto-compacting (2.1.251) — so a session declared the
|
|
61
|
+
// full window can grow into a band the provider already rejects but the
|
|
62
|
+
// CLI still considers legal: it retries forever, auto-compact never
|
|
63
|
+
// fires, and once past the boundary the compaction call itself is
|
|
64
|
+
// over-limit (the ParallSRE 08-31 lockup). Subtracting the catalog
|
|
65
|
+
// max_tokens keeps the compaction trigger strictly inside what the
|
|
66
|
+
// provider accepts.
|
|
67
|
+
let inputBudget = opts.contextWindow;
|
|
68
|
+
if (
|
|
69
|
+
typeof opts.maxTokens === 'number' &&
|
|
70
|
+
Number.isSafeInteger(opts.maxTokens) &&
|
|
71
|
+
opts.maxTokens > 0 &&
|
|
72
|
+
opts.maxTokens < opts.contextWindow
|
|
73
|
+
) {
|
|
74
|
+
inputBudget = opts.contextWindow - opts.maxTokens;
|
|
75
|
+
}
|
|
76
|
+
result.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(inputBudget);
|
|
77
|
+
}
|
|
78
|
+
// Channel-capability shims resolve ahead of any globally-installed CLI of
|
|
79
|
+
// the same name (the shim owns auth; the real binary is found further down
|
|
80
|
+
// the PATH by the shim itself). On Windows env keys are case-insensitive and
|
|
81
|
+
// the host key is usually `Path`; after the plain-object spread a literal
|
|
82
|
+
// `PATH` write would SPLIT the key (undefined which wins in the child), so
|
|
83
|
+
// reuse the parent's casing. On POSIX keys are case-SENSITIVE — `PATH` is
|
|
84
|
+
// the real one and a stray `Path` is unrelated, so match exactly there or we
|
|
85
|
+
// could prepend to the wrong variable and silently bypass the shim.
|
|
86
|
+
if (opts.capabilityBinDir) {
|
|
87
|
+
const pathKey =
|
|
88
|
+
process.platform === 'win32'
|
|
89
|
+
? (Object.keys(result).find((k) => k.toUpperCase() === 'PATH') ?? 'PATH')
|
|
90
|
+
: 'PATH';
|
|
91
|
+
const existing = result[pathKey];
|
|
92
|
+
result[pathKey] = existing
|
|
93
|
+
? `${opts.capabilityBinDir}${path.delimiter}${existing}`
|
|
94
|
+
: opts.capabilityBinDir;
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|