@agentproto/adapter-mastra-agent 0.5.5 → 0.7.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-6ILIPWNM.mjs → chunk-2FKFITFT.mjs} +183 -24
- package/dist/chunk-2FKFITFT.mjs.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.ts +216 -178
- package/dist/index.mjs +1 -1
- package/package.json +8 -7
- package/dist/chunk-6ILIPWNM.mjs.map +0 -1
|
@@ -17,6 +17,8 @@ import { ProviderHistoryCompat } from '@mastra/core/processors';
|
|
|
17
17
|
import { AgentController } from '@mastra/core/agent-controller';
|
|
18
18
|
import { Workspace } from '@mastra/core/workspace';
|
|
19
19
|
import { createNotificationInboxTool } from '@mastra/core/notifications';
|
|
20
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
21
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
20
22
|
import { SignalProvider } from '@mastra/core/signals';
|
|
21
23
|
import { PROTOCOL_VERSION, ndJsonStream, AgentSideConnection } from '@agentclientprotocol/sdk';
|
|
22
24
|
import { Writable, Readable } from 'stream';
|
|
@@ -334,6 +336,19 @@ function makeWorkspaceTools(opts) {
|
|
|
334
336
|
const cwd = resolve(opts.cwd);
|
|
335
337
|
const allowExec = opts.allowExec ?? true;
|
|
336
338
|
const execTimeoutMs = opts.execTimeoutMs ?? 12e4;
|
|
339
|
+
const additionalReadPaths = new Set(
|
|
340
|
+
(opts.additionalReadPaths ?? []).filter(isAbsolute).map((p) => resolve(p))
|
|
341
|
+
);
|
|
342
|
+
const resolveForWrite = (p) => resolveInCwd(cwd, p);
|
|
343
|
+
const resolveForRead = (p) => {
|
|
344
|
+
try {
|
|
345
|
+
return resolveInCwd(cwd, p);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const target = isAbsolute(p) ? resolve(p) : resolve(cwd, p);
|
|
348
|
+
if (additionalReadPaths.has(target)) return target;
|
|
349
|
+
throw err;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
337
352
|
const execEnv = { ...process.env, GIT_CEILING_DIRECTORIES: resolve(cwd, "..") };
|
|
338
353
|
const guard = (id, execute) => withTimeoutGuard(id, execTimeoutMs, execute);
|
|
339
354
|
const doListDir = async (input) => {
|
|
@@ -344,11 +359,11 @@ function makeWorkspaceTools(opts) {
|
|
|
344
359
|
};
|
|
345
360
|
};
|
|
346
361
|
const doReadFile = async (input) => {
|
|
347
|
-
const file =
|
|
362
|
+
const file = resolveForRead(input.path);
|
|
348
363
|
return { content: await promises.readFile(file, "utf8") };
|
|
349
364
|
};
|
|
350
365
|
const doWriteFile = async (input) => {
|
|
351
|
-
const file =
|
|
366
|
+
const file = resolveForWrite(input.path);
|
|
352
367
|
await promises.mkdir(resolve(file, ".."), { recursive: true });
|
|
353
368
|
await promises.writeFile(file, input.content, "utf8");
|
|
354
369
|
return { path: input.path, bytes: Buffer.byteLength(input.content, "utf8") };
|
|
@@ -419,7 +434,7 @@ function makeWorkspaceTools(opts) {
|
|
|
419
434
|
}),
|
|
420
435
|
outputSchema: z.object({ path: z.string(), replaced: z.boolean() }),
|
|
421
436
|
execute: guard("edit_file", async (input) => {
|
|
422
|
-
const file =
|
|
437
|
+
const file = resolveForWrite(input.path);
|
|
423
438
|
const current = await promises.readFile(file, "utf8");
|
|
424
439
|
const count = current.split(input.old_string).length - 1;
|
|
425
440
|
if (count === 0) throw new Error(`old_string not found in '${input.path}'.`);
|
|
@@ -445,7 +460,7 @@ function makeWorkspaceTools(opts) {
|
|
|
445
460
|
created: z.string()
|
|
446
461
|
}),
|
|
447
462
|
execute: guard("file_info", async (input) => {
|
|
448
|
-
const abs =
|
|
463
|
+
const abs = resolveForRead(input.path);
|
|
449
464
|
const info = await promises.stat(abs);
|
|
450
465
|
return {
|
|
451
466
|
name: abs.split(sep).pop() ?? input.path,
|
|
@@ -552,7 +567,7 @@ function makeWorkspaceTools(opts) {
|
|
|
552
567
|
outputSchema: z.object({ diff: z.string() }),
|
|
553
568
|
execute: guard("read_diff", async (input) => {
|
|
554
569
|
const relPaths = (input.paths ?? []).map((p) => {
|
|
555
|
-
const abs =
|
|
570
|
+
const abs = resolveForRead(p);
|
|
556
571
|
return relative(cwd, abs) || ".";
|
|
557
572
|
});
|
|
558
573
|
const args = [
|
|
@@ -583,7 +598,7 @@ function makeWorkspaceTools(opts) {
|
|
|
583
598
|
outputSchema: z.object({ applied: z.boolean(), output: z.string() }),
|
|
584
599
|
execute: guard("apply_patch", async (input) => {
|
|
585
600
|
for (const p of extractPatchPaths(input.patch)) {
|
|
586
|
-
|
|
601
|
+
resolveForWrite(p);
|
|
587
602
|
}
|
|
588
603
|
const patchFile = join(tmpdir(), `mastra-agent-patch-${randomUUID()}.diff`);
|
|
589
604
|
await promises.writeFile(patchFile, input.patch, "utf8");
|
|
@@ -787,6 +802,61 @@ function resolveMastraModel(ref, env = process.env) {
|
|
|
787
802
|
}
|
|
788
803
|
return modelId;
|
|
789
804
|
}
|
|
805
|
+
async function connectDaemonMcpClient(opts = {}) {
|
|
806
|
+
const endpoint = await discoverDaemonEndpoint(opts);
|
|
807
|
+
if (!endpoint) throw new DaemonNotFoundError();
|
|
808
|
+
const env = opts.env ?? process.env;
|
|
809
|
+
const url = new URL(`${endpoint.url}/mcp`);
|
|
810
|
+
if (env.AGENTPROTO_SESSION_ID) url.searchParams.set("callerSessionId", env.AGENTPROTO_SESSION_ID);
|
|
811
|
+
const client = new Client({ name: "agentproto-mastra-agent", version: "0.1.0" }, { capabilities: {} });
|
|
812
|
+
const transport = new StreamableHTTPClientTransport(url, {
|
|
813
|
+
...endpoint.token ? { requestInit: { headers: { authorization: `Bearer ${endpoint.token}` } } } : {}
|
|
814
|
+
});
|
|
815
|
+
await client.connect(transport);
|
|
816
|
+
return client;
|
|
817
|
+
}
|
|
818
|
+
async function listDaemonMcpTools(client) {
|
|
819
|
+
const { tools } = await client.listTools();
|
|
820
|
+
return tools.map((t) => ({
|
|
821
|
+
name: t.name,
|
|
822
|
+
...t.description !== void 0 ? { description: t.description } : {},
|
|
823
|
+
inputSchema: t.inputSchema ?? { type: "object" }
|
|
824
|
+
}));
|
|
825
|
+
}
|
|
826
|
+
function injectAppId(toolName, args, appId) {
|
|
827
|
+
if (!appId) return args;
|
|
828
|
+
if (!toolName.startsWith("app_")) return args;
|
|
829
|
+
if (args.appId !== void 0) return args;
|
|
830
|
+
return { ...args, appId };
|
|
831
|
+
}
|
|
832
|
+
function relaxAppIdRequirement(inputSchema, toolName, appId) {
|
|
833
|
+
if (!appId || !toolName.startsWith("app_")) return inputSchema;
|
|
834
|
+
if (!Array.isArray(inputSchema.required) || !inputSchema.required.includes("appId")) return inputSchema;
|
|
835
|
+
return { ...inputSchema, required: inputSchema.required.filter((id) => id !== "appId") };
|
|
836
|
+
}
|
|
837
|
+
function extractText(content) {
|
|
838
|
+
if (!Array.isArray(content)) return "";
|
|
839
|
+
return content.filter(
|
|
840
|
+
(block) => !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string"
|
|
841
|
+
).map((block) => block.text).join("\n");
|
|
842
|
+
}
|
|
843
|
+
function makeDaemonMcpProxyTool(def, opts) {
|
|
844
|
+
const config = {
|
|
845
|
+
id: def.name,
|
|
846
|
+
description: def.description ?? `Daemon tool "${def.name}", proxied via the daemon's MCP gateway.`,
|
|
847
|
+
inputSchema: relaxAppIdRequirement(def.inputSchema, def.name, opts.appId),
|
|
848
|
+
execute: async (input) => {
|
|
849
|
+
const client = await opts.getClient();
|
|
850
|
+
const args = injectAppId(def.name, input ?? {}, opts.appId);
|
|
851
|
+
const result = await client.callTool({ name: def.name, arguments: args });
|
|
852
|
+
if (result.isError) {
|
|
853
|
+
throw new Error(`daemon tool "${def.name}" failed: ${extractText(result.content) || "(no error detail)"}`);
|
|
854
|
+
}
|
|
855
|
+
return result.structuredContent ?? extractText(result.content);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
return createTool(config);
|
|
859
|
+
}
|
|
790
860
|
var WATCHED_EVENT_KINDS = [
|
|
791
861
|
"turn-end",
|
|
792
862
|
"error",
|
|
@@ -1352,6 +1422,18 @@ var DEFAULT_TOOL_IDS = [
|
|
|
1352
1422
|
function envFlag(value) {
|
|
1353
1423
|
return Boolean(value);
|
|
1354
1424
|
}
|
|
1425
|
+
function parseAdditionalReadPathsEnv(env = process.env) {
|
|
1426
|
+
const raw = env.AGENTPROTO_ADDITIONAL_READ_PATHS;
|
|
1427
|
+
if (!raw) return void 0;
|
|
1428
|
+
try {
|
|
1429
|
+
const parsed = JSON.parse(raw);
|
|
1430
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
1431
|
+
const paths = parsed.filter((p) => typeof p === "string" && p.length > 0);
|
|
1432
|
+
return paths.length > 0 ? paths : void 0;
|
|
1433
|
+
} catch {
|
|
1434
|
+
return void 0;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1355
1437
|
function defaultAgentManifest(model) {
|
|
1356
1438
|
return [
|
|
1357
1439
|
"---",
|
|
@@ -1429,8 +1511,31 @@ function makeAgentFactory(opts = {}) {
|
|
|
1429
1511
|
const workspaceTools = makeWorkspaceTools({
|
|
1430
1512
|
cwd,
|
|
1431
1513
|
allowExec: opts.allowExec,
|
|
1514
|
+
// The daemon's exact-file AGENTS.md read grant (pointer-mode contract)
|
|
1515
|
+
// — explicit option wins, else the driver-set env var.
|
|
1516
|
+
additionalReadPaths: opts.additionalReadPaths ?? parseAdditionalReadPathsEnv(),
|
|
1432
1517
|
extraTools: opts.extraTools
|
|
1433
1518
|
});
|
|
1519
|
+
const modesEnabled = opts.modes !== false && !envFlag(process.env.AGENTPROTO_MASTRA_NO_MODES);
|
|
1520
|
+
const daemonClient = new DaemonClient({ cwd });
|
|
1521
|
+
const daemonSubAgentTools = modesEnabled ? makeDaemonTools({ client: daemonClient }) : {};
|
|
1522
|
+
let daemonMcpClient;
|
|
1523
|
+
let daemonMcpToolDefsPromise;
|
|
1524
|
+
const resolvedDaemonMcpTools = {};
|
|
1525
|
+
const appId = opts.appId ?? process.env.AGENTPROTO_APP_ID;
|
|
1526
|
+
async function getDaemonMcpToolDefs() {
|
|
1527
|
+
if (!daemonMcpToolDefsPromise) {
|
|
1528
|
+
daemonMcpToolDefsPromise = (async () => {
|
|
1529
|
+
try {
|
|
1530
|
+
daemonMcpClient = opts.daemonMcp?.client ?? await connectDaemonMcpClient({ cwd, ...opts.daemonMcp?.discoverOptions });
|
|
1531
|
+
return await listDaemonMcpTools(daemonMcpClient);
|
|
1532
|
+
} catch {
|
|
1533
|
+
return [];
|
|
1534
|
+
}
|
|
1535
|
+
})();
|
|
1536
|
+
}
|
|
1537
|
+
return daemonMcpToolDefsPromise;
|
|
1538
|
+
}
|
|
1434
1539
|
const store = buildSqliteStore();
|
|
1435
1540
|
let memory;
|
|
1436
1541
|
const historyCompat = new ProviderHistoryCompat({
|
|
@@ -1439,21 +1544,38 @@ function makeAgentFactory(opts = {}) {
|
|
|
1439
1544
|
const { agent } = await buildMastraAgent(handle, {
|
|
1440
1545
|
inputProcessors: [historyCompat],
|
|
1441
1546
|
resolveModel: (ref) => resolveMastraModel(ref),
|
|
1442
|
-
// Match each declared tool ref
|
|
1443
|
-
//
|
|
1444
|
-
//
|
|
1445
|
-
//
|
|
1446
|
-
//
|
|
1447
|
-
//
|
|
1448
|
-
//
|
|
1449
|
-
//
|
|
1450
|
-
|
|
1547
|
+
// Match each declared tool ref, in order: the workspace toolset, the
|
|
1548
|
+
// curated daemon sub-agent tools, then (modes-on only) the generic
|
|
1549
|
+
// daemon MCP proxy — ONLY for a ref an AGENT.md actually declares,
|
|
1550
|
+
// which is what keeps the proxy an allowlist rather than a blanket
|
|
1551
|
+
// grant of the daemon's whole toolset. A ref matching none of those
|
|
1552
|
+
// still resolves — to a stub that fails fast and clearly on call —
|
|
1553
|
+
// rather than being dropped: a dropped ref leaves the model unable to
|
|
1554
|
+
// see it at all, and (if the model still tries the name from AGENT.md
|
|
1555
|
+
// prose) surfaces as an opaque provider NoSuchToolError this adapter's
|
|
1556
|
+
// ACP layer silently swallows (see tool-call-map.ts), which is how a
|
|
1557
|
+
// declared-but-unwired tool used to hang a turn with zero recorded
|
|
1558
|
+
// tool calls instead of failing fast.
|
|
1559
|
+
resolveTool: async (ref) => {
|
|
1451
1560
|
const id = toolRefId(ref);
|
|
1452
1561
|
if (!id) return void 0;
|
|
1453
|
-
const
|
|
1454
|
-
if (
|
|
1562
|
+
const workspaceTool = workspaceTools[id];
|
|
1563
|
+
if (workspaceTool) return { name: id, tool: workspaceTool };
|
|
1564
|
+
const daemonSubAgentTool = daemonSubAgentTools[id];
|
|
1565
|
+
if (daemonSubAgentTool) return { name: id, tool: daemonSubAgentTool };
|
|
1566
|
+
if (modesEnabled) {
|
|
1567
|
+
const def = (await getDaemonMcpToolDefs()).find((d) => d.name === id);
|
|
1568
|
+
if (def) {
|
|
1569
|
+
const tool = makeDaemonMcpProxyTool(def, {
|
|
1570
|
+
getClient: async () => daemonMcpClient,
|
|
1571
|
+
...appId !== void 0 ? { appId } : {}
|
|
1572
|
+
});
|
|
1573
|
+
resolvedDaemonMcpTools[id] = tool;
|
|
1574
|
+
return { name: id, tool };
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1455
1577
|
console.warn(
|
|
1456
|
-
`[@agentproto/adapter-mastra-agent] agent '${handle.id}' declares tool '${id}' but no executor is wired for it in
|
|
1578
|
+
`[@agentproto/adapter-mastra-agent] agent '${handle.id}' declares tool '${id}' but no executor is wired for it (not in the workspace toolset, the daemon sub-agent tools, or the daemon's own MCP tool list) \u2014 calls to it will fail immediately instead of hanging. Wire it in workspace-tools.ts, pass it via extraTools, or expose it from the daemon.`
|
|
1457
1579
|
);
|
|
1458
1580
|
return { name: id, tool: makeUnwiredToolStub(id) };
|
|
1459
1581
|
},
|
|
@@ -1461,12 +1583,10 @@ function makeAgentFactory(opts = {}) {
|
|
|
1461
1583
|
// The markdown body is the agent's primary system prompt (AIP-42).
|
|
1462
1584
|
body
|
|
1463
1585
|
});
|
|
1464
|
-
const modesEnabled = opts.modes !== false && !envFlag(process.env.AGENTPROTO_MASTRA_NO_MODES);
|
|
1465
1586
|
const { modes, defaultModeId } = modesEnabled ? resolveModes(body) : { modes: [{ id: "main", metadata: { default: true } }], defaultModeId: "main" };
|
|
1466
1587
|
let tools = workspaceTools;
|
|
1467
1588
|
if (modesEnabled) {
|
|
1468
|
-
|
|
1469
|
-
tools = { ...workspaceTools, ...makeDaemonTools({ client: daemonClient }) };
|
|
1589
|
+
tools = { ...workspaceTools, ...daemonSubAgentTools, ...resolvedDaemonMcpTools };
|
|
1470
1590
|
const signalProvider = new AgentprotoSignalProvider({
|
|
1471
1591
|
client: daemonClient,
|
|
1472
1592
|
stateEmitter: new DaemonStateEmitter({ client: daemonClient, cwd })
|
|
@@ -1628,6 +1748,7 @@ var SUSPENSION_OPTIONS = [
|
|
|
1628
1748
|
var META_SUSPEND_PAYLOAD = "mastra-agent/suspendPayload";
|
|
1629
1749
|
var META_RESUME_SCHEMA = "mastra-agent/resumeSchema";
|
|
1630
1750
|
var META_RESUME_DATA = "mastra-agent/resumeData";
|
|
1751
|
+
var META_FEEDBACK = "agentproto/feedback";
|
|
1631
1752
|
function emptySessionState() {
|
|
1632
1753
|
return {
|
|
1633
1754
|
session: null,
|
|
@@ -1781,6 +1902,19 @@ var MastraAcpAgent = class {
|
|
|
1781
1902
|
state.turn = turn;
|
|
1782
1903
|
const { text, files } = promptContent(params);
|
|
1783
1904
|
try {
|
|
1905
|
+
if (!interrupted && state.suspensions.size > 0) {
|
|
1906
|
+
await this.#conn.sessionUpdate({
|
|
1907
|
+
sessionId: params.sessionId,
|
|
1908
|
+
update: {
|
|
1909
|
+
sessionUpdate: "agent_message_chunk",
|
|
1910
|
+
content: {
|
|
1911
|
+
type: "text",
|
|
1912
|
+
text: "\n[mastra-agent] run is suspended awaiting approval \u2014 resolve the pending permission request (or cancel the session) before sending another prompt.\n"
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
return { stopReason: "refusal" };
|
|
1917
|
+
}
|
|
1784
1918
|
const session = await this.#ensureSession(params.sessionId, state);
|
|
1785
1919
|
const map = createEventMapper();
|
|
1786
1920
|
let endReason;
|
|
@@ -1798,6 +1932,21 @@ var MastraAcpAgent = class {
|
|
|
1798
1932
|
if (event.type === "agent_end") {
|
|
1799
1933
|
endReason = event.reason ?? "complete";
|
|
1800
1934
|
resolveAgentEnd?.();
|
|
1935
|
+
if (event.reason === "suspended") {
|
|
1936
|
+
relay = relay.then(
|
|
1937
|
+
() => this.#conn.sessionUpdate({
|
|
1938
|
+
sessionId: params.sessionId,
|
|
1939
|
+
update: {
|
|
1940
|
+
sessionUpdate: "agent_message_chunk",
|
|
1941
|
+
content: {
|
|
1942
|
+
type: "text",
|
|
1943
|
+
text: "\n[mastra-agent] run suspended \u2014 waiting for approval before continuing.\n"
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
})
|
|
1947
|
+
).catch(() => {
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1801
1950
|
return;
|
|
1802
1951
|
}
|
|
1803
1952
|
if (event.type === "tool_approval_required") {
|
|
@@ -1994,7 +2143,11 @@ var MastraAcpAgent = class {
|
|
|
1994
2143
|
if (pending.cancelled) return;
|
|
1995
2144
|
if (outcome.outcome !== "selected") return;
|
|
1996
2145
|
const meta = outcome._meta;
|
|
1997
|
-
const
|
|
2146
|
+
const feedback = meta && typeof meta[META_FEEDBACK] === "string" ? meta[META_FEEDBACK] : void 0;
|
|
2147
|
+
const resumeData = meta && META_RESUME_DATA in meta ? meta[META_RESUME_DATA] : {
|
|
2148
|
+
approved: outcome.optionId === "approve",
|
|
2149
|
+
...feedback ? { feedback } : {}
|
|
2150
|
+
};
|
|
1998
2151
|
await session.respondToToolSuspension({
|
|
1999
2152
|
resumeData,
|
|
2000
2153
|
toolCallId: event.toolCallId
|
|
@@ -2028,6 +2181,12 @@ var MastraAcpAgent = class {
|
|
|
2028
2181
|
threadId: acpSessionId
|
|
2029
2182
|
});
|
|
2030
2183
|
session.subscribe((event) => {
|
|
2184
|
+
if (event.type === "tool_suspension_cancelled") {
|
|
2185
|
+
const pending = state.suspensions.get(event.toolCallId);
|
|
2186
|
+
if (pending) pending.cancelled = true;
|
|
2187
|
+
state.suspensions.delete(event.toolCallId);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2031
2190
|
if (event.type === "mode_changed") {
|
|
2032
2191
|
void this.#conn.sessionUpdate({
|
|
2033
2192
|
sessionId: acpSessionId,
|
|
@@ -2091,5 +2250,5 @@ function runAcpOverStdio(buildController) {
|
|
|
2091
2250
|
}
|
|
2092
2251
|
|
|
2093
2252
|
export { DEFAULT_MODEL, DEFAULT_MODEL_CATALOG, DEFAULT_TOOL_IDS, DISABLED_BUILTIN_TOOL_IDS, DaemonClient, DaemonHttpError, DaemonNotFoundError, MastraAcpAgent, buildSqliteMemory, buildSqliteStore, createEventMapper, defaultAgentManifest, discoverDaemonEndpoint, makeAgentFactory, makeDaemonTools, makeWorkspaceTools, messageText, modelRefToString, promptContent, promptText, providerOf, resolveInCwd, resolveMastraModel, resolveMemoryDbPath, runAcpOverStdio, toolCallTitle, toolKindFor };
|
|
2094
|
-
//# sourceMappingURL=chunk-
|
|
2095
|
-
//# sourceMappingURL=chunk-
|
|
2253
|
+
//# sourceMappingURL=chunk-2FKFITFT.mjs.map
|
|
2254
|
+
//# sourceMappingURL=chunk-2FKFITFT.mjs.map
|