@agentproto/cli 0.1.0 → 0.1.2
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/cli.mjs +807 -171
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.mjs +706 -94
- package/dist/index.mjs.map +1 -1
- package/dist/registry/builtins.mjs +78 -54
- package/dist/registry/builtins.mjs.map +1 -1
- package/dist/registry/manifest.mjs +1 -1
- package/dist/registry/plugins.mjs +1 -1
- package/dist/registry/runtime.mjs +1 -1
- package/dist/util/credentials.mjs +1 -1
- package/package.json +7 -5
package/dist/index.mjs
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import * as childProc from 'child_process';
|
|
2
2
|
import { execFile, spawn } from 'child_process';
|
|
3
3
|
import { createHash, randomUUID, randomBytes } from 'crypto';
|
|
4
|
-
import {
|
|
4
|
+
import { rm, readFile, mkdir, writeFile, readdir, mkdtemp, chmod, unlink, stat } from 'fs/promises';
|
|
5
5
|
import { tmpdir, userInfo, hostname, homedir, platform } from 'os';
|
|
6
|
-
import {
|
|
6
|
+
import { isAbsolute, join, dirname, resolve, normalize as normalize$1, relative, basename } from 'path';
|
|
7
7
|
import { promisify, parseArgs } from 'util';
|
|
8
8
|
import { promises, existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
9
10
|
import { createInterface } from 'readline/promises';
|
|
10
11
|
import { stdout, stdin } from 'process';
|
|
11
12
|
import { createAgentCliRuntime } from '@agentproto/driver-agent-cli';
|
|
12
|
-
import { wrapWebSocket, createTunnelServer } from '@agentproto/acp/tunnel';
|
|
13
|
+
import { wrapWebSocket, createTunnelServer, DEFAULT_WS_DIAL_TIMEOUT_MS, DEFAULT_HTTP_FORWARD_TIMEOUT_MS } from '@agentproto/acp/tunnel';
|
|
13
14
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
15
|
import '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
16
|
import { z } from 'zod';
|
|
16
17
|
import matter from 'gray-matter';
|
|
18
|
+
import { driverSpec } from '@agentproto/driver';
|
|
17
19
|
import { EventEmitter } from 'events';
|
|
18
20
|
import { createServer } from 'http';
|
|
19
21
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
@@ -24,7 +26,7 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
|
|
24
26
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
|
-
* @agentproto/cli v0.1.
|
|
29
|
+
* @agentproto/cli v0.1.2
|
|
28
30
|
* The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
|
|
29
31
|
*/
|
|
30
32
|
|
|
@@ -36,24 +38,37 @@ async function resolveAdapter(slug) {
|
|
|
36
38
|
);
|
|
37
39
|
}
|
|
38
40
|
const packageName = `@agentproto/adapter-${slug}`;
|
|
41
|
+
const camel = slugToCamel(slug);
|
|
39
42
|
let mod;
|
|
43
|
+
let resolvedPackageName = packageName;
|
|
40
44
|
try {
|
|
41
45
|
mod = await import(packageName);
|
|
42
|
-
} catch (
|
|
43
|
-
const
|
|
46
|
+
} catch (primaryErr) {
|
|
47
|
+
const hyphen = slug.lastIndexOf("-");
|
|
48
|
+
if (hyphen > 0) {
|
|
49
|
+
const parentPkg = `@agentproto/adapter-${slug.slice(0, hyphen)}`;
|
|
50
|
+
try {
|
|
51
|
+
const parentMod = await import(parentPkg);
|
|
52
|
+
const parentCandidate = parentMod[camel];
|
|
53
|
+
if (parentCandidate && typeof parentCandidate === "object" && "name" in parentCandidate) {
|
|
54
|
+
return { slug, handle: parentCandidate, source: "npm", packageName: parentPkg };
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const cause = primaryErr instanceof Error ? primaryErr.message : String(primaryErr);
|
|
44
60
|
throw new Error(
|
|
45
61
|
`agentproto: could not load adapter '${slug}'. Tried '${packageName}'. Install it with: npm i -g ${packageName}
|
|
46
62
|
cause: ${cause}`
|
|
47
63
|
);
|
|
48
64
|
}
|
|
49
|
-
const camel = slugToCamel(slug);
|
|
50
65
|
const candidate = mod[camel] ?? mod.default ?? mod.handle;
|
|
51
66
|
if (!candidate || typeof candidate !== "object" || !("name" in candidate)) {
|
|
52
67
|
throw new Error(
|
|
53
|
-
`agentproto: adapter '${
|
|
68
|
+
`agentproto: adapter '${resolvedPackageName}' does not export an AgentCliHandle (looked for export '${camel}', 'default', or 'handle').`
|
|
54
69
|
);
|
|
55
70
|
}
|
|
56
|
-
return { slug, handle: candidate, source: "npm", packageName };
|
|
71
|
+
return { slug, handle: candidate, source: "npm", packageName: resolvedPackageName };
|
|
57
72
|
}
|
|
58
73
|
async function listInstalledAdapters(opts) {
|
|
59
74
|
const roots = await collectAgentprotoNamespaceRoots(opts?.searchRoot);
|
|
@@ -74,6 +89,7 @@ async function listInstalledAdapters(opts) {
|
|
|
74
89
|
try {
|
|
75
90
|
const resolved = await resolveAdapter(slug);
|
|
76
91
|
const handle = resolved.handle;
|
|
92
|
+
const modelsField = handle.models?.allowed;
|
|
77
93
|
const info = {
|
|
78
94
|
slug,
|
|
79
95
|
name: typeof handle.name === "string" ? handle.name : slug,
|
|
@@ -81,7 +97,9 @@ async function listInstalledAdapters(opts) {
|
|
|
81
97
|
description: typeof handle.description === "string" ? handle.description : "",
|
|
82
98
|
protocol: typeof handle.protocol === "string" ? handle.protocol : "unknown",
|
|
83
99
|
streaming: !!handle.capabilities?.streaming,
|
|
84
|
-
packageName: resolved.packageName ?? `@agentproto/adapter-${slug}
|
|
100
|
+
packageName: resolved.packageName ?? `@agentproto/adapter-${slug}`,
|
|
101
|
+
commands: Array.isArray(handle.commands) ? handle.commands : [],
|
|
102
|
+
models: Array.isArray(modelsField) ? modelsField.filter((m) => typeof m === "string") : []
|
|
85
103
|
};
|
|
86
104
|
out.push(info);
|
|
87
105
|
} catch (err) {
|
|
@@ -97,26 +115,95 @@ async function listInstalledAdapters(opts) {
|
|
|
97
115
|
return out.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
98
116
|
}
|
|
99
117
|
async function collectAgentprotoNamespaceRoots(start) {
|
|
118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
100
119
|
const roots = [];
|
|
101
|
-
let cur = start ?? process.cwd();
|
|
102
120
|
const candidatesAt = (dir) => [
|
|
103
121
|
resolve(dir, "node_modules", "@agentproto"),
|
|
104
122
|
resolve(dir, "node_modules", ".pnpm", "node_modules", "@agentproto")
|
|
105
123
|
];
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
async function walkUp(from) {
|
|
125
|
+
let cur = from;
|
|
126
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
127
|
+
if (seen.has(cur)) break;
|
|
128
|
+
seen.add(cur);
|
|
129
|
+
for (const candidate of candidatesAt(cur)) {
|
|
130
|
+
try {
|
|
131
|
+
const s = await promises.stat(candidate);
|
|
132
|
+
if (s.isDirectory() && !roots.includes(candidate)) roots.push(candidate);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
112
135
|
}
|
|
136
|
+
const parent = resolve(cur, "..");
|
|
137
|
+
if (parent === cur) break;
|
|
138
|
+
cur = parent;
|
|
113
139
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
}
|
|
141
|
+
await walkUp(start ?? process.cwd());
|
|
142
|
+
try {
|
|
143
|
+
await walkUp(dirname(fileURLToPath(import.meta.url)));
|
|
144
|
+
} catch {
|
|
117
145
|
}
|
|
118
146
|
return roots;
|
|
119
147
|
}
|
|
148
|
+
async function listAdaptersWithCatalog(catalog) {
|
|
149
|
+
const seenSlugs = /* @__PURE__ */ new Set();
|
|
150
|
+
const out = [];
|
|
151
|
+
const agentprotoHome = process.env["AGENTPROTO_HOME"] ?? join(homedir(), ".agentproto");
|
|
152
|
+
async function hasSetupLedger(slug) {
|
|
153
|
+
try {
|
|
154
|
+
await promises.access(join(agentprotoHome, "setup", `${slug}.json`));
|
|
155
|
+
return true;
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const entry of catalog) {
|
|
161
|
+
seenSlugs.add(entry.slug);
|
|
162
|
+
try {
|
|
163
|
+
const resolved = await resolveAdapter(entry.slug);
|
|
164
|
+
const handle = resolved.handle;
|
|
165
|
+
const setupSteps = Array.isArray(handle.setup) ? handle.setup : [];
|
|
166
|
+
const ledgerPresent = await hasSetupLedger(entry.slug);
|
|
167
|
+
const status = setupSteps.length === 0 || ledgerPresent ? "ready" : "available";
|
|
168
|
+
const catalogModels = handle.models?.allowed;
|
|
169
|
+
out.push({
|
|
170
|
+
slug: entry.slug,
|
|
171
|
+
name: typeof handle.name === "string" ? handle.name : entry.name,
|
|
172
|
+
version: typeof handle.version === "string" ? handle.version : "?",
|
|
173
|
+
description: typeof handle.description === "string" ? handle.description : entry.description,
|
|
174
|
+
protocol: typeof handle.protocol === "string" ? handle.protocol : "unknown",
|
|
175
|
+
streaming: !!handle.capabilities?.streaming,
|
|
176
|
+
packageName: resolved.packageName ?? entry.packageName,
|
|
177
|
+
commands: Array.isArray(handle.commands) ? handle.commands : [],
|
|
178
|
+
models: Array.isArray(catalogModels) ? catalogModels.filter((m) => typeof m === "string") : [],
|
|
179
|
+
status,
|
|
180
|
+
hint: entry.hint
|
|
181
|
+
});
|
|
182
|
+
} catch {
|
|
183
|
+
out.push({
|
|
184
|
+
slug: entry.slug,
|
|
185
|
+
name: entry.name,
|
|
186
|
+
version: "not installed",
|
|
187
|
+
description: entry.description,
|
|
188
|
+
protocol: "unknown",
|
|
189
|
+
streaming: false,
|
|
190
|
+
packageName: entry.packageName,
|
|
191
|
+
commands: [],
|
|
192
|
+
models: [],
|
|
193
|
+
status: "supported",
|
|
194
|
+
hint: entry.hint
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const installed = await listInstalledAdapters();
|
|
199
|
+
for (const info of installed) {
|
|
200
|
+
if (seenSlugs.has(info.slug)) continue;
|
|
201
|
+
seenSlugs.add(info.slug);
|
|
202
|
+
const ledgerPresent = await hasSetupLedger(info.slug);
|
|
203
|
+
out.push({ ...info, status: ledgerPresent ? "ready" : "available" });
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
120
207
|
async function runSetup(opts) {
|
|
121
208
|
const steps = opts.handle.setup ?? [];
|
|
122
209
|
if (steps.length === 0) {
|
|
@@ -263,10 +350,10 @@ async function runExternalStep(step, ledger, head) {
|
|
|
263
350
|
`);
|
|
264
351
|
const opener = platform() === "darwin" ? "open" : platform() === "win32" ? "cmd" : "xdg-open";
|
|
265
352
|
const args = platform() === "win32" ? ["/c", "start", step.url] : [step.url];
|
|
266
|
-
await new Promise((
|
|
353
|
+
await new Promise((resolve5) => {
|
|
267
354
|
const child = spawn(opener, args, { stdio: "ignore", detached: true });
|
|
268
|
-
child.once("error", () =>
|
|
269
|
-
child.once("spawn", () =>
|
|
355
|
+
child.once("error", () => resolve5());
|
|
356
|
+
child.once("spawn", () => resolve5());
|
|
270
357
|
});
|
|
271
358
|
let value = "";
|
|
272
359
|
if (step.callback?.param) {
|
|
@@ -339,7 +426,7 @@ async function promptString(question, opts) {
|
|
|
339
426
|
process.stdin.setRawMode?.(true);
|
|
340
427
|
let buf = "";
|
|
341
428
|
process.stdout.write(prompt);
|
|
342
|
-
return new Promise((
|
|
429
|
+
return new Promise((resolve5) => {
|
|
343
430
|
const onData = (chunk) => {
|
|
344
431
|
for (const code of chunk) {
|
|
345
432
|
if (code === 13 || code === 10) {
|
|
@@ -347,7 +434,7 @@ async function promptString(question, opts) {
|
|
|
347
434
|
process.stdin.setRawMode?.(false);
|
|
348
435
|
process.stdout.write("\n");
|
|
349
436
|
rl.close();
|
|
350
|
-
|
|
437
|
+
resolve5(buf || opts.defaultValue || "");
|
|
351
438
|
return;
|
|
352
439
|
}
|
|
353
440
|
if (code === 3) {
|
|
@@ -429,7 +516,7 @@ async function resolveSelectOptions(options) {
|
|
|
429
516
|
});
|
|
430
517
|
}
|
|
431
518
|
async function runShellCapturing(cmd, opts) {
|
|
432
|
-
return new Promise((
|
|
519
|
+
return new Promise((resolve5) => {
|
|
433
520
|
const child = spawn("bash", ["-lc", cmd], {
|
|
434
521
|
stdio: opts.interactive ? "inherit" : ["ignore", "pipe", "pipe"]
|
|
435
522
|
});
|
|
@@ -447,11 +534,11 @@ async function runShellCapturing(cmd, opts) {
|
|
|
447
534
|
}, opts.timeoutMs);
|
|
448
535
|
child.once("error", () => {
|
|
449
536
|
clearTimeout(timer);
|
|
450
|
-
|
|
537
|
+
resolve5({ exitCode: 127, stdout, stderr: stderr || "spawn error" });
|
|
451
538
|
});
|
|
452
539
|
child.once("exit", (code) => {
|
|
453
540
|
clearTimeout(timer);
|
|
454
|
-
|
|
541
|
+
resolve5({ exitCode: code ?? 0, stdout, stderr });
|
|
455
542
|
});
|
|
456
543
|
});
|
|
457
544
|
}
|
|
@@ -1086,7 +1173,7 @@ async function runDownloadInstaller(opts) {
|
|
|
1086
1173
|
}
|
|
1087
1174
|
if (extractCode !== 0) return extractCode;
|
|
1088
1175
|
const srcBin = join(extractDir, opts.extractBin);
|
|
1089
|
-
const binDir = process.env["AGENTPROTO_BIN_DIR"] ?? join(
|
|
1176
|
+
const binDir = process.env["AGENTPROTO_BIN_DIR"] ?? join(homedir4(), ".local", "bin");
|
|
1090
1177
|
await mkdir(binDir, { recursive: true });
|
|
1091
1178
|
const destBin = join(binDir, opts.extractBin.split("/").pop());
|
|
1092
1179
|
const cpCode = await spawnInherit("cp", ["-p", srcBin, destBin]);
|
|
@@ -1112,19 +1199,19 @@ async function runDownloadInstaller(opts) {
|
|
|
1112
1199
|
});
|
|
1113
1200
|
}
|
|
1114
1201
|
}
|
|
1115
|
-
function
|
|
1202
|
+
function homedir4() {
|
|
1116
1203
|
return process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/";
|
|
1117
1204
|
}
|
|
1118
1205
|
function spawnInherit(cmd, argv) {
|
|
1119
|
-
return new Promise((
|
|
1206
|
+
return new Promise((resolve5, reject) => {
|
|
1120
1207
|
const child = spawn(cmd, argv, { stdio: "inherit" });
|
|
1121
1208
|
child.once("error", reject);
|
|
1122
|
-
child.once("exit", (code) =>
|
|
1209
|
+
child.once("exit", (code) => resolve5(code ?? 0));
|
|
1123
1210
|
});
|
|
1124
1211
|
}
|
|
1125
1212
|
async function runVersionCheck(check) {
|
|
1126
1213
|
if (!check) return { ok: false, message: "no version_check declared" };
|
|
1127
|
-
return new Promise((
|
|
1214
|
+
return new Promise((resolve5) => {
|
|
1128
1215
|
const child = spawn("bash", ["-lc", check.cmd], {
|
|
1129
1216
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1130
1217
|
});
|
|
@@ -1132,19 +1219,19 @@ async function runVersionCheck(check) {
|
|
|
1132
1219
|
child.stdout.on("data", (c) => {
|
|
1133
1220
|
buf += c.toString("utf8");
|
|
1134
1221
|
});
|
|
1135
|
-
child.once("error", () =>
|
|
1222
|
+
child.once("error", () => resolve5({ ok: false, message: "check failed" }));
|
|
1136
1223
|
child.once("exit", (code) => {
|
|
1137
1224
|
if (code !== 0) {
|
|
1138
|
-
|
|
1225
|
+
resolve5({ ok: false, message: `check exited ${code}` });
|
|
1139
1226
|
return;
|
|
1140
1227
|
}
|
|
1141
1228
|
const re = new RegExp(check.parse);
|
|
1142
1229
|
const m = buf.match(re);
|
|
1143
1230
|
if (!m || !m[1]) {
|
|
1144
|
-
|
|
1231
|
+
resolve5({ ok: false, message: "could not parse version" });
|
|
1145
1232
|
return;
|
|
1146
1233
|
}
|
|
1147
|
-
|
|
1234
|
+
resolve5({ ok: true, message: `version ${m[1]}` });
|
|
1148
1235
|
});
|
|
1149
1236
|
});
|
|
1150
1237
|
}
|
|
@@ -1456,6 +1543,48 @@ function normalizeConfig(parsed) {
|
|
|
1456
1543
|
if (active !== void 0) out.active = active;
|
|
1457
1544
|
return out;
|
|
1458
1545
|
}
|
|
1546
|
+
var IMPORTED_MCPS_PATH = () => resolve(homedir(), ".agentproto", "imported-mcps.json");
|
|
1547
|
+
var IMPORTED_MCPS_VERSION = 1;
|
|
1548
|
+
var EMPTY = {
|
|
1549
|
+
version: IMPORTED_MCPS_VERSION,
|
|
1550
|
+
imports: []
|
|
1551
|
+
};
|
|
1552
|
+
async function loadImportedMcps(path = IMPORTED_MCPS_PATH()) {
|
|
1553
|
+
let raw;
|
|
1554
|
+
try {
|
|
1555
|
+
raw = await promises.readFile(path, "utf8");
|
|
1556
|
+
} catch (err) {
|
|
1557
|
+
if (err.code === "ENOENT") return EMPTY;
|
|
1558
|
+
throw err;
|
|
1559
|
+
}
|
|
1560
|
+
let parsed;
|
|
1561
|
+
try {
|
|
1562
|
+
parsed = JSON.parse(raw);
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
`agentproto: ${path} is not valid JSON (${err instanceof Error ? err.message : String(err)}). Delete or fix manually.`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
return normalize(parsed);
|
|
1569
|
+
}
|
|
1570
|
+
function normalize(parsed) {
|
|
1571
|
+
if (!parsed || typeof parsed !== "object") return EMPTY;
|
|
1572
|
+
const obj = parsed;
|
|
1573
|
+
const imports = [];
|
|
1574
|
+
if (Array.isArray(obj.imports)) {
|
|
1575
|
+
for (const entry of obj.imports) {
|
|
1576
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1577
|
+
const e = entry;
|
|
1578
|
+
const id = typeof e.id === "string" ? e.id : "";
|
|
1579
|
+
const alias = typeof e.alias === "string" ? e.alias : id;
|
|
1580
|
+
const addedAt = typeof e.addedAt === "string" ? e.addedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
1581
|
+
const snapshot = e.snapshot;
|
|
1582
|
+
if (!id || !snapshot) continue;
|
|
1583
|
+
imports.push({ id, alias, addedAt, snapshot });
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return { version: IMPORTED_MCPS_VERSION, imports };
|
|
1587
|
+
}
|
|
1459
1588
|
|
|
1460
1589
|
// ../define-doctype/dist/index.mjs
|
|
1461
1590
|
var DEFAULT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{1,79}$/;
|
|
@@ -1591,7 +1720,7 @@ function createVerbs(spec) {
|
|
|
1591
1720
|
}
|
|
1592
1721
|
return { path, handle: newHandle, rendered };
|
|
1593
1722
|
}
|
|
1594
|
-
async function
|
|
1723
|
+
async function resolve5(block, ctx = {}) {
|
|
1595
1724
|
if ("inline" in block) {
|
|
1596
1725
|
return spec.define(block.inline);
|
|
1597
1726
|
}
|
|
@@ -1617,7 +1746,7 @@ function createVerbs(spec) {
|
|
|
1617
1746
|
async function deleteFn(path) {
|
|
1618
1747
|
await rm(path, { force: true });
|
|
1619
1748
|
}
|
|
1620
|
-
return { create, load, list, update, resolve:
|
|
1749
|
+
return { create, load, list, update, resolve: resolve5, delete: deleteFn };
|
|
1621
1750
|
}
|
|
1622
1751
|
function defaultBody(spec, frontmatter) {
|
|
1623
1752
|
const id = typeof frontmatter.name === "string" ? frontmatter.name : typeof frontmatter.id === "string" ? frontmatter.id : typeof frontmatter.slug === "string" ? frontmatter.slug : spec.name;
|
|
@@ -1755,8 +1884,374 @@ function resolvePathTemplate(template, handle, doctypeSlug) {
|
|
|
1755
1884
|
const identity = typeof handle.id === "string" && handle.id || typeof handle.slug === "string" && handle.slug || typeof handle.name === "string" && handle.name || "unknown";
|
|
1756
1885
|
return template.replace(/<slug>/g, identity).replace(/<DOCTYPE>/g, doctypeSlug.toUpperCase());
|
|
1757
1886
|
}
|
|
1887
|
+
var agentFrontmatterSchema = z.object({ "schema": z.literal("agent/v1"), "id": z.string().regex(new RegExp("^[a-z0-9@][a-z0-9.@/_-]*$")).min(2).max(80).describe("Machine identifier. Optional @<owner>/ prefix. Unique within the registry that hosts the agent."), "description": z.string().min(1).max(2e3).describe("One-paragraph purpose."), "version": z.string().regex(new RegExp("^\\d+\\.\\d+\\.\\d+(?:[-+][\\w.\\-]+)?$")).describe("Spec version of THIS file.").default("1.0.0"), "extends": z.any().describe("Parent AGENT this extends. Inherits all fields; child overrides win.").optional(), "model": z.any().describe("The brain \u2014 provider/model id string or structured ref."), "identity": z.any().describe("AIP-23 identity ref. Defaults to host policy.").optional(), "persona": z.any().describe("AIP-25 persona ref.").optional(), "runner": z.any().describe("AIP-17 runtime engine ref.").optional(), "tools": z.array(z.any()).describe("AIP-14 tool refs.").default([]), "actions": z.object({ "granted": z.array(z.any()).default([]) }).strict().describe("Explicit action allow-list. POLICY checks union of this + tools' implements.").optional(), "skills": z.array(z.any()).describe("AIP-3 skill refs.").default([]), "workflows": z.array(z.any()).describe("AIP-15 workflow refs.").default([]), "routines": z.array(z.any()).describe("AIP-41 routine refs that auto-fire this agent.").default([]), "delegates_to": z.array(z.any()).describe("Sub-agents this agent may dispatch to (council pattern, AIP-24).").default([]), "memory": z.any().describe("Memory configuration.").optional(), "governance": z.any().describe("AIP-7 governance binding.").optional(), "policy": z.any().describe("AIP-38 POLICY binding.").optional(), "traits": z.record(z.string(), z.number().gte(0).lte(10)).describe("Free-form numeric trait scores (0-10).").default({}), "autonomy": z.number().int().gte(0).lte(10).describe("How much the agent acts vs asks. 0 = always asks, 10 = full autonomy.").default(5), "boundaries": z.array(z.string().min(1)).describe("Hard rules the agent MUST decline / escalate. Surfaced into system prompt.").default([]), "on": z.record(z.string(), z.any().superRefine((x, ctx) => {
|
|
1888
|
+
const schemas = [z.any(), z.array(z.any())];
|
|
1889
|
+
const { errors, failed } = schemas.reduce(
|
|
1890
|
+
({ errors: errors2, failed: failed2 }, schema) => ((result) => result.error ? {
|
|
1891
|
+
errors: [...errors2, ...result.error.issues],
|
|
1892
|
+
failed: failed2 + 1
|
|
1893
|
+
} : { errors: errors2, failed: failed2 })(
|
|
1894
|
+
schema.safeParse(x)
|
|
1895
|
+
),
|
|
1896
|
+
{ errors: [], failed: 0 }
|
|
1897
|
+
);
|
|
1898
|
+
const passed = schemas.length - failed;
|
|
1899
|
+
if (passed !== 1) {
|
|
1900
|
+
ctx.addIssue(errors.length ? {
|
|
1901
|
+
path: [],
|
|
1902
|
+
code: "invalid_union",
|
|
1903
|
+
errors: [errors],
|
|
1904
|
+
message: "Invalid input: Should pass single schema. Passed " + passed
|
|
1905
|
+
} : {
|
|
1906
|
+
path: [],
|
|
1907
|
+
code: "custom",
|
|
1908
|
+
errors: [errors],
|
|
1909
|
+
message: "Invalid input: Should pass single schema. Passed " + passed
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
})).describe("Lifecycle hooks. AIP-37 event name -> action-ref(s) to invoke.").default({}), "publish": z.object({ "visibility": z.enum(["private", "unlisted", "public"]).default("private"), "registry": z.string().optional() }).strict().default({ "visibility": "private" }), "tags": z.array(z.string()).default([]), "metadata": z.record(z.string(), z.any()).optional() }).strict().describe("Validates the YAML frontmatter portion of an AIP-42 AGENT.md manifest. Composes identity, persona, model, tools, actions, skills, workflows, runner, memory, governance, policy, routines into a single runnable definition.");
|
|
1913
|
+
var defineAgent = createDoctype({
|
|
1914
|
+
aip: 42,
|
|
1915
|
+
name: "agent",
|
|
1916
|
+
// AIP-42 ids accept an optional `@<owner>/` prefix for namespacing
|
|
1917
|
+
// across registries (e.g. `@agentik/writer`). Bare ids stay valid.
|
|
1918
|
+
idPattern: /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]{0,79}$/,
|
|
1919
|
+
validate(def) {
|
|
1920
|
+
const result = agentFrontmatterSchema.safeParse(def);
|
|
1921
|
+
if (!result.success) {
|
|
1922
|
+
throw new Error(
|
|
1923
|
+
`defineAgent (AIP-42): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
},
|
|
1927
|
+
build(def) {
|
|
1928
|
+
return { ...def };
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
function parseAgentManifest(source) {
|
|
1932
|
+
const parsed = matter(source);
|
|
1933
|
+
if (Object.keys(parsed.data).length === 0) {
|
|
1934
|
+
throw new Error("parseAgentManifest: missing or empty frontmatter");
|
|
1935
|
+
}
|
|
1936
|
+
const result = agentFrontmatterSchema.safeParse(parsed.data);
|
|
1937
|
+
if (!result.success) {
|
|
1938
|
+
throw new Error(
|
|
1939
|
+
`parseAgentManifest: invalid frontmatter \u2014 ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
return { frontmatter: result.data, body: parsed.content };
|
|
1943
|
+
}
|
|
1758
1944
|
|
|
1759
|
-
// ../
|
|
1945
|
+
// ../agent/dist/index.mjs
|
|
1946
|
+
var agentSpec = {
|
|
1947
|
+
name: "agent",
|
|
1948
|
+
aip: 42,
|
|
1949
|
+
schemaLiteral: "agent/v1",
|
|
1950
|
+
pathOf: (h) => `.agents/${h.id}/AGENT.md`,
|
|
1951
|
+
define: defineAgent,
|
|
1952
|
+
parse: (source) => {
|
|
1953
|
+
const m = parseAgentManifest(source);
|
|
1954
|
+
return {
|
|
1955
|
+
frontmatter: m.frontmatter,
|
|
1956
|
+
body: m.body
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
var agentVerbs = createVerbs(agentSpec);
|
|
1961
|
+
var PROVIDER_KINDS = [
|
|
1962
|
+
"cli",
|
|
1963
|
+
"http",
|
|
1964
|
+
"mcp",
|
|
1965
|
+
"sdk",
|
|
1966
|
+
"builtin"
|
|
1967
|
+
];
|
|
1968
|
+
var constructTool = createDoctype({
|
|
1969
|
+
aip: 14,
|
|
1970
|
+
name: "tool",
|
|
1971
|
+
validate(def) {
|
|
1972
|
+
if ("execute" in def) {
|
|
1973
|
+
throw new Error(
|
|
1974
|
+
`defineTool: id='${def.id}' carries an 'execute' property. Bodies live on AIP-30 PROVIDER manifests, not on the TOOL contract. See https://agentproto.sh/docs/aip-30 for migration.`
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
},
|
|
1978
|
+
build(def) {
|
|
1979
|
+
return {
|
|
1980
|
+
id: def.id,
|
|
1981
|
+
name: def.name ?? def.id,
|
|
1982
|
+
description: def.description,
|
|
1983
|
+
version: def.version,
|
|
1984
|
+
inputSchema: def.inputSchema,
|
|
1985
|
+
outputSchema: def.outputSchema,
|
|
1986
|
+
contextSchema: def.contextSchema,
|
|
1987
|
+
mutates: Object.freeze([...def.mutates ?? []]),
|
|
1988
|
+
requires: freezeCapabilities(def.requires),
|
|
1989
|
+
approval: defaultApproval(def.approval, def.mutates),
|
|
1990
|
+
riskLevel: def.riskLevel ?? 0,
|
|
1991
|
+
costClass: def.costClass ?? "trivial",
|
|
1992
|
+
timeoutMs: def.timeoutMs ?? 3e4,
|
|
1993
|
+
retry: def.retry,
|
|
1994
|
+
tags: Object.freeze([...def.tags ?? []]),
|
|
1995
|
+
metadata: Object.freeze({ ...def.metadata ?? {} }),
|
|
1996
|
+
idempotent: def.idempotent ?? false,
|
|
1997
|
+
defaultImplementation: def.defaultImplementation,
|
|
1998
|
+
driverConstraints: freezeProviderConstraints(def.driverConstraints)
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
function defineTool(definition) {
|
|
2003
|
+
return constructTool(
|
|
2004
|
+
definition
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
function defaultApproval(declared, mutates) {
|
|
2008
|
+
if (declared) return declared;
|
|
2009
|
+
return mutates && mutates.length > 0 ? "on-mutate" : "auto";
|
|
2010
|
+
}
|
|
2011
|
+
function freezeCapabilities(caps) {
|
|
2012
|
+
return Object.freeze({
|
|
2013
|
+
network: Object.freeze([...caps?.network ?? []]),
|
|
2014
|
+
secrets: Object.freeze([...caps?.secrets ?? []]),
|
|
2015
|
+
tools: Object.freeze([...caps?.tools ?? []])
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
function freezeProviderConstraints(c) {
|
|
2019
|
+
const forbid = (c?.forbid ?? []).filter(
|
|
2020
|
+
(k) => PROVIDER_KINDS.includes(k)
|
|
2021
|
+
);
|
|
2022
|
+
const requireKind = (c?.requireKind ?? []).filter(
|
|
2023
|
+
(k) => PROVIDER_KINDS.includes(k)
|
|
2024
|
+
);
|
|
2025
|
+
return Object.freeze({
|
|
2026
|
+
forbid: Object.freeze(forbid),
|
|
2027
|
+
requireKind: Object.freeze(requireKind)
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
var toolManifestFrontmatterSchema = z.object({
|
|
2031
|
+
schema: z.literal("agentproto/tool/v1").optional(),
|
|
2032
|
+
name: z.string().min(1).max(80),
|
|
2033
|
+
id: z.string().regex(/^[a-z][a-z0-9._-]{1,63}$/),
|
|
2034
|
+
description: z.string().min(1).max(2e3),
|
|
2035
|
+
version: z.string().regex(/^\d+\.\d+\.\d+/),
|
|
2036
|
+
// Optional metadata
|
|
2037
|
+
mutates: z.array(z.string()).optional(),
|
|
2038
|
+
requires: z.object({
|
|
2039
|
+
network: z.array(z.string()).optional(),
|
|
2040
|
+
secrets: z.array(z.string()).optional(),
|
|
2041
|
+
tools: z.array(z.string()).optional()
|
|
2042
|
+
}).optional(),
|
|
2043
|
+
// `ApprovalClass` is `"auto" | "always" | "on-mutate" | \`policy:${string}\``.
|
|
2044
|
+
// z.union widens the regex branch to plain `string` (zod can't express
|
|
2045
|
+
// template literal types), so we use `z.custom` to keep the inferred
|
|
2046
|
+
// type exact — matters because `defineTool` accepts `ApprovalClass`.
|
|
2047
|
+
approval: z.custom(
|
|
2048
|
+
(v) => typeof v === "string" && (v === "auto" || v === "always" || v === "on-mutate" || /^policy:/.test(v)),
|
|
2049
|
+
{ message: "expected 'auto' | 'always' | 'on-mutate' | 'policy:<name>'" }
|
|
2050
|
+
).optional(),
|
|
2051
|
+
risk_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional(),
|
|
2052
|
+
cost_class: z.enum(["trivial", "metered", "expensive"]).optional(),
|
|
2053
|
+
timeout_ms: z.number().int().positive().optional(),
|
|
2054
|
+
idempotent: z.boolean().optional(),
|
|
2055
|
+
tags: z.array(z.string()).optional(),
|
|
2056
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
2057
|
+
// AIP-26 / AIP-17 / AIP-19 references — kept loose; validated by their
|
|
2058
|
+
// respective AIPs' adapters when consumed.
|
|
2059
|
+
code: z.unknown().optional(),
|
|
2060
|
+
run: z.unknown().optional(),
|
|
2061
|
+
runner: z.unknown().optional(),
|
|
2062
|
+
secrets: z.unknown().optional(),
|
|
2063
|
+
network: z.unknown().optional()
|
|
2064
|
+
});
|
|
2065
|
+
function parseToolManifest(source) {
|
|
2066
|
+
const parsed = matter(source);
|
|
2067
|
+
if (Object.keys(parsed.data).length === 0) {
|
|
2068
|
+
throw new Error("parseToolManifest: missing or empty frontmatter");
|
|
2069
|
+
}
|
|
2070
|
+
const result = toolManifestFrontmatterSchema.safeParse(parsed.data);
|
|
2071
|
+
if (!result.success) {
|
|
2072
|
+
throw new Error(
|
|
2073
|
+
`parseToolManifest: invalid frontmatter \u2014 ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
return { frontmatter: result.data, body: parsed.content };
|
|
2077
|
+
}
|
|
2078
|
+
var toolSpec = {
|
|
2079
|
+
name: "tool",
|
|
2080
|
+
aip: 14,
|
|
2081
|
+
schemaLiteral: "agentproto/tool/v1",
|
|
2082
|
+
pathOf: (h) => `${h.id}/TOOL.md`,
|
|
2083
|
+
define: (params) => defineTool(params),
|
|
2084
|
+
parse: (source) => {
|
|
2085
|
+
const m = parseToolManifest(source);
|
|
2086
|
+
return {
|
|
2087
|
+
frontmatter: m.frontmatter,
|
|
2088
|
+
body: m.body
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
var toolVerbs = createVerbs(toolSpec);
|
|
2093
|
+
var routineFrontmatterSchema = z.object({ "schema": z.literal("routine/v1"), "id": z.string().regex(new RegExp("^[a-z0-9@][a-z0-9.@/_-]*$")).min(2).max(80).describe("Machine identifier. Lowercase, digits, dashes, dots, optional @owner/ prefix. Unique within the registry that hosts the routine."), "description": z.string().min(1).max(2e3).describe("One-paragraph purpose."), "version": z.string().regex(new RegExp("^\\d+\\.\\d+\\.\\d+(?:[-+][\\w.\\-]+)?$")).describe("Spec version of THIS file.").default("1.0.0"), "schedule": z.any().describe("When the routine fires."), "target": z.any().describe("What the routine invokes when it fires."), "identity": z.any().describe("Identity that owns the routine fire (AIP-23). Defaults to host policy.").optional(), "retry": z.any().describe("Retry behaviour on failure.").optional(), "on_failure": z.any().describe("Where to route failures after retries exhaust.").optional(), "history": z.any().describe("Run history retention.").optional(), "fires_events": z.array(z.string().min(1)).describe("AIP-37 LIFECYCLE event names this routine fires.").default(["routine-triggered", "routine-completed", "routine-failed"]), "enabled": z.boolean().describe("If false, routine registers but does not fire. Useful for staging.").default(true), "tags": z.array(z.string()).describe("Free-form discovery tags.").default([]), "metadata": z.record(z.string(), z.any()).describe("Free-form, namespaced.").optional() }).strict().describe("Validates the YAML frontmatter portion of an AIP-41 ROUTINE.md manifest. Decouples 'when' (schedule) from 'what' (target action/workflow/tool).");
|
|
2094
|
+
var defineRoutine = createDoctype({
|
|
2095
|
+
aip: 41,
|
|
2096
|
+
name: "routine",
|
|
2097
|
+
validate(def) {
|
|
2098
|
+
const result = routineFrontmatterSchema.safeParse(def);
|
|
2099
|
+
if (!result.success) {
|
|
2100
|
+
throw new Error(
|
|
2101
|
+
`defineRoutine (AIP-41): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
},
|
|
2105
|
+
build(def) {
|
|
2106
|
+
return { ...def };
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
function parseRoutineManifest(source) {
|
|
2110
|
+
const parsed = matter(source);
|
|
2111
|
+
if (Object.keys(parsed.data).length === 0) {
|
|
2112
|
+
throw new Error("parseRoutineManifest: missing or empty frontmatter");
|
|
2113
|
+
}
|
|
2114
|
+
const result = routineFrontmatterSchema.safeParse(parsed.data);
|
|
2115
|
+
if (!result.success) {
|
|
2116
|
+
throw new Error(
|
|
2117
|
+
`parseRoutineManifest: invalid frontmatter \u2014 ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
return { frontmatter: result.data, body: parsed.content };
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// ../routine/dist/index.mjs
|
|
2124
|
+
var routineSpec = {
|
|
2125
|
+
name: "routine",
|
|
2126
|
+
aip: 41,
|
|
2127
|
+
schemaLiteral: "routine/v1",
|
|
2128
|
+
pathOf: (h) => `.routines/${h.id}/ROUTINE.md`,
|
|
2129
|
+
define: defineRoutine,
|
|
2130
|
+
parse: (source) => {
|
|
2131
|
+
const m = parseRoutineManifest(source);
|
|
2132
|
+
return {
|
|
2133
|
+
frontmatter: m.frontmatter,
|
|
2134
|
+
body: m.body
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
var routineVerbs = createVerbs(routineSpec);
|
|
2139
|
+
async function summarizeToolRef(raw, workspace) {
|
|
2140
|
+
if (typeof raw === "string") {
|
|
2141
|
+
return { id: raw, description: "(string ref \u2014 not locally resolved)" };
|
|
2142
|
+
}
|
|
2143
|
+
if (typeof raw !== "object" || raw === null) {
|
|
2144
|
+
return { id: String(raw), description: "(unknown ref shape)" };
|
|
2145
|
+
}
|
|
2146
|
+
const block = raw;
|
|
2147
|
+
if (block.file) {
|
|
2148
|
+
try {
|
|
2149
|
+
const handle = await toolVerbs.resolve(
|
|
2150
|
+
{ file: block.file },
|
|
2151
|
+
{ baseDir: workspace }
|
|
2152
|
+
);
|
|
2153
|
+
return { id: handle.id, description: handle.description ?? "" };
|
|
2154
|
+
} catch {
|
|
2155
|
+
return { id: block.file, description: "(file ref \u2014 could not load)" };
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (block.inline) {
|
|
2159
|
+
return {
|
|
2160
|
+
id: String(block.inline.id ?? ""),
|
|
2161
|
+
description: String(block.inline.description ?? "")
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
if (block.ref) {
|
|
2165
|
+
return { id: block.ref, description: "(ref \u2014 not locally resolved)" };
|
|
2166
|
+
}
|
|
2167
|
+
return { id: JSON.stringify(raw), description: "(unknown ref shape)" };
|
|
2168
|
+
}
|
|
2169
|
+
async function summarizeRoutineRef(raw, workspace) {
|
|
2170
|
+
if (typeof raw === "string") {
|
|
2171
|
+
return { id: raw, description: "(string ref \u2014 not locally resolved)" };
|
|
2172
|
+
}
|
|
2173
|
+
if (typeof raw !== "object" || raw === null) {
|
|
2174
|
+
return { id: String(raw), description: "(unknown ref shape)" };
|
|
2175
|
+
}
|
|
2176
|
+
const block = raw;
|
|
2177
|
+
if (block.file) {
|
|
2178
|
+
try {
|
|
2179
|
+
const handle = await routineVerbs.resolve(
|
|
2180
|
+
{ file: block.file },
|
|
2181
|
+
{ baseDir: workspace }
|
|
2182
|
+
);
|
|
2183
|
+
return { id: handle.id, description: handle.description ?? "" };
|
|
2184
|
+
} catch {
|
|
2185
|
+
return { id: block.file, description: "(file ref \u2014 could not load)" };
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
if (block.inline) {
|
|
2189
|
+
return {
|
|
2190
|
+
id: String(block.inline.id ?? ""),
|
|
2191
|
+
description: String(block.inline.description ?? "")
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
if (block.ref) {
|
|
2195
|
+
return { id: block.ref, description: "(ref \u2014 not locally resolved)" };
|
|
2196
|
+
}
|
|
2197
|
+
return { id: JSON.stringify(raw), description: "(unknown ref shape)" };
|
|
2198
|
+
}
|
|
2199
|
+
async function selfInspect(agentId, workspace) {
|
|
2200
|
+
const agentPath = join(workspace, ".agents", agentId, "AGENT.md");
|
|
2201
|
+
let handle;
|
|
2202
|
+
try {
|
|
2203
|
+
const result = await agentVerbs.load(agentPath);
|
|
2204
|
+
handle = result.handle;
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
const e = new Error(
|
|
2207
|
+
`No AGENT.md found for agent '${agentId}' at '${agentPath}'. Ensure an AGENT.md exists at <workspace>/.agents/${agentId}/AGENT.md.`
|
|
2208
|
+
);
|
|
2209
|
+
e.code = "agent_not_found";
|
|
2210
|
+
throw e;
|
|
2211
|
+
}
|
|
2212
|
+
const rawTools = handle.tools ?? [];
|
|
2213
|
+
const rawRoutines = handle.routines ?? [];
|
|
2214
|
+
const [tools, routines] = await Promise.all([
|
|
2215
|
+
Promise.all(rawTools.map((r) => summarizeToolRef(r, workspace))),
|
|
2216
|
+
Promise.all(rawRoutines.map((r) => summarizeRoutineRef(r, workspace)))
|
|
2217
|
+
]);
|
|
2218
|
+
return { agentPath, tools, routines };
|
|
2219
|
+
}
|
|
2220
|
+
function registerSelfInspectTool(server, opts) {
|
|
2221
|
+
server.tool(
|
|
2222
|
+
"self_inspect",
|
|
2223
|
+
"Return this agent's AIP-42 manifest summary (tools + routines) without requiring knowledge of disk paths. Pass the agent's logical `id` (the `id:` field in its AGENT.md frontmatter); the runtime resolves it to `<workspace>/.agents/<agentId>/AGENT.md`. Returns `{ agentPath, tools, routines }` \u2014 each item has at minimum `{ id, description }`. File refs are loaded; string/ref refs are surfaced as-is.",
|
|
2224
|
+
{
|
|
2225
|
+
agentId: z.string().min(1).describe(
|
|
2226
|
+
"The agent's logical id (e.g. 'writer'). Matches the `id:` field in the AGENT.md frontmatter. The runtime resolves this to `<workspace>/.agents/<agentId>/AGENT.md` \u2014 you do not need to know the absolute disk path."
|
|
2227
|
+
)
|
|
2228
|
+
},
|
|
2229
|
+
async ({ agentId }) => {
|
|
2230
|
+
try {
|
|
2231
|
+
const result = await selfInspect(agentId, opts.workspace);
|
|
2232
|
+
return {
|
|
2233
|
+
content: [
|
|
2234
|
+
{
|
|
2235
|
+
type: "text",
|
|
2236
|
+
text: JSON.stringify(result, null, 2)
|
|
2237
|
+
}
|
|
2238
|
+
]
|
|
2239
|
+
};
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2242
|
+
return {
|
|
2243
|
+
content: [
|
|
2244
|
+
{
|
|
2245
|
+
type: "text",
|
|
2246
|
+
text: JSON.stringify({ error: "agent_not_found", message }, null, 2)
|
|
2247
|
+
}
|
|
2248
|
+
],
|
|
2249
|
+
isError: true
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
1760
2255
|
async function createMcpServer(opts) {
|
|
1761
2256
|
const server = new McpServer({
|
|
1762
2257
|
name: opts.name ?? "agentproto-mcp-server",
|
|
@@ -1771,6 +2266,9 @@ async function createMcpServer(opts) {
|
|
|
1771
2266
|
for (const spec of allSpecs) {
|
|
1772
2267
|
registerVerbs(server, spec, anchor);
|
|
1773
2268
|
}
|
|
2269
|
+
if (opts.workspace) {
|
|
2270
|
+
registerSelfInspectTool(server, { workspace: opts.workspace });
|
|
2271
|
+
}
|
|
1774
2272
|
return {
|
|
1775
2273
|
server,
|
|
1776
2274
|
registered: allSpecs.map((s) => s.name)
|
|
@@ -1798,7 +2296,7 @@ function registerVerbs(server, spec, anchor) {
|
|
|
1798
2296
|
body,
|
|
1799
2297
|
dryRun
|
|
1800
2298
|
});
|
|
1801
|
-
return
|
|
2299
|
+
return contentText2({
|
|
1802
2300
|
path: result.path,
|
|
1803
2301
|
rendered: result.rendered
|
|
1804
2302
|
});
|
|
@@ -1812,7 +2310,7 @@ function registerVerbs(server, spec, anchor) {
|
|
|
1812
2310
|
},
|
|
1813
2311
|
async ({ path }) => {
|
|
1814
2312
|
const result = await verbs.load(anchor(path));
|
|
1815
|
-
return
|
|
2313
|
+
return contentText2({ path: result.path, handle: result.handle });
|
|
1816
2314
|
}
|
|
1817
2315
|
);
|
|
1818
2316
|
server.tool(
|
|
@@ -1827,7 +2325,7 @@ function registerVerbs(server, spec, anchor) {
|
|
|
1827
2325
|
},
|
|
1828
2326
|
async ({ dir, skipDirs }) => {
|
|
1829
2327
|
const handles = await verbs.list(anchor(dir), { skipDirs });
|
|
1830
|
-
return
|
|
2328
|
+
return contentText2({ count: handles.length, handles });
|
|
1831
2329
|
}
|
|
1832
2330
|
);
|
|
1833
2331
|
server.tool(
|
|
@@ -1847,7 +2345,7 @@ function registerVerbs(server, spec, anchor) {
|
|
|
1847
2345
|
(existing) => ({ ...existing, ...patch }),
|
|
1848
2346
|
{ body }
|
|
1849
2347
|
);
|
|
1850
|
-
return
|
|
2348
|
+
return contentText2({ path: result.path, rendered: result.rendered });
|
|
1851
2349
|
}
|
|
1852
2350
|
);
|
|
1853
2351
|
server.tool(
|
|
@@ -1861,14 +2359,16 @@ function registerVerbs(server, spec, anchor) {
|
|
|
1861
2359
|
z.object({ inline: z.record(z.string(), z.unknown()) }),
|
|
1862
2360
|
z.object({ ref: z.string() }),
|
|
1863
2361
|
z.object({ file: z.string() })
|
|
1864
|
-
]).describe(
|
|
2362
|
+
]).describe(
|
|
2363
|
+
'The composition block. Exactly one of: { "inline": {...params} } | { "ref": "@scope/id" } | { "file": "relative/path.md" }. Omitting the wrapping key causes invalid_union.'
|
|
2364
|
+
),
|
|
1865
2365
|
baseDir: z.string().optional().describe("Base dir for `file:` references.")
|
|
1866
2366
|
},
|
|
1867
2367
|
async ({ block, baseDir }) => {
|
|
1868
2368
|
const handle = await verbs.resolve(block, {
|
|
1869
2369
|
baseDir: baseDir ? anchor(baseDir) : void 0
|
|
1870
2370
|
});
|
|
1871
|
-
return
|
|
2371
|
+
return contentText2({ handle });
|
|
1872
2372
|
}
|
|
1873
2373
|
);
|
|
1874
2374
|
server.tool(
|
|
@@ -1880,7 +2380,7 @@ function registerVerbs(server, spec, anchor) {
|
|
|
1880
2380
|
async ({ path }) => {
|
|
1881
2381
|
const target = anchor(path);
|
|
1882
2382
|
await verbs.delete(target);
|
|
1883
|
-
return
|
|
2383
|
+
return contentText2({ deleted: target });
|
|
1884
2384
|
}
|
|
1885
2385
|
);
|
|
1886
2386
|
}
|
|
@@ -1924,7 +2424,7 @@ async function loadExtensions(workspace, specs) {
|
|
|
1924
2424
|
}
|
|
1925
2425
|
return out;
|
|
1926
2426
|
}
|
|
1927
|
-
function
|
|
2427
|
+
function contentText2(payload) {
|
|
1928
2428
|
return {
|
|
1929
2429
|
content: [
|
|
1930
2430
|
{
|
|
@@ -2026,7 +2526,7 @@ function makeCwdAnchor(workspace) {
|
|
|
2026
2526
|
const root = resolve(workspace);
|
|
2027
2527
|
return (input2) => {
|
|
2028
2528
|
if (!input2 || input2.length === 0) return root;
|
|
2029
|
-
const candidate = isAbsolute(input2) ? normalize(input2) : normalize(join(root, input2));
|
|
2529
|
+
const candidate = isAbsolute(input2) ? normalize$1(input2) : normalize$1(join(root, input2));
|
|
2030
2530
|
const rel = relative(root, candidate);
|
|
2031
2531
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
2032
2532
|
throw new Error(
|
|
@@ -2321,7 +2821,7 @@ function makeAnchor(workspace) {
|
|
|
2321
2821
|
if (typeof input2 !== "string" || input2.length === 0) {
|
|
2322
2822
|
throw new FsPathError("path must be a non-empty string");
|
|
2323
2823
|
}
|
|
2324
|
-
const candidate = isAbsolute(input2) ? normalize(input2) : normalize(join(root, input2));
|
|
2824
|
+
const candidate = isAbsolute(input2) ? normalize$1(input2) : normalize$1(join(root, input2));
|
|
2325
2825
|
const rel = relative(root, candidate);
|
|
2326
2826
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
2327
2827
|
throw new FsPathError(`path escapes the workspace: '${input2}'`);
|
|
@@ -2702,18 +3202,18 @@ function stringifyValues(raw) {
|
|
|
2702
3202
|
}
|
|
2703
3203
|
return out;
|
|
2704
3204
|
}
|
|
2705
|
-
var
|
|
2706
|
-
var
|
|
2707
|
-
var
|
|
2708
|
-
version:
|
|
3205
|
+
var IMPORTED_MCPS_PATH2 = () => resolve(homedir(), ".agentproto", "imported-mcps.json");
|
|
3206
|
+
var IMPORTED_MCPS_VERSION2 = 1;
|
|
3207
|
+
var EMPTY2 = {
|
|
3208
|
+
version: IMPORTED_MCPS_VERSION2,
|
|
2709
3209
|
imports: []
|
|
2710
3210
|
};
|
|
2711
|
-
async function
|
|
3211
|
+
async function loadImportedMcps2(path = IMPORTED_MCPS_PATH2()) {
|
|
2712
3212
|
let raw;
|
|
2713
3213
|
try {
|
|
2714
3214
|
raw = await promises.readFile(path, "utf8");
|
|
2715
3215
|
} catch (err) {
|
|
2716
|
-
if (err.code === "ENOENT") return
|
|
3216
|
+
if (err.code === "ENOENT") return EMPTY2;
|
|
2717
3217
|
throw err;
|
|
2718
3218
|
}
|
|
2719
3219
|
let parsed;
|
|
@@ -2726,7 +3226,7 @@ async function loadImportedMcps(path = IMPORTED_MCPS_PATH()) {
|
|
|
2726
3226
|
}
|
|
2727
3227
|
return normalize3(parsed);
|
|
2728
3228
|
}
|
|
2729
|
-
async function saveImportedMcps(config, path =
|
|
3229
|
+
async function saveImportedMcps(config, path = IMPORTED_MCPS_PATH2()) {
|
|
2730
3230
|
await promises.mkdir(dirname(path), { recursive: true });
|
|
2731
3231
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
2732
3232
|
await promises.writeFile(tmp, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
@@ -2751,7 +3251,7 @@ function removeImport(config, id) {
|
|
|
2751
3251
|
return { ...config, imports: config.imports.filter((e) => e.id !== id) };
|
|
2752
3252
|
}
|
|
2753
3253
|
function normalize3(parsed) {
|
|
2754
|
-
if (!parsed || typeof parsed !== "object") return
|
|
3254
|
+
if (!parsed || typeof parsed !== "object") return EMPTY2;
|
|
2755
3255
|
const obj = parsed;
|
|
2756
3256
|
const imports = [];
|
|
2757
3257
|
if (Array.isArray(obj.imports)) {
|
|
@@ -2766,7 +3266,7 @@ function normalize3(parsed) {
|
|
|
2766
3266
|
imports.push({ id, alias, addedAt, snapshot });
|
|
2767
3267
|
}
|
|
2768
3268
|
}
|
|
2769
|
-
return { version:
|
|
3269
|
+
return { version: IMPORTED_MCPS_VERSION2, imports };
|
|
2770
3270
|
}
|
|
2771
3271
|
function registerSessionTools(server, opts) {
|
|
2772
3272
|
const { registry, resolveAgentAdapter, listAgentAdapters, mcpProxy } = opts;
|
|
@@ -3056,7 +3556,7 @@ function registerSessionTools(server, opts) {
|
|
|
3056
3556
|
{},
|
|
3057
3557
|
async () => {
|
|
3058
3558
|
try {
|
|
3059
|
-
const config = await
|
|
3559
|
+
const config = await loadImportedMcps2();
|
|
3060
3560
|
return {
|
|
3061
3561
|
content: [
|
|
3062
3562
|
{ type: "text", text: JSON.stringify(config, null, 2) }
|
|
@@ -3101,7 +3601,7 @@ function registerSessionTools(server, opts) {
|
|
|
3101
3601
|
isError: true
|
|
3102
3602
|
};
|
|
3103
3603
|
}
|
|
3104
|
-
const cfg = await
|
|
3604
|
+
const cfg = await loadImportedMcps2();
|
|
3105
3605
|
const next = addImport(cfg, {
|
|
3106
3606
|
snapshot,
|
|
3107
3607
|
...input2.alias ? { alias: input2.alias } : {}
|
|
@@ -3134,7 +3634,7 @@ function registerSessionTools(server, opts) {
|
|
|
3134
3634
|
},
|
|
3135
3635
|
async (input2) => {
|
|
3136
3636
|
try {
|
|
3137
|
-
const cfg = await
|
|
3637
|
+
const cfg = await loadImportedMcps2();
|
|
3138
3638
|
if (!cfg.imports.some((e) => e.id === input2.id)) {
|
|
3139
3639
|
return {
|
|
3140
3640
|
content: [
|
|
@@ -4016,7 +4516,7 @@ async function startHttpServer(opts) {
|
|
|
4016
4516
|
return;
|
|
4017
4517
|
}
|
|
4018
4518
|
if (path === "/mcps/imports" && req.method === "GET") {
|
|
4019
|
-
const config = await
|
|
4519
|
+
const config = await loadImportedMcps2();
|
|
4020
4520
|
res.writeHead(200, { "content-type": "application/json" });
|
|
4021
4521
|
res.end(JSON.stringify(config));
|
|
4022
4522
|
return;
|
|
@@ -4040,7 +4540,7 @@ async function startHttpServer(opts) {
|
|
|
4040
4540
|
);
|
|
4041
4541
|
return;
|
|
4042
4542
|
}
|
|
4043
|
-
const cfg = await
|
|
4543
|
+
const cfg = await loadImportedMcps2();
|
|
4044
4544
|
const next = addImport(cfg, {
|
|
4045
4545
|
snapshot,
|
|
4046
4546
|
...body.alias ? { alias: body.alias } : {}
|
|
@@ -4053,7 +4553,7 @@ async function startHttpServer(opts) {
|
|
|
4053
4553
|
const importMatch = path.match(/^\/mcps\/imports\/(.+)$/);
|
|
4054
4554
|
if (importMatch && req.method === "DELETE") {
|
|
4055
4555
|
const id = decodeURIComponent(importMatch[1] ?? "");
|
|
4056
|
-
const cfg = await
|
|
4556
|
+
const cfg = await loadImportedMcps2();
|
|
4057
4557
|
if (!cfg.imports.some((e) => e.id === id)) {
|
|
4058
4558
|
res.writeHead(404, { "content-type": "application/json" });
|
|
4059
4559
|
res.end(JSON.stringify({ error: "import_not_found", id }));
|
|
@@ -5471,13 +5971,13 @@ var McpProxyRegistry = class {
|
|
|
5471
5971
|
async ensureCurrent() {
|
|
5472
5972
|
let mtimeMs = 0;
|
|
5473
5973
|
try {
|
|
5474
|
-
const st = await promises.stat(
|
|
5974
|
+
const st = await promises.stat(IMPORTED_MCPS_PATH2());
|
|
5475
5975
|
mtimeMs = st.mtimeMs;
|
|
5476
5976
|
} catch {
|
|
5477
5977
|
mtimeMs = 0;
|
|
5478
5978
|
}
|
|
5479
5979
|
if (this.hasReadOnce && mtimeMs === this.lastMtimeMs) return;
|
|
5480
|
-
const config = await
|
|
5980
|
+
const config = await loadImportedMcps2().catch(() => ({
|
|
5481
5981
|
version: 1,
|
|
5482
5982
|
imports: []
|
|
5483
5983
|
}));
|
|
@@ -6046,7 +6546,7 @@ function createWorkspaceFs(opts) {
|
|
|
6046
6546
|
"absolute paths are not allowed; use a workspace-relative path"
|
|
6047
6547
|
);
|
|
6048
6548
|
}
|
|
6049
|
-
const joined = normalize(join(root, path));
|
|
6549
|
+
const joined = normalize$1(join(root, path));
|
|
6050
6550
|
const rel = relative(root, joined);
|
|
6051
6551
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
6052
6552
|
throw new WorkspacePathError(
|
|
@@ -6250,6 +6750,51 @@ async function runBoot(workspace, opts, conversations, events) {
|
|
|
6250
6750
|
durationMs: 0
|
|
6251
6751
|
});
|
|
6252
6752
|
}
|
|
6753
|
+
|
|
6754
|
+
// src/registry/catalog.ts
|
|
6755
|
+
var CATALOG = [
|
|
6756
|
+
// ── Agent CLIs ────────────────────────────────────────────────────────
|
|
6757
|
+
{
|
|
6758
|
+
type: "agent-cli",
|
|
6759
|
+
slug: "claude-code",
|
|
6760
|
+
name: "Claude Code",
|
|
6761
|
+
description: "Anthropic's Claude Code via @agentclientprotocol/claude-agent-acp ACP wrapper.",
|
|
6762
|
+
packageName: "@agentproto/adapter-claude-code",
|
|
6763
|
+
hint: "anthropic \xB7 ACP \xB7 resumable"
|
|
6764
|
+
},
|
|
6765
|
+
{
|
|
6766
|
+
type: "agent-cli",
|
|
6767
|
+
slug: "opencode",
|
|
6768
|
+
name: "OpenCode",
|
|
6769
|
+
description: "sst/opencode with first-party ACP mode. Multi-provider: Anthropic, OpenAI, OpenRouter, Groq.",
|
|
6770
|
+
packageName: "@agentproto/adapter-opencode",
|
|
6771
|
+
hint: "multi-provider \xB7 ACP \xB7 resumable"
|
|
6772
|
+
},
|
|
6773
|
+
{
|
|
6774
|
+
type: "agent-cli",
|
|
6775
|
+
slug: "codex",
|
|
6776
|
+
name: "Codex",
|
|
6777
|
+
description: "OpenAI Codex coding agent via Zed's @zed-industries/codex-acp ACP wrapper.",
|
|
6778
|
+
packageName: "@agentproto/adapter-codex",
|
|
6779
|
+
hint: "openai \xB7 ACP \xB7 resumable"
|
|
6780
|
+
},
|
|
6781
|
+
{
|
|
6782
|
+
type: "agent-cli",
|
|
6783
|
+
slug: "hermes",
|
|
6784
|
+
name: "Hermes",
|
|
6785
|
+
description: "Nous Research Hermes agent with skills, sandboxes, and memory plugin surface.",
|
|
6786
|
+
packageName: "@agentproto/adapter-hermes",
|
|
6787
|
+
hint: "nous \xB7 ACP \xB7 sub-agents"
|
|
6788
|
+
},
|
|
6789
|
+
{
|
|
6790
|
+
type: "agent-cli",
|
|
6791
|
+
slug: "openclaw",
|
|
6792
|
+
name: "OpenClaw",
|
|
6793
|
+
description: "OpenClaw coding-agent platform with native ACP bridge and plugin surface.",
|
|
6794
|
+
packageName: "@agentproto/adapter-openclaw",
|
|
6795
|
+
hint: "gateway \xB7 ACP \xB7 plugins"
|
|
6796
|
+
}
|
|
6797
|
+
];
|
|
6253
6798
|
async function runServe(args) {
|
|
6254
6799
|
const { values } = parseArgs({
|
|
6255
6800
|
args: [...args],
|
|
@@ -6356,10 +6901,11 @@ async function runServe(args) {
|
|
|
6356
6901
|
const adapter = await resolveAdapter(slug);
|
|
6357
6902
|
const runtime = createAgentCliRuntime(adapter.handle);
|
|
6358
6903
|
return {
|
|
6359
|
-
async startSession({ cwd, resumeSessionId }) {
|
|
6904
|
+
async startSession({ cwd, resumeSessionId, model }) {
|
|
6360
6905
|
return runtime.start({
|
|
6361
6906
|
cwd,
|
|
6362
|
-
...resumeSessionId ? { resumeSessionId } : {}
|
|
6907
|
+
...resumeSessionId ? { resumeSessionId } : {},
|
|
6908
|
+
...model ? { config: { options: { model } } } : {}
|
|
6363
6909
|
});
|
|
6364
6910
|
},
|
|
6365
6911
|
commandPreview: `${adapter.handle.bin} ${(adapter.handle.bin_args ?? []).join(" ")}`.trim()
|
|
@@ -6378,15 +6924,15 @@ async function runServe(args) {
|
|
|
6378
6924
|
workspace: opts.workspace,
|
|
6379
6925
|
port: opts.port,
|
|
6380
6926
|
bind: opts.bind,
|
|
6381
|
-
specs: [],
|
|
6927
|
+
specs: [driverSpec],
|
|
6382
6928
|
name: "agentproto-serve",
|
|
6383
6929
|
// BOOT.md is silly for a tunnel daemon — skip it.
|
|
6384
6930
|
boot: false,
|
|
6385
6931
|
resolveAgentAdapter,
|
|
6386
6932
|
// Discovery for UIs / operators — `GET /adapters` + `list_adapters`
|
|
6387
|
-
// MCP tool.
|
|
6388
|
-
//
|
|
6389
|
-
listAgentAdapters:
|
|
6933
|
+
// MCP tool. Starts from the bundled catalog so known adapters always
|
|
6934
|
+
// appear (with status "supported") even when not yet installed.
|
|
6935
|
+
listAgentAdapters: () => listAdaptersWithCatalog(CATALOG),
|
|
6390
6936
|
...spawnPty ? { spawnPty } : {},
|
|
6391
6937
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
6392
6938
|
...opts.strictOrigins ? { strictOrigins: true } : {}
|
|
@@ -6398,6 +6944,10 @@ async function runServe(args) {
|
|
|
6398
6944
|
);
|
|
6399
6945
|
return 1;
|
|
6400
6946
|
}
|
|
6947
|
+
const installedAdapterSlugs = await listAdaptersWithCatalog(CATALOG).then((list) => list.filter((a) => a.status !== "supported").map((a) => a.slug)).catch(() => []);
|
|
6948
|
+
const announcedTools = [
|
|
6949
|
+
.../* @__PURE__ */ new Set([...gateway.registered, ...installedAdapterSlugs])
|
|
6950
|
+
];
|
|
6401
6951
|
printBootBanner({
|
|
6402
6952
|
url: gateway.url,
|
|
6403
6953
|
workspace: gateway.workspace,
|
|
@@ -6455,8 +7005,8 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
|
|
|
6455
7005
|
AGENTPROTO_DAEMON_TOKEN: gateway.token
|
|
6456
7006
|
}
|
|
6457
7007
|
});
|
|
6458
|
-
await new Promise((
|
|
6459
|
-
child.once("exit", () =>
|
|
7008
|
+
await new Promise((resolve5) => {
|
|
7009
|
+
child.once("exit", () => resolve5());
|
|
6460
7010
|
aborter.signal.addEventListener("abort", () => {
|
|
6461
7011
|
try {
|
|
6462
7012
|
child.kill("SIGTERM");
|
|
@@ -6476,8 +7026,8 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
|
|
|
6476
7026
|
`${color.dim}Press Ctrl-C to stop.${color.reset}
|
|
6477
7027
|
`
|
|
6478
7028
|
);
|
|
6479
|
-
await new Promise((
|
|
6480
|
-
aborter.signal.addEventListener("abort", () =>
|
|
7029
|
+
await new Promise((resolve5) => {
|
|
7030
|
+
aborter.signal.addEventListener("abort", () => resolve5());
|
|
6481
7031
|
});
|
|
6482
7032
|
return 0;
|
|
6483
7033
|
}
|
|
@@ -6490,7 +7040,14 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
|
|
|
6490
7040
|
const reconnectState = { immediate: false };
|
|
6491
7041
|
while (!aborter.signal.aborted) {
|
|
6492
7042
|
try {
|
|
6493
|
-
await runOneTunnel(
|
|
7043
|
+
await runOneTunnel(
|
|
7044
|
+
opts,
|
|
7045
|
+
gateway,
|
|
7046
|
+
announcedTools,
|
|
7047
|
+
spawnPty,
|
|
7048
|
+
aborter.signal,
|
|
7049
|
+
reconnectState
|
|
7050
|
+
);
|
|
6494
7051
|
backoffMs = opts.reconnectMinMs ?? 1e3;
|
|
6495
7052
|
if (reconnectState.immediate) {
|
|
6496
7053
|
reconnectState.immediate = false;
|
|
@@ -6511,17 +7068,17 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
|
|
|
6511
7068
|
}
|
|
6512
7069
|
return 0;
|
|
6513
7070
|
}
|
|
6514
|
-
async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
|
|
7071
|
+
async function runOneTunnel(opts, gateway, announcedTools, spawnPty, signal, reconnectState) {
|
|
6515
7072
|
if (!opts.connect) throw new Error("runOneTunnel: --connect not set");
|
|
6516
7073
|
const headers = {
|
|
6517
|
-
"user-agent": "
|
|
7074
|
+
"user-agent": `agentproto/${"0.1.2"}`
|
|
6518
7075
|
};
|
|
6519
7076
|
if (opts.token) headers.authorization = `Bearer ${opts.token}`;
|
|
6520
7077
|
const ws = new WebSocket(opts.connect, { headers });
|
|
6521
|
-
await new Promise((
|
|
7078
|
+
await new Promise((resolve5, reject) => {
|
|
6522
7079
|
const onOpen = () => {
|
|
6523
7080
|
ws.off("error", onError);
|
|
6524
|
-
|
|
7081
|
+
resolve5();
|
|
6525
7082
|
};
|
|
6526
7083
|
const onError = (err) => {
|
|
6527
7084
|
ws.off("open", onOpen);
|
|
@@ -6552,18 +7109,34 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
|
|
|
6552
7109
|
label: opts.label,
|
|
6553
7110
|
pty: spawnPty !== null,
|
|
6554
7111
|
...spawnPty ? { spawnPty } : {},
|
|
7112
|
+
// Announce what this daemon serves (doctypes + installed agent adapters)
|
|
7113
|
+
// so a multi-daemon host can enumerate + route by capability.
|
|
7114
|
+
...announcedTools.length ? { tools: announcedTools } : {},
|
|
6555
7115
|
// Generic HTTP-relay upstream for tunnel `http_request` frames.
|
|
6556
7116
|
// Cloud-side callers (e.g. the API's local-daemon filesystem
|
|
6557
7117
|
// provider) can now route MCP JSON-RPC + any other HTTP through
|
|
6558
7118
|
// the daemon without needing a public URL. We point at the local
|
|
6559
7119
|
// gateway since that's where `/mcp`, `/sessions`, `/events` live.
|
|
6560
7120
|
httpUpstream: gateway.url,
|
|
7121
|
+
// Forward bounds, stated explicitly so they're visible and tunable here
|
|
7122
|
+
// (the local gateway is fast, so the package defaults fit; bump these if
|
|
7123
|
+
// this daemon ever fronts a slower upstream):
|
|
7124
|
+
// - connect + buffered-body / connect + stream-headers ceiling
|
|
7125
|
+
httpForwardTimeoutMs: DEFAULT_HTTP_FORWARD_TIMEOUT_MS,
|
|
7126
|
+
// - WS upgrade dial ceiling
|
|
7127
|
+
wsDialTimeoutMs: DEFAULT_WS_DIAL_TIMEOUT_MS,
|
|
7128
|
+
// Bound a streaming forward against a silent upstream: if an SSE/ndjson
|
|
7129
|
+
// stream sends headers then stalls with no bytes for 2 min, end it instead
|
|
7130
|
+
// of holding the reqId open forever. The window resets per chunk, so a
|
|
7131
|
+
// well-behaved stream (events or heartbeat comments) is never cut — only a
|
|
7132
|
+
// genuinely dead upstream trips it.
|
|
7133
|
+
httpStreamIdleTimeoutMs: 12e4,
|
|
6561
7134
|
// WS forwarding upstream — daemon dials the local gateway's WS
|
|
6562
7135
|
// endpoints (/sessions/:id/pty, etc) and pipes frames to the host.
|
|
6563
7136
|
// Used by the cloud tunnel pod so browsers on mobile can attach to
|
|
6564
7137
|
// interactive PTY sessions even though the daemon is only reachable
|
|
6565
7138
|
// through the host (not directly).
|
|
6566
|
-
dialUpstreamWs: async ({ url, protocols, headers: headers2 }) => {
|
|
7139
|
+
dialUpstreamWs: async ({ url, protocols, headers: headers2, signal: signal2 }) => {
|
|
6567
7140
|
const upstreamHeaders = {
|
|
6568
7141
|
...headers2
|
|
6569
7142
|
};
|
|
@@ -6575,14 +7148,28 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
|
|
|
6575
7148
|
} catch {
|
|
6576
7149
|
}
|
|
6577
7150
|
}
|
|
6578
|
-
return await new Promise((
|
|
7151
|
+
return await new Promise((resolve5, reject) => {
|
|
6579
7152
|
const sock = new WebSocket(url, protocols ? [...protocols] : void 0, {
|
|
6580
7153
|
headers: upstreamHeaders
|
|
6581
7154
|
});
|
|
7155
|
+
const onAbort = () => {
|
|
7156
|
+
sock.off("open", onceOpen);
|
|
7157
|
+
sock.off("error", onceError);
|
|
7158
|
+
sock.off("unexpected-response", onceUnexpected);
|
|
7159
|
+
try {
|
|
7160
|
+
sock.terminate();
|
|
7161
|
+
} catch {
|
|
7162
|
+
}
|
|
7163
|
+
reject(
|
|
7164
|
+
signal2?.reason instanceof Error ? signal2.reason : new Error("ws dial aborted")
|
|
7165
|
+
);
|
|
7166
|
+
};
|
|
7167
|
+
const detachAbort = () => signal2?.removeEventListener("abort", onAbort);
|
|
6582
7168
|
const onceOpen = () => {
|
|
7169
|
+
detachAbort();
|
|
6583
7170
|
sock.off("error", onceError);
|
|
6584
7171
|
sock.off("unexpected-response", onceUnexpected);
|
|
6585
|
-
|
|
7172
|
+
resolve5({
|
|
6586
7173
|
protocol: sock.protocol ?? "",
|
|
6587
7174
|
send: (data, sendOpts) => {
|
|
6588
7175
|
sock.send(data, { binary: sendOpts.binary });
|
|
@@ -6609,18 +7196,43 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
|
|
|
6609
7196
|
});
|
|
6610
7197
|
};
|
|
6611
7198
|
const onceError = (err) => {
|
|
7199
|
+
detachAbort();
|
|
6612
7200
|
sock.off("open", onceOpen);
|
|
6613
7201
|
reject(err);
|
|
6614
7202
|
};
|
|
6615
7203
|
const onceUnexpected = (_req, res) => {
|
|
7204
|
+
detachAbort();
|
|
6616
7205
|
sock.off("open", onceOpen);
|
|
6617
7206
|
reject(new Error(`Unexpected server response: ${res.statusCode ?? 0}`));
|
|
6618
7207
|
};
|
|
7208
|
+
if (signal2) {
|
|
7209
|
+
if (signal2.aborted) {
|
|
7210
|
+
onAbort();
|
|
7211
|
+
return;
|
|
7212
|
+
}
|
|
7213
|
+
signal2.addEventListener("abort", onAbort, { once: true });
|
|
7214
|
+
}
|
|
6619
7215
|
sock.once("open", onceOpen);
|
|
6620
7216
|
sock.once("error", onceError);
|
|
6621
7217
|
sock.once("unexpected-response", onceUnexpected);
|
|
6622
7218
|
});
|
|
6623
7219
|
},
|
|
7220
|
+
// Resolve a named WS upstream to a registered import's origin. A host
|
|
7221
|
+
// can watch a tab on an imported capability server (e.g. a browser
|
|
7222
|
+
// daemon) by aliasing the import instead of the default gateway — the
|
|
7223
|
+
// path rides the import's own origin (`http://127.0.0.1:<port>`), the
|
|
7224
|
+
// daemon never accepts a raw origin from the host.
|
|
7225
|
+
resolveWsUpstream: async (alias) => {
|
|
7226
|
+
const cfg = await loadImportedMcps();
|
|
7227
|
+
const entry = cfg.imports.find((e) => e.alias === alias);
|
|
7228
|
+
const url = entry?.snapshot.url;
|
|
7229
|
+
if (!url) return void 0;
|
|
7230
|
+
try {
|
|
7231
|
+
return new URL(url).origin;
|
|
7232
|
+
} catch {
|
|
7233
|
+
return void 0;
|
|
7234
|
+
}
|
|
7235
|
+
},
|
|
6624
7236
|
// v0 authorize hook: trust the bearer-authenticated host completely.
|
|
6625
7237
|
// Token possession proves the host was provisioned for this daemon.
|
|
6626
7238
|
// Per-spawn policy filtering will land alongside the policy.toml.
|
|
@@ -6656,31 +7268,31 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
|
|
|
6656
7268
|
}
|
|
6657
7269
|
}
|
|
6658
7270
|
});
|
|
6659
|
-
await new Promise((
|
|
7271
|
+
await new Promise((resolve5) => {
|
|
6660
7272
|
const offClose = sink.onClose(() => {
|
|
6661
7273
|
clearInterval(keepaliveInterval);
|
|
6662
7274
|
offClose();
|
|
6663
|
-
|
|
7275
|
+
resolve5();
|
|
6664
7276
|
});
|
|
6665
7277
|
if (signal.aborted) {
|
|
6666
7278
|
clearInterval(keepaliveInterval);
|
|
6667
|
-
server.close().finally(() =>
|
|
7279
|
+
server.close().finally(() => resolve5());
|
|
6668
7280
|
}
|
|
6669
7281
|
signal.addEventListener("abort", () => {
|
|
6670
7282
|
clearInterval(keepaliveInterval);
|
|
6671
|
-
server.close().finally(() =>
|
|
7283
|
+
server.close().finally(() => resolve5());
|
|
6672
7284
|
});
|
|
6673
7285
|
});
|
|
6674
7286
|
process.stderr.write(`agentproto serve: tunnel closed.
|
|
6675
7287
|
`);
|
|
6676
7288
|
}
|
|
6677
7289
|
function sleep(ms, signal) {
|
|
6678
|
-
return new Promise((
|
|
6679
|
-
if (signal.aborted) return
|
|
6680
|
-
const timer = setTimeout(
|
|
7290
|
+
return new Promise((resolve5) => {
|
|
7291
|
+
if (signal.aborted) return resolve5();
|
|
7292
|
+
const timer = setTimeout(resolve5, ms);
|
|
6681
7293
|
signal.addEventListener("abort", () => {
|
|
6682
7294
|
clearTimeout(timer);
|
|
6683
|
-
|
|
7295
|
+
resolve5();
|
|
6684
7296
|
});
|
|
6685
7297
|
});
|
|
6686
7298
|
}
|