@parall/claude-agent 1.36.1 → 1.38.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 +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/dispatch.d.ts +9 -0
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +5 -0
- package/dist/index.js +42 -41
- package/package.json +4 -4
- package/src/config.ts +11 -0
- package/src/dispatch.ts +14 -0
- package/src/index.ts +47 -43
package/dist/config.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ export declare function resolveWsUrl(apiUrl: string, explicitWsUrl?: string, swi
|
|
|
22
22
|
export declare function buildClaudeRuntimeKey(agentUserId: string): string;
|
|
23
23
|
export declare function sessionStateFilePathForRuntime(stateDir: string, runtimeKey: string): string;
|
|
24
24
|
export declare function contextFilePathForSession(stateDir: string, sessionKey: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* PRLL_CONTEXT_DIR root: per-lane dispatch context files, keyed by the
|
|
27
|
+
* canonical lane-key (see @parall/agent-core lane-key.ts). Anchored in the
|
|
28
|
+
* per-agent stateDir — never the workspace (workspace paths are
|
|
29
|
+
* user-overridable and not an isolation boundary).
|
|
30
|
+
*/
|
|
31
|
+
export declare function dispatchContextDirPath(stateDir: string): string;
|
|
25
32
|
/** @deprecated Use contextFilePathForSession. */
|
|
26
33
|
export declare function stepIdFilePathForSession(stateDir: string, sessionKey: string): string;
|
|
27
34
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,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;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,iDAAiD;AACjD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGrF"}
|
package/dist/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dispatchLaneContextDir } from '@parall/agent-core';
|
|
1
2
|
import * as os from 'node:os';
|
|
2
3
|
import * as path from 'node:path';
|
|
3
4
|
function requireEnv(env, name) {
|
|
@@ -65,6 +66,15 @@ export function contextFilePathForSession(stateDir, sessionKey) {
|
|
|
65
66
|
const fileName = Buffer.from(sessionKey).toString('base64url');
|
|
66
67
|
return path.join(stateDir, 'dispatch-context', `${fileName}.json`);
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* PRLL_CONTEXT_DIR root: per-lane dispatch context files, keyed by the
|
|
71
|
+
* canonical lane-key (see @parall/agent-core lane-key.ts). Anchored in the
|
|
72
|
+
* per-agent stateDir — never the workspace (workspace paths are
|
|
73
|
+
* user-overridable and not an isolation boundary).
|
|
74
|
+
*/
|
|
75
|
+
export function dispatchContextDirPath(stateDir) {
|
|
76
|
+
return dispatchLaneContextDir(stateDir);
|
|
77
|
+
}
|
|
68
78
|
/** @deprecated Use contextFilePathForSession. */
|
|
69
79
|
export function stepIdFilePathForSession(stateDir, sessionKey) {
|
|
70
80
|
const fileName = Buffer.from(sessionKey).toString('base64url');
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ type ClaudeCodeAdapterOptions = Pick<ClaudeAgentConfig, 'additionalDirs' | 'allo
|
|
|
9
9
|
contextFilePathForSession?: (sessionKey: string) => string;
|
|
10
10
|
/** @deprecated Use contextFilePathForSession. */
|
|
11
11
|
stepIdFilePathForSession?: (sessionKey: string) => string;
|
|
12
|
+
/**
|
|
13
|
+
* PRLL_CONTEXT_DIR root for per-lane dispatch context files. Like the
|
|
14
|
+
* Parall credentials above, this is bridge-static — the long-lived
|
|
15
|
+
* subprocess env is built ONCE from the placeholder context, so a value
|
|
16
|
+
* only present on per-dispatch DispatchContexts would never reach the
|
|
17
|
+
* CLI and every reply would silently lose its lane binding (turns would
|
|
18
|
+
* resolve no_action instead of reply-effect dedupe).
|
|
19
|
+
*/
|
|
20
|
+
contextDirPath?: string;
|
|
12
21
|
};
|
|
13
22
|
export declare function buildSpawnEnv(parentEnv: NodeJS.ProcessEnv, claudeHome: string, context: DispatchOpts['context'], opts: {
|
|
14
23
|
allowApiKey: boolean;
|
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,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;
|
|
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;IAC1D;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,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,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E,MAAM,CAAC,UAAU,CAkCnB;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;IAuDpB,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,uBAAuB;CAmBhC"}
|
package/dist/dispatch.js
CHANGED
|
@@ -37,6 +37,10 @@ export function buildSpawnEnv(parentEnv, claudeHome, context, opts) {
|
|
|
37
37
|
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
38
38
|
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
39
39
|
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
40
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
41
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
42
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
43
|
+
PRLL_CONTEXT_DIR: context.contextDirPath ?? '',
|
|
40
44
|
};
|
|
41
45
|
// Pin the wiki workspace to the agent workspace (same convention as the
|
|
42
46
|
// OpenClaw runtime). Without this, `parall wiki` resolves mounts relative
|
|
@@ -491,6 +495,7 @@ export class ClaudeCodeAdapter {
|
|
|
491
495
|
noReply: false,
|
|
492
496
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? '',
|
|
493
497
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? '',
|
|
498
|
+
contextDirPath: this.opts.contextDirPath ?? '',
|
|
494
499
|
client: undefined,
|
|
495
500
|
};
|
|
496
501
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as os from 'node:os';
|
|
3
|
-
import { ParallAgentGateway, createPlatformConfigManager, createLogger, createOtelLogger, childLogger,
|
|
3
|
+
import { ParallAgentGateway, createPlatformConfigManager, createLogger, createOtelLogger, childLogger, deriveModelIsPin, resolveRuntimeModel, parseShutdownDeadlineMs, parseForkDeadlineMs, parseDispatchDeadlineMs, parseProviderConfig, clearAllProviderCreds, llmSource, initAgentTelemetry, } from '@parall/agent-core';
|
|
4
4
|
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
5
|
-
import { buildClaudeRuntimeKey, contextFilePathForSession, resolveClaudeAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from './config.js';
|
|
5
|
+
import { buildClaudeRuntimeKey, contextFilePathForSession, dispatchContextDirPath, resolveClaudeAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from './config.js';
|
|
6
6
|
import { ClaudeCodeAdapter } from './dispatch.js';
|
|
7
7
|
import { ClaudeSessionManager } from './session-manager.js';
|
|
8
8
|
import { ensureClaudeWorkspace } from './workspace.js';
|
|
@@ -81,26 +81,19 @@ async function main() {
|
|
|
81
81
|
log: agentLog,
|
|
82
82
|
});
|
|
83
83
|
const platformDefaults = await configMgr.fetch();
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
// platform-managed AND not explicitly self-managed: this treats legacy hosted
|
|
91
|
-
// agents (model_management=null, resolved to platform server-side) as
|
|
92
|
-
// overrides, while excluding local self agents whose delivered model is a
|
|
93
|
-
// floor. effort stays an operator override (platform).
|
|
94
|
-
let platformModelOverride = isPlatformModelOverride(me.agent_profile);
|
|
84
|
+
// Model precedence: operator PIN (override) > env > server FLOOR. The server
|
|
85
|
+
// now says which it is via model_is_pin (deriveModelIsPin presence-gates the
|
|
86
|
+
// dual-read: old servers omit it → fall back to model_management). A PIN beats
|
|
87
|
+
// an explicit env override; a FLOOR loses to env. effort is delivered by the
|
|
88
|
+
// server only when pinned, so we no longer gate it on a local flag.
|
|
89
|
+
const platformModelOverride = deriveModelIsPin(platformDefaults, me.agent_profile);
|
|
95
90
|
const resolvedModel = resolveRuntimeModel(platformModelOverride, platformDefaults.model, config.model);
|
|
96
|
-
const resolvedEffort =
|
|
97
|
-
|
|
98
|
-
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined))
|
|
99
|
-
: process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined;
|
|
91
|
+
const resolvedEffort = platformDefaults.thinkingEffort ??
|
|
92
|
+
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined);
|
|
100
93
|
if (platformDefaults.model)
|
|
101
|
-
agentLog.info(`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? '
|
|
94
|
+
agentLog.info(`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? 'pin/override' : 'floor'})`);
|
|
102
95
|
if (platformDefaults.thinkingEffort)
|
|
103
|
-
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}
|
|
96
|
+
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
|
|
104
97
|
const adapter = new ClaudeCodeAdapter({
|
|
105
98
|
claudeBin: config.claudeBin,
|
|
106
99
|
claudeHome: config.claudeHome,
|
|
@@ -121,10 +114,37 @@ async function main() {
|
|
|
121
114
|
orgId: config.orgId,
|
|
122
115
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
123
116
|
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
117
|
+
contextDirPath: dispatchContextDirPath(config.stateDir),
|
|
124
118
|
});
|
|
125
119
|
if (resolvedEffort) {
|
|
126
120
|
adapter.updateConfig({ effort: resolvedEffort });
|
|
127
121
|
}
|
|
122
|
+
// Shared by onConfigUpdate + onSessionReady. /agents/me is only consumed as
|
|
123
|
+
// deriveModelIsPin's legacy fallback when the server omits model_is_pin
|
|
124
|
+
// (old server) — skip that round trip otherwise.
|
|
125
|
+
const refreshPlatformConfig = async () => {
|
|
126
|
+
const updated = await configMgr.fetch();
|
|
127
|
+
let refreshedProfile;
|
|
128
|
+
if (updated.modelIsPin === undefined) {
|
|
129
|
+
try {
|
|
130
|
+
refreshedProfile = (await getAgentMeWithLegacyFallback(client, config.orgId))
|
|
131
|
+
.agent_profile;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
// configMgr.fetch() degrades to cache on failure; mirror that for the
|
|
135
|
+
// legacy fallback so a transient /agents/me error doesn't reject this
|
|
136
|
+
// config-refresh callback. No profile → deriveModelIsPin treats the
|
|
137
|
+
// delivered model as a floor (safe default).
|
|
138
|
+
agentLog.warn(`platform config refresh: legacy /agents/me fallback failed: ${String(err)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const isPin = deriveModelIsPin(updated, refreshedProfile);
|
|
142
|
+
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
143
|
+
adapter.updateConfig({
|
|
144
|
+
model: resolveRuntimeModel(isPin, updated.model, config.model) ?? null,
|
|
145
|
+
effort: updated.thinkingEffort ?? localEffort,
|
|
146
|
+
});
|
|
147
|
+
};
|
|
128
148
|
const gateway = new ParallAgentGateway({
|
|
129
149
|
accountId: agentUserId,
|
|
130
150
|
client,
|
|
@@ -151,28 +171,9 @@ async function main() {
|
|
|
151
171
|
dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
|
|
152
172
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
153
173
|
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
158
|
-
const updated = await configMgr.fetch();
|
|
159
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
160
|
-
adapter.updateConfig({
|
|
161
|
-
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
162
|
-
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
163
|
-
});
|
|
164
|
-
},
|
|
165
|
-
onSessionReady: async () => {
|
|
166
|
-
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
167
|
-
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
168
|
-
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
169
|
-
const updated = await configMgr.fetch();
|
|
170
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
171
|
-
adapter.updateConfig({
|
|
172
|
-
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
173
|
-
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
174
|
-
});
|
|
175
|
-
},
|
|
174
|
+
dispatchContextDir: dispatchContextDirPath(config.stateDir),
|
|
175
|
+
onConfigUpdate: refreshPlatformConfig,
|
|
176
|
+
onSessionReady: refreshPlatformConfig,
|
|
176
177
|
// Long-lived `claude` subprocesses outlive a single dispatch, so the
|
|
177
178
|
// gateway's shuttingDown flag alone does not tear them down. Piggyback on
|
|
178
179
|
// onBeforeDisconnect to close stdin and SIGTERM any survivors after
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/claude-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.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/cli": "1.
|
|
30
|
-
"@parall/sdk": "1.
|
|
28
|
+
"@parall/agent-core": "1.38.0",
|
|
29
|
+
"@parall/cli": "1.38.0",
|
|
30
|
+
"@parall/sdk": "1.38.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|
package/src/config.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dispatchLaneContextDir } from '@parall/agent-core';
|
|
1
2
|
import * as os from 'node:os';
|
|
2
3
|
import * as path from 'node:path';
|
|
3
4
|
|
|
@@ -101,6 +102,16 @@ export function contextFilePathForSession(stateDir: string, sessionKey: string):
|
|
|
101
102
|
return path.join(stateDir, 'dispatch-context', `${fileName}.json`);
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/**
|
|
106
|
+
* PRLL_CONTEXT_DIR root: per-lane dispatch context files, keyed by the
|
|
107
|
+
* canonical lane-key (see @parall/agent-core lane-key.ts). Anchored in the
|
|
108
|
+
* per-agent stateDir — never the workspace (workspace paths are
|
|
109
|
+
* user-overridable and not an isolation boundary).
|
|
110
|
+
*/
|
|
111
|
+
export function dispatchContextDirPath(stateDir: string): string {
|
|
112
|
+
return dispatchLaneContextDir(stateDir);
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
/** @deprecated Use contextFilePathForSession. */
|
|
105
116
|
export function stepIdFilePathForSession(stateDir: string, sessionKey: string): string {
|
|
106
117
|
const fileName = Buffer.from(sessionKey).toString('base64url');
|
package/src/dispatch.ts
CHANGED
|
@@ -38,6 +38,15 @@ type ClaudeCodeAdapterOptions = Pick<
|
|
|
38
38
|
contextFilePathForSession?: (sessionKey: string) => string;
|
|
39
39
|
/** @deprecated Use contextFilePathForSession. */
|
|
40
40
|
stepIdFilePathForSession?: (sessionKey: string) => string;
|
|
41
|
+
/**
|
|
42
|
+
* PRLL_CONTEXT_DIR root for per-lane dispatch context files. Like the
|
|
43
|
+
* Parall credentials above, this is bridge-static — the long-lived
|
|
44
|
+
* subprocess env is built ONCE from the placeholder context, so a value
|
|
45
|
+
* only present on per-dispatch DispatchContexts would never reach the
|
|
46
|
+
* CLI and every reply would silently lose its lane binding (turns would
|
|
47
|
+
* resolve no_action instead of reply-effect dedupe).
|
|
48
|
+
*/
|
|
49
|
+
contextDirPath?: string;
|
|
41
50
|
};
|
|
42
51
|
|
|
43
52
|
const IS_WIN32 = process.platform === 'win32';
|
|
@@ -79,6 +88,10 @@ export function buildSpawnEnv(
|
|
|
79
88
|
PRLL_NO_REPLY: context.noReply ? '1' : '',
|
|
80
89
|
PRLL_CONTEXT_FILE: context.contextFilePath ?? '',
|
|
81
90
|
PRLL_STEP_ID_FILE: context.stepIdFilePath ?? '',
|
|
91
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
92
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
93
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
94
|
+
PRLL_CONTEXT_DIR: context.contextDirPath ?? '',
|
|
82
95
|
};
|
|
83
96
|
// Pin the wiki workspace to the agent workspace (same convention as the
|
|
84
97
|
// OpenClaw runtime). Without this, `parall wiki` resolves mounts relative
|
|
@@ -613,6 +626,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
613
626
|
noReply: false,
|
|
614
627
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? '',
|
|
615
628
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? '',
|
|
629
|
+
contextDirPath: this.opts.contextDirPath ?? '',
|
|
616
630
|
client: undefined as unknown as DispatchOpts['context']['client'],
|
|
617
631
|
};
|
|
618
632
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,7 @@ import {
|
|
|
7
7
|
createLogger,
|
|
8
8
|
createOtelLogger,
|
|
9
9
|
childLogger,
|
|
10
|
-
|
|
11
|
-
isPlatformModelOverride,
|
|
10
|
+
deriveModelIsPin,
|
|
12
11
|
resolveRuntimeModel,
|
|
13
12
|
parseShutdownDeadlineMs,
|
|
14
13
|
parseForkDeadlineMs,
|
|
@@ -22,6 +21,7 @@ import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
|
22
21
|
import {
|
|
23
22
|
buildClaudeRuntimeKey,
|
|
24
23
|
contextFilePathForSession,
|
|
24
|
+
dispatchContextDirPath,
|
|
25
25
|
resolveClaudeAgentConfig,
|
|
26
26
|
resolveWsUrl,
|
|
27
27
|
sessionStateFilePathForRuntime,
|
|
@@ -112,34 +112,26 @@ async function main() {
|
|
|
112
112
|
log: agentLog,
|
|
113
113
|
});
|
|
114
114
|
const platformDefaults = await configMgr.fetch();
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
// platform-managed AND not explicitly self-managed: this treats legacy hosted
|
|
122
|
-
// agents (model_management=null, resolved to platform server-side) as
|
|
123
|
-
// overrides, while excluding local self agents whose delivered model is a
|
|
124
|
-
// floor. effort stays an operator override (platform).
|
|
125
|
-
let platformModelOverride = isPlatformModelOverride(me.agent_profile);
|
|
115
|
+
// Model precedence: operator PIN (override) > env > server FLOOR. The server
|
|
116
|
+
// now says which it is via model_is_pin (deriveModelIsPin presence-gates the
|
|
117
|
+
// dual-read: old servers omit it → fall back to model_management). A PIN beats
|
|
118
|
+
// an explicit env override; a FLOOR loses to env. effort is delivered by the
|
|
119
|
+
// server only when pinned, so we no longer gate it on a local flag.
|
|
120
|
+
const platformModelOverride = deriveModelIsPin(platformDefaults, me.agent_profile);
|
|
126
121
|
const resolvedModel = resolveRuntimeModel(
|
|
127
122
|
platformModelOverride,
|
|
128
123
|
platformDefaults.model,
|
|
129
124
|
config.model,
|
|
130
125
|
);
|
|
131
|
-
const resolvedEffort =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
: process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined;
|
|
126
|
+
const resolvedEffort =
|
|
127
|
+
platformDefaults.thinkingEffort ??
|
|
128
|
+
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined);
|
|
135
129
|
if (platformDefaults.model)
|
|
136
130
|
agentLog.info(
|
|
137
|
-
`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? '
|
|
131
|
+
`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? 'pin/override' : 'floor'})`,
|
|
138
132
|
);
|
|
139
133
|
if (platformDefaults.thinkingEffort)
|
|
140
|
-
agentLog.info(
|
|
141
|
-
`platform config: thinking_effort=${platformDefaults.thinkingEffort} (${platformModelOverride ? 'applied' : 'ignored, model_management=self'})`,
|
|
142
|
-
);
|
|
134
|
+
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
|
|
143
135
|
|
|
144
136
|
const adapter = new ClaudeCodeAdapter({
|
|
145
137
|
claudeBin: config.claudeBin,
|
|
@@ -163,11 +155,42 @@ async function main() {
|
|
|
163
155
|
contextFilePathForSession(config.stateDir, sessionKey),
|
|
164
156
|
stepIdFilePathForSession: (sessionKey) =>
|
|
165
157
|
stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
158
|
+
contextDirPath: dispatchContextDirPath(config.stateDir),
|
|
166
159
|
});
|
|
167
160
|
if (resolvedEffort) {
|
|
168
161
|
adapter.updateConfig({ effort: resolvedEffort });
|
|
169
162
|
}
|
|
170
163
|
|
|
164
|
+
// Shared by onConfigUpdate + onSessionReady. /agents/me is only consumed as
|
|
165
|
+
// deriveModelIsPin's legacy fallback when the server omits model_is_pin
|
|
166
|
+
// (old server) — skip that round trip otherwise.
|
|
167
|
+
const refreshPlatformConfig = async () => {
|
|
168
|
+
const updated = await configMgr.fetch();
|
|
169
|
+
let refreshedProfile:
|
|
170
|
+
| Awaited<ReturnType<typeof getAgentMeWithLegacyFallback>>['agent_profile']
|
|
171
|
+
| undefined;
|
|
172
|
+
if (updated.modelIsPin === undefined) {
|
|
173
|
+
try {
|
|
174
|
+
refreshedProfile = (await getAgentMeWithLegacyFallback(client, config.orgId))
|
|
175
|
+
.agent_profile;
|
|
176
|
+
} catch (err) {
|
|
177
|
+
// configMgr.fetch() degrades to cache on failure; mirror that for the
|
|
178
|
+
// legacy fallback so a transient /agents/me error doesn't reject this
|
|
179
|
+
// config-refresh callback. No profile → deriveModelIsPin treats the
|
|
180
|
+
// delivered model as a floor (safe default).
|
|
181
|
+
agentLog.warn(
|
|
182
|
+
`platform config refresh: legacy /agents/me fallback failed: ${String(err)}`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const isPin = deriveModelIsPin(updated, refreshedProfile);
|
|
187
|
+
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
188
|
+
adapter.updateConfig({
|
|
189
|
+
model: resolveRuntimeModel(isPin, updated.model, config.model) ?? null,
|
|
190
|
+
effort: updated.thinkingEffort ?? localEffort,
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
171
194
|
const gateway = new ParallAgentGateway({
|
|
172
195
|
accountId: agentUserId,
|
|
173
196
|
client,
|
|
@@ -196,28 +219,9 @@ async function main() {
|
|
|
196
219
|
contextFilePathForSession(config.stateDir, sessionKey),
|
|
197
220
|
stepIdFilePathForSession: (sessionKey) =>
|
|
198
221
|
stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
203
|
-
const updated = await configMgr.fetch();
|
|
204
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
205
|
-
adapter.updateConfig({
|
|
206
|
-
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
207
|
-
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
208
|
-
});
|
|
209
|
-
},
|
|
210
|
-
onSessionReady: async () => {
|
|
211
|
-
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
212
|
-
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
213
|
-
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
214
|
-
const updated = await configMgr.fetch();
|
|
215
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
216
|
-
adapter.updateConfig({
|
|
217
|
-
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
218
|
-
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
219
|
-
});
|
|
220
|
-
},
|
|
222
|
+
dispatchContextDir: dispatchContextDirPath(config.stateDir),
|
|
223
|
+
onConfigUpdate: refreshPlatformConfig,
|
|
224
|
+
onSessionReady: refreshPlatformConfig,
|
|
221
225
|
// Long-lived `claude` subprocesses outlive a single dispatch, so the
|
|
222
226
|
// gateway's shuttingDown flag alone does not tear them down. Piggyback on
|
|
223
227
|
// onBeforeDisconnect to close stdin and SIGTERM any survivors after
|