@ours.network/fleet 0.17.9 → 0.17.10
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/build-info.json +4 -4
- package/dist/config.js +15 -8
- package/dist/model-env.d.ts +71 -0
- package/dist/model-env.js +106 -0
- package/dist/runner.d.ts +9 -0
- package/dist/runner.js +27 -3
- package/dist/spawn.js +28 -16
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.17.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "0.17.10",
|
|
3
|
+
"buildId": "1b9e94d03b53",
|
|
4
|
+
"commit": "f9932f0a2c99b8352286a3c4588bdfb57eed08f1",
|
|
5
5
|
"dirty": false,
|
|
6
|
-
"builtAt": "2026-08-
|
|
6
|
+
"builtAt": "2026-08-19T19:43:42.121Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"monitor.interrupt.after_tool"
|
|
9
9
|
]
|
package/dist/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import { agentDir, defaultConfigPath, fleetDDir, home } from './paths.js';
|
|
|
4
4
|
import { parseFleetDocument, } from './config-yaml.js';
|
|
5
5
|
import { harnessRuntimeDir, resolveIsolation, validateIsolationConfig, } from './isolation/policy.js';
|
|
6
6
|
import { getAdapter } from './harness/registry.js';
|
|
7
|
+
import { resolveRoleModelEnv } from './model-env.js';
|
|
7
8
|
import { resolveWatchdogs } from './watchdog/config.js';
|
|
8
9
|
import { resolveLoops } from './loops/config.js';
|
|
9
10
|
import { CAPABILITIES, CAP_MONITOR_INTERRUPT_AFTER_TOOL } from './capabilities.js';
|
|
@@ -217,17 +218,23 @@ export function loadConfig(configPath, options = {}) {
|
|
|
217
218
|
const harness = r.harness ?? defaults.harness ?? 'claude-code';
|
|
218
219
|
const defaultHarness = defaults.harness ?? 'claude-code';
|
|
219
220
|
const inheritsModelDefaults = harness === defaultHarness && r.model !== null;
|
|
220
|
-
|
|
221
|
+
if (authProxy && harness !== 'claude-code')
|
|
222
|
+
throw new ConfigError(`${file}: role '${name}' auth_proxy is supported only by claude-code`);
|
|
223
|
+
// Environment and runtime model are resolved together so `model:` and the
|
|
224
|
+
// harness's model pin can never disagree (see src/model-env.ts).
|
|
225
|
+
const modelEnv = resolveRoleModelEnv({
|
|
226
|
+
harness,
|
|
227
|
+
model: resolveRoleModel(r.model, r.harness, defaults),
|
|
228
|
+
modelWasExplicit: r.model !== undefined,
|
|
229
|
+
defaultsEnv: (defaults.env ?? {}),
|
|
230
|
+
roleEnv: r.env,
|
|
231
|
+
...(authProxy ? { authProxyBaseUrl: authProxy.base_url } : {}),
|
|
232
|
+
}, message => new ConfigError(`${file}: role '${name}' ${message}`));
|
|
233
|
+
const env = modelEnv.env;
|
|
234
|
+
const model = modelEnv.model;
|
|
221
235
|
const modelChain = resolveModelChain(model, r.model_chain ?? (inheritsModelDefaults
|
|
222
236
|
? defaults.model_chain
|
|
223
237
|
: undefined), file, name);
|
|
224
|
-
if (authProxy && harness !== 'claude-code')
|
|
225
|
-
throw new ConfigError(`${file}: role '${name}' auth_proxy is supported only by claude-code`);
|
|
226
|
-
const env = {
|
|
227
|
-
...(defaults.env ?? {}),
|
|
228
|
-
...(r.env ?? {}),
|
|
229
|
-
...(authProxy ? { ANTHROPIC_BASE_URL: authProxy.base_url } : {}),
|
|
230
|
-
};
|
|
231
238
|
roles.push({
|
|
232
239
|
...r,
|
|
233
240
|
name,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ResolvedRole } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Which environment variable a harness reads to pin the model it RUNS.
|
|
4
|
+
*
|
|
5
|
+
* This is not a convenience: for `claude-code` it is the only channel that
|
|
6
|
+
* reaches the ACP backend at all. `buildLaunch` (tmux) passes `--model`, but
|
|
7
|
+
* `buildAcpLaunch` launches the ACP adapter with no model argument, and that
|
|
8
|
+
* adapter resolves its model in this order — ANTHROPIC_MODEL, then
|
|
9
|
+
* `settings.model`, then a resumed session's live model, then its first
|
|
10
|
+
* catalogue entry. A role's declared model was therefore invisible to every
|
|
11
|
+
* ACP role, and a fleet-wide `defaults.env.ANTHROPIC_MODEL` silently outranked
|
|
12
|
+
* an explicitly requested one.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MODEL_ENV_BY_HARNESS: Readonly<Record<string, string>>;
|
|
15
|
+
/** The model-pin variable for a harness, or undefined if it pins no model by env. */
|
|
16
|
+
export declare function modelEnvVar(harness: string | undefined): string | undefined;
|
|
17
|
+
export interface RoleModelEnvInput {
|
|
18
|
+
harness: string;
|
|
19
|
+
/** Already resolved by `resolveRoleModel` — may come from the fleet default. */
|
|
20
|
+
model: string | undefined;
|
|
21
|
+
/** True when the role (or `--model`) named a model, including `model: null`. */
|
|
22
|
+
modelWasExplicit: boolean;
|
|
23
|
+
defaultsEnv?: Record<string, string>;
|
|
24
|
+
roleEnv?: Record<string, string>;
|
|
25
|
+
authProxyBaseUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface RoleModelEnv {
|
|
28
|
+
env: Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* The model the harness will actually run. Equal to `env[pin]` for a harness
|
|
31
|
+
* that pins by env, so anything reporting this value reports the runtime.
|
|
32
|
+
*/
|
|
33
|
+
model: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a role's environment and its runtime model TOGETHER, so the two can
|
|
37
|
+
* never disagree.
|
|
38
|
+
*
|
|
39
|
+
* Precedence, highest first:
|
|
40
|
+
* 1. an explicit `model:` / `--model` on the role
|
|
41
|
+
* 2. the role's own `env:` pin
|
|
42
|
+
* 3. the fleet `defaults.model`
|
|
43
|
+
* 4. the fleet `defaults.env` pin
|
|
44
|
+
*
|
|
45
|
+
* Inheriting the fleet default remains correct when the role names no model
|
|
46
|
+
* (2, 3, 4); an explicitly named one wins (1). Where both are explicit and they
|
|
47
|
+
* disagree, there is no defensible winner, so this refuses rather than picking
|
|
48
|
+
* one silently — the silence is what let a day of "Fable" work run on Opus.
|
|
49
|
+
*
|
|
50
|
+
* `model: null` explicitly asks for no fleet-chosen model, so it also clears an
|
|
51
|
+
* inherited pin instead of leaving one in place to act as a hidden default.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveRoleModelEnv(input: RoleModelEnvInput, describe?: (message: string) => Error): RoleModelEnv;
|
|
54
|
+
/**
|
|
55
|
+
* The model a role will actually run, read back from the environment it was
|
|
56
|
+
* resolved with. Use this wherever a model is reported to a human.
|
|
57
|
+
*/
|
|
58
|
+
export declare function effectiveRoleModel(role: ResolvedRole): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Move a role's env pin onto a new model. Anything that changes the model a
|
|
61
|
+
* role runs after resolution — model-chain recovery is the live example — must
|
|
62
|
+
* go through this, or it changes only the label.
|
|
63
|
+
*/
|
|
64
|
+
export declare function repinModelEnv(role: ResolvedRole, model: string | undefined): Record<string, string> | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Last line of defence, at the exact point a child's environment is composed:
|
|
67
|
+
* refuse to launch a role whose child would run a model other than the one the
|
|
68
|
+
* role declares and the banner reports. A spawn that cannot keep those two in
|
|
69
|
+
* agreement must fail loudly, not start and be believed.
|
|
70
|
+
*/
|
|
71
|
+
export declare function assertModelPinReachesChild(role: ResolvedRole, childEnv: Record<string, string | undefined>): void;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which environment variable a harness reads to pin the model it RUNS.
|
|
3
|
+
*
|
|
4
|
+
* This is not a convenience: for `claude-code` it is the only channel that
|
|
5
|
+
* reaches the ACP backend at all. `buildLaunch` (tmux) passes `--model`, but
|
|
6
|
+
* `buildAcpLaunch` launches the ACP adapter with no model argument, and that
|
|
7
|
+
* adapter resolves its model in this order — ANTHROPIC_MODEL, then
|
|
8
|
+
* `settings.model`, then a resumed session's live model, then its first
|
|
9
|
+
* catalogue entry. A role's declared model was therefore invisible to every
|
|
10
|
+
* ACP role, and a fleet-wide `defaults.env.ANTHROPIC_MODEL` silently outranked
|
|
11
|
+
* an explicitly requested one.
|
|
12
|
+
*/
|
|
13
|
+
export const MODEL_ENV_BY_HARNESS = {
|
|
14
|
+
'claude-code': 'ANTHROPIC_MODEL',
|
|
15
|
+
};
|
|
16
|
+
/** The model-pin variable for a harness, or undefined if it pins no model by env. */
|
|
17
|
+
export function modelEnvVar(harness) {
|
|
18
|
+
return harness === undefined ? undefined : MODEL_ENV_BY_HARNESS[harness];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a role's environment and its runtime model TOGETHER, so the two can
|
|
22
|
+
* never disagree.
|
|
23
|
+
*
|
|
24
|
+
* Precedence, highest first:
|
|
25
|
+
* 1. an explicit `model:` / `--model` on the role
|
|
26
|
+
* 2. the role's own `env:` pin
|
|
27
|
+
* 3. the fleet `defaults.model`
|
|
28
|
+
* 4. the fleet `defaults.env` pin
|
|
29
|
+
*
|
|
30
|
+
* Inheriting the fleet default remains correct when the role names no model
|
|
31
|
+
* (2, 3, 4); an explicitly named one wins (1). Where both are explicit and they
|
|
32
|
+
* disagree, there is no defensible winner, so this refuses rather than picking
|
|
33
|
+
* one silently — the silence is what let a day of "Fable" work run on Opus.
|
|
34
|
+
*
|
|
35
|
+
* `model: null` explicitly asks for no fleet-chosen model, so it also clears an
|
|
36
|
+
* inherited pin instead of leaving one in place to act as a hidden default.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveRoleModelEnv(input, describe = message => new Error(message)) {
|
|
39
|
+
const env = {
|
|
40
|
+
...(input.defaultsEnv ?? {}),
|
|
41
|
+
...(input.roleEnv ?? {}),
|
|
42
|
+
...(input.authProxyBaseUrl ? { ANTHROPIC_BASE_URL: input.authProxyBaseUrl } : {}),
|
|
43
|
+
};
|
|
44
|
+
const pin = modelEnvVar(input.harness);
|
|
45
|
+
if (!pin)
|
|
46
|
+
return { env, model: input.model };
|
|
47
|
+
const rolePin = input.roleEnv?.[pin];
|
|
48
|
+
if (input.modelWasExplicit) {
|
|
49
|
+
if (rolePin !== undefined && rolePin !== input.model)
|
|
50
|
+
throw describe(`model '${input.model ?? '(none)'}' contradicts env.${pin} '${rolePin}'; `
|
|
51
|
+
+ `remove one — ${pin} is what the harness actually runs`);
|
|
52
|
+
if (input.model === undefined)
|
|
53
|
+
delete env[pin];
|
|
54
|
+
else
|
|
55
|
+
env[pin] = input.model;
|
|
56
|
+
return { env, model: input.model };
|
|
57
|
+
}
|
|
58
|
+
// Not explicit: a role-level pin is the most specific thing said about this
|
|
59
|
+
// role, so it decides — and the reported model follows it.
|
|
60
|
+
if (rolePin !== undefined)
|
|
61
|
+
return { env, model: rolePin };
|
|
62
|
+
if (input.model !== undefined)
|
|
63
|
+
env[pin] = input.model;
|
|
64
|
+
return { env, model: input.model ?? env[pin] };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The model a role will actually run, read back from the environment it was
|
|
68
|
+
* resolved with. Use this wherever a model is reported to a human.
|
|
69
|
+
*/
|
|
70
|
+
export function effectiveRoleModel(role) {
|
|
71
|
+
const pin = modelEnvVar(role.harness);
|
|
72
|
+
return (pin ? role.env?.[pin] : undefined) ?? role.model;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Move a role's env pin onto a new model. Anything that changes the model a
|
|
76
|
+
* role runs after resolution — model-chain recovery is the live example — must
|
|
77
|
+
* go through this, or it changes only the label.
|
|
78
|
+
*/
|
|
79
|
+
export function repinModelEnv(role, model) {
|
|
80
|
+
const pin = modelEnvVar(role.harness);
|
|
81
|
+
if (!pin)
|
|
82
|
+
return role.env;
|
|
83
|
+
const env = { ...(role.env ?? {}) };
|
|
84
|
+
if (model === undefined)
|
|
85
|
+
delete env[pin];
|
|
86
|
+
else
|
|
87
|
+
env[pin] = model;
|
|
88
|
+
return Object.keys(env).length ? env : undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Last line of defence, at the exact point a child's environment is composed:
|
|
92
|
+
* refuse to launch a role whose child would run a model other than the one the
|
|
93
|
+
* role declares and the banner reports. A spawn that cannot keep those two in
|
|
94
|
+
* agreement must fail loudly, not start and be believed.
|
|
95
|
+
*/
|
|
96
|
+
export function assertModelPinReachesChild(role, childEnv) {
|
|
97
|
+
const pin = modelEnvVar(role.harness);
|
|
98
|
+
if (!pin || role.model === undefined)
|
|
99
|
+
return;
|
|
100
|
+
const actual = childEnv[pin];
|
|
101
|
+
if (actual === role.model)
|
|
102
|
+
return;
|
|
103
|
+
throw new Error(`[${role.name}] refusing to launch: role model is '${role.model}' but the child's `
|
|
104
|
+
+ `${pin} is ${actual === undefined ? 'unset' : `'${actual}'`} — the session would run a `
|
|
105
|
+
+ 'different model than the one reported');
|
|
106
|
+
}
|
package/dist/runner.d.ts
CHANGED
|
@@ -35,6 +35,15 @@ export interface RunnerDeps {
|
|
|
35
35
|
}
|
|
36
36
|
/** Environment injected only into the managed harness process. */
|
|
37
37
|
export declare function managedFleetProxyEnv(role: ResolvedRole, stateDir: string): Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* The environment a managed harness child actually receives, checked at the one
|
|
40
|
+
* point where it is composed. `role.env` deliberately wins over harness prep,
|
|
41
|
+
* which is exactly how a stale fleet-wide model pin used to outrank the model
|
|
42
|
+
* the role was spawned with — so the model pin is verified here rather than
|
|
43
|
+
* trusted, and a disagreement stops the launch instead of being reported as a
|
|
44
|
+
* success (see src/model-env.ts).
|
|
45
|
+
*/
|
|
46
|
+
export declare function harnessChildEnv(role: ResolvedRole, launchEnv: Record<string, string> | undefined, stateDir: string): Record<string, string>;
|
|
38
47
|
/**
|
|
39
48
|
* Record who owns wake delivery for this run. Returning true means a fleet
|
|
40
49
|
* monitor is taking ownership back from a native harness and must start at the
|
package/dist/runner.js
CHANGED
|
@@ -24,6 +24,7 @@ import { RoleTurnArbiter } from './session/arbiter.js';
|
|
|
24
24
|
import { ScheduledLoopManager, } from './loops/manager.js';
|
|
25
25
|
import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, inheritCallerSpawnDefaults, } from './fleet-proxy.js';
|
|
26
26
|
import { effectivePermissionMode } from './permissions.js';
|
|
27
|
+
import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
|
|
27
28
|
import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
|
|
28
29
|
const defaultDeps = () => ({
|
|
29
30
|
tmux: new Tmux(),
|
|
@@ -72,6 +73,19 @@ export function managedFleetProxyEnv(role, stateDir) {
|
|
|
72
73
|
[FLEET_PROXY_CALLER_ENV]: role.name,
|
|
73
74
|
};
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The environment a managed harness child actually receives, checked at the one
|
|
78
|
+
* point where it is composed. `role.env` deliberately wins over harness prep,
|
|
79
|
+
* which is exactly how a stale fleet-wide model pin used to outrank the model
|
|
80
|
+
* the role was spawned with — so the model pin is verified here rather than
|
|
81
|
+
* trusted, and a disagreement stops the launch instead of being reported as a
|
|
82
|
+
* success (see src/model-env.ts).
|
|
83
|
+
*/
|
|
84
|
+
export function harnessChildEnv(role, launchEnv, stateDir) {
|
|
85
|
+
const env = { ...(launchEnv ?? {}), ...managedFleetProxyEnv(role, stateDir) };
|
|
86
|
+
assertModelPinReachesChild(role, env);
|
|
87
|
+
return env;
|
|
88
|
+
}
|
|
75
89
|
/**
|
|
76
90
|
* Execute a typed proxy request in the caller's supervisor. Dynamic imports
|
|
77
91
|
* avoid a runner↔spawn initialization cycle (spawn imports runner constants).
|
|
@@ -109,7 +123,9 @@ async function executeManagedSpawn(caller, configPath, requested, log) {
|
|
|
109
123
|
statePath,
|
|
110
124
|
harness: preview.harness,
|
|
111
125
|
session: preview.session,
|
|
112
|
-
|
|
126
|
+
// Read back from the resolved environment, not from the request: the banner
|
|
127
|
+
// must name the model the child will run, not the one that was asked for.
|
|
128
|
+
...(effectiveRoleModel(preview) ? { model: effectiveRoleModel(preview) } : {}),
|
|
113
129
|
monitor: { mode: preview.monitor.mode, interrupt: preview.monitor.interrupt },
|
|
114
130
|
permissionMode: effectivePermissionMode(preview),
|
|
115
131
|
inherited,
|
|
@@ -117,6 +133,7 @@ async function executeManagedSpawn(caller, configPath, requested, log) {
|
|
|
117
133
|
};
|
|
118
134
|
log(`[${caller.name}] managed fleet proxy spawned ${result.lifetime} role ${result.role} `
|
|
119
135
|
+ `harness=${result.harness} session=${result.session} `
|
|
136
|
+
+ `model=${result.model ?? '(harness default)'} `
|
|
120
137
|
+ `permission=${result.permissionMode.fleetMode} `
|
|
121
138
|
+ `native=${result.permissionMode.nativeMode}`);
|
|
122
139
|
return result;
|
|
@@ -458,11 +475,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
458
475
|
const effectiveModel = effectiveModelForRole(dir, role);
|
|
459
476
|
if (effectiveModel !== role.model) {
|
|
460
477
|
deps.log(`[${name}] model recovery drift: declared=${role.model ?? '(none)'} effective=${effectiveModel}`);
|
|
461
|
-
|
|
478
|
+
// The env pin has to move with it. A down-shift that changed only
|
|
479
|
+
// `role.model` was reported as a model change while the child kept running
|
|
480
|
+
// the model that had just failed, because the pin is what the harness reads.
|
|
481
|
+
role = { ...role, model: effectiveModel, env: repinModelEnv(role, effectiveModel) };
|
|
462
482
|
}
|
|
463
483
|
if (modelRecoveryHeld(dir))
|
|
464
484
|
throw new Error(`[${name}] model chain exhausted — held down until config changes or recovery reset`);
|
|
465
485
|
const adapter = getAdapter(role.harness);
|
|
486
|
+
// Say the running model out loud, once, from the resolved environment. The
|
|
487
|
+
// spawn banner is a claim made before the process exists; this is the log line
|
|
488
|
+
// that can be checked against the session afterwards.
|
|
489
|
+
deps.log(`[${name}] model: ${effectiveRoleModel(role) ?? '(harness default)'}`);
|
|
466
490
|
mkdirSync(dir, { recursive: true });
|
|
467
491
|
const rotation = rotateWorklog(join(dir, 'WORKLOG.md'), role.worklog);
|
|
468
492
|
if (rotation.deferred)
|
|
@@ -603,7 +627,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
603
627
|
name,
|
|
604
628
|
argv: wrappedArgv,
|
|
605
629
|
cwd: runCwd,
|
|
606
|
-
env:
|
|
630
|
+
env: harnessChildEnv(role, launch.env, dir),
|
|
607
631
|
stateDir: dir,
|
|
608
632
|
mode,
|
|
609
633
|
permissions: perms,
|
package/dist/spawn.js
CHANGED
|
@@ -5,6 +5,7 @@ import { parse, stringify } from 'yaml';
|
|
|
5
5
|
import { agentDir, fleetDDir } from './paths.js';
|
|
6
6
|
import { validateIsolationConfig } from './isolation/policy.js';
|
|
7
7
|
import { loadConfig, resolveAuthProxy, resolveModelChain, resolveMonitorConfig, resolveOwnerChannelConfig, resolvePermissions, resolveRoleModel, resolveWorklogPolicy, validateMonitorConfig, } from './config.js';
|
|
8
|
+
import { resolveRoleModelEnv } from './model-env.js';
|
|
8
9
|
import { applyRole, up } from './ops.js';
|
|
9
10
|
import { START_STAGGER_FILE } from './runner.js';
|
|
10
11
|
import { buildProvenance, daemonIdentityProvisioner, ensureIdentity, provenanceOf, withCreationTransaction, writeProvenance, writeRoleFile, } from './creation.js';
|
|
@@ -191,7 +192,17 @@ export function spawnDryRun(o) {
|
|
|
191
192
|
const harness = raw.harness ?? cfg.defaults.harness ?? 'claude-code';
|
|
192
193
|
const defaultHarness = cfg.defaults.harness ?? 'claude-code';
|
|
193
194
|
const inheritsModelDefaults = harness === defaultHarness && raw.model !== null;
|
|
194
|
-
const
|
|
195
|
+
const authProxy = resolveAuthProxy(cfg.defaults.auth_proxy, raw.auth_proxy);
|
|
196
|
+
// One resolution for the environment and the model it pins (src/model-env.ts).
|
|
197
|
+
const modelEnv = resolveRoleModelEnv({
|
|
198
|
+
harness,
|
|
199
|
+
model: resolveRoleModel(raw.model, raw.harness, cfg.defaults),
|
|
200
|
+
modelWasExplicit: raw.model !== undefined,
|
|
201
|
+
defaultsEnv: (cfg.defaults.env ?? {}),
|
|
202
|
+
roleEnv: raw.env,
|
|
203
|
+
...(authProxy ? { authProxyBaseUrl: authProxy.base_url } : {}),
|
|
204
|
+
});
|
|
205
|
+
const model = modelEnv.model;
|
|
195
206
|
const session = raw.session ?? cfg.defaults.session ?? 'tmux';
|
|
196
207
|
const resolvedRole = {
|
|
197
208
|
...raw,
|
|
@@ -212,15 +223,9 @@ export function spawnDryRun(o) {
|
|
|
212
223
|
monitor: resolveMonitorConfig(cfg.defaults.monitor, raw.monitor),
|
|
213
224
|
owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, raw.owner_channel, session),
|
|
214
225
|
worklog: resolveWorklogPolicy(cfg.defaults.worklog, raw.worklog),
|
|
215
|
-
auth_proxy:
|
|
216
|
-
};
|
|
217
|
-
resolvedRole.env = {
|
|
218
|
-
...(cfg.defaults.env ?? {}),
|
|
219
|
-
...(raw.env ?? {}),
|
|
220
|
-
...(resolvedRole.auth_proxy
|
|
221
|
-
? { ANTHROPIC_BASE_URL: resolvedRole.auth_proxy.base_url }
|
|
222
|
-
: {}),
|
|
226
|
+
auth_proxy: authProxy,
|
|
223
227
|
};
|
|
228
|
+
resolvedRole.env = modelEnv.env;
|
|
224
229
|
const adapter = getAdapter(resolvedRole.harness);
|
|
225
230
|
if (resolvedRole.auth_proxy && resolvedRole.harness !== 'claude-code')
|
|
226
231
|
throw new Error('auth_proxy is supported only by claude-code');
|
|
@@ -403,7 +408,18 @@ async function spawnTempInner(o, binPath, launch, tx, guarantee, onStage) {
|
|
|
403
408
|
};
|
|
404
409
|
const harness = o.harness ?? defaultHarness ?? 'claude-code';
|
|
405
410
|
const inheritsModelDefaults = harness === (defaultHarness ?? 'claude-code') && o.model !== null;
|
|
406
|
-
const
|
|
411
|
+
const tempAuthProxy = resolveAuthProxy(cfg.defaults.auth_proxy, fromOpts.auth_proxy);
|
|
412
|
+
// An explicitly requested --model must reach the child, not just the banner
|
|
413
|
+
// (src/model-env.ts).
|
|
414
|
+
const modelEnv = resolveRoleModelEnv({
|
|
415
|
+
harness,
|
|
416
|
+
model: resolveRoleModel(o.model, o.harness, cfg.defaults),
|
|
417
|
+
modelWasExplicit: o.model !== undefined,
|
|
418
|
+
defaultsEnv: (cfg.defaults.env ?? {}),
|
|
419
|
+
roleEnv: fromOpts.env,
|
|
420
|
+
...(tempAuthProxy ? { authProxyBaseUrl: tempAuthProxy.base_url } : {}),
|
|
421
|
+
});
|
|
422
|
+
const model = modelEnv.model;
|
|
407
423
|
const session = o.session ?? cfg.defaults.session ?? 'tmux';
|
|
408
424
|
const role = {
|
|
409
425
|
...fromOpts, // includes `isolation` when --isolation-file was given
|
|
@@ -422,14 +438,10 @@ async function spawnTempInner(o, binPath, launch, tx, guarantee, onStage) {
|
|
|
422
438
|
monitor: resolveMonitorConfig(cfg.defaults.monitor, fromOpts.monitor),
|
|
423
439
|
owner_channel: resolveOwnerChannelConfig(cfg.defaults.owner_channel, fromOpts.owner_channel, session),
|
|
424
440
|
worklog: resolveWorklogPolicy(cfg.defaults.worklog, fromOpts.worklog),
|
|
425
|
-
auth_proxy:
|
|
441
|
+
auth_proxy: tempAuthProxy,
|
|
426
442
|
sourceFile: '(temp)',
|
|
427
443
|
};
|
|
428
|
-
role.env =
|
|
429
|
-
...(cfg.defaults.env ?? {}),
|
|
430
|
-
...(fromOpts.env ?? {}),
|
|
431
|
-
...(role.auth_proxy ? { ANTHROPIC_BASE_URL: role.auth_proxy.base_url } : {}),
|
|
432
|
-
};
|
|
444
|
+
role.env = modelEnv.env;
|
|
433
445
|
if (role.auth_proxy && role.harness !== 'claude-code')
|
|
434
446
|
throw new Error('auth_proxy is supported only by claude-code');
|
|
435
447
|
onStage?.('writing_role');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.10",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux or ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|