@agentproto/driver-agent-cli 2.0.0 → 2.1.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.
@@ -5,8 +5,9 @@ import { spawn } from 'child_process';
5
5
  import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, rmdirSync, mkdtempSync } from 'fs';
6
6
  import { createInterface } from 'readline';
7
7
  import { join } from 'path';
8
+ import { tmpdir, homedir } from 'os';
9
+ import { loadAdapterSpawnSandboxConfig, resolveCommandSandbox } from '@agentproto/command-sandbox';
8
10
  import { randomUUID } from 'crypto';
9
- import { tmpdir } from 'os';
10
11
  import { createDoctype } from '@agentproto/define-doctype';
11
12
 
12
13
  /**
@@ -151,10 +152,27 @@ var authSchema = z.object({
151
152
  }).strict().optional()
152
153
  }).strict();
153
154
  var authSubscriptionSchema = z.object({
154
- setEnv: z.string().min(1),
155
+ setEnv: z.string().min(1).optional(),
156
+ external: z.boolean().optional(),
155
157
  conflictEnv: z.array(z.string()).optional(),
156
158
  unsetEnvAdd: z.array(z.string()).optional()
157
- }).strict();
159
+ }).strict().superRefine((v, ctx) => {
160
+ if (v.external) {
161
+ if (v.setEnv !== void 0) {
162
+ ctx.addIssue({
163
+ code: z.ZodIssueCode.custom,
164
+ message: "authSubscription.external: true must NOT declare setEnv \u2014 an external (file-based) subscription injects no bearer; the CLI reads its own login file.",
165
+ path: ["setEnv"]
166
+ });
167
+ }
168
+ } else if (v.setEnv === void 0) {
169
+ ctx.addIssue({
170
+ code: z.ZodIssueCode.custom,
171
+ message: "authSubscription requires setEnv unless external: true (the bearer must be injected into some env var).",
172
+ path: ["setEnv"]
173
+ });
174
+ }
175
+ });
158
176
  var sessionSchema = z.object({
159
177
  mode: z.enum(["ephemeral", "persistent", "resumable"]).default("ephemeral"),
160
178
  idle_timeout_ms: z.number().int().min(1e3).default(6e5),
@@ -185,8 +203,7 @@ var modelsSchema = z.object({
185
203
  // How a model is selected at session start: "config" (ACP
186
204
  // set_config_option, default) | "command" (a `/model <id>` control turn,
187
205
  // for agents like hermes that ignore the ACP session model config) |
188
- // "arg" (a CLI argument composed into bin_args at spawn, for agents
189
- // like codex-acp that take their model as `-c model="<id>"`).
206
+ // "arg" (a CLI argument composed into bin_args at spawn).
190
207
  apply: z.enum(["config", "command", "arg"]).optional(),
191
208
  // Argv template for apply:"arg" — {model} interpolates the requested
192
209
  // model id. See AgentCliModels.bin_args_template.
@@ -200,6 +217,13 @@ var capabilitiesSchema = z.object({
200
217
  multimodal: z.boolean().optional(),
201
218
  resumable: z.boolean().optional(),
202
219
  bidirectional: z.boolean().optional(),
220
+ /**
221
+ * Adapter has a verified native CLI resume into a real terminal/TUI
222
+ * session (e.g. `claude --resume <id>` or `hermes --resume <id> --tui`).
223
+ * When true, the host MAY restart this adapter via its native resume argv
224
+ * as a PTY session. Distinct from ACP-level `resumable`.
225
+ */
226
+ nativeTerminalResume: z.boolean().optional(),
203
227
  /**
204
228
  * Adapter can ingest a filesystem path that the host UI just placed
205
229
  * on disk (host-side drag-drop into a terminal pastes the path
@@ -321,6 +345,26 @@ var agentCliFrontmatterSchema = z.object({
321
345
  // keep this generic doctype decoupled from @agentproto/model-catalog).
322
346
  provider: z.string().min(1).optional(),
323
347
  authSubscription: authSubscriptionSchema.optional(),
348
+ // How this adapter receives a GATEWAY-routed bearer credential — see the
349
+ // doc on AgentCliDefinition.gatewayAuth. Distinct from a gateway preset's
350
+ // `keyEnv` (the providers-store lookup key) and from
351
+ // authSubscription.setEnv (this adapter's own native bearer).
352
+ gatewayAuth: z.object({
353
+ setEnv: z.string().min(1)
354
+ }).strict().optional(),
355
+ // Auth-derivation axis (DECISION 3): true when the adapter's api-key auth
356
+ // is derived from the requested model rather than a fixed `provider`
357
+ // (e.g. `pi`, `opencode`, `mastracode`). When set, the runtime resolver
358
+ // allows `"api-key"` mode on the model-derived direct endpoint and
359
+ // includes it in eligibility manifests for by-model routers. DISTINCT
360
+ // from `routeSelection` (the UI-facing route-choice axis).
361
+ modelDerivedApiKey: z.boolean().optional(),
362
+ // UI-facing route-choice axis (AIP-45 launch-menu drill-down, WP1).
363
+ // Absent ⇒ "free" (route is an independent choice); "derived-from-model"
364
+ // means the endpoint falls out of the model id's vendor prefix. DISTINCT
365
+ // from `modelDerivedApiKey` (the auth-derivation axis) — see the field
366
+ // doc on AgentCliDefinition.routeSelection.
367
+ routeSelection: z.enum(["free", "derived-from-model"]).optional(),
324
368
  authEnforce: z.enum(["always", "when-configured"]).optional(),
325
369
  sandbox: z.union([z.string(), z.record(z.string(), z.unknown())]),
326
370
  runner: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
@@ -539,6 +583,69 @@ function toFileBasedMcpServers(servers) {
539
583
  }
540
584
  return out;
541
585
  }
586
+ function defaultToolchainWritePaths() {
587
+ const home = homedir();
588
+ return [
589
+ join(home, "Library", "pnpm", ".tools"),
590
+ // macOS pnpm self-managed toolchain
591
+ join(home, ".local", "share", "pnpm"),
592
+ // Linux pnpm self-managed toolchain
593
+ join(home, ".npm"),
594
+ // npm cache — self-update / global installs
595
+ join(home, ".claude", "session-env")
596
+ // claude-code's own Bash-tool session bookkeeping
597
+ ];
598
+ }
599
+ function defaultToolchainReadPaths() {
600
+ const home = homedir();
601
+ return [
602
+ join(home, ".npmrc"),
603
+ join(home, "Library", "Preferences", "pnpm", "rc"),
604
+ join(home, ".gitconfig"),
605
+ // git's own global config (credential.helper, user.*, …)
606
+ join(home, ".config", "git"),
607
+ // XDG git config fallback
608
+ join(home, ".config", "gh"),
609
+ // gh's config.yml + hosts.yml
610
+ join(home, "Library", "Keychains")
611
+ // macOS Keychain DB — git/gh credential retrieval (read-only; see doc above)
612
+ ];
613
+ }
614
+ async function wrapAgentCliSpawn(bin, args, opts) {
615
+ const cfg = await loadAdapterSpawnSandboxConfig(opts.cwd);
616
+ const mode = opts.mode ?? cfg.mode;
617
+ if (mode === void 0) return [bin, args];
618
+ if (mode === "off") {
619
+ console.error(
620
+ `[agent-cli] \u26A0 spawning '${opts.label}' UNCONFINED \u2014 no OS-level sandbox is active (commandSandbox mode is "off").`
621
+ );
622
+ return [bin, args];
623
+ }
624
+ const backend = resolveCommandSandbox();
625
+ if (!backend) {
626
+ throw new Error(
627
+ `agent-cli '${opts.label}': commandSandbox mode="${mode}" is configured but no sandbox backend is available on ${process.platform} (macOS needs sandbox-exec, Linux needs bwrap installed). Refusing to spawn unconfined \u2014 pass commandSandbox: "off" to explicitly accept unconfined execution, or install the missing backend.`
628
+ );
629
+ }
630
+ const policy = {
631
+ workspace: opts.cwd,
632
+ extraReadPaths: [
633
+ ...defaultToolchainReadPaths(),
634
+ ...cfg.extraReadPaths,
635
+ ...opts.extraReadPaths ?? []
636
+ ],
637
+ extraWritePaths: [
638
+ ...defaultToolchainWritePaths(),
639
+ ...cfg.extraWritePaths,
640
+ ...opts.extraWritePaths ?? []
641
+ ],
642
+ network: mode === "strict" || cfg.network === "deny" ? "deny" : "allow"
643
+ };
644
+ const wrapped = backend.wrap([bin, ...args], policy);
645
+ return [wrapped[0] ?? bin, wrapped.slice(1)];
646
+ }
647
+
648
+ // src/protocol/print-arm.ts
542
649
  var DEFAULT_OUTPUT = ["--output-format", "stream-json"];
543
650
  var DEFAULT_PRE_PROMPT = ["--no-interactive"];
544
651
  var DEFAULT_RESUME = { flag: "--resume", kind: "value" };
@@ -572,7 +679,13 @@ function createPrintSession(opts) {
572
679
  } else {
573
680
  args.push(prompt);
574
681
  }
575
- const child = spawn(opts.bin, args, {
682
+ const [execBin, execArgs] = await wrapAgentCliSpawn(opts.bin, args, {
683
+ mode: opts.commandSandbox,
684
+ cwd: opts.cwd,
685
+ ...opts.extraWritePaths ? { extraWritePaths: opts.extraWritePaths } : {},
686
+ label: "print-arm"
687
+ });
688
+ const child = spawn(execBin, execArgs, {
576
689
  cwd: opts.cwd,
577
690
  env: opts.env,
578
691
  stdio: ["ignore", "pipe", "pipe"]
@@ -1074,7 +1187,7 @@ var RuntimeConfigError = class extends Error {
1074
1187
  this.name = "RuntimeConfigError";
1075
1188
  }
1076
1189
  };
1077
- function composeSpawn(handle, config) {
1190
+ function composeSpawn(handle, config, opts) {
1078
1191
  if (!config)
1079
1192
  return {
1080
1193
  binArgs: [...handle.bin_args ?? []],
@@ -1140,7 +1253,7 @@ function composeSpawn(handle, config) {
1140
1253
  );
1141
1254
  }
1142
1255
  }
1143
- if (typeof requestedModel === "string" && handle.models?.apply === "arg" && handle.models.bin_args_template) {
1256
+ if (typeof requestedModel === "string" && handle.models?.apply === "arg" && handle.models.bin_args_template && !opts?.skipModelArg) {
1144
1257
  append.push(
1145
1258
  ...handle.models.bin_args_template.map(
1146
1259
  (token) => token.replace("{model}", requestedModel)
@@ -1262,6 +1375,102 @@ function interpolateEnv(env, value) {
1262
1375
  }
1263
1376
  return out;
1264
1377
  }
1378
+ function createArmSessionControls(arm, definition) {
1379
+ const modelApply = definition.models?.apply ?? "config";
1380
+ return {
1381
+ // Capability read-surface (SPEC §3.9/§3.4a) — snapshotted once
1382
+ // `arm.connect()` has resolved. Not live-updated by a later
1383
+ // `setModel`/`setSessionMode` call.
1384
+ availableConfigOptions: arm.availableConfigOptions ?? [],
1385
+ availableModes: arm.availableModes ?? [],
1386
+ currentModeId: arm.currentModeId,
1387
+ /**
1388
+ * Mid-session model switch — the runtime counterpart to the
1389
+ * spawn-time model apply. Dispatches on the same
1390
+ * `definition.models.apply` strategy so a live switch behaves
1391
+ * exactly like the spawn-time apply would have, just against an
1392
+ * already-running session.
1393
+ */
1394
+ async setModel(modelId) {
1395
+ if (modelApply === "arg") {
1396
+ return { applied: false, reason: "requires-restart" };
1397
+ }
1398
+ if (modelApply === "command") {
1399
+ return applyModelCommand(arm, modelId);
1400
+ }
1401
+ if (!arm.setConfigOption) {
1402
+ return { applied: false, reason: "not-supported" };
1403
+ }
1404
+ const result = await arm.setConfigOption("model", modelId);
1405
+ return result.applied ? { applied: true, model: modelId } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1406
+ },
1407
+ /**
1408
+ * Mid-session posture switch — the native-mode counterpart to
1409
+ * `setModel`, wired directly to the arm's `setSessionMode` (only the
1410
+ * ACP arm implements it).
1411
+ */
1412
+ async setSessionMode(modeId) {
1413
+ if (!arm.setSessionMode) {
1414
+ return { applied: false, reason: "not-supported" };
1415
+ }
1416
+ const result = await arm.setSessionMode(modeId);
1417
+ return result.applied ? { applied: true, modeId } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1418
+ },
1419
+ /**
1420
+ * Mid-session effort switch — the effort-axis counterpart to
1421
+ * `setModel`'s `"config"` strategy, wired to the same
1422
+ * `arm.setConfigOption` surface with `configId:"effort"`. Best-effort:
1423
+ * an effort label the current model rejects resolves
1424
+ * `{applied:false, reason}` (SPEC risk R7).
1425
+ */
1426
+ async setEffort(effort) {
1427
+ if (!arm.setConfigOption) {
1428
+ return { applied: false, reason: "not-supported" };
1429
+ }
1430
+ const result = await arm.setConfigOption("effort", effort);
1431
+ return result.applied ? { applied: true, effort } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1432
+ }
1433
+ };
1434
+ }
1435
+ async function applyModelCommand(arm, modelId) {
1436
+ const turnId = randomUUID();
1437
+ let acked = false;
1438
+ try {
1439
+ for await (const evt of promptTurn(arm, turnId, {
1440
+ type: "text",
1441
+ text: `/model ${modelId}`
1442
+ })) {
1443
+ if (/switch|model\s+set|now using/i.test(JSON.stringify(evt))) acked = true;
1444
+ }
1445
+ } catch (err) {
1446
+ const reason = err instanceof Error ? err.message : String(err);
1447
+ console.warn(
1448
+ `[agent-cli] /model ${modelId} control turn failed (continuing on default):`,
1449
+ err instanceof Error ? err.message : err
1450
+ );
1451
+ return { applied: false, reason };
1452
+ }
1453
+ if (!acked) {
1454
+ const reason = "no switch acknowledgement \u2014 agent may be on its default model";
1455
+ console.warn(`[agent-cli] /model ${modelId}: ${reason}`);
1456
+ return { applied: false, reason };
1457
+ }
1458
+ return { applied: true, model: modelId };
1459
+ }
1460
+ async function* promptTurn(arm, turnId, message) {
1461
+ await arm.send(turnId, message);
1462
+ const stderrTail = arm._stderrTail;
1463
+ for await (const evt of arm.events()) {
1464
+ if (evt.kind === "error" && typeof stderrTail === "function") {
1465
+ const tail = stderrTail();
1466
+ if (tail) {
1467
+ const existing = evt.error.data ?? {};
1468
+ evt.error.data = { ...existing, stderr: tail };
1469
+ }
1470
+ }
1471
+ yield evt;
1472
+ }
1473
+ }
1265
1474
  var defineAgentCli = createDoctype(
1266
1475
  {
1267
1476
  aip: 45,
@@ -1311,7 +1520,11 @@ function createAgentCliRuntime(definition) {
1311
1520
  definition,
1312
1521
  async start(opts) {
1313
1522
  const cwd = opts?.cwd ?? process.cwd();
1314
- const composed = composeSpawn(definition, opts?.config);
1523
+ const contextMode = opts?.contextProfile ? (definition.modes ?? []).find(
1524
+ (mode) => mode.id === opts.contextProfile && mode.kind === "context"
1525
+ ) : void 0;
1526
+ const config = opts?.config?.mode || !contextMode ? opts?.config : { ...opts?.config, mode: contextMode.id };
1527
+ const composed = composeSpawn(definition, config);
1315
1528
  const env = {
1316
1529
  ...filterStringEnv(process.env),
1317
1530
  ...composed.env
@@ -1320,36 +1533,41 @@ function createAgentCliRuntime(definition) {
1320
1533
  const authSpec = opts?.auth;
1321
1534
  const hasGatewayAuthToken = "ANTHROPIC_AUTH_TOKEN" in composed.env;
1322
1535
  const hasGatewayBaseUrl = "ANTHROPIC_BASE_URL" in composed.env;
1323
- const engageAuth = !!authSpec && !hasGatewayAuthToken && !hasGatewayBaseUrl && (authSpec.enforce === "always" || authSpec.explicit === true);
1536
+ const resolverCoupledGateway = authSpec?.baseUrl !== void 0;
1537
+ const engageAuth = !!authSpec && !hasGatewayAuthToken && (resolverCoupledGateway || !hasGatewayBaseUrl) && (authSpec.enforce === "always" || authSpec.explicit === true);
1324
1538
  if (authSpec && engageAuth) {
1325
1539
  for (const key of authSpec.unsetEnv) {
1326
1540
  if (!(key in composed.env)) delete env[key];
1327
1541
  }
1328
- if (!authSpec.credential) {
1329
- const slug = definition.id;
1330
- const providerLabel = definition.provider ?? "<provider>";
1331
- const configBlock = `{"defaults":{"adapters":{"${slug}":{"auth":{"mode":"api-key"}}}}} in ~/.agentproto/config.json`;
1332
- const storeHint = authSpec.ignoredApiKeyInStore ? ` (providers.json already has a stored key for ${providerLabel} \u2014 ignored until that block exists)` : "";
1333
- const supportsSub = !!definition.authSubscription;
1334
- const message = authSpec.neitherConfigured && supportsSub ? `agent-cli '${slug}': no billing auth. Subscription \u2192 \`claude setup-token\`. Api-key \u2192 \`agentproto auth provider set ${providerLabel} sk-\u2026\`${storeHint}. Add ${configBlock}. Never inherited from the shell.` : authSpec.mode === "subscription" ? `agent-cli '${slug}': run \`claude setup-token\` and configure the result \u2014 bills the Max/Pro subscription, not API credits. Never inherited from the shell.` : `agent-cli '${slug}': run \`agentproto auth provider set ${providerLabel} sk-\u2026\`${storeHint}, then add ${configBlock}. Never inherited from the shell.`;
1335
- throw new RuntimeConfigError("missing_auth_credential", "opts.auth.credential", message);
1542
+ if (!authSpec.externalCredential) {
1543
+ if (!authSpec.credential) {
1544
+ const slug = definition.id;
1545
+ const providerLabel = definition.provider ?? "<provider>";
1546
+ const configBlock = `{"defaults":{"adapters":{"${slug}":{"auth":{"mode":"api-key"}}}}} in ~/.agentproto/config.json`;
1547
+ const storeHint = authSpec.ignoredApiKeyInStore ? ` (providers.json already has a stored key for ${providerLabel} \u2014 ignored until that block exists)` : "";
1548
+ const supportsSub = !!definition.authSubscription;
1549
+ const message = authSpec.neitherConfigured && supportsSub ? `agent-cli '${slug}': no billing auth. Subscription \u2192 \`claude setup-token\`. Api-key \u2192 \`agentproto auth provider set ${providerLabel} sk-\u2026\`${storeHint}. Add ${configBlock}. Never inherited from the shell.` : authSpec.mode === "subscription" ? `agent-cli '${slug}': run \`claude setup-token\` and configure the result \u2014 bills the Max/Pro subscription, not API credits. Never inherited from the shell.` : `agent-cli '${slug}': run \`agentproto auth provider set ${providerLabel} sk-\u2026\`${storeHint}, then add ${configBlock}. Never inherited from the shell.`;
1550
+ throw new RuntimeConfigError("missing_auth_credential", "opts.auth.credential", message);
1551
+ }
1552
+ env[authSpec.setEnv] = authSpec.credential;
1336
1553
  }
1337
- env[authSpec.setEnv] = authSpec.credential;
1338
1554
  }
1339
1555
  Object.assign(env, opts?.env ?? {});
1340
1556
  const permissionMode = resolveClaudeCodePermissionMode(
1341
1557
  definition,
1342
- opts?.config?.mode
1558
+ opts?.posture,
1559
+ config?.mode
1343
1560
  );
1561
+ let claudeConfigDir;
1344
1562
  if (permissionMode) {
1345
- const configDir = mkdtempSync(
1563
+ claudeConfigDir = mkdtempSync(
1346
1564
  join(tmpdir(), "agentproto-claude-config-")
1347
1565
  );
1348
1566
  writeFileSync(
1349
- join(configDir, "settings.json"),
1567
+ join(claudeConfigDir, "settings.json"),
1350
1568
  JSON.stringify({ permissions: { defaultMode: permissionMode } })
1351
1569
  );
1352
- env.CLAUDE_CONFIG_DIR = configDir;
1570
+ env.CLAUDE_CONFIG_DIR = claudeConfigDir;
1353
1571
  }
1354
1572
  if (definition.protocol === "print") {
1355
1573
  return createPrintSession({
@@ -1359,13 +1577,25 @@ function createAgentCliRuntime(definition) {
1359
1577
  env,
1360
1578
  ...opts?.resumeSessionId ? { resumeSessionId: opts.resumeSessionId } : {},
1361
1579
  ...opts?.mcpServers ? { mcpServers: opts.mcpServers } : {},
1362
- printConfig: definition.print
1580
+ printConfig: definition.print,
1581
+ commandSandbox: opts?.commandSandbox,
1582
+ ...claudeConfigDir ? { extraWritePaths: [claudeConfigDir] } : {}
1363
1583
  });
1364
1584
  }
1365
1585
  let child;
1366
1586
  const stderrBuf = [];
1367
1587
  if (definition.protocol !== "proprietary") {
1368
- child = spawn(definition.bin, composed.binArgs, {
1588
+ const [execBin, execArgs] = await wrapAgentCliSpawn(
1589
+ definition.bin,
1590
+ composed.binArgs,
1591
+ {
1592
+ mode: opts?.commandSandbox,
1593
+ cwd,
1594
+ ...claudeConfigDir ? { extraWritePaths: [claudeConfigDir] } : {},
1595
+ label: definition.id
1596
+ }
1597
+ );
1598
+ child = spawn(execBin, execArgs, {
1369
1599
  cwd,
1370
1600
  env,
1371
1601
  stdio: ["pipe", "pipe", "pipe"],
@@ -1385,7 +1615,7 @@ function createAgentCliRuntime(definition) {
1385
1615
  definition,
1386
1616
  child,
1387
1617
  cwd,
1388
- opts?.config?.mode,
1618
+ config?.mode,
1389
1619
  opts?.permissionHold ?? false
1390
1620
  );
1391
1621
  arm._stderrTail = () => stderrBuf.join("\n");
@@ -1395,11 +1625,11 @@ function createAgentCliRuntime(definition) {
1395
1625
  once: true
1396
1626
  });
1397
1627
  }
1398
- const optModel = opts?.config?.options?.model;
1399
- const optEffort = opts?.config?.options?.effort;
1628
+ const optModel = config?.options?.model;
1629
+ const optEffort = config?.options?.effort;
1400
1630
  const modelApply = definition.models?.apply ?? "config";
1401
1631
  const configModel = optModel && modelApply === "config" ? String(optModel) : void 0;
1402
- const requestedModeId = opts?.config?.mode;
1632
+ const requestedModeId = config?.mode;
1403
1633
  const requestedModeDecl = requestedModeId ? (definition.modes ?? []).find((m) => m.id === requestedModeId) : void 0;
1404
1634
  const configMode = requestedModeDecl?.apply === "config" ? requestedModeId : void 0;
1405
1635
  const turnIdleTimeoutMs = opts?.turnIdleTimeoutMs ?? definition.session?.turn_idle_timeout_ms;
@@ -1424,15 +1654,11 @@ function createAgentCliRuntime(definition) {
1424
1654
  return {
1425
1655
  sessionId,
1426
1656
  pid: child?.pid,
1427
- // Capability read-surface (SPEC §3.9/§3.4a) — snapshotted once
1428
- // `arm.connect()` above has resolved, so an ACP arm's captured
1429
- // `newSession`/`loadSession` response is already populated. Arms
1430
- // that don't model this (print, proprietary) leave the getters
1431
- // undefined, defaulted here to the same empty/absent shape their
1432
- // `setModel`-style counterparts use for "not supported".
1433
- availableConfigOptions: arm.availableConfigOptions ?? [],
1434
- availableModes: arm.availableModes ?? [],
1435
- currentModeId: arm.currentModeId,
1657
+ // Live model/mode/effort switches + the capability read-surface,
1658
+ // all pure arm delegation — shared with every host that builds
1659
+ // its own session over another transport. See session-controls.ts.
1660
+ // Snapshotted after `arm.connect()` above resolved.
1661
+ ...createArmSessionControls(arm, definition),
1436
1662
  send(message) {
1437
1663
  const turnId = randomUUID();
1438
1664
  currentTurnId = turnId;
@@ -1471,58 +1697,6 @@ function createAgentCliRuntime(definition) {
1471
1697
  return arm.respondPermission(requestId, resolution);
1472
1698
  }
1473
1699
  } : {},
1474
- /**
1475
- * Mid-session model switch — the runtime counterpart to the
1476
- * spawn-time `modelApply` handling above. Dispatches on the same
1477
- * `definition.models.apply` strategy so a live switch behaves
1478
- * exactly like the spawn-time apply would have, just against an
1479
- * already-running session. See {@link SetModelResult} for the
1480
- * reason vocabulary; this never throws and never tears down the
1481
- * session on a rejected switch.
1482
- */
1483
- async setModel(modelId) {
1484
- if (modelApply === "arg") {
1485
- return { applied: false, reason: "requires-restart" };
1486
- }
1487
- if (modelApply === "command") {
1488
- return applyModelCommand(arm, modelId);
1489
- }
1490
- if (!arm.setConfigOption) {
1491
- return { applied: false, reason: "not-supported" };
1492
- }
1493
- const result = await arm.setConfigOption("model", modelId);
1494
- return result.applied ? { applied: true, model: modelId } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1495
- },
1496
- /**
1497
- * Mid-session posture switch — the native-mode counterpart to
1498
- * `setModel`, wired directly to the arm's `setSessionMode` (only
1499
- * the ACP arm implements it). See {@link SetSessionModeResult} for
1500
- * the reason vocabulary; this never throws and never tears down
1501
- * the session on a rejected switch.
1502
- */
1503
- async setSessionMode(modeId) {
1504
- if (!arm.setSessionMode) {
1505
- return { applied: false, reason: "not-supported" };
1506
- }
1507
- const result = await arm.setSessionMode(modeId);
1508
- return result.applied ? { applied: true, modeId } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1509
- },
1510
- /**
1511
- * Mid-session effort switch — the effort-axis counterpart to
1512
- * `setModel`'s `"config"` strategy, wired to the same
1513
- * `arm.setConfigOption` surface with `configId:"effort"` (only the
1514
- * ACP arm implements it). Best-effort and non-fatal: an effort label
1515
- * the current model rejects resolves `{applied:false, reason}` (SPEC
1516
- * risk R7), never thrown and never tearing down the session. See
1517
- * {@link SetEffortResult} for the reason vocabulary.
1518
- */
1519
- async setEffort(effort) {
1520
- if (!arm.setConfigOption) {
1521
- return { applied: false, reason: "not-supported" };
1522
- }
1523
- const result = await arm.setConfigOption("effort", effort);
1524
- return result.applied ? { applied: true, effort } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1525
- },
1526
1700
  async close() {
1527
1701
  await arm.close();
1528
1702
  if (child && !child.killed) child.kill("SIGTERM");
@@ -1531,45 +1705,6 @@ function createAgentCliRuntime(definition) {
1531
1705
  }
1532
1706
  };
1533
1707
  }
1534
- async function applyModelCommand(arm, modelId) {
1535
- const turnId = randomUUID();
1536
- let acked = false;
1537
- try {
1538
- for await (const evt of promptTurn(arm, turnId, {
1539
- type: "text",
1540
- text: `/model ${modelId}`
1541
- })) {
1542
- if (/switch|model\s+set|now using/i.test(JSON.stringify(evt))) acked = true;
1543
- }
1544
- } catch (err) {
1545
- const reason = err instanceof Error ? err.message : String(err);
1546
- console.warn(
1547
- `[agent-cli] /model ${modelId} control turn failed (continuing on default):`,
1548
- err instanceof Error ? err.message : err
1549
- );
1550
- return { applied: false, reason };
1551
- }
1552
- if (!acked) {
1553
- const reason = "no switch acknowledgement \u2014 agent may be on its default model";
1554
- console.warn(`[agent-cli] /model ${modelId}: ${reason}`);
1555
- return { applied: false, reason };
1556
- }
1557
- return { applied: true, model: modelId };
1558
- }
1559
- async function* promptTurn(arm, turnId, message) {
1560
- await arm.send(turnId, message);
1561
- const stderrTail = arm._stderrTail;
1562
- for await (const evt of arm.events()) {
1563
- if (evt.kind === "error" && typeof stderrTail === "function") {
1564
- const tail = stderrTail();
1565
- if (tail) {
1566
- const existing = evt.error.data ?? {};
1567
- evt.error.data = { ...existing, stderr: tail };
1568
- }
1569
- }
1570
- yield evt;
1571
- }
1572
- }
1573
1708
  async function buildProtocolArm(def, child, cwd, requestedMode, permissionHold) {
1574
1709
  switch (def.protocol) {
1575
1710
  case "acp":
@@ -1596,8 +1731,18 @@ async function buildProtocolArm(def, child, cwd, requestedMode, permissionHold)
1596
1731
  throw new Error("createAgentCliRuntime: print arm bypasses buildProtocolArm");
1597
1732
  }
1598
1733
  }
1599
- function resolveClaudeCodePermissionMode(definition, configMode) {
1600
- if (definition.id !== "claude-code" || !configMode) return void 0;
1734
+ function resolveClaudeCodePermissionMode(definition, posture, configMode) {
1735
+ if (definition.id !== "claude-code") return void 0;
1736
+ const canonicalPermissionMode = {
1737
+ plan: "plan",
1738
+ "read-only": "plan",
1739
+ "accept-edits": "acceptEdits",
1740
+ bypass: "bypassPermissions"
1741
+ };
1742
+ if (posture && canonicalPermissionMode[posture]) {
1743
+ return canonicalPermissionMode[posture];
1744
+ }
1745
+ if (!configMode) return void 0;
1601
1746
  const mode = (definition.modes ?? []).find((m) => m.id === configMode);
1602
1747
  const args = mode?.bin_args_append ?? [];
1603
1748
  const flagIndex = args.indexOf("--permission-mode");
@@ -1612,6 +1757,6 @@ function filterStringEnv(env) {
1612
1757
  return out;
1613
1758
  }
1614
1759
 
1615
- export { LEGACY_GATEWAY_MODE_IDS, LEGACY_POSTURE_MODE_IDS, RuntimeConfigError, agentCliFrontmatterSchema, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, inferLegacyModeKind, isLegacyExtractedModeId, mapMastraEvent, planModePermissionHandler, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
1616
- //# sourceMappingURL=chunk-LP542BMX.mjs.map
1617
- //# sourceMappingURL=chunk-LP542BMX.mjs.map
1760
+ export { LEGACY_GATEWAY_MODE_IDS, LEGACY_POSTURE_MODE_IDS, RuntimeConfigError, agentCliFrontmatterSchema, applyModelCommand, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createArmSessionControls, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, inferLegacyModeKind, isLegacyExtractedModeId, mapMastraEvent, planModePermissionHandler, promptTurn, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
1761
+ //# sourceMappingURL=chunk-QL3CS6E4.mjs.map
1762
+ //# sourceMappingURL=chunk-QL3CS6E4.mjs.map