@integrity-labs/agt-cli 0.28.766 → 0.28.768

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.
@@ -18,7 +18,7 @@ import {
18
18
  rotateDailySession,
19
19
  sessionFileExists,
20
20
  todayLocalIso
21
- } from "./chunk-23KTLVYX.js";
21
+ } from "./chunk-MBUWL4UL.js";
22
22
  import {
23
23
  reapOrphanChannelMcps
24
24
  } from "./chunk-XWVM4KPK.js";
@@ -3262,6 +3262,22 @@ var CREDENTIAL_SOURCES = [
3262
3262
  ];
3263
3263
  var ROLES = ["primary", "secondary"];
3264
3264
  var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3265
+ function parseModelPolicyModels(raw) {
3266
+ if (raw === void 0 || raw === null) return [];
3267
+ if (!Array.isArray(raw)) return [];
3268
+ const out = [];
3269
+ for (const entry of raw) {
3270
+ if (!isRecord(entry)) return [];
3271
+ const role = entry["role"];
3272
+ const slot = entry["slot"];
3273
+ const modelId = entry["model_id"];
3274
+ if (typeof role !== "string" || !ROLES.includes(role)) return [];
3275
+ if (typeof slot !== "string" || slot.trim() === "") return [];
3276
+ if (typeof modelId !== "string" || modelId.trim() === "") return [];
3277
+ out.push({ role, slot: slot.trim(), modelId: modelId.trim() });
3278
+ }
3279
+ return out;
3280
+ }
3265
3281
  function parseBinding(raw) {
3266
3282
  if (!isRecord(raw)) return null;
3267
3283
  const role = raw["role"];
@@ -3310,7 +3326,8 @@ function parseModelPolicy(raw) {
3310
3326
  name,
3311
3327
  revision,
3312
3328
  resolvedFrom,
3313
- bindings
3329
+ bindings,
3330
+ models: parseModelPolicyModels(raw["models"])
3314
3331
  };
3315
3332
  }
3316
3333
  function modelPolicyAuthComponent(policy) {
@@ -3365,16 +3382,38 @@ function policyPutsAnthropicApiKeyInEnv(policy) {
3365
3382
  if (!policy) return false;
3366
3383
  return policy.bindings.some((b) => sourceSetsAnthropicApiKey(b.credentialSource));
3367
3384
  }
3385
+ var CLAUDE_CODE_SLOT_ENV = /* @__PURE__ */ new Map([
3386
+ ["primary", "ANTHROPIC_MODEL"],
3387
+ ["small_fast", "ANTHROPIC_SMALL_FAST_MODEL"]
3388
+ ]);
3389
+ function modelEnvForRole(policy, role) {
3390
+ if (!policy) return {};
3391
+ const out = {};
3392
+ for (const m of policy.models) {
3393
+ if (m.role !== role) continue;
3394
+ const key = CLAUDE_CODE_SLOT_ENV.get(m.slot);
3395
+ if (!key) continue;
3396
+ out[key] = m.modelId;
3397
+ }
3398
+ return out;
3399
+ }
3368
3400
  function resolveModelPolicySpawnEnv(input) {
3369
3401
  const { policy, openRouterMode, gatewayToken, codeName } = input;
3370
3402
  const primary = primaryBinding(policy);
3371
- if (!policy || !primary) return { env: {}, notice: null };
3403
+ if (!policy || !primary) return { env: {}, modelEnv: {}, notice: null };
3372
3404
  if (openRouterMode) {
3373
3405
  return {
3374
3406
  env: {},
3407
+ // ENG-9816: no model either. OpenRouter sets `ANTHROPIC_MODEL` from its own
3408
+ // config a few lines below in the caller, and two writers of one variable
3409
+ // is how the "two actuators" problem above becomes a wrong model rather
3410
+ // than a logged conflict.
3411
+ modelEnv: {},
3375
3412
  notice: `[model-policy] '${codeName}' has policy '${policy.name}' but is in OpenRouter mode \u2014 OpenRouter wins, policy not applied (ENG-9483: unifying the two actuators is a separate migration)`
3376
3413
  };
3377
3414
  }
3415
+ const modelEnv = modelEnvForRole(policy, primary.role);
3416
+ const modelNote = Object.keys(modelEnv).length > 0 ? ` models=${Object.entries(modelEnv).map(([k, v]) => `${k}=${v}`).join(" ")}` : "";
3378
3417
  const resolution = resolveBindingCredential(primary, gatewayToken);
3379
3418
  switch (resolution.kind) {
3380
3419
  case "gateway":
@@ -3383,15 +3422,26 @@ function resolveModelPolicySpawnEnv(input) {
3383
3422
  ANTHROPIC_BASE_URL: resolution.baseUrl,
3384
3423
  ANTHROPIC_AUTH_TOKEN: resolution.authToken
3385
3424
  },
3386
- notice: `[model-policy] '${codeName}' inference via gateway ${resolution.baseUrl} (policy '${policy.name}' rev ${policy.revision}, from ${policy.resolvedFrom})`
3425
+ modelEnv,
3426
+ notice: `[model-policy] '${codeName}' inference via gateway ${resolution.baseUrl} (policy '${policy.name}' rev ${policy.revision}, from ${policy.resolvedFrom})${modelNote}`
3387
3427
  };
3388
3428
  case "blocked":
3389
3429
  return {
3390
3430
  env: {},
3431
+ // No partial application. The chosen model belongs to an endpoint this
3432
+ // manager could not honour, so applying it alone would run the agent on
3433
+ // the host's existing credential against a model the operator picked for
3434
+ // somewhere else — a policy half-applied, which is the one outcome this
3435
+ // file has no branch for.
3436
+ modelEnv: {},
3391
3437
  notice: `[model-policy] '${codeName}' policy '${policy.name}' NOT applied \u2014 reason=${resolution.reason}; keeping existing auth (transport=${primary.transport}, credential_source=${primary.credentialSource})`
3392
3438
  };
3393
3439
  case "host-default":
3394
- return { env: {}, notice: null };
3440
+ return {
3441
+ env: {},
3442
+ modelEnv,
3443
+ notice: modelNote ? `[model-policy] '${codeName}' model from policy '${policy.name}' rev ${policy.revision} (from ${policy.resolvedFrom});${modelNote}` : null
3444
+ };
3395
3445
  }
3396
3446
  }
3397
3447
 
@@ -3588,7 +3638,7 @@ function restartEgressSidecar(codeName) {
3588
3638
  }
3589
3639
  }
3590
3640
  function buildDockerRunCommand(args) {
3591
- const { codeName, agentId, wrapperPath, projectDir, homeDir, runId, passApiKey, passOpenRouter, passModelPolicyGateway, egress, forwardSlackReplyBinding, forwardBlockTurnEndAllMarkers, forwardKanbanWaiting, forwardNotifyDispatch, forwardTurnFailureNotice, forwardChannelSecrets } = args;
3641
+ const { codeName, agentId, wrapperPath, projectDir, homeDir, runId, passApiKey, passOpenRouter, passModelPolicyGateway, passModelPolicyModel, egress, forwardSlackReplyBinding, forwardBlockTurnEndAllMarkers, forwardKanbanWaiting, forwardNotifyDispatch, forwardTurnFailureNotice, forwardChannelSecrets } = args;
3592
3642
  const q = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
3593
3643
  const agentDir2 = join6(homeDir, ".augmented", codeName);
3594
3644
  const agentIdDir = join6(homeDir, ".augmented", agentId);
@@ -3620,6 +3670,10 @@ function buildDockerRunCommand(args) {
3620
3670
  envArgs.push("-e ANTHROPIC_BASE_URL");
3621
3671
  envArgs.push("-e ANTHROPIC_AUTH_TOKEN");
3622
3672
  }
3673
+ if (passModelPolicyModel) {
3674
+ envArgs.push("-e ANTHROPIC_MODEL");
3675
+ envArgs.push("-e ANTHROPIC_SMALL_FAST_MODEL");
3676
+ }
3623
3677
  if (runId) envArgs.push(`-e ${q(`AGT_RUN_ID=${runId}`)}`);
3624
3678
  envArgs.push("-e AGT_API_KEY");
3625
3679
  envArgs.push("-e AGT_HOST");
@@ -4007,7 +4061,11 @@ function spawnSession(config, session) {
4007
4061
  const { codeName, projectDir, mcpConfigPath, claudeMdPath, channels, devChannels, apiHost, log: log2 } = config;
4008
4062
  const claudeAuthMode = config.claudeAuthMode ?? "subscription";
4009
4063
  const openRouterMode = !!config.openRouter;
4010
- const { env: policyEnv, notice: policyNotice } = resolveModelPolicySpawnEnv({
4064
+ const {
4065
+ env: policyEnv,
4066
+ modelEnv: policyModelEnv,
4067
+ notice: policyNotice
4068
+ } = resolveModelPolicySpawnEnv({
4011
4069
  policy: config.modelPolicy ?? null,
4012
4070
  openRouterMode,
4013
4071
  gatewayToken: config.modelPolicyGatewayToken ?? null,
@@ -4015,6 +4073,7 @@ function spawnSession(config, session) {
4015
4073
  });
4016
4074
  if (policyNotice) log2(policyNotice);
4017
4075
  const policyEnvActive = Object.keys(policyEnv).length > 0;
4076
+ const policyModelActive = Object.keys(policyModelEnv).length > 0;
4018
4077
  const tmuxSession = `agt-${codeName}`;
4019
4078
  log2(
4020
4079
  `[persistent-session] Starting tmux session '${tmuxSession}' for '${codeName}' (auth=${openRouterMode ? "openrouter" : claudeAuthMode})`
@@ -4109,7 +4168,7 @@ function spawnSession(config, session) {
4109
4168
  if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
4110
4169
  args.push("--mcp-config", mcpConfigPath);
4111
4170
  if (existsSync5(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
4112
- if (!openRouterMode) {
4171
+ if (!openRouterMode && !policyModelActive) {
4113
4172
  const modelAlias = claudeModelAlias(config.primaryModel);
4114
4173
  if (modelAlias) args.push("--model", modelAlias);
4115
4174
  }
@@ -4152,6 +4211,9 @@ function spawnSession(config, session) {
4152
4211
  } else if (claudeAuthMode === "api_key" && config.anthropicApiKey) {
4153
4212
  tmuxSessionEnvArgs.push("-e", `ANTHROPIC_API_KEY=${config.anthropicApiKey}`);
4154
4213
  }
4214
+ for (const [k, v] of Object.entries(policyModelEnv)) {
4215
+ tmuxSessionEnvArgs.push("-e", `${k}=${v}`);
4216
+ }
4155
4217
  if (config.slackReplyBindingMode && config.slackReplyBindingMode !== "shadow" && !process.env["AGT_SLACK_REPLY_BINDING"]) {
4156
4218
  tmuxSessionEnvArgs.push("-e", `AGT_SLACK_REPLY_BINDING=${config.slackReplyBindingMode}`);
4157
4219
  }
@@ -4199,6 +4261,7 @@ function spawnSession(config, session) {
4199
4261
  // forwarded here — see the flag's docblock for why omitting this is a
4200
4262
  // silent fallback to the host's auth rather than a visible failure.
4201
4263
  passModelPolicyGateway: policyEnvActive,
4264
+ passModelPolicyModel: policyModelActive,
4202
4265
  egress,
4203
4266
  // ENG-6476: forward the reply-routing flag env if it will be present in the
4204
4267
  // session env (operator-set OR materialized from the flag above).
@@ -4246,12 +4309,39 @@ function spawnSession(config, session) {
4246
4309
  ...apiKeyEnv,
4247
4310
  ...openRouterEnv,
4248
4311
  ...policyEnv,
4312
+ // ENG-9816: the third site. `policyModelEnv` is empty in OpenRouter
4313
+ // mode and for a blocked binding, so it can never overwrite
4314
+ // `openRouterEnv`'s ANTHROPIC_MODEL above.
4315
+ ...policyModelEnv,
4249
4316
  ...channelSecretEnv,
4250
4317
  ...config.runId ? { AGT_RUN_ID: config.runId } : {},
4251
4318
  AGT_API_KEY: tmuxEnv["AGT_API_KEY"],
4252
4319
  AGT_HOST: tmuxEnv["AGT_HOST"],
4253
4320
  AGT_AGENT_ID: config.agentId
4254
- } : { ...tmuxEnv, ...apiKeyEnv, ...openRouterEnv, ...channelSecretEnv };
4321
+ } : {
4322
+ ...tmuxEnv,
4323
+ ...apiKeyEnv,
4324
+ ...openRouterEnv,
4325
+ // ENG-9816 (CodeRabbit, PR #5366): the BARE-host branch needs the
4326
+ // policy vars too, and it was missing BOTH.
4327
+ //
4328
+ // `tmuxEnv` is `buildSpawnEnv({ base: process.env })`, so it cannot
4329
+ // contain anything delivered by `tmux new-session -e` — which is
4330
+ // exactly why apiKeyEnv / openRouterEnv / channelSecretEnv are each
4331
+ // spread back in above. The policy vars arrive the same way and were
4332
+ // not, so `probeMcpEnvSubstitution` reports them unset on every
4333
+ // bare-host spawn: a false "missing var" line for a var that IS in
4334
+ // the real spawn env — the inverse of the silent failure this probe
4335
+ // exists to catch, and just as good at training an operator to skim
4336
+ // past it.
4337
+ //
4338
+ // `policyEnv` is ENG-9483's and had the identical gap; the review
4339
+ // flagged only the new variable. Fixing one and leaving the other
4340
+ // would leave the same defect one identifier away.
4341
+ ...policyEnv,
4342
+ ...policyModelEnv,
4343
+ ...channelSecretEnv
4344
+ };
4255
4345
  for (const f of probeMcpEnvSubstitution({
4256
4346
  mcpConfigPath,
4257
4347
  envIntegrationsPath: join6(projectDir, ".env.integrations"),
@@ -5051,4 +5141,4 @@ export {
5051
5141
  stopAllSessionsAndWait,
5052
5142
  getProjectDir
5053
5143
  };
5054
- //# sourceMappingURL=chunk-LVBINNTQ.js.map
5144
+ //# sourceMappingURL=chunk-THSP5NB6.js.map