@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.
- package/dist/{chunk-VIYTAHJJ.mjs → chunk-KHHRPQ2Q.mjs} +563 -31
- package/dist/chunk-KHHRPQ2Q.mjs.map +1 -0
- package/dist/index.d.ts +211 -4
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/manifest/index.d.ts +2 -2
- package/dist/manifest/index.mjs +2 -2
- package/dist/{schema-Cq4w3654.d.ts → schema-Ds8jc9fL.d.ts} +188 -3
- package/package.json +2 -2
- package/dist/chunk-VIYTAHJJ.mjs.map +0 -1
|
@@ -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 ??
|
|
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({
|
|
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
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
662
|
-
|
|
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-
|
|
676
|
-
//# sourceMappingURL=chunk-
|
|
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
|