@agentproto/driver-agent-cli 0.1.1 → 0.3.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.
@@ -2,7 +2,11 @@ import { z } from 'zod';
2
2
  import { Writable, Readable } from 'stream';
3
3
  import { createAcpClient } from '@agentproto/acp/client';
4
4
  import { spawn } from 'child_process';
5
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, rmdirSync, mkdtempSync } from 'fs';
6
+ import { createInterface } from 'readline';
7
+ import { join } from 'path';
5
8
  import { randomUUID } from 'crypto';
9
+ import { tmpdir } from 'os';
6
10
  import { createDoctype } from '@agentproto/define-doctype';
7
11
 
8
12
  /**
@@ -149,13 +153,22 @@ var authSchema = z.object({
149
153
  var sessionSchema = z.object({
150
154
  mode: z.enum(["ephemeral", "persistent", "resumable"]).default("ephemeral"),
151
155
  idle_timeout_ms: z.number().int().min(1e3).default(6e5),
156
+ // No default: undefined disables the per-turn watchdog entirely. Only
157
+ // adapters known to sometimes drop the final `prompt` response (e.g.
158
+ // hermes) should declare this — applying it broadly would risk
159
+ // false-positiving on another adapter's legitimately long turns.
160
+ turn_idle_timeout_ms: z.number().int().min(1e3).optional(),
152
161
  max_turns: z.number().int().positive().optional(),
153
162
  context_carryover: z.boolean().default(true)
154
163
  }).strict();
155
164
  var modelsSchema = z.object({
156
165
  default: z.string().optional(),
157
166
  allowed: z.array(z.string()).optional(),
158
- env: z.record(z.string(), z.string()).optional()
167
+ env: z.record(z.string(), z.string()).optional(),
168
+ // How a model is selected at session start: "config" (ACP
169
+ // set_config_option, default) | "command" (a `/model <id>` control turn,
170
+ // for agents like hermes that ignore the ACP session model config).
171
+ apply: z.enum(["config", "command"]).optional()
159
172
  }).strict();
160
173
  var capabilitiesSchema = z.object({
161
174
  streaming: z.boolean().optional(),
@@ -220,6 +233,16 @@ var mcpBlockSchema = z.object({
220
233
  transport: z.enum(["stdio", "http", "sse"]),
221
234
  url: z.string().url().optional()
222
235
  }).strict();
236
+ var printConfigSchema = z.object({
237
+ prompt_flag: z.string().optional(),
238
+ output_format: z.array(z.string()).optional(),
239
+ pre_prompt: z.array(z.string()).optional(),
240
+ resume: z.object({
241
+ flag: z.string(),
242
+ kind: z.enum(["value", "boolean"])
243
+ }).strict().optional(),
244
+ event_schema: z.enum(["claude-stream-json", "mastra-jsonl"]).optional()
245
+ }).strict().optional();
223
246
  var agentCliFrontmatterSchema = z.object({
224
247
  name: z.string().min(1).max(80),
225
248
  id: z.string().regex(ID_PATTERN),
@@ -233,10 +256,11 @@ var agentCliFrontmatterSchema = z.object({
233
256
  auth: authSchema.optional(),
234
257
  sandbox: z.union([z.string(), z.record(z.string(), z.unknown())]),
235
258
  runner: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
236
- protocol: z.enum(["acp", "mcp", "proprietary"]),
259
+ protocol: z.enum(["acp", "mcp", "proprietary", "print"]),
237
260
  acp: z.string().optional(),
238
261
  mcp: mcpBlockSchema.optional(),
239
262
  adapter: z.string().regex(ADAPTER_PATTERN).optional(),
263
+ print: printConfigSchema,
240
264
  session: sessionSchema.optional(),
241
265
  models: modelsSchema.optional(),
242
266
  capabilities: capabilitiesSchema.optional(),
@@ -320,6 +344,21 @@ var autoAllowPermissionHandler = ({
320
344
  const allow = options.find((o) => typeof o.kind === "string" && o.kind.startsWith("allow_")) ?? options[0];
321
345
  return { outcome: { outcome: "selected", optionId: allow.optionId } };
322
346
  };
347
+ var PLAN_MODE_EXIT_TOOL_CALL_KIND = "switch_mode";
348
+ var planModePermissionHandler = (params) => {
349
+ const { options, toolCall } = params;
350
+ if (!options || options.length === 0) {
351
+ return { outcome: { outcome: "cancelled" } };
352
+ }
353
+ if (toolCall?.kind === PLAN_MODE_EXIT_TOOL_CALL_KIND) {
354
+ const keepPlanning = options.find((o) => o.kind === "reject_once");
355
+ return keepPlanning ? { outcome: { outcome: "selected", optionId: keepPlanning.optionId } } : { outcome: { outcome: "cancelled" } };
356
+ }
357
+ return autoAllowPermissionHandler(params);
358
+ };
359
+ function defaultPermissionHandlerForMode(requestedMode) {
360
+ return requestedMode === "plan" ? planModePermissionHandler : autoAllowPermissionHandler;
361
+ }
323
362
  function createAcpProtocolArm(options) {
324
363
  const { child, cwd } = options;
325
364
  if (!child.stdin || !child.stdout) {
@@ -337,7 +376,7 @@ function createAcpProtocolArm(options) {
337
376
  return session?.sessionId;
338
377
  },
339
378
  async connect(opts) {
340
- const permissionHandler = options.onPermissionRequest ?? autoAllowPermissionHandler;
379
+ const permissionHandler = options.onPermissionRequest ?? defaultPermissionHandlerForMode(options.requestedMode);
341
380
  client = await createAcpClient({
342
381
  output,
343
382
  input,
@@ -345,6 +384,8 @@ function createAcpProtocolArm(options) {
345
384
  capabilities: {
346
385
  fs: { readTextFile: true, writeTextFile: true }
347
386
  },
387
+ onActivity: opts.onActivity,
388
+ turnIdleTimeoutMs: opts.turnIdleTimeoutMs,
348
389
  // Wire the permission handler so the agent's `session/request_permission`
349
390
  // callbacks get a real answer instead of bubbling up as
350
391
  // "AcpClient.requestPermission: no handler configured" → which
@@ -357,10 +398,16 @@ function createAcpProtocolArm(options) {
357
398
  if (opts.resumeSessionId) {
358
399
  session = await client.loadSession({
359
400
  sessionId: opts.resumeSessionId,
360
- cwd
401
+ cwd,
402
+ mcpServers: opts.mcpServers
361
403
  });
362
404
  } else {
363
- session = await client.newSession({ cwd });
405
+ session = await client.newSession({
406
+ cwd,
407
+ mcpServers: opts.mcpServers,
408
+ ...opts.model ? { model: opts.model } : {},
409
+ ...opts.effort ? { effort: opts.effort } : {}
410
+ });
364
411
  }
365
412
  },
366
413
  async send(turnId, message) {
@@ -385,6 +432,409 @@ function createAcpProtocolArm(options) {
385
432
  };
386
433
  }
387
434
 
435
+ // src/mcp-servers.ts
436
+ function toFileBasedMcpServers(servers) {
437
+ if (!servers || servers.length === 0) return void 0;
438
+ const out = {};
439
+ for (const s of servers) {
440
+ out[s.name] = s.transport === "stdio" ? { command: s.ref ?? "" } : { url: s.ref ?? "" };
441
+ }
442
+ return out;
443
+ }
444
+ var DEFAULT_OUTPUT = ["--output-format", "stream-json"];
445
+ var DEFAULT_PRE_PROMPT = ["--no-interactive"];
446
+ var DEFAULT_RESUME = { flag: "--resume", kind: "value" };
447
+ function createPrintSession(opts) {
448
+ const config = opts.printConfig;
449
+ const outputFormat = config?.output_format ?? DEFAULT_OUTPUT;
450
+ const prePrompt = config?.pre_prompt ?? DEFAULT_PRE_PROMPT;
451
+ const promptFlag = config?.prompt_flag;
452
+ const resumeCfg = config?.resume ?? DEFAULT_RESUME;
453
+ const eventSchema = config?.event_schema ?? "claude-stream-json";
454
+ let sessionId = opts.resumeSessionId ?? "";
455
+ let activeChild = null;
456
+ const mcpRestore = setupMcpConfigFile(opts, eventSchema);
457
+ return {
458
+ get sessionId() {
459
+ return sessionId;
460
+ },
461
+ async *send(message) {
462
+ const prompt = extractPromptText(message);
463
+ const args = [
464
+ ...opts.baseArgs,
465
+ ...outputFormat,
466
+ ...prePrompt
467
+ ];
468
+ if (sessionId) {
469
+ args.push(resumeCfg.flag);
470
+ if (resumeCfg.kind === "value") args.push(sessionId);
471
+ }
472
+ if (promptFlag) {
473
+ args.push(promptFlag, prompt);
474
+ } else {
475
+ args.push(prompt);
476
+ }
477
+ const child = spawn(opts.bin, args, {
478
+ cwd: opts.cwd,
479
+ env: opts.env,
480
+ stdio: ["ignore", "pipe", "pipe"]
481
+ });
482
+ activeChild = child;
483
+ const stderrLines = [];
484
+ const STDERR_KEEP = 80;
485
+ child.stderr?.setEncoding("utf8");
486
+ child.stderr?.on("data", (chunk) => {
487
+ for (const line of chunk.split(/\r?\n/)) {
488
+ if (!line) continue;
489
+ stderrLines.push(line);
490
+ if (stderrLines.length > STDERR_KEEP) stderrLines.shift();
491
+ }
492
+ });
493
+ try {
494
+ if (!child.stdout)
495
+ throw new Error("print-arm: child has no stdout pipe");
496
+ const rl = createInterface({ input: child.stdout, crlfDelay: Infinity });
497
+ let capturedSessionId = "";
498
+ const mastraState = eventSchema === "mastra-jsonl" ? createMastraMapperState() : void 0;
499
+ for await (const line of rl) {
500
+ if (!line.trim()) continue;
501
+ let evt;
502
+ try {
503
+ evt = JSON.parse(line);
504
+ } catch {
505
+ continue;
506
+ }
507
+ const csid = captureSessionId(evt, eventSchema);
508
+ if (csid) capturedSessionId = csid;
509
+ const sid = capturedSessionId || sessionId || "";
510
+ const mapped = mapEvent(evt, sid, stderrLines, eventSchema, mastraState);
511
+ if (!mapped) continue;
512
+ yield mapped;
513
+ }
514
+ const exitCode = await waitForExit(child);
515
+ if (capturedSessionId) sessionId = capturedSessionId;
516
+ if (exitCode !== 0 && exitCode !== null) {
517
+ const binLabel = eventSchema === "mastra-jsonl" ? "mastracode" : "claude";
518
+ const errEvt = {
519
+ kind: "error",
520
+ error: {
521
+ message: `${binLabel} exited with code ${exitCode}`,
522
+ ...stderrLines.length ? { data: { stderr: stderrLines.join("\n") } } : {}
523
+ }
524
+ };
525
+ yield errEvt;
526
+ }
527
+ } finally {
528
+ activeChild = null;
529
+ }
530
+ },
531
+ async cancel() {
532
+ activeChild?.kill("SIGTERM");
533
+ },
534
+ async close() {
535
+ activeChild?.kill("SIGTERM");
536
+ mcpRestore?.();
537
+ }
538
+ };
539
+ }
540
+ function setupMcpConfigFile(opts, eventSchema) {
541
+ if (eventSchema !== "mastra-jsonl") return void 0;
542
+ if (!opts.mcpServers || opts.mcpServers.length === 0) return void 0;
543
+ const dir = join(opts.cwd, ".mastracode");
544
+ const file = join(dir, "mcp.json");
545
+ const injected = toFileBasedMcpServers(opts.mcpServers);
546
+ if (!injected) return void 0;
547
+ const fileExisted = existsSync(file);
548
+ let originalContent;
549
+ if (fileExisted) {
550
+ try {
551
+ originalContent = readFileSync(file, "utf8");
552
+ } catch {
553
+ originalContent = void 0;
554
+ }
555
+ }
556
+ const dirExisted = existsSync(dir);
557
+ if (!dirExisted) {
558
+ mkdirSync(dir, { recursive: true });
559
+ }
560
+ let existingServers = {};
561
+ if (originalContent !== void 0) {
562
+ try {
563
+ const parsed = JSON.parse(originalContent);
564
+ if (parsed && typeof parsed.mcpServers === "object" && parsed.mcpServers !== null) {
565
+ existingServers = parsed.mcpServers;
566
+ }
567
+ } catch {
568
+ existingServers = {};
569
+ }
570
+ }
571
+ const merged = { ...existingServers, ...injected };
572
+ const output = JSON.stringify({ mcpServers: merged }, null, 2);
573
+ writeFileSync(file, output, "utf8");
574
+ return () => {
575
+ try {
576
+ if (!fileExisted) {
577
+ rmSync(file, { force: true });
578
+ if (!dirExisted) {
579
+ try {
580
+ rmdirSync(dir);
581
+ } catch {
582
+ }
583
+ }
584
+ } else if (originalContent !== void 0) {
585
+ let current = {};
586
+ try {
587
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
588
+ if (parsed && typeof parsed.mcpServers === "object" && parsed.mcpServers !== null) {
589
+ current = parsed.mcpServers;
590
+ }
591
+ } catch {
592
+ writeFileSync(file, originalContent, "utf8");
593
+ return;
594
+ }
595
+ for (const key of Object.keys(injected)) {
596
+ delete current[key];
597
+ }
598
+ if (Object.keys(current).length === 0) {
599
+ writeFileSync(file, originalContent, "utf8");
600
+ } else {
601
+ writeFileSync(file, JSON.stringify({ mcpServers: current }, null, 2), "utf8");
602
+ }
603
+ }
604
+ } catch {
605
+ }
606
+ };
607
+ }
608
+ function extractPromptText(message) {
609
+ if (typeof message === "string") return message;
610
+ if (message !== null && typeof message === "object") {
611
+ const m = message;
612
+ if (typeof m.text === "string") return m.text;
613
+ if (Array.isArray(message)) {
614
+ return message.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
615
+ }
616
+ }
617
+ return JSON.stringify(message);
618
+ }
619
+ function captureSessionId(evt, schema) {
620
+ switch (schema) {
621
+ case "claude-stream-json":
622
+ if (evt.type === "result" && typeof evt.session_id === "string" && evt.session_id)
623
+ return evt.session_id;
624
+ return null;
625
+ case "mastra-jsonl":
626
+ if (evt.type === "result" && typeof evt.threadId === "string" && evt.threadId)
627
+ return evt.threadId;
628
+ return null;
629
+ }
630
+ }
631
+ function mapEvent(evt, sessionId, stderrLines, schema, mastraState) {
632
+ switch (schema) {
633
+ case "claude-stream-json":
634
+ return mapClaudeEvent(evt, sessionId, stderrLines);
635
+ case "mastra-jsonl": {
636
+ if (!mastraState) {
637
+ return mapClaudeEvent(evt, sessionId, stderrLines);
638
+ }
639
+ return mapMastraEvent(evt, sessionId, stderrLines, mastraState);
640
+ }
641
+ default:
642
+ return mapClaudeEvent(evt, sessionId, stderrLines);
643
+ }
644
+ }
645
+ function mapClaudeEvent(evt, sessionId, stderrLines) {
646
+ switch (evt.type) {
647
+ case "text":
648
+ return typeof evt.text === "string" ? { kind: "text-delta", sessionId, text: evt.text } : null;
649
+ case "thinking":
650
+ return typeof evt.thinking === "string" ? { kind: "thought", sessionId, text: evt.thinking } : null;
651
+ case "tool_use":
652
+ return {
653
+ kind: "tool-call",
654
+ sessionId,
655
+ toolCallId: typeof evt.id === "string" ? evt.id : "",
656
+ toolName: typeof evt.name === "string" ? evt.name : "?",
657
+ arguments: evt.input ?? {}
658
+ };
659
+ case "tool_result":
660
+ return {
661
+ kind: "tool-result",
662
+ sessionId,
663
+ toolCallId: typeof evt.tool_use_id === "string" ? evt.tool_use_id : "",
664
+ result: evt.content ?? null,
665
+ isError: evt.is_error === true
666
+ };
667
+ case "result":
668
+ if (evt.subtype === "error_during_execution" || evt.is_error === true) {
669
+ return {
670
+ kind: "error",
671
+ sessionId,
672
+ error: {
673
+ message: typeof evt.error === "string" ? evt.error : "Unknown error",
674
+ ...stderrLines.length ? { data: { stderr: stderrLines.join("\n") } } : {}
675
+ }
676
+ };
677
+ }
678
+ if (evt.subtype === "success") {
679
+ return { kind: "turn-end", sessionId, reason: "completed" };
680
+ }
681
+ return null;
682
+ case "system":
683
+ case "assistant":
684
+ return null;
685
+ default:
686
+ return null;
687
+ }
688
+ }
689
+ function createMastraMapperState() {
690
+ return { lastTextLength: 0 };
691
+ }
692
+ function extractTextFromBlocks(content) {
693
+ if (!Array.isArray(content)) return "";
694
+ return content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("");
695
+ }
696
+ function mapMastraEvent(evt, sessionId, stderrLines, state) {
697
+ switch (evt.type) {
698
+ // ── Text streaming ──────────────────────────────────────────
699
+ case "message_update": {
700
+ const content = evt.message?.content;
701
+ const fullText = extractTextFromBlocks(content);
702
+ if (fullText.length > state.lastTextLength) {
703
+ const delta = fullText.slice(state.lastTextLength);
704
+ state.lastTextLength = fullText.length;
705
+ return delta ? { kind: "text-delta", sessionId, text: delta } : null;
706
+ }
707
+ return null;
708
+ }
709
+ case "message_end": {
710
+ const msg = evt.message;
711
+ if (msg?.role !== "assistant") return null;
712
+ const fullText = extractTextFromBlocks(msg?.content);
713
+ if (fullText.length > state.lastTextLength) {
714
+ const delta = fullText.slice(state.lastTextLength);
715
+ state.lastTextLength = fullText.length;
716
+ return delta ? { kind: "text-delta", sessionId, text: delta } : null;
717
+ }
718
+ return null;
719
+ }
720
+ // ── Tool calls ──────────────────────────────────────────────
721
+ case "tool_start":
722
+ return {
723
+ kind: "tool-call",
724
+ sessionId,
725
+ toolCallId: typeof evt.toolCallId === "string" ? evt.toolCallId : "",
726
+ toolName: typeof evt.toolName === "string" ? evt.toolName : "?",
727
+ // Mastra's controller event carries the tool call payload under
728
+ // `args`, not `input` (that's the Claude Code stream-json field
729
+ // name used by the sibling mapClaudeEvent tool_use case below).
730
+ arguments: evt.args ?? {}
731
+ };
732
+ case "tool_end":
733
+ return {
734
+ kind: "tool-result",
735
+ sessionId,
736
+ toolCallId: typeof evt.toolCallId === "string" ? evt.toolCallId : "",
737
+ result: evt.result ?? null,
738
+ isError: evt.isError === true
739
+ };
740
+ // ── Turn end ────────────────────────────────────────────────
741
+ case "agent_end": {
742
+ const rawReason = typeof evt.reason === "string" ? evt.reason : "complete";
743
+ const reason = mapMastraFinishReason(rawReason);
744
+ return { kind: "turn-end", sessionId, reason };
745
+ }
746
+ // ── Final result line ────────────────────────────────────────
747
+ // Always written last, once, whether or not `agent_end` fired —
748
+ // pre-flight failures (bad model, unresolvable thread, missing API
749
+ // key) call straight into this without ever streaming an
750
+ // `agent_end` or `error` event. `turn-end` already came from
751
+ // `agent_end` on the success path, so this only needs to surface
752
+ // an error for the pre-flight-failure path.
753
+ case "result": {
754
+ const err = evt.error;
755
+ if (!err) return null;
756
+ return {
757
+ kind: "error",
758
+ sessionId,
759
+ error: {
760
+ message: typeof err.message === "string" ? err.message : "Unknown error",
761
+ ...stderrLines.length ? { data: { stderr: stderrLines.join("\n") } } : {}
762
+ }
763
+ };
764
+ }
765
+ // ── Errors ─────────────────────────────────────────────────
766
+ case "error": {
767
+ const err = evt.error;
768
+ return {
769
+ kind: "error",
770
+ sessionId,
771
+ error: {
772
+ message: typeof err?.message === "string" ? err.message : "Unknown error",
773
+ ...stderrLines.length ? { data: { stderr: stderrLines.join("\n") } } : {}
774
+ }
775
+ };
776
+ }
777
+ // ── Shell output (tool-like) ────────────────────────────────
778
+ case "shell_output":
779
+ return typeof evt.output === "string" ? {
780
+ kind: "tool-result",
781
+ sessionId,
782
+ toolCallId: "",
783
+ result: evt.output,
784
+ isError: false
785
+ } : null;
786
+ // ── Skipped events ─────────────────────────────────────────
787
+ case "agent_start":
788
+ case "subagent_start":
789
+ case "subagent_end":
790
+ case "tool_approval_required":
791
+ case "tool_suspended":
792
+ return null;
793
+ default:
794
+ return null;
795
+ }
796
+ }
797
+ function mapMastraFinishReason(raw) {
798
+ switch (raw) {
799
+ case "complete":
800
+ return "completed";
801
+ case "aborted":
802
+ case "suspended":
803
+ return "cancelled";
804
+ case "error":
805
+ return "error";
806
+ default:
807
+ return "completed";
808
+ }
809
+ }
810
+ function waitForExit(child) {
811
+ return new Promise((resolve) => {
812
+ child.once("exit", (code) => resolve(code));
813
+ child.once("error", () => resolve(null));
814
+ });
815
+ }
816
+
817
+ // src/protocol/proprietary.ts
818
+ async function createProprietaryProtocolArm(options) {
819
+ let mod;
820
+ try {
821
+ mod = await import(options.adapter);
822
+ } catch (err) {
823
+ const cause = err instanceof Error ? err.message : String(err);
824
+ throw new Error(
825
+ `createProprietaryProtocolArm: could not load adapter package '${options.adapter}'. Install it with: npm i ${options.adapter}
826
+ cause: ${cause}`
827
+ );
828
+ }
829
+ const candidate = "createAgentCliClient" in mod ? mod.createAgentCliClient : mod.default;
830
+ if (typeof candidate !== "function") {
831
+ throw new Error(
832
+ `createProprietaryProtocolArm: adapter package '${options.adapter}' does not export a 'createAgentCliClient' factory function (or a default export function). A protocol="proprietary" adapter must export createAgentCliClient(definition: AgentCliHandle): AgentCliClient | Promise<AgentCliClient>.`
833
+ );
834
+ }
835
+ return await candidate(options.definition);
836
+ }
837
+
388
838
  // src/manifest/compose.ts
389
839
  var RuntimeConfigError = class extends Error {
390
840
  code;
@@ -585,23 +1035,51 @@ function createAgentCliRuntime(definition) {
585
1035
  ...composed.env,
586
1036
  ...opts?.env ?? {}
587
1037
  };
588
- const child = spawn(definition.bin, composed.binArgs, {
589
- cwd,
590
- env,
591
- stdio: ["pipe", "pipe", "pipe"],
592
- signal: opts?.signal
593
- });
1038
+ const permissionMode = resolveClaudeCodePermissionMode(
1039
+ definition,
1040
+ opts?.config?.mode
1041
+ );
1042
+ if (permissionMode) {
1043
+ const configDir = mkdtempSync(
1044
+ join(tmpdir(), "agentproto-claude-config-")
1045
+ );
1046
+ writeFileSync(
1047
+ join(configDir, "settings.json"),
1048
+ JSON.stringify({ permissions: { defaultMode: permissionMode } })
1049
+ );
1050
+ env.CLAUDE_CONFIG_DIR = configDir;
1051
+ }
1052
+ if (definition.protocol === "print") {
1053
+ return createPrintSession({
1054
+ bin: definition.bin,
1055
+ baseArgs: composed.binArgs,
1056
+ cwd,
1057
+ env,
1058
+ ...opts?.resumeSessionId ? { resumeSessionId: opts.resumeSessionId } : {},
1059
+ ...opts?.mcpServers ? { mcpServers: opts.mcpServers } : {},
1060
+ printConfig: definition.print
1061
+ });
1062
+ }
1063
+ let child;
594
1064
  const stderrBuf = [];
595
- const STDERR_KEEP_LINES = 80;
596
- child.stderr?.setEncoding("utf8");
597
- child.stderr?.on("data", (chunk) => {
598
- for (const line of chunk.split(/\r?\n/)) {
599
- if (!line) continue;
600
- stderrBuf.push(line);
601
- if (stderrBuf.length > STDERR_KEEP_LINES) stderrBuf.shift();
602
- }
603
- });
604
- const arm = buildProtocolArm(definition, child, cwd);
1065
+ if (definition.protocol !== "proprietary") {
1066
+ child = spawn(definition.bin, composed.binArgs, {
1067
+ cwd,
1068
+ env,
1069
+ stdio: ["pipe", "pipe", "pipe"],
1070
+ signal: opts?.signal
1071
+ });
1072
+ const STDERR_KEEP_LINES = 80;
1073
+ child.stderr?.setEncoding("utf8");
1074
+ child.stderr?.on("data", (chunk) => {
1075
+ for (const line of chunk.split(/\r?\n/)) {
1076
+ if (!line) continue;
1077
+ stderrBuf.push(line);
1078
+ if (stderrBuf.length > STDERR_KEEP_LINES) stderrBuf.shift();
1079
+ }
1080
+ });
1081
+ }
1082
+ const arm = await buildProtocolArm(definition, child, cwd, opts?.config?.mode);
605
1083
  arm._stderrTail = () => stderrBuf.join("\n");
606
1084
  const abortController = new AbortController();
607
1085
  if (opts?.signal) {
@@ -609,15 +1087,29 @@ function createAgentCliRuntime(definition) {
609
1087
  once: true
610
1088
  });
611
1089
  }
1090
+ const optModel = opts?.config?.options?.model;
1091
+ const optEffort = opts?.config?.options?.effort;
1092
+ const modelApply = definition.models?.apply ?? "config";
1093
+ const configModel = optModel && modelApply === "config" ? String(optModel) : void 0;
1094
+ const turnIdleTimeoutMs = opts?.turnIdleTimeoutMs ?? definition.session?.turn_idle_timeout_ms;
612
1095
  await arm.connect({
613
1096
  cwd,
614
1097
  env,
615
1098
  abortSignal: abortController.signal,
616
- ...opts?.resumeSessionId ? { resumeSessionId: opts.resumeSessionId } : {}
1099
+ ...opts?.resumeSessionId ? { resumeSessionId: opts.resumeSessionId } : {},
1100
+ ...opts?.mcpServers ? { mcpServers: opts.mcpServers } : {},
1101
+ ...configModel ? { model: configModel } : {},
1102
+ ...optEffort ? { effort: String(optEffort) } : {},
1103
+ ...opts?.onActivity ? { onActivity: opts.onActivity } : {},
1104
+ ...turnIdleTimeoutMs !== void 0 ? { turnIdleTimeoutMs } : {}
617
1105
  });
1106
+ if (optModel && modelApply === "command") {
1107
+ await applyModelCommand(arm, String(optModel));
1108
+ }
618
1109
  const sessionId = arm.sessionId ?? randomUUID();
619
1110
  return {
620
1111
  sessionId,
1112
+ pid: child?.pid,
621
1113
  send(message) {
622
1114
  const turnId = randomUUID();
623
1115
  return promptTurn(arm, turnId, message);
@@ -627,12 +1119,35 @@ function createAgentCliRuntime(definition) {
627
1119
  },
628
1120
  async close() {
629
1121
  await arm.close();
630
- if (!child.killed) child.kill("SIGTERM");
1122
+ if (child && !child.killed) child.kill("SIGTERM");
631
1123
  }
632
1124
  };
633
1125
  }
634
1126
  };
635
1127
  }
1128
+ async function applyModelCommand(arm, modelId) {
1129
+ const turnId = randomUUID();
1130
+ let acked = false;
1131
+ try {
1132
+ for await (const evt of promptTurn(arm, turnId, {
1133
+ type: "text",
1134
+ text: `/model ${modelId}`
1135
+ })) {
1136
+ if (/switch|model\s+set|now using/i.test(JSON.stringify(evt))) acked = true;
1137
+ }
1138
+ } catch (err) {
1139
+ console.warn(
1140
+ `[agent-cli] /model ${modelId} control turn failed (continuing on default):`,
1141
+ err instanceof Error ? err.message : err
1142
+ );
1143
+ return;
1144
+ }
1145
+ if (!acked) {
1146
+ console.warn(
1147
+ `[agent-cli] /model ${modelId}: no switch acknowledgement \u2014 agent may be on its default model`
1148
+ );
1149
+ }
1150
+ }
636
1151
  async function* promptTurn(arm, turnId, message) {
637
1152
  await arm.send(turnId, message);
638
1153
  const stderrTail = arm._stderrTail;
@@ -647,22 +1162,39 @@ async function* promptTurn(arm, turnId, message) {
647
1162
  yield evt;
648
1163
  }
649
1164
  }
650
- function buildProtocolArm(def, child, cwd) {
1165
+ async function buildProtocolArm(def, child, cwd, requestedMode) {
651
1166
  switch (def.protocol) {
652
1167
  case "acp":
1168
+ if (!child) {
1169
+ throw new Error("createAgentCliRuntime: acp protocol requires a spawned subprocess");
1170
+ }
653
1171
  return createAcpProtocolArm({
654
1172
  child,
655
1173
  cwd,
656
- clientInfo: { name: def.id, version: def.version }
1174
+ clientInfo: { name: def.id, version: def.version },
1175
+ requestedMode
657
1176
  });
658
1177
  case "mcp":
659
1178
  throw new Error("createAgentCliRuntime: mcp protocol arm not yet implemented");
660
1179
  case "proprietary":
661
- throw new Error(
662
- "createAgentCliRuntime: proprietary protocol arm not yet implemented"
663
- );
1180
+ if (!def.adapter) {
1181
+ throw new Error(
1182
+ "createAgentCliRuntime: proprietary protocol requires manifest.adapter"
1183
+ );
1184
+ }
1185
+ return createProprietaryProtocolArm({ adapter: def.adapter, definition: def });
1186
+ case "print":
1187
+ throw new Error("createAgentCliRuntime: print arm bypasses buildProtocolArm");
664
1188
  }
665
1189
  }
1190
+ function resolveClaudeCodePermissionMode(definition, configMode) {
1191
+ if (definition.id !== "claude-code" || !configMode) return void 0;
1192
+ const mode = (definition.modes ?? []).find((m) => m.id === configMode);
1193
+ const args = mode?.bin_args_append ?? [];
1194
+ const flagIndex = args.indexOf("--permission-mode");
1195
+ if (flagIndex === -1 || flagIndex + 1 >= args.length) return void 0;
1196
+ return args[flagIndex + 1];
1197
+ }
666
1198
  function filterStringEnv(env) {
667
1199
  const out = {};
668
1200
  for (const [k, v] of Object.entries(env)) {
@@ -671,6 +1203,6 @@ function filterStringEnv(env) {
671
1203
  return out;
672
1204
  }
673
1205
 
674
- export { RuntimeConfigError, agentCliFrontmatterSchema, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, defineAgentCli, resolveContinuationStrategy, runtimeConfigSchema };
675
- //# sourceMappingURL=chunk-VIYTAHJJ.mjs.map
676
- //# sourceMappingURL=chunk-VIYTAHJJ.mjs.map
1206
+ export { RuntimeConfigError, agentCliFrontmatterSchema, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, mapMastraEvent, planModePermissionHandler, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
1207
+ //# sourceMappingURL=chunk-KHHRPQ2Q.mjs.map
1208
+ //# sourceMappingURL=chunk-KHHRPQ2Q.mjs.map