@agentproto/runtime 2.1.0 → 2.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/README.md +1 -0
- package/dist/index.d.ts +31 -2
- package/dist/index.mjs +900 -116
- package/dist/index.mjs.map +1 -1
- package/dist/resume-strategies.mjs.map +1 -1
- package/package.json +12 -11
package/dist/index.mjs
CHANGED
|
@@ -29,12 +29,13 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
29
29
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
30
30
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
31
31
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
32
|
-
import {
|
|
32
|
+
import { loadAppHandle } from '@agentproto/app-kit';
|
|
33
|
+
import { normalizeToolId } from '@agentproto/driver';
|
|
34
|
+
import { runWorkflow, buildAgentStep, compileWorkflow } from '@agentproto/workflow-runtime';
|
|
33
35
|
import { loadWorkflowHandle } from '@agentproto/workflow-loader';
|
|
34
36
|
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
35
37
|
import { Cron } from 'croner';
|
|
36
38
|
import { parseRoutineManifest } from '@agentproto/routine';
|
|
37
|
-
import { normalizeToolId } from '@agentproto/driver';
|
|
38
39
|
import { createServer as createServer$1 } from 'net';
|
|
39
40
|
import { promisify } from 'util';
|
|
40
41
|
import { createRequire } from 'module';
|
|
@@ -309,11 +310,16 @@ function createTranscriptWriter(opts) {
|
|
|
309
310
|
}, DEBOUNCE_MS);
|
|
310
311
|
};
|
|
311
312
|
return {
|
|
312
|
-
recordPrompt(sessionId, message) {
|
|
313
|
+
recordPrompt(sessionId, message, opts2) {
|
|
313
314
|
const state = getState(sessionId);
|
|
314
315
|
flushBuffers(sessionId, state);
|
|
315
316
|
const text9 = typeof message === "string" ? message : JSON.stringify(message);
|
|
316
|
-
writeRecord(sessionId, state, {
|
|
317
|
+
writeRecord(sessionId, state, {
|
|
318
|
+
kind: "user-prompt",
|
|
319
|
+
sessionId,
|
|
320
|
+
text: text9,
|
|
321
|
+
...opts2?.source ? { source: opts2.source } : {}
|
|
322
|
+
});
|
|
317
323
|
},
|
|
318
324
|
recordEvent(sessionId, evt) {
|
|
319
325
|
const state = getState(sessionId);
|
|
@@ -403,6 +409,22 @@ function createTranscriptWriter(opts) {
|
|
|
403
409
|
options: evt.options
|
|
404
410
|
});
|
|
405
411
|
break;
|
|
412
|
+
// Durable counterpart to "agent-prompt" — recorded when
|
|
413
|
+
// `respondPermission` (or a dying session's cancel-in-flight sweep)
|
|
414
|
+
// resolves the ask, keyed by the same `toolCallId` so a reader can
|
|
415
|
+
// tell an answered request from a still-pending one without relying
|
|
416
|
+
// on the in-memory pending-permissions map (see sessions.ts's
|
|
417
|
+
// `resolvePendingPermission` / `cancelPendingPermissionsForSession`).
|
|
418
|
+
case "permission-resolved":
|
|
419
|
+
flushBuffers(sessionId, state);
|
|
420
|
+
writeRecord(sessionId, state, {
|
|
421
|
+
kind: "permission-resolved",
|
|
422
|
+
sessionId,
|
|
423
|
+
toolCallId: evt.toolCallId,
|
|
424
|
+
decision: evt.decision,
|
|
425
|
+
...evt.optionId ? { optionId: evt.optionId } : {}
|
|
426
|
+
});
|
|
427
|
+
break;
|
|
406
428
|
case "turn-end":
|
|
407
429
|
flushBuffers(sessionId, state);
|
|
408
430
|
writeRecord(sessionId, state, { kind: "turn-end", sessionId, reason: evt.reason });
|
|
@@ -5115,6 +5137,14 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5115
5137
|
};
|
|
5116
5138
|
});
|
|
5117
5139
|
}
|
|
5140
|
+
if (mcpServers === void 0 && parentSessionId && buildOrchestratorMcp && input.orchestrator !== false && input.sandbox === void 0) {
|
|
5141
|
+
const injection = buildOrchestratorMcp({
|
|
5142
|
+
tools: ["message_parent"],
|
|
5143
|
+
role: role.name
|
|
5144
|
+
});
|
|
5145
|
+
mcpServers = [injection.entry];
|
|
5146
|
+
bindOrchestratorLifecycle = injection.bindLifecycle;
|
|
5147
|
+
}
|
|
5118
5148
|
const spawnDefaults = resolveSpawnDefaults(configDefaults, input.adapter, {
|
|
5119
5149
|
skills: input.skills,
|
|
5120
5150
|
options: input.options,
|
|
@@ -5276,9 +5306,8 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5276
5306
|
details: { adapter: input.adapter, gateway }
|
|
5277
5307
|
};
|
|
5278
5308
|
}
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
${input.prompt}` : input.prompt;
|
|
5309
|
+
const parentContextLine = parentSessionId ? `You were spawned by session ${parentSessionId} (also available in the ${PARENT_SESSION_ID_ENV} env var). When you finish \u2014 or hit a blocker you cannot resolve \u2014 report back to it via the message_parent tool if one is available (no session id needed; the daemon resolves your parent).` : void 0;
|
|
5310
|
+
let effectivePrompt = input.prompt ? [composeRoleContext(role, input.promptAppend, roleRegistry), parentContextLine, input.prompt].filter((p) => !!p).join("\n\n") : input.prompt;
|
|
5282
5311
|
const explicitTitle = input.title?.trim() ? input.title.trim() : void 0;
|
|
5283
5312
|
const spawnLabel = input.label?.trim() ? input.label.trim() : void 0;
|
|
5284
5313
|
const initialTitle = explicitTitle ?? spawnLabel ?? (input.prompt ? deriveSessionTitle(input.prompt) : void 0);
|
|
@@ -5535,7 +5564,12 @@ ${asyncPrompt}`;
|
|
|
5535
5564
|
// collide with, so this is the entire env for this spawn.
|
|
5536
5565
|
env: {
|
|
5537
5566
|
[SESSION_ID_ENV]: mintedSessionId,
|
|
5538
|
-
[WORKSPACE_SLUG_ENV]: resolvedSlug
|
|
5567
|
+
[WORKSPACE_SLUG_ENV]: resolvedSlug,
|
|
5568
|
+
// Lineage (PARENT_SESSION_ID_ENV's doc, sessions.ts) — mirrors
|
|
5569
|
+
// the descriptor's `parentSessionId` so the child can discover
|
|
5570
|
+
// who spawned it without a registry round-trip. Absent on a
|
|
5571
|
+
// parentless root spawn.
|
|
5572
|
+
...parentSessionId ? { [PARENT_SESSION_ID_ENV]: parentSessionId } : {}
|
|
5539
5573
|
},
|
|
5540
5574
|
onActivity: () => {
|
|
5541
5575
|
if (liveSessionId) registry.pulseActivity(liveSessionId);
|
|
@@ -6720,8 +6754,8 @@ function composeSessionObservers(observers) {
|
|
|
6720
6754
|
);
|
|
6721
6755
|
};
|
|
6722
6756
|
return {
|
|
6723
|
-
recordPrompt(sessionId, message) {
|
|
6724
|
-
forEachSafe((o) => o.recordPrompt(sessionId, message));
|
|
6757
|
+
recordPrompt(sessionId, message, opts) {
|
|
6758
|
+
forEachSafe((o) => o.recordPrompt(sessionId, message, opts));
|
|
6725
6759
|
},
|
|
6726
6760
|
recordEvent(sessionId, evt) {
|
|
6727
6761
|
forEachSafe((o) => o.recordEvent(sessionId, evt));
|
|
@@ -6739,10 +6773,10 @@ function composeSessionObservers(observers) {
|
|
|
6739
6773
|
}
|
|
6740
6774
|
function filterSessionObserver(inner, shouldObserve) {
|
|
6741
6775
|
return {
|
|
6742
|
-
recordPrompt(sessionId, message) {
|
|
6776
|
+
recordPrompt(sessionId, message, opts) {
|
|
6743
6777
|
if (!shouldObserve(sessionId)) return;
|
|
6744
6778
|
try {
|
|
6745
|
-
inner.recordPrompt(sessionId, message);
|
|
6779
|
+
inner.recordPrompt(sessionId, message, opts);
|
|
6746
6780
|
} catch {
|
|
6747
6781
|
}
|
|
6748
6782
|
},
|
|
@@ -7445,6 +7479,7 @@ function mintSessionId() {
|
|
|
7445
7479
|
}
|
|
7446
7480
|
var SESSION_ID_ENV = "AGENTPROTO_SESSION_ID";
|
|
7447
7481
|
var WORKSPACE_SLUG_ENV = "AGENTPROTO_WORKSPACE_SLUG";
|
|
7482
|
+
var PARENT_SESSION_ID_ENV = "AGENTPROTO_PARENT_SESSION_ID";
|
|
7448
7483
|
var SessionNotAliveError = class extends Error {
|
|
7449
7484
|
sessionId;
|
|
7450
7485
|
status;
|
|
@@ -7747,6 +7782,11 @@ function createSessionsRegistry(opts) {
|
|
|
7747
7782
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
7748
7783
|
});
|
|
7749
7784
|
}
|
|
7785
|
+
transcriptWriter.recordEvent(rt.desc.id, {
|
|
7786
|
+
kind: "permission-resolved",
|
|
7787
|
+
toolCallId: id,
|
|
7788
|
+
decision: "cancelled"
|
|
7789
|
+
});
|
|
7750
7790
|
}
|
|
7751
7791
|
delete rt.desc.awaitingPermission;
|
|
7752
7792
|
};
|
|
@@ -7802,6 +7842,12 @@ function createSessionsRegistry(opts) {
|
|
|
7802
7842
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
7803
7843
|
});
|
|
7804
7844
|
}
|
|
7845
|
+
transcriptWriter.recordEvent(pending.sessionId, {
|
|
7846
|
+
kind: "permission-resolved",
|
|
7847
|
+
toolCallId: id,
|
|
7848
|
+
decision: input.decision,
|
|
7849
|
+
...chosenOptionId ? { optionId: chosenOptionId } : {}
|
|
7850
|
+
});
|
|
7805
7851
|
schedulePersist();
|
|
7806
7852
|
if (!okResolved) {
|
|
7807
7853
|
return { ok: true, permission: pending, decision: input.decision, ...chosenOptionId ? { optionId: chosenOptionId } : {} };
|
|
@@ -8434,7 +8480,7 @@ function createSessionsRegistry(opts) {
|
|
|
8434
8480
|
return;
|
|
8435
8481
|
}
|
|
8436
8482
|
}
|
|
8437
|
-
const runAgentTurn = async (rt, message) => {
|
|
8483
|
+
const runAgentTurn = async (rt, message, turnOpts) => {
|
|
8438
8484
|
if (!rt.agentSession) {
|
|
8439
8485
|
throw new Error("runAgentTurn: session has no agentSession");
|
|
8440
8486
|
}
|
|
@@ -8470,7 +8516,11 @@ ${message}`;
|
|
|
8470
8516
|
`\x1B[2m\u2500\u2500 \u25B6 ${typeof message === "string" ? message : JSON.stringify(message)} \u2500\u2500\x1B[0m`,
|
|
8471
8517
|
"stdout"
|
|
8472
8518
|
);
|
|
8473
|
-
transcriptWriter.recordPrompt(
|
|
8519
|
+
transcriptWriter.recordPrompt(
|
|
8520
|
+
rt.desc.id,
|
|
8521
|
+
message,
|
|
8522
|
+
turnOpts?.promptSource ? { source: turnOpts.promptSource } : void 0
|
|
8523
|
+
);
|
|
8474
8524
|
const wrapped = typeof message === "string" ? { type: "text", text: message } : message;
|
|
8475
8525
|
for await (const evt of rt.agentSession.send(wrapped)) {
|
|
8476
8526
|
transcriptWriter.recordEvent(rt.desc.id, evt);
|
|
@@ -8881,7 +8931,11 @@ ${message}`;
|
|
|
8881
8931
|
schedulePersist();
|
|
8882
8932
|
recordConversationLink(rt);
|
|
8883
8933
|
if (input.initialPrompt) {
|
|
8884
|
-
void runAgentTurn(
|
|
8934
|
+
void runAgentTurn(
|
|
8935
|
+
rt,
|
|
8936
|
+
input.initialPrompt,
|
|
8937
|
+
desc.parentSessionId ? { promptSource: `agent:${desc.parentSessionId}` } : void 0
|
|
8938
|
+
).catch((err) => {
|
|
8885
8939
|
appendLine(
|
|
8886
8940
|
rt,
|
|
8887
8941
|
`[turn error] ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -9009,7 +9063,11 @@ ${message}`;
|
|
|
9009
9063
|
schedulePersist();
|
|
9010
9064
|
recordConversationLink(rt);
|
|
9011
9065
|
if (outcome.initialPrompt) {
|
|
9012
|
-
void runAgentTurn(
|
|
9066
|
+
void runAgentTurn(
|
|
9067
|
+
rt,
|
|
9068
|
+
outcome.initialPrompt,
|
|
9069
|
+
rt.desc.parentSessionId ? { promptSource: `agent:${rt.desc.parentSessionId}` } : void 0
|
|
9070
|
+
).catch((err) => {
|
|
9013
9071
|
appendLine(
|
|
9014
9072
|
rt,
|
|
9015
9073
|
`[turn error] ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -9267,7 +9325,7 @@ ${message}`;
|
|
|
9267
9325
|
}
|
|
9268
9326
|
if (rtPre) await maybeResumeAgent(rtPre);
|
|
9269
9327
|
const rt = validateAgentTurn(id, "sendPrompt");
|
|
9270
|
-
await runAgentTurn(rt, message);
|
|
9328
|
+
await runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0);
|
|
9271
9329
|
},
|
|
9272
9330
|
async enqueuePrompt(id, message, opts2) {
|
|
9273
9331
|
const rtPre = sessions.get(id);
|
|
@@ -9279,7 +9337,7 @@ ${message}`;
|
|
|
9279
9337
|
}
|
|
9280
9338
|
await maybeResumeAgent(rtPre);
|
|
9281
9339
|
const rt = validateAgentTurn(id, "enqueuePrompt");
|
|
9282
|
-
void runAgentTurn(rt, message).catch((err) => {
|
|
9340
|
+
void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
|
|
9283
9341
|
appendLine(
|
|
9284
9342
|
rtPre,
|
|
9285
9343
|
`[error] ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -10706,12 +10764,17 @@ function registerFsTools(server, opts) {
|
|
|
10706
10764
|
const anchor = makeAnchor(opts.workspace);
|
|
10707
10765
|
server.tool(
|
|
10708
10766
|
"file_read",
|
|
10709
|
-
|
|
10710
|
-
{
|
|
10711
|
-
|
|
10767
|
+
`Read a file from the workspace. Defaults to UTF-8 text; pass encoding: "base64" for binary files (images, audio, video, \u2026) \u2014 UTF-8 decoding a non-text file replaces invalid byte sequences (e.g. a PNG's 0x89 header byte) with U+FFFD, corrupting the content.`,
|
|
10768
|
+
{
|
|
10769
|
+
path: z.string().describe("Workspace-relative path to the file."),
|
|
10770
|
+
encoding: z.enum(["utf8", "base64"]).optional().describe(
|
|
10771
|
+
'"utf8" (default) for text files, "base64" for binary files.'
|
|
10772
|
+
)
|
|
10773
|
+
},
|
|
10774
|
+
async ({ path, encoding }) => {
|
|
10712
10775
|
const abs = anchor(path);
|
|
10713
10776
|
const buf = await readFile(abs);
|
|
10714
|
-
return text(buf.toString("utf8"));
|
|
10777
|
+
return text(buf.toString(encoding === "base64" ? "base64" : "utf8"));
|
|
10715
10778
|
}
|
|
10716
10779
|
);
|
|
10717
10780
|
server.tool(
|
|
@@ -11287,8 +11350,10 @@ function registerAgentTools(server, opts) {
|
|
|
11287
11350
|
const sessionId = resolveSessionIdArg(input);
|
|
11288
11351
|
if (!sessionId) return missingSessionIdError("agent_prompt");
|
|
11289
11352
|
try {
|
|
11353
|
+
const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
|
|
11290
11354
|
await registry.enqueuePrompt(sessionId, input.prompt, {
|
|
11291
|
-
interrupt: input.interrupt
|
|
11355
|
+
interrupt: input.interrupt,
|
|
11356
|
+
...promptSource ? { source: `agent:${promptSource}` } : {}
|
|
11292
11357
|
});
|
|
11293
11358
|
return {
|
|
11294
11359
|
content: [
|
|
@@ -11315,6 +11380,67 @@ function registerAgentTools(server, opts) {
|
|
|
11315
11380
|
}
|
|
11316
11381
|
}
|
|
11317
11382
|
);
|
|
11383
|
+
server.tool(
|
|
11384
|
+
"message_parent",
|
|
11385
|
+
"Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts). Errors if this session has no recorded parent or the parent is gone.",
|
|
11386
|
+
{
|
|
11387
|
+
message: z.string().min(1).describe("The message to deliver to your parent session (plain text).")
|
|
11388
|
+
},
|
|
11389
|
+
async (input) => {
|
|
11390
|
+
const fail = (text9) => ({
|
|
11391
|
+
content: [{ type: "text", text: text9 }],
|
|
11392
|
+
isError: true
|
|
11393
|
+
});
|
|
11394
|
+
const selfId = callerScope?.ownerSessionId ?? callerSessionId;
|
|
11395
|
+
if (!selfId) {
|
|
11396
|
+
return fail(
|
|
11397
|
+
"message_parent: cannot identify the calling session \u2014 this tool needs gateway access attributed to a session (a scoped orchestrator gateway, or a daemon `/mcp` URL carrying `?callerSessionId=`). A human/root caller has no parent to message."
|
|
11398
|
+
);
|
|
11399
|
+
}
|
|
11400
|
+
const self = registry.get(selfId);
|
|
11401
|
+
if (!self) {
|
|
11402
|
+
return fail(`message_parent: calling session "${selfId}" is not in the registry.`);
|
|
11403
|
+
}
|
|
11404
|
+
const parentId = self.parentSessionId;
|
|
11405
|
+
if (!parentId || parentId === selfId) {
|
|
11406
|
+
return fail(
|
|
11407
|
+
"message_parent: this session has no recorded parent \u2014 it was spawned at the root, so there is no one to report up to."
|
|
11408
|
+
);
|
|
11409
|
+
}
|
|
11410
|
+
const parent = registry.get(parentId);
|
|
11411
|
+
if (!parent) {
|
|
11412
|
+
return fail(`message_parent: parent session "${parentId}" no longer exists.`);
|
|
11413
|
+
}
|
|
11414
|
+
if (parent.status !== "running" && parent.status !== "starting") {
|
|
11415
|
+
return fail(
|
|
11416
|
+
`message_parent: parent session "${parentId}" is not running (status: ${parent.status}) \u2014 the message cannot be delivered.`
|
|
11417
|
+
);
|
|
11418
|
+
}
|
|
11419
|
+
const who = self.label ?? selfId;
|
|
11420
|
+
const notice = `[child-message] ${who} (${selfId}): ${input.message}`;
|
|
11421
|
+
const done = (delivery) => ({
|
|
11422
|
+
content: [
|
|
11423
|
+
{
|
|
11424
|
+
type: "text",
|
|
11425
|
+
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery }, null, 2)
|
|
11426
|
+
}
|
|
11427
|
+
]
|
|
11428
|
+
});
|
|
11429
|
+
if (!parent.busy) {
|
|
11430
|
+
try {
|
|
11431
|
+
await registry.enqueuePrompt(parentId, notice, {});
|
|
11432
|
+
return done("enqueued");
|
|
11433
|
+
} catch {
|
|
11434
|
+
}
|
|
11435
|
+
}
|
|
11436
|
+
if (!registry.stampPendingChildCrashNotice(parentId, notice)) {
|
|
11437
|
+
return fail(
|
|
11438
|
+
`message_parent: parent session "${parentId}" vanished mid-delivery.`
|
|
11439
|
+
);
|
|
11440
|
+
}
|
|
11441
|
+
return done("queued-next-turn");
|
|
11442
|
+
}
|
|
11443
|
+
);
|
|
11318
11444
|
server.tool(
|
|
11319
11445
|
"agent_output",
|
|
11320
11446
|
"Tail the recent output of a session. Returns the last N lines of the ring buffer (stdout + stderr inter-leaved, newest last). Use this to read an agent's reply after `agent_prompt`.",
|
|
@@ -21944,6 +22070,18 @@ async function startHttpServer(opts) {
|
|
|
21944
22070
|
const handled = await handleRoutineDefs(req, res, path, opts.routineRegistrar);
|
|
21945
22071
|
if (handled) return;
|
|
21946
22072
|
}
|
|
22073
|
+
if (opts.appRegistry && opts.performAppInstall && opts.listRegisteredToolIds && (path.startsWith("/apps/") || path.startsWith("/scopes/"))) {
|
|
22074
|
+
const handled = await handleApps(
|
|
22075
|
+
req,
|
|
22076
|
+
res,
|
|
22077
|
+
path,
|
|
22078
|
+
opts.appRegistry,
|
|
22079
|
+
opts.performAppInstall,
|
|
22080
|
+
opts.listRegisteredToolIds,
|
|
22081
|
+
opts.resolveAgentAdapter
|
|
22082
|
+
);
|
|
22083
|
+
if (handled) return;
|
|
22084
|
+
}
|
|
21947
22085
|
res.writeHead(404, { "content-type": "application/json" });
|
|
21948
22086
|
res.end(JSON.stringify({ error: "not_found", path }));
|
|
21949
22087
|
} catch (err) {
|
|
@@ -24152,6 +24290,116 @@ async function handleProviderInbound(req, res, slug, deps2) {
|
|
|
24152
24290
|
res.writeHead(200, { "content-type": "application/json" });
|
|
24153
24291
|
res.end(JSON.stringify(result));
|
|
24154
24292
|
}
|
|
24293
|
+
async function handleApps(req, res, path, appRegistry, performInstall2, listRegisteredToolIds, resolveAgentAdapter) {
|
|
24294
|
+
const method = req.method ?? "GET";
|
|
24295
|
+
const applyMatch = path.match(/^\/apps\/([^/]+)\/apply$/);
|
|
24296
|
+
if (applyMatch && method === "POST") {
|
|
24297
|
+
const appId = decodeURIComponent(applyMatch[1]);
|
|
24298
|
+
const body = await readJsonBody(req);
|
|
24299
|
+
const scopeId = body?.scopeId ?? "root";
|
|
24300
|
+
let installed = appRegistry.getApp(appId);
|
|
24301
|
+
if (!installed && body?.dir) {
|
|
24302
|
+
const installResult = await performInstall2(body.dir, appRegistry, listRegisteredToolIds, resolveAgentAdapter);
|
|
24303
|
+
if (!installResult.ok) {
|
|
24304
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
24305
|
+
res.end(JSON.stringify({ error: installResult.error }));
|
|
24306
|
+
return true;
|
|
24307
|
+
}
|
|
24308
|
+
installed = installResult.record;
|
|
24309
|
+
}
|
|
24310
|
+
if (!installed) {
|
|
24311
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
24312
|
+
res.end(
|
|
24313
|
+
JSON.stringify({
|
|
24314
|
+
error: `app "${appId}" is not installed. Either call app_install first or provide a 'dir' parameter.`
|
|
24315
|
+
})
|
|
24316
|
+
);
|
|
24317
|
+
return true;
|
|
24318
|
+
}
|
|
24319
|
+
if (installed.requires && installed.requires.length > 0) {
|
|
24320
|
+
const applied = appRegistry.listApplied(scopeId);
|
|
24321
|
+
const appliedIds = new Set(applied.map((m) => m.appId));
|
|
24322
|
+
const missing = installed.requires.filter((reqId) => !appliedIds.has(reqId));
|
|
24323
|
+
if (missing.length > 0) {
|
|
24324
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
24325
|
+
res.end(
|
|
24326
|
+
JSON.stringify({
|
|
24327
|
+
error: `app "${appId}" requires the following apps to be applied to scope "${scopeId}" first: ${missing.join(", ")}`
|
|
24328
|
+
})
|
|
24329
|
+
);
|
|
24330
|
+
return true;
|
|
24331
|
+
}
|
|
24332
|
+
}
|
|
24333
|
+
const mount = appRegistry.applyApp({ scopeId, appId });
|
|
24334
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
24335
|
+
res.end(
|
|
24336
|
+
JSON.stringify({
|
|
24337
|
+
scopeId: mount.scopeId,
|
|
24338
|
+
appId: mount.appId,
|
|
24339
|
+
appliedAt: mount.appliedAt,
|
|
24340
|
+
agents: installed.agents,
|
|
24341
|
+
workflows: installed.workflows,
|
|
24342
|
+
unvalidatedAgentTools: installed.unvalidatedAgentTools
|
|
24343
|
+
})
|
|
24344
|
+
);
|
|
24345
|
+
return true;
|
|
24346
|
+
}
|
|
24347
|
+
if (applyMatch && method === "DELETE") {
|
|
24348
|
+
const appId = decodeURIComponent(applyMatch[1]);
|
|
24349
|
+
const body = await readJsonBody(req);
|
|
24350
|
+
const url = new URL(req.url ?? "", `http://${req.headers.host}`);
|
|
24351
|
+
const scopeId = body?.scopeId ?? url.searchParams.get("scopeId") ?? "root";
|
|
24352
|
+
const applied = appRegistry.listApplied(scopeId);
|
|
24353
|
+
const dependents = [];
|
|
24354
|
+
for (const mount of applied) {
|
|
24355
|
+
if (mount.appId === appId) continue;
|
|
24356
|
+
const app = appRegistry.getApp(mount.appId);
|
|
24357
|
+
if (app?.requires?.includes(appId)) {
|
|
24358
|
+
dependents.push(mount.appId);
|
|
24359
|
+
}
|
|
24360
|
+
}
|
|
24361
|
+
if (dependents.length > 0) {
|
|
24362
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
24363
|
+
res.end(
|
|
24364
|
+
JSON.stringify({
|
|
24365
|
+
error: `cannot unapply app "${appId}" from scope "${scopeId}" \u2014 the following apps in this scope require it: ${dependents.join(", ")}`
|
|
24366
|
+
})
|
|
24367
|
+
);
|
|
24368
|
+
return true;
|
|
24369
|
+
}
|
|
24370
|
+
const removed = appRegistry.unapplyApp({ scopeId, appId });
|
|
24371
|
+
if (!removed) {
|
|
24372
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
24373
|
+
res.end(JSON.stringify({ error: `app "${appId}" is not applied to scope "${scopeId}".` }));
|
|
24374
|
+
return true;
|
|
24375
|
+
}
|
|
24376
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
24377
|
+
res.end(JSON.stringify({ scopeId: removed.scopeId, appId: removed.appId, appliedAt: removed.appliedAt }));
|
|
24378
|
+
return true;
|
|
24379
|
+
}
|
|
24380
|
+
const scopesMatch = path.match(/^\/scopes\/([^/]+)\/apps$/);
|
|
24381
|
+
if (scopesMatch && method === "GET") {
|
|
24382
|
+
const scopeId = decodeURIComponent(scopesMatch[1]);
|
|
24383
|
+
const mounts = appRegistry.listApplied(scopeId);
|
|
24384
|
+
const result = mounts.map((mount) => {
|
|
24385
|
+
const app = appRegistry.getApp(mount.appId);
|
|
24386
|
+
return {
|
|
24387
|
+
scopeId: mount.scopeId,
|
|
24388
|
+
appId: mount.appId,
|
|
24389
|
+
appliedAt: mount.appliedAt,
|
|
24390
|
+
...app ? {
|
|
24391
|
+
agents: app.agents,
|
|
24392
|
+
workflows: app.workflows,
|
|
24393
|
+
unvalidatedAgentTools: app.unvalidatedAgentTools
|
|
24394
|
+
} : {}
|
|
24395
|
+
};
|
|
24396
|
+
});
|
|
24397
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
24398
|
+
res.end(JSON.stringify(result));
|
|
24399
|
+
return true;
|
|
24400
|
+
}
|
|
24401
|
+
return false;
|
|
24402
|
+
}
|
|
24155
24403
|
async function readJsonBody(req) {
|
|
24156
24404
|
const chunks = [];
|
|
24157
24405
|
for await (const chunk of req) {
|
|
@@ -25260,6 +25508,525 @@ stderr: ${stderrText.slice(-600)}` : msg);
|
|
|
25260
25508
|
`import "${entry.alias}" has unsupported transport type "${snap.type}"`
|
|
25261
25509
|
);
|
|
25262
25510
|
}
|
|
25511
|
+
function collectToolIds(steps, out) {
|
|
25512
|
+
for (const step of steps) {
|
|
25513
|
+
switch (step.kind) {
|
|
25514
|
+
case "tool": {
|
|
25515
|
+
const ref = typeof step.tool === "string" ? step.tool : step.tool.entry;
|
|
25516
|
+
out.add(normalizeToolId(ref));
|
|
25517
|
+
break;
|
|
25518
|
+
}
|
|
25519
|
+
case "map":
|
|
25520
|
+
case "loop":
|
|
25521
|
+
collectToolIds(step.steps, out);
|
|
25522
|
+
break;
|
|
25523
|
+
case "parallel":
|
|
25524
|
+
for (const branch of step.branches) collectToolIds(branch.steps, out);
|
|
25525
|
+
break;
|
|
25526
|
+
}
|
|
25527
|
+
}
|
|
25528
|
+
}
|
|
25529
|
+
function passthroughToolHandle(id) {
|
|
25530
|
+
return {
|
|
25531
|
+
id,
|
|
25532
|
+
name: id,
|
|
25533
|
+
description: `Daemon tool '${id}', dispatched via the routine/cron dispatchTool bridge.`,
|
|
25534
|
+
mutates: [],
|
|
25535
|
+
requires: { network: [], secrets: [], tools: [] },
|
|
25536
|
+
approval: "auto",
|
|
25537
|
+
riskLevel: 0,
|
|
25538
|
+
costClass: "trivial",
|
|
25539
|
+
timeoutMs: 3e4,
|
|
25540
|
+
tags: [],
|
|
25541
|
+
metadata: {},
|
|
25542
|
+
idempotent: false,
|
|
25543
|
+
driverConstraints: { forbid: [], requireKind: [] }
|
|
25544
|
+
};
|
|
25545
|
+
}
|
|
25546
|
+
function unwrapDispatchResult(result) {
|
|
25547
|
+
if (!result || typeof result !== "object" || !("content" in result)) return result;
|
|
25548
|
+
const r = result;
|
|
25549
|
+
const text9 = (r.content ?? []).map((c) => c.text ?? "").join("\n").trim();
|
|
25550
|
+
if (r.isError) throw new Error(text9 || "tool call failed");
|
|
25551
|
+
if (!text9) return void 0;
|
|
25552
|
+
try {
|
|
25553
|
+
return JSON.parse(text9);
|
|
25554
|
+
} catch {
|
|
25555
|
+
return text9;
|
|
25556
|
+
}
|
|
25557
|
+
}
|
|
25558
|
+
function createDaemonToolRegistry(handle, dispatchTool) {
|
|
25559
|
+
const toolIds = /* @__PURE__ */ new Set();
|
|
25560
|
+
collectToolIds(handle.steps, toolIds);
|
|
25561
|
+
const tools = {};
|
|
25562
|
+
const execute = {};
|
|
25563
|
+
for (const id of toolIds) {
|
|
25564
|
+
tools[id] = passthroughToolHandle(id);
|
|
25565
|
+
execute[id] = async ({ input }) => unwrapDispatchResult(await dispatchTool(id, input ?? {}));
|
|
25566
|
+
}
|
|
25567
|
+
if (toolIds.size === 0) return { tools, candidates: [] };
|
|
25568
|
+
const daemonDriver = {
|
|
25569
|
+
id: "daemon-tool-dispatch",
|
|
25570
|
+
name: "Daemon tool dispatch",
|
|
25571
|
+
description: "Catch-all AIP-30 provider routing every referenced WORKFLOW.md tool step through the daemon's dispatchTool.",
|
|
25572
|
+
kind: "builtin",
|
|
25573
|
+
implements: [...toolIds].map((id) => ({ tool: id, version: "*" })),
|
|
25574
|
+
execute,
|
|
25575
|
+
install: [],
|
|
25576
|
+
network: { egress: [], ingress: [] },
|
|
25577
|
+
region: ["global"],
|
|
25578
|
+
policyTags: [],
|
|
25579
|
+
tags: [],
|
|
25580
|
+
metadata: {}
|
|
25581
|
+
};
|
|
25582
|
+
return { tools, candidates: [daemonDriver] };
|
|
25583
|
+
}
|
|
25584
|
+
var DEFAULT_PERSIST_PATH2 = () => join(homedir(), ".agentproto", "apps.json");
|
|
25585
|
+
function loadState(persistPath) {
|
|
25586
|
+
const empty = { apps: [], runs: [], applied: [] };
|
|
25587
|
+
if (!existsSync(persistPath)) return empty;
|
|
25588
|
+
let raw;
|
|
25589
|
+
try {
|
|
25590
|
+
raw = readFileSync(persistPath, "utf8");
|
|
25591
|
+
} catch {
|
|
25592
|
+
return empty;
|
|
25593
|
+
}
|
|
25594
|
+
try {
|
|
25595
|
+
const parsed = JSON.parse(raw);
|
|
25596
|
+
return {
|
|
25597
|
+
apps: Array.isArray(parsed.apps) ? parsed.apps : [],
|
|
25598
|
+
runs: Array.isArray(parsed.runs) ? parsed.runs : [],
|
|
25599
|
+
applied: Array.isArray(parsed.applied) ? parsed.applied : []
|
|
25600
|
+
};
|
|
25601
|
+
} catch {
|
|
25602
|
+
return empty;
|
|
25603
|
+
}
|
|
25604
|
+
}
|
|
25605
|
+
function saveState(state, persistPath) {
|
|
25606
|
+
try {
|
|
25607
|
+
mkdirSync(dirname(persistPath), { recursive: true });
|
|
25608
|
+
const tmp = `${persistPath}.tmp.${process.pid}`;
|
|
25609
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
25610
|
+
renameSync(tmp, persistPath);
|
|
25611
|
+
} catch {
|
|
25612
|
+
}
|
|
25613
|
+
}
|
|
25614
|
+
function createAppRegistry(opts) {
|
|
25615
|
+
const persistPath = opts?.persistPath ?? DEFAULT_PERSIST_PATH2();
|
|
25616
|
+
const shouldPersist = opts?.persist ?? opts?.persistPath !== void 0;
|
|
25617
|
+
const state = shouldPersist ? loadState(persistPath) : { apps: [], runs: [], applied: [] };
|
|
25618
|
+
const persist = () => {
|
|
25619
|
+
if (shouldPersist) saveState(state, persistPath);
|
|
25620
|
+
};
|
|
25621
|
+
return {
|
|
25622
|
+
upsertApp(input) {
|
|
25623
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25624
|
+
const idx = state.apps.findIndex((a) => a.appId === input.appId);
|
|
25625
|
+
const record2 = {
|
|
25626
|
+
...input,
|
|
25627
|
+
installedAt: idx === -1 ? now : state.apps[idx].installedAt,
|
|
25628
|
+
updatedAt: now
|
|
25629
|
+
};
|
|
25630
|
+
if (idx === -1) state.apps.push(record2);
|
|
25631
|
+
else state.apps[idx] = record2;
|
|
25632
|
+
persist();
|
|
25633
|
+
return record2;
|
|
25634
|
+
},
|
|
25635
|
+
getApp(appId) {
|
|
25636
|
+
return state.apps.find((a) => a.appId === appId);
|
|
25637
|
+
},
|
|
25638
|
+
listApps() {
|
|
25639
|
+
return [...state.apps];
|
|
25640
|
+
},
|
|
25641
|
+
createRun(input) {
|
|
25642
|
+
const run = {
|
|
25643
|
+
appRunId: `apprun_${randomUUID()}`,
|
|
25644
|
+
appId: input.appId,
|
|
25645
|
+
sessions: input.sessions,
|
|
25646
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25647
|
+
status: "running"
|
|
25648
|
+
};
|
|
25649
|
+
state.runs.push(run);
|
|
25650
|
+
persist();
|
|
25651
|
+
return run;
|
|
25652
|
+
},
|
|
25653
|
+
getRun(appRunId) {
|
|
25654
|
+
return state.runs.find((r) => r.appRunId === appRunId);
|
|
25655
|
+
},
|
|
25656
|
+
listRuns() {
|
|
25657
|
+
return [...state.runs];
|
|
25658
|
+
},
|
|
25659
|
+
endRun(appRunId) {
|
|
25660
|
+
const run = state.runs.find((r) => r.appRunId === appRunId);
|
|
25661
|
+
if (!run) return void 0;
|
|
25662
|
+
run.status = "stopped";
|
|
25663
|
+
run.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25664
|
+
persist();
|
|
25665
|
+
return run;
|
|
25666
|
+
},
|
|
25667
|
+
applyApp(input) {
|
|
25668
|
+
const idx = state.applied.findIndex(
|
|
25669
|
+
(m) => m.scopeId === input.scopeId && m.appId === input.appId
|
|
25670
|
+
);
|
|
25671
|
+
const mount = {
|
|
25672
|
+
scopeId: input.scopeId,
|
|
25673
|
+
appId: input.appId,
|
|
25674
|
+
appliedAt: idx === -1 ? (/* @__PURE__ */ new Date()).toISOString() : state.applied[idx].appliedAt
|
|
25675
|
+
};
|
|
25676
|
+
if (idx === -1) state.applied.push(mount);
|
|
25677
|
+
else state.applied[idx] = mount;
|
|
25678
|
+
persist();
|
|
25679
|
+
return mount;
|
|
25680
|
+
},
|
|
25681
|
+
unapplyApp(input) {
|
|
25682
|
+
const idx = state.applied.findIndex(
|
|
25683
|
+
(m) => m.scopeId === input.scopeId && m.appId === input.appId
|
|
25684
|
+
);
|
|
25685
|
+
if (idx === -1) return void 0;
|
|
25686
|
+
const removed = state.applied[idx];
|
|
25687
|
+
state.applied.splice(idx, 1);
|
|
25688
|
+
persist();
|
|
25689
|
+
return removed;
|
|
25690
|
+
},
|
|
25691
|
+
listApplied(scopeId) {
|
|
25692
|
+
if (scopeId === void 0) return [...state.applied];
|
|
25693
|
+
return state.applied.filter((m) => m.scopeId === scopeId);
|
|
25694
|
+
}
|
|
25695
|
+
};
|
|
25696
|
+
}
|
|
25697
|
+
|
|
25698
|
+
// src/app-tools.ts
|
|
25699
|
+
var DEFAULT_AGENT_ADAPTER = "mastra-agent";
|
|
25700
|
+
function resolveAgentRefsForWorkflow(appRegistry, workflowId) {
|
|
25701
|
+
const app = appRegistry.listApps().find((a) => a.workflows.some((w) => w.id === workflowId));
|
|
25702
|
+
if (!app) return void 0;
|
|
25703
|
+
const refs = {};
|
|
25704
|
+
for (const agent of app.agents) {
|
|
25705
|
+
refs[agent.id] = { adapter: DEFAULT_AGENT_ADAPTER, options: { agent: agent.path } };
|
|
25706
|
+
}
|
|
25707
|
+
return refs;
|
|
25708
|
+
}
|
|
25709
|
+
function textResult(body) {
|
|
25710
|
+
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }] };
|
|
25711
|
+
}
|
|
25712
|
+
function errorResult(text9) {
|
|
25713
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: text9 }) }], isError: true };
|
|
25714
|
+
}
|
|
25715
|
+
function refIdOf(ref) {
|
|
25716
|
+
if (typeof ref === "string") return ref;
|
|
25717
|
+
return ref.ref ?? ref.file ?? "inline";
|
|
25718
|
+
}
|
|
25719
|
+
function resolveRef(dir, path) {
|
|
25720
|
+
return isAbsolute(path) ? path : join(dir, path);
|
|
25721
|
+
}
|
|
25722
|
+
async function readAppRefs(dir) {
|
|
25723
|
+
const appPath = join(dir, ".agentproto", "APP.md");
|
|
25724
|
+
const source = await readFile(appPath, "utf8");
|
|
25725
|
+
const { data } = matter(source);
|
|
25726
|
+
const toRefs = (v) => Array.isArray(v) ? v.filter(
|
|
25727
|
+
(e) => typeof e === "object" && e !== null && typeof e.id === "string" && typeof e.path === "string"
|
|
25728
|
+
).map((e) => ({ id: e.id, path: resolveRef(dir, e.path) })) : [];
|
|
25729
|
+
return { agents: toRefs(data.agents), workflows: toRefs(data.workflows) };
|
|
25730
|
+
}
|
|
25731
|
+
async function performInstall(dir, appRegistry, listRegisteredToolIds, resolveAgentAdapter) {
|
|
25732
|
+
let handle;
|
|
25733
|
+
try {
|
|
25734
|
+
handle = await loadAppHandle(dir);
|
|
25735
|
+
} catch (err) {
|
|
25736
|
+
return { ok: false, error: `${err instanceof Error ? err.message : String(err)}` };
|
|
25737
|
+
}
|
|
25738
|
+
if (!handle.id) {
|
|
25739
|
+
return { ok: false, error: "the app has no `id` \u2014 set one in defineApp()/APP.md frontmatter to install it." };
|
|
25740
|
+
}
|
|
25741
|
+
const missingByWorkflow = {};
|
|
25742
|
+
const registeredIds = new Set(await listRegisteredToolIds());
|
|
25743
|
+
for (const workflow of handle.workflows) {
|
|
25744
|
+
const { tools } = createDaemonToolRegistry(workflow, async () => void 0);
|
|
25745
|
+
const missing = Object.keys(tools).filter((id) => !registeredIds.has(id));
|
|
25746
|
+
if (missing.length > 0) missingByWorkflow[workflow.id] = missing;
|
|
25747
|
+
}
|
|
25748
|
+
if (Object.keys(missingByWorkflow).length > 0) {
|
|
25749
|
+
return {
|
|
25750
|
+
ok: false,
|
|
25751
|
+
error: `unknown daemon tool id(s) referenced by workflow step(s) \u2014 would otherwise fail at STEP-DISPATCH time: ${JSON.stringify(missingByWorkflow)}`
|
|
25752
|
+
};
|
|
25753
|
+
}
|
|
25754
|
+
if (handle.agents.length > 0) {
|
|
25755
|
+
const resolved = resolveAgentAdapter ? await resolveAgentAdapter(DEFAULT_AGENT_ADAPTER) : null;
|
|
25756
|
+
if (!resolved) {
|
|
25757
|
+
return {
|
|
25758
|
+
ok: false,
|
|
25759
|
+
error: `agent adapter "${DEFAULT_AGENT_ADAPTER}" could not be resolved \u2014 run \`agentproto install ${DEFAULT_AGENT_ADAPTER}\` first.`
|
|
25760
|
+
};
|
|
25761
|
+
}
|
|
25762
|
+
}
|
|
25763
|
+
const refs = await readAppRefs(dir);
|
|
25764
|
+
const unvalidatedAgentTools = [
|
|
25765
|
+
...new Set(handle.agents.flatMap((e) => (e.agent.tools ?? []).map(refIdOf)))
|
|
25766
|
+
];
|
|
25767
|
+
const record2 = appRegistry.upsertApp({
|
|
25768
|
+
appId: handle.id,
|
|
25769
|
+
dir,
|
|
25770
|
+
...handle.version ? { version: handle.version } : {},
|
|
25771
|
+
...handle.name ? { name: handle.name } : {},
|
|
25772
|
+
agents: refs.agents,
|
|
25773
|
+
workflows: refs.workflows,
|
|
25774
|
+
unvalidatedAgentTools,
|
|
25775
|
+
...handle.requires ? { requires: handle.requires } : {}
|
|
25776
|
+
});
|
|
25777
|
+
return { ok: true, record: record2 };
|
|
25778
|
+
}
|
|
25779
|
+
function registerAppTools(server, opts) {
|
|
25780
|
+
const { registry, resolveAgentAdapter, listRegisteredToolIds, workflowRunner } = opts;
|
|
25781
|
+
const appRegistry = opts.appRegistry ?? createAppRegistry({
|
|
25782
|
+
...opts.persistPath !== void 0 ? { persistPath: opts.persistPath } : {},
|
|
25783
|
+
...opts.persist !== void 0 ? { persist: opts.persist } : {}
|
|
25784
|
+
});
|
|
25785
|
+
const notEnabled = (tool) => errorResult(
|
|
25786
|
+
`${tool} is not enabled \u2014 the daemon was started without an adapter resolver. Re-run the daemon with the \`@agentproto/cli\` shim wired (see playground/scripts/gateway.ts).`
|
|
25787
|
+
);
|
|
25788
|
+
server.tool(
|
|
25789
|
+
"app_install",
|
|
25790
|
+
"Install an @agentproto/app-kit app from its emitted directory (`<dir>/.agentproto/APP.md` \u2014 see `defineApp().emit(dir)`). Validates every WORKFLOW.md `tool` step's id against the daemon's dispatchable tools (missing ids are reported ALL at once, instead of failing one at a time at STEP-DISPATCH time) and checks the `mastra-agent` adapter resolves. Agent-declared tool refs (workspace tools like `read_file`) are the adapter's own business and are never validated here \u2014 see `unvalidatedAgentTools` on the result. Re-installing the same appId upserts.",
|
|
25791
|
+
{ dir: z.string().describe("Absolute path to the app's directory.") },
|
|
25792
|
+
async (input) => {
|
|
25793
|
+
const result = await performInstall(input.dir, appRegistry, listRegisteredToolIds, resolveAgentAdapter);
|
|
25794
|
+
if (!result.ok) return errorResult(`app_install: ${result.error}`);
|
|
25795
|
+
return textResult(result.record);
|
|
25796
|
+
}
|
|
25797
|
+
);
|
|
25798
|
+
server.tool(
|
|
25799
|
+
"app_list",
|
|
25800
|
+
"List installed apps, each with a summary of its app_run history.",
|
|
25801
|
+
{},
|
|
25802
|
+
async () => {
|
|
25803
|
+
const runs = appRegistry.listRuns();
|
|
25804
|
+
const apps = appRegistry.listApps().map((app) => ({
|
|
25805
|
+
...app,
|
|
25806
|
+
runs: runs.filter((r) => r.appId === app.appId).map((r) => ({
|
|
25807
|
+
appRunId: r.appRunId,
|
|
25808
|
+
status: r.status,
|
|
25809
|
+
startedAt: r.startedAt,
|
|
25810
|
+
...r.endedAt ? { endedAt: r.endedAt } : {},
|
|
25811
|
+
sessions: r.sessions.length
|
|
25812
|
+
}))
|
|
25813
|
+
}));
|
|
25814
|
+
return textResult(apps);
|
|
25815
|
+
}
|
|
25816
|
+
);
|
|
25817
|
+
server.tool(
|
|
25818
|
+
"app_run",
|
|
25819
|
+
"Run an installed app's agents as live sessions \u2014 one `agent_start`-equivalent spawn per selected agent (adapter `mastra-agent`, pointed at that agent's emitted AGENT.md via the adapter's `agent` option), grouped under a fresh appRunId. Re-reads the app's directory first, so a stale install record (paths moved, a workflow renamed) is refreshed before spawning \u2014 the same refreshed paths are what make `workflow_run_file` work against this app's WORKFLOW.md files. Poll with `app_status`, kill with `app_stop`.",
|
|
25820
|
+
{
|
|
25821
|
+
appId: z.string(),
|
|
25822
|
+
agents: z.array(z.string()).optional().describe("Agent ids to run. Omit to run every agent the app bundles."),
|
|
25823
|
+
prompt: z.string().optional().describe("Prompt to send to each spawned agent session."),
|
|
25824
|
+
cwd: z.string().optional().describe("Working directory for spawned sessions. Defaults to the app's installed `dir`."),
|
|
25825
|
+
scopeId: z.string().optional().describe("When passed, refuse to run if the app is not applied to this scope.")
|
|
25826
|
+
// follow-up: no sandbox support in this WP — the e2b image doesn't carry
|
|
25827
|
+
// the mastra-agent adapter yet (see output/phase-a-findings.md A3). Thread
|
|
25828
|
+
// a `sandbox` field through to `spawnAgentSession` here once an image
|
|
25829
|
+
// provisions it (or `app_install`/boot does `agentproto install
|
|
25830
|
+
// mastra-agent` inside the box).
|
|
25831
|
+
},
|
|
25832
|
+
async (input) => {
|
|
25833
|
+
if (!resolveAgentAdapter) return notEnabled("app_run");
|
|
25834
|
+
const installed = appRegistry.getApp(input.appId);
|
|
25835
|
+
if (!installed) {
|
|
25836
|
+
return errorResult(`app_run: no installed app "${input.appId}" \u2014 call app_install first.`);
|
|
25837
|
+
}
|
|
25838
|
+
if (input.scopeId) {
|
|
25839
|
+
const applied = appRegistry.listApplied(input.scopeId);
|
|
25840
|
+
if (!applied.some((m) => m.appId === input.appId)) {
|
|
25841
|
+
return errorResult(
|
|
25842
|
+
`app_run: app "${input.appId}" is not applied to scope "${input.scopeId}". Call app_apply first.`
|
|
25843
|
+
);
|
|
25844
|
+
}
|
|
25845
|
+
}
|
|
25846
|
+
let refs;
|
|
25847
|
+
try {
|
|
25848
|
+
refs = await readAppRefs(installed.dir);
|
|
25849
|
+
} catch (err) {
|
|
25850
|
+
return errorResult(
|
|
25851
|
+
`app_run: could not re-read "${installed.dir}": ${err instanceof Error ? err.message : String(err)}`
|
|
25852
|
+
);
|
|
25853
|
+
}
|
|
25854
|
+
const app = appRegistry.upsertApp({ ...installed, agents: refs.agents, workflows: refs.workflows });
|
|
25855
|
+
const selected = input.agents ?? app.agents.map((a) => a.id);
|
|
25856
|
+
const unknown = selected.filter((id) => !app.agents.some((a) => a.id === id));
|
|
25857
|
+
if (unknown.length > 0) {
|
|
25858
|
+
return errorResult(
|
|
25859
|
+
`app_run: unknown agent id(s) for app "${input.appId}": ${unknown.join(", ")}`
|
|
25860
|
+
);
|
|
25861
|
+
}
|
|
25862
|
+
const sessions = [];
|
|
25863
|
+
const errors = [];
|
|
25864
|
+
for (const agentId of selected) {
|
|
25865
|
+
const agentPath = app.agents.find((a) => a.id === agentId).path;
|
|
25866
|
+
const result = await spawnAgentSession(
|
|
25867
|
+
{ registry, resolveAgentAdapter },
|
|
25868
|
+
{
|
|
25869
|
+
adapter: DEFAULT_AGENT_ADAPTER,
|
|
25870
|
+
cwd: input.cwd ?? app.dir,
|
|
25871
|
+
...input.prompt ? { prompt: input.prompt } : {},
|
|
25872
|
+
options: { agent: agentPath },
|
|
25873
|
+
label: `app:${app.appId}:${agentId}`
|
|
25874
|
+
}
|
|
25875
|
+
);
|
|
25876
|
+
if (result.ok) sessions.push({ agentId, sessionId: result.descriptor.id });
|
|
25877
|
+
else errors.push({ agentId, error: result.message });
|
|
25878
|
+
}
|
|
25879
|
+
const run = appRegistry.createRun({ appId: app.appId, sessions });
|
|
25880
|
+
return textResult({
|
|
25881
|
+
appRunId: run.appRunId,
|
|
25882
|
+
sessions,
|
|
25883
|
+
...errors.length > 0 ? { errors } : {}
|
|
25884
|
+
});
|
|
25885
|
+
}
|
|
25886
|
+
);
|
|
25887
|
+
server.tool(
|
|
25888
|
+
"app_status",
|
|
25889
|
+
"Status of an app_run: its sessions' live descriptors, plus any workflow runs belonging to the app (any run of one of its bundled WORKFLOW.md files, however it was started).",
|
|
25890
|
+
{ appRunId: z.string() },
|
|
25891
|
+
async (input) => {
|
|
25892
|
+
const run = appRegistry.getRun(input.appRunId);
|
|
25893
|
+
if (!run) return errorResult(`app_status: no app run "${input.appRunId}".`);
|
|
25894
|
+
const app = appRegistry.getApp(run.appId);
|
|
25895
|
+
const sessions = run.sessions.map((s) => ({
|
|
25896
|
+
agentId: s.agentId,
|
|
25897
|
+
sessionId: s.sessionId,
|
|
25898
|
+
descriptor: registry.get(s.sessionId)
|
|
25899
|
+
}));
|
|
25900
|
+
const workflowRuns = workflowRunner && app ? workflowRunner.list().filter((r) => app.workflows.some((w) => w.id === r.workflowId)) : [];
|
|
25901
|
+
return textResult({
|
|
25902
|
+
appRunId: run.appRunId,
|
|
25903
|
+
appId: run.appId,
|
|
25904
|
+
status: run.status,
|
|
25905
|
+
startedAt: run.startedAt,
|
|
25906
|
+
...run.endedAt ? { endedAt: run.endedAt } : {},
|
|
25907
|
+
sessions,
|
|
25908
|
+
workflowRuns
|
|
25909
|
+
});
|
|
25910
|
+
}
|
|
25911
|
+
);
|
|
25912
|
+
server.tool(
|
|
25913
|
+
"app_stop",
|
|
25914
|
+
"Kill every session in an app_run (existing kill path) and mark the run ended.",
|
|
25915
|
+
{ appRunId: z.string() },
|
|
25916
|
+
async (input) => {
|
|
25917
|
+
const run = appRegistry.getRun(input.appRunId);
|
|
25918
|
+
if (!run) return errorResult(`app_stop: no app run "${input.appRunId}".`);
|
|
25919
|
+
const killed = [];
|
|
25920
|
+
const notFound = [];
|
|
25921
|
+
for (const s of run.sessions) {
|
|
25922
|
+
if (registry.kill(s.sessionId)) killed.push(s.sessionId);
|
|
25923
|
+
else notFound.push(s.sessionId);
|
|
25924
|
+
}
|
|
25925
|
+
const ended = appRegistry.endRun(input.appRunId);
|
|
25926
|
+
return textResult({
|
|
25927
|
+
appRunId: input.appRunId,
|
|
25928
|
+
killed,
|
|
25929
|
+
...notFound.length > 0 ? { notFound } : {},
|
|
25930
|
+
status: ended?.status ?? run.status
|
|
25931
|
+
});
|
|
25932
|
+
}
|
|
25933
|
+
);
|
|
25934
|
+
server.tool(
|
|
25935
|
+
"app_apply",
|
|
25936
|
+
"Apply an app to a scope, making its capabilities available in that scope. If the app is not installed and `dir` is provided, installs it first. Validates that all `requires` dependencies are already applied to the same scope. Idempotent \u2014 re-applying the same app to the same scope updates the timestamp.",
|
|
25937
|
+
{
|
|
25938
|
+
appId: z.string(),
|
|
25939
|
+
scopeId: z.string().optional().describe("Scope to apply to. Defaults to 'root'."),
|
|
25940
|
+
dir: z.string().optional().describe("Absolute path to install from if not already installed.")
|
|
25941
|
+
},
|
|
25942
|
+
async (input) => {
|
|
25943
|
+
const scopeId = input.scopeId ?? "root";
|
|
25944
|
+
let installed = appRegistry.getApp(input.appId);
|
|
25945
|
+
if (!installed && input.dir) {
|
|
25946
|
+
const installResult = await performInstall(input.dir, appRegistry, listRegisteredToolIds, resolveAgentAdapter);
|
|
25947
|
+
if (!installResult.ok) return errorResult(`app_apply: ${installResult.error}`);
|
|
25948
|
+
installed = installResult.record;
|
|
25949
|
+
} else if (!installed) {
|
|
25950
|
+
return errorResult(
|
|
25951
|
+
`app_apply: app "${input.appId}" is not installed. Either call app_install first or provide a 'dir' parameter.`
|
|
25952
|
+
);
|
|
25953
|
+
}
|
|
25954
|
+
if (installed.requires && installed.requires.length > 0) {
|
|
25955
|
+
const applied = appRegistry.listApplied(scopeId);
|
|
25956
|
+
const appliedIds = new Set(applied.map((m) => m.appId));
|
|
25957
|
+
const missing = installed.requires.filter((reqId) => !appliedIds.has(reqId));
|
|
25958
|
+
if (missing.length > 0) {
|
|
25959
|
+
return errorResult(
|
|
25960
|
+
`app_apply: app "${input.appId}" requires the following apps to be applied to scope "${scopeId}" first: ${missing.join(", ")}`
|
|
25961
|
+
);
|
|
25962
|
+
}
|
|
25963
|
+
}
|
|
25964
|
+
const mount = appRegistry.applyApp({ scopeId, appId: input.appId });
|
|
25965
|
+
return textResult({
|
|
25966
|
+
scopeId: mount.scopeId,
|
|
25967
|
+
appId: mount.appId,
|
|
25968
|
+
appliedAt: mount.appliedAt,
|
|
25969
|
+
agents: installed.agents,
|
|
25970
|
+
workflows: installed.workflows,
|
|
25971
|
+
unvalidatedAgentTools: installed.unvalidatedAgentTools
|
|
25972
|
+
});
|
|
25973
|
+
}
|
|
25974
|
+
);
|
|
25975
|
+
server.tool(
|
|
25976
|
+
"app_unapply",
|
|
25977
|
+
"Remove an app from a scope. Refuses if another applied app in the same scope requires this one.",
|
|
25978
|
+
{
|
|
25979
|
+
appId: z.string(),
|
|
25980
|
+
scopeId: z.string().optional().describe("Scope to unapply from. Defaults to 'root'.")
|
|
25981
|
+
},
|
|
25982
|
+
async (input) => {
|
|
25983
|
+
const scopeId = input.scopeId ?? "root";
|
|
25984
|
+
const applied = appRegistry.listApplied(scopeId);
|
|
25985
|
+
const dependents = [];
|
|
25986
|
+
for (const mount of applied) {
|
|
25987
|
+
if (mount.appId === input.appId) continue;
|
|
25988
|
+
const app = appRegistry.getApp(mount.appId);
|
|
25989
|
+
if (app?.requires?.includes(input.appId)) {
|
|
25990
|
+
dependents.push(mount.appId);
|
|
25991
|
+
}
|
|
25992
|
+
}
|
|
25993
|
+
if (dependents.length > 0) {
|
|
25994
|
+
return errorResult(
|
|
25995
|
+
`app_unapply: cannot unapply app "${input.appId}" from scope "${scopeId}" \u2014 the following apps in this scope require it: ${dependents.join(", ")}`
|
|
25996
|
+
);
|
|
25997
|
+
}
|
|
25998
|
+
const removed = appRegistry.unapplyApp({ scopeId, appId: input.appId });
|
|
25999
|
+
if (!removed) {
|
|
26000
|
+
return errorResult(`app_unapply: app "${input.appId}" is not applied to scope "${scopeId}".`);
|
|
26001
|
+
}
|
|
26002
|
+
return textResult({ scopeId: removed.scopeId, appId: removed.appId, appliedAt: removed.appliedAt });
|
|
26003
|
+
}
|
|
26004
|
+
);
|
|
26005
|
+
server.tool(
|
|
26006
|
+
"app_list_applied",
|
|
26007
|
+
"List applied mounts, optionally filtered by scope. Each mount is joined with its installed app summary.",
|
|
26008
|
+
{
|
|
26009
|
+
scopeId: z.string().optional().describe("Filter by scope. Omit to list all scopes.")
|
|
26010
|
+
},
|
|
26011
|
+
async (input) => {
|
|
26012
|
+
const mounts = appRegistry.listApplied(input.scopeId);
|
|
26013
|
+
const result = mounts.map((mount) => {
|
|
26014
|
+
const app = appRegistry.getApp(mount.appId);
|
|
26015
|
+
return {
|
|
26016
|
+
scopeId: mount.scopeId,
|
|
26017
|
+
appId: mount.appId,
|
|
26018
|
+
appliedAt: mount.appliedAt,
|
|
26019
|
+
...app ? {
|
|
26020
|
+
agents: app.agents,
|
|
26021
|
+
workflows: app.workflows,
|
|
26022
|
+
unvalidatedAgentTools: app.unvalidatedAgentTools
|
|
26023
|
+
} : {}
|
|
26024
|
+
};
|
|
26025
|
+
});
|
|
26026
|
+
return textResult(result);
|
|
26027
|
+
}
|
|
26028
|
+
);
|
|
26029
|
+
}
|
|
25263
26030
|
function createSessionEventBus() {
|
|
25264
26031
|
const ee = new EventEmitter();
|
|
25265
26032
|
ee.setMaxListeners(100);
|
|
@@ -25433,7 +26200,8 @@ var SessionsRegistryAgentHost = class {
|
|
|
25433
26200
|
cwd,
|
|
25434
26201
|
workspaceSlug,
|
|
25435
26202
|
sandbox,
|
|
25436
|
-
label: `agent-step:${adapter}
|
|
26203
|
+
label: `agent-step:${adapter}`,
|
|
26204
|
+
...opts.options !== void 0 ? { options: opts.options } : {}
|
|
25437
26205
|
}
|
|
25438
26206
|
);
|
|
25439
26207
|
if (!result.ok) {
|
|
@@ -25452,7 +26220,8 @@ var SessionsRegistryAgentHost = class {
|
|
|
25452
26220
|
env: {
|
|
25453
26221
|
[SESSION_ID_ENV]: stepSessionId,
|
|
25454
26222
|
[WORKSPACE_SLUG_ENV]: workspaceSlug
|
|
25455
|
-
}
|
|
26223
|
+
},
|
|
26224
|
+
...opts.options !== void 0 ? { options: opts.options } : {}
|
|
25456
26225
|
});
|
|
25457
26226
|
const desc = this.registry.spawnAgent({
|
|
25458
26227
|
id: stepSessionId,
|
|
@@ -25648,16 +26417,32 @@ function translateStages(stages, workflowId) {
|
|
|
25648
26417
|
const branches = stage.steps.map((step) => ({
|
|
25649
26418
|
id: step.label,
|
|
25650
26419
|
steps: [
|
|
25651
|
-
{
|
|
25652
|
-
|
|
25653
|
-
|
|
26420
|
+
buildAgentStep(step.label, {
|
|
26421
|
+
prompt: (b) => {
|
|
26422
|
+
const base = step.prompt ?? "";
|
|
26423
|
+
const prevTexts = [];
|
|
26424
|
+
if (b.steps && typeof b.steps === "object") {
|
|
26425
|
+
for (const [id, val] of Object.entries(b.steps)) {
|
|
26426
|
+
if (val && typeof val === "object" && "text" in val) {
|
|
26427
|
+
const text9 = val.text;
|
|
26428
|
+
if (text9) prevTexts.push(`[Output from step "${id}"]
|
|
26429
|
+
${text9}`);
|
|
26430
|
+
}
|
|
26431
|
+
}
|
|
26432
|
+
}
|
|
26433
|
+
if (prevTexts.length > 0) return `${prevTexts.join("\n\n")}
|
|
26434
|
+
|
|
26435
|
+
---
|
|
26436
|
+
|
|
26437
|
+
${base}`;
|
|
26438
|
+
return base;
|
|
26439
|
+
},
|
|
25654
26440
|
...step.adapter !== void 0 ? { adapter: step.adapter } : {},
|
|
25655
26441
|
...step.sessionRef !== void 0 ? { sessionRef: step.sessionRef } : {},
|
|
25656
26442
|
...step.sandbox !== void 0 ? { sandbox: step.sandbox } : {},
|
|
25657
26443
|
...step.cacheable ? { cacheable: true } : {},
|
|
25658
|
-
|
|
25659
|
-
|
|
25660
|
-
}
|
|
26444
|
+
policy: step.policy
|
|
26445
|
+
})
|
|
25661
26446
|
]
|
|
25662
26447
|
}));
|
|
25663
26448
|
return {
|
|
@@ -25710,7 +26495,7 @@ function runtimeWorkflowToStages(workflow) {
|
|
|
25710
26495
|
}
|
|
25711
26496
|
];
|
|
25712
26497
|
}
|
|
25713
|
-
var
|
|
26498
|
+
var DEFAULT_PERSIST_PATH3 = () => join(homedir(), ".agentproto", "workflow-runs.json");
|
|
25714
26499
|
function loadRuns(persistPath) {
|
|
25715
26500
|
const result = /* @__PURE__ */ new Map();
|
|
25716
26501
|
if (!existsSync(persistPath)) return result;
|
|
@@ -25825,7 +26610,7 @@ function fillStepStates(stages, defs, agents) {
|
|
|
25825
26610
|
}
|
|
25826
26611
|
return sessionIds;
|
|
25827
26612
|
}
|
|
25828
|
-
async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cacheKey, input) {
|
|
26613
|
+
async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cacheKey, input, persist) {
|
|
25829
26614
|
try {
|
|
25830
26615
|
await runWorkflow({
|
|
25831
26616
|
workflow: runtimeWf,
|
|
@@ -25834,13 +26619,49 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cache
|
|
|
25834
26619
|
cwd: state.cwd,
|
|
25835
26620
|
workspaceSlug: state.workspaceSlug,
|
|
25836
26621
|
input,
|
|
25837
|
-
...cache ? { cache, cacheKey } : {}
|
|
26622
|
+
...cache ? { cache, cacheKey } : {},
|
|
26623
|
+
onStepStart: (stepId) => {
|
|
26624
|
+
for (const stage of state.run.stages) {
|
|
26625
|
+
const step = stage.steps.find((s) => s.label === stepId);
|
|
26626
|
+
if (step) {
|
|
26627
|
+
if (step.status === "pending") {
|
|
26628
|
+
step.status = "running";
|
|
26629
|
+
step.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26630
|
+
}
|
|
26631
|
+
if (stage.status === "pending") {
|
|
26632
|
+
stage.status = "running";
|
|
26633
|
+
}
|
|
26634
|
+
persist?.();
|
|
26635
|
+
break;
|
|
26636
|
+
}
|
|
26637
|
+
}
|
|
26638
|
+
},
|
|
26639
|
+
onStepComplete: (stepId, output) => {
|
|
26640
|
+
for (const stage of state.run.stages) {
|
|
26641
|
+
const step = stage.steps.find((s) => s.label === stepId);
|
|
26642
|
+
if (step) {
|
|
26643
|
+
step.status = "done";
|
|
26644
|
+
step.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26645
|
+
if (output && typeof output === "object" && "sessionId" in output) {
|
|
26646
|
+
step.sessionId = output.sessionId;
|
|
26647
|
+
}
|
|
26648
|
+
const allDone = stage.steps.every((s) => s.status === "done");
|
|
26649
|
+
if (allDone) {
|
|
26650
|
+
stage.status = "done";
|
|
26651
|
+
}
|
|
26652
|
+
persist?.();
|
|
26653
|
+
break;
|
|
26654
|
+
}
|
|
26655
|
+
}
|
|
26656
|
+
}
|
|
25838
26657
|
});
|
|
25839
26658
|
for (const stage of state.run.stages) {
|
|
25840
|
-
stage.status = "done";
|
|
26659
|
+
if (stage.status !== "done") stage.status = "done";
|
|
25841
26660
|
for (const step of stage.steps) {
|
|
25842
|
-
step.status
|
|
25843
|
-
|
|
26661
|
+
if (step.status !== "done") {
|
|
26662
|
+
step.status = "done";
|
|
26663
|
+
step.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26664
|
+
}
|
|
25844
26665
|
}
|
|
25845
26666
|
}
|
|
25846
26667
|
state.run.status = "done";
|
|
@@ -25875,7 +26696,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cache
|
|
|
25875
26696
|
}
|
|
25876
26697
|
function createWorkflowRunner(opts) {
|
|
25877
26698
|
const { registry, sessionEvents, resolveAgentAdapter, compileWorkflow: compileWorkflow2 } = opts;
|
|
25878
|
-
const persistPath = opts.persistPath ??
|
|
26699
|
+
const persistPath = opts.persistPath ?? DEFAULT_PERSIST_PATH3();
|
|
25879
26700
|
const shouldPersist = opts.persist ?? opts.persistPath !== void 0;
|
|
25880
26701
|
const runs = shouldPersist ? loadRuns(persistPath) : /* @__PURE__ */ new Map();
|
|
25881
26702
|
const persist = () => {
|
|
@@ -25926,7 +26747,7 @@ function createWorkflowRunner(opts) {
|
|
|
25926
26747
|
}
|
|
25927
26748
|
);
|
|
25928
26749
|
const cache = input.cacheKey ? createFileStepCache(input.cacheKey) : void 0;
|
|
25929
|
-
void executeRunWorkflow(state, workflow, agents, abort.signal, cache, input.cacheKey).then(() => {
|
|
26750
|
+
void executeRunWorkflow(state, workflow, agents, abort.signal, cache, input.cacheKey, void 0, persist).then(() => {
|
|
25930
26751
|
persist();
|
|
25931
26752
|
});
|
|
25932
26753
|
return run;
|
|
@@ -25987,7 +26808,8 @@ function createWorkflowRunner(opts) {
|
|
|
25987
26808
|
abort.signal,
|
|
25988
26809
|
cache,
|
|
25989
26810
|
args.cacheKey,
|
|
25990
|
-
args.input
|
|
26811
|
+
args.input,
|
|
26812
|
+
persist
|
|
25991
26813
|
).then(() => {
|
|
25992
26814
|
persist();
|
|
25993
26815
|
});
|
|
@@ -26620,7 +27442,7 @@ function toDescriptor2(state) {
|
|
|
26620
27442
|
spawned: state.spawned
|
|
26621
27443
|
};
|
|
26622
27444
|
}
|
|
26623
|
-
var
|
|
27445
|
+
var DEFAULT_PERSIST_PATH4 = () => join(homedir(), ".agentproto", "cron-jobs.json");
|
|
26624
27446
|
var TICK_INTERVAL_MS = 2e4;
|
|
26625
27447
|
function loadJobs(persistPath) {
|
|
26626
27448
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -26678,7 +27500,7 @@ function nextFireDate(cronInstance) {
|
|
|
26678
27500
|
}
|
|
26679
27501
|
function createCronScheduler(opts) {
|
|
26680
27502
|
const { sessionEvents, registry, resolveAgentAdapter, dispatchTool, workspace } = opts;
|
|
26681
|
-
const persistPath = opts.persistPath ??
|
|
27503
|
+
const persistPath = opts.persistPath ?? DEFAULT_PERSIST_PATH4();
|
|
26682
27504
|
const shouldPersist = opts.persist ?? opts.persistPath !== void 0;
|
|
26683
27505
|
const jobs = shouldPersist ? loadJobs(persistPath) : /* @__PURE__ */ new Map();
|
|
26684
27506
|
for (const state of jobs.values()) {
|
|
@@ -27123,84 +27945,16 @@ function createRoutineRegistrar(opts) {
|
|
|
27123
27945
|
}
|
|
27124
27946
|
};
|
|
27125
27947
|
}
|
|
27126
|
-
function collectToolIds(steps, out) {
|
|
27127
|
-
for (const step of steps) {
|
|
27128
|
-
switch (step.kind) {
|
|
27129
|
-
case "tool": {
|
|
27130
|
-
const ref = typeof step.tool === "string" ? step.tool : step.tool.entry;
|
|
27131
|
-
out.add(normalizeToolId(ref));
|
|
27132
|
-
break;
|
|
27133
|
-
}
|
|
27134
|
-
case "map":
|
|
27135
|
-
case "loop":
|
|
27136
|
-
collectToolIds(step.steps, out);
|
|
27137
|
-
break;
|
|
27138
|
-
case "parallel":
|
|
27139
|
-
for (const branch of step.branches) collectToolIds(branch.steps, out);
|
|
27140
|
-
break;
|
|
27141
|
-
}
|
|
27142
|
-
}
|
|
27143
|
-
}
|
|
27144
|
-
function passthroughToolHandle(id) {
|
|
27145
|
-
return {
|
|
27146
|
-
id,
|
|
27147
|
-
name: id,
|
|
27148
|
-
description: `Daemon tool '${id}', dispatched via the routine/cron dispatchTool bridge.`,
|
|
27149
|
-
mutates: [],
|
|
27150
|
-
requires: { network: [], secrets: [], tools: [] },
|
|
27151
|
-
approval: "auto",
|
|
27152
|
-
riskLevel: 0,
|
|
27153
|
-
costClass: "trivial",
|
|
27154
|
-
timeoutMs: 3e4,
|
|
27155
|
-
tags: [],
|
|
27156
|
-
metadata: {},
|
|
27157
|
-
idempotent: false,
|
|
27158
|
-
driverConstraints: { forbid: [], requireKind: [] }
|
|
27159
|
-
};
|
|
27160
|
-
}
|
|
27161
|
-
function unwrapDispatchResult(result) {
|
|
27162
|
-
if (!result || typeof result !== "object" || !("content" in result)) return result;
|
|
27163
|
-
const r = result;
|
|
27164
|
-
const text9 = (r.content ?? []).map((c) => c.text ?? "").join("\n").trim();
|
|
27165
|
-
if (r.isError) throw new Error(text9 || "tool call failed");
|
|
27166
|
-
if (!text9) return void 0;
|
|
27167
|
-
try {
|
|
27168
|
-
return JSON.parse(text9);
|
|
27169
|
-
} catch {
|
|
27170
|
-
return text9;
|
|
27171
|
-
}
|
|
27172
|
-
}
|
|
27173
|
-
function createDaemonToolRegistry(handle, dispatchTool) {
|
|
27174
|
-
const toolIds = /* @__PURE__ */ new Set();
|
|
27175
|
-
collectToolIds(handle.steps, toolIds);
|
|
27176
|
-
const tools = {};
|
|
27177
|
-
const execute = {};
|
|
27178
|
-
for (const id of toolIds) {
|
|
27179
|
-
tools[id] = passthroughToolHandle(id);
|
|
27180
|
-
execute[id] = async ({ input }) => unwrapDispatchResult(await dispatchTool(id, input ?? {}));
|
|
27181
|
-
}
|
|
27182
|
-
if (toolIds.size === 0) return { tools, candidates: [] };
|
|
27183
|
-
const daemonDriver = {
|
|
27184
|
-
id: "daemon-tool-dispatch",
|
|
27185
|
-
name: "Daemon tool dispatch",
|
|
27186
|
-
description: "Catch-all AIP-30 provider routing every referenced WORKFLOW.md tool step through the daemon's dispatchTool.",
|
|
27187
|
-
kind: "builtin",
|
|
27188
|
-
implements: [...toolIds].map((id) => ({ tool: id, version: "*" })),
|
|
27189
|
-
execute,
|
|
27190
|
-
install: [],
|
|
27191
|
-
network: { egress: [], ingress: [] },
|
|
27192
|
-
region: ["global"],
|
|
27193
|
-
policyTags: [],
|
|
27194
|
-
tags: [],
|
|
27195
|
-
metadata: {}
|
|
27196
|
-
};
|
|
27197
|
-
return { tools, candidates: [daemonDriver] };
|
|
27198
|
-
}
|
|
27199
27948
|
var DEFAULT_ORCHESTRATOR_TOOLS = [
|
|
27200
27949
|
"agent_start",
|
|
27201
27950
|
"agent_prompt",
|
|
27202
27951
|
"agent_output",
|
|
27203
27952
|
"agent_kill",
|
|
27953
|
+
// Child→parent report-back. NOT a delegation tool (takes no session id;
|
|
27954
|
+
// the daemon resolves the caller's own recorded parent, and it can reach
|
|
27955
|
+
// nothing else) — it's also the sole tool of the minimal report-only
|
|
27956
|
+
// scope `session-spawn.ts` mints for a gateway-less child with a parent.
|
|
27957
|
+
"message_parent",
|
|
27204
27958
|
"session_monitor",
|
|
27205
27959
|
"session_events_poll",
|
|
27206
27960
|
"session_list",
|
|
@@ -30776,6 +31530,13 @@ async function createGateway(opts) {
|
|
|
30776
31530
|
}
|
|
30777
31531
|
return dispatchToolBox.fn(name, inputs);
|
|
30778
31532
|
};
|
|
31533
|
+
const listToolIdsBox = {};
|
|
31534
|
+
const listRegisteredToolIds = async () => {
|
|
31535
|
+
if (!listToolIdsBox.fn) {
|
|
31536
|
+
throw new Error("app_install: tool registry not ready yet (daemon still booting)");
|
|
31537
|
+
}
|
|
31538
|
+
return listToolIdsBox.fn();
|
|
31539
|
+
};
|
|
30779
31540
|
const cronScheduler = createCronScheduler({
|
|
30780
31541
|
sessionEvents,
|
|
30781
31542
|
registry: sessions,
|
|
@@ -30789,6 +31550,7 @@ async function createGateway(opts) {
|
|
|
30789
31550
|
cronScheduler,
|
|
30790
31551
|
dispatchTool
|
|
30791
31552
|
});
|
|
31553
|
+
const appRegistry = createAppRegistry();
|
|
30792
31554
|
const workflowRunner = opts.resolveAgentAdapter ? createWorkflowRunner({
|
|
30793
31555
|
registry: sessions,
|
|
30794
31556
|
sessionEvents,
|
|
@@ -30805,7 +31567,13 @@ async function createGateway(opts) {
|
|
|
30805
31567
|
// the same `dispatchTool` the routine registrar / cron scheduler use
|
|
30806
31568
|
// (see `workflow-tool-registry.ts`). Agent-step workflows are
|
|
30807
31569
|
// unaffected: an empty tool registry compiles them exactly as before.
|
|
30808
|
-
|
|
31570
|
+
// `agentRefs` resolves a declarative agent-step's `agent.ref` against
|
|
31571
|
+
// whichever installed app bundles this workflow id (undefined when
|
|
31572
|
+
// none does — a plain `workflow_run_file` outside any app).
|
|
31573
|
+
compileWorkflow: (handle) => compileWorkflow(handle, {
|
|
31574
|
+
...createDaemonToolRegistry(handle, dispatchTool),
|
|
31575
|
+
agentRefs: resolveAgentRefsForWorkflow(appRegistry, handle.id)
|
|
31576
|
+
})
|
|
30809
31577
|
}) : void 0;
|
|
30810
31578
|
const operatorWorkspaceSlug = await loadWorkspacesConfig().then((cfg) => getActiveWorkspace(cfg)?.slug).catch(() => void 0);
|
|
30811
31579
|
const taskLedger = createTaskLedger({
|
|
@@ -30981,6 +31749,13 @@ async function createGateway(opts) {
|
|
|
30981
31749
|
endpointStore: inboundEndpointStore,
|
|
30982
31750
|
telegramCreds: telegramBotCreds
|
|
30983
31751
|
});
|
|
31752
|
+
registerAppTools(server, {
|
|
31753
|
+
registry: sessions,
|
|
31754
|
+
listRegisteredToolIds,
|
|
31755
|
+
appRegistry,
|
|
31756
|
+
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
31757
|
+
...workflowRunner ? { workflowRunner } : {}
|
|
31758
|
+
});
|
|
30984
31759
|
registerTelegramBotTools(server, { telegramCreds: telegramBotCreds });
|
|
30985
31760
|
const listSessionsFiltered = (filter) => {
|
|
30986
31761
|
let rows = sessions.list().filter((s) => s.kind !== "command");
|
|
@@ -31078,6 +31853,12 @@ async function createGateway(opts) {
|
|
|
31078
31853
|
}
|
|
31079
31854
|
return tool.handler(inputs, {});
|
|
31080
31855
|
};
|
|
31856
|
+
listToolIdsBox.fn = async () => {
|
|
31857
|
+
if (!internalToolServerPromise) internalToolServerPromise = mcpServerFactory();
|
|
31858
|
+
const internalServer = await internalToolServerPromise;
|
|
31859
|
+
const internal = internalServer;
|
|
31860
|
+
return Object.keys(internal._registeredTools ?? {});
|
|
31861
|
+
};
|
|
31081
31862
|
try {
|
|
31082
31863
|
routineRegistrar.reconcile();
|
|
31083
31864
|
} catch (err) {
|
|
@@ -31136,6 +31917,9 @@ async function createGateway(opts) {
|
|
|
31136
31917
|
activityProjector,
|
|
31137
31918
|
taskLedger,
|
|
31138
31919
|
...workflowRunner ? { workflowRunner } : {},
|
|
31920
|
+
appRegistry,
|
|
31921
|
+
performAppInstall: performInstall,
|
|
31922
|
+
listRegisteredToolIds,
|
|
31139
31923
|
// POST /inbound push ingress — same shared transmitterBindings store
|
|
31140
31924
|
// and liveness/restart adapters the inbound watcher's "route"/
|
|
31141
31925
|
// "route-or-spawn" modes use above. No `spawnForContact`: an
|