@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 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
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAsBF,wBAAgB,wBAAwB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,iBAAiB,CAgChG;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAMR;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,iDAAiD;AACjD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGrF"}
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');
@@ -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;
@@ -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;CAC3D,CAAC;AAkBF,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E,MAAM,CAAC,UAAU,CA8BnB;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;CAkBhC"}
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, isPlatformManagedProfile, isPlatformModelOverride, resolveRuntimeModel, parseShutdownDeadlineMs, parseForkDeadlineMs, parseDispatchDeadlineMs, parseProviderConfig, clearAllProviderCreds, llmSource, initAgentTelemetry, } from '@parall/agent-core';
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
- let platformManaged = isPlatformManagedProfile(me.agent_profile);
85
- // Model precedence: operator override (platform-managed) > env > server floor.
86
- // The server delivers a model floor for Parall-proxy routes even when
87
- // model_management=self, so the CLI never falls back to a built-in default
88
- // the catalog might reject but that floor must lose to an explicit env
89
- // override (env > floor). A profile is an operator-override only when it is
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 = platformManaged
97
- ? (platformDefaults.thinkingEffort ??
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 ? 'platform-managed' : 'floor/self'})`);
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} (${platformModelOverride ? 'applied' : 'ignored, model_management=self'})`);
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
- onConfigUpdate: async () => {
155
- const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
156
- platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
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.36.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.36.1",
29
- "@parall/cli": "1.36.1",
30
- "@parall/sdk": "1.36.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
- isPlatformManagedProfile,
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
- let platformManaged = isPlatformManagedProfile(me.agent_profile);
116
- // Model precedence: operator override (platform-managed) > env > server floor.
117
- // The server delivers a model floor for Parall-proxy routes even when
118
- // model_management=self, so the CLI never falls back to a built-in default
119
- // the catalog might reject but that floor must lose to an explicit env
120
- // override (env > floor). A profile is an operator-override only when it is
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 = platformManaged
132
- ? (platformDefaults.thinkingEffort ??
133
- (process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined))
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 ? 'platform-managed' : 'floor/self'})`,
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
- onConfigUpdate: async () => {
200
- const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
201
- platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
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