@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/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 { mkdtemp, writeFile, chmod, rm, mkdir, readFile, unlink, readdir, stat } from 'fs/promises';
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 { resolve, join, dirname, isAbsolute, normalize, relative, basename } from 'path';
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.0-alpha
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 (err) {
43
- const cause = err instanceof Error ? err.message : String(err);
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 '${packageName}' does not export an AgentCliHandle (looked for export '${camel}', 'default', or 'handle').`
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
- for (let depth = 0; depth < 16; depth++) {
107
- for (const candidate of candidatesAt(cur)) {
108
- try {
109
- const stat3 = await promises.stat(candidate);
110
- if (stat3.isDirectory()) roots.push(candidate);
111
- } catch {
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
- const parent = resolve(cur, "..");
115
- if (parent === cur) break;
116
- cur = parent;
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((resolve4) => {
353
+ await new Promise((resolve5) => {
267
354
  const child = spawn(opener, args, { stdio: "ignore", detached: true });
268
- child.once("error", () => resolve4());
269
- child.once("spawn", () => resolve4());
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((resolve4) => {
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
- resolve4(buf || opts.defaultValue || "");
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((resolve4) => {
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
- resolve4({ exitCode: 127, stdout, stderr: stderr || "spawn error" });
537
+ resolve5({ exitCode: 127, stdout, stderr: stderr || "spawn error" });
451
538
  });
452
539
  child.once("exit", (code) => {
453
540
  clearTimeout(timer);
454
- resolve4({ exitCode: code ?? 0, stdout, stderr });
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(homedir3(), ".local", "bin");
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 homedir3() {
1202
+ function homedir4() {
1116
1203
  return process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/";
1117
1204
  }
1118
1205
  function spawnInherit(cmd, argv) {
1119
- return new Promise((resolve4, reject) => {
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) => resolve4(code ?? 0));
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((resolve4) => {
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", () => resolve4({ ok: false, message: "check failed" }));
1222
+ child.once("error", () => resolve5({ ok: false, message: "check failed" }));
1136
1223
  child.once("exit", (code) => {
1137
1224
  if (code !== 0) {
1138
- resolve4({ ok: false, message: `check exited ${code}` });
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
- resolve4({ ok: false, message: "could not parse version" });
1231
+ resolve5({ ok: false, message: "could not parse version" });
1145
1232
  return;
1146
1233
  }
1147
- resolve4({ ok: true, message: `version ${m[1]}` });
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 resolve4(block, ctx = {}) {
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: resolve4, delete: deleteFn };
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
- // ../mcp-server/dist/index.mjs
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 contentText({
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 contentText({ path: result.path, handle: result.handle });
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 contentText({ count: handles.length, handles });
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 contentText({ path: result.path, rendered: result.rendered });
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("The composition block."),
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 contentText({ handle });
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 contentText({ deleted: target });
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 contentText(payload) {
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 IMPORTED_MCPS_PATH = () => resolve(homedir(), ".agentproto", "imported-mcps.json");
2706
- var IMPORTED_MCPS_VERSION = 1;
2707
- var EMPTY = {
2708
- version: IMPORTED_MCPS_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 loadImportedMcps(path = IMPORTED_MCPS_PATH()) {
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 EMPTY;
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 = IMPORTED_MCPS_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 EMPTY;
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: IMPORTED_MCPS_VERSION, imports };
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 loadImportedMcps();
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 loadImportedMcps();
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 loadImportedMcps();
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 loadImportedMcps();
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 loadImportedMcps();
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 loadImportedMcps();
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(IMPORTED_MCPS_PATH());
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 loadImportedMcps().catch(() => ({
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. Walks node_modules @agentproto/adapter-* on each call;
6388
- // cheap enough that we don't bother caching here.
6389
- listAgentAdapters: listInstalledAdapters,
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((resolve4) => {
6459
- child.once("exit", () => resolve4());
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((resolve4) => {
6480
- aborter.signal.addEventListener("abort", () => resolve4());
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(opts, gateway, spawnPty, aborter.signal, reconnectState);
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": "agentproto/0.1.0-alpha"
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((resolve4, reject) => {
7078
+ await new Promise((resolve5, reject) => {
6522
7079
  const onOpen = () => {
6523
7080
  ws.off("error", onError);
6524
- resolve4();
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((resolve4, reject) => {
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
- resolve4({
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((resolve4) => {
7271
+ await new Promise((resolve5) => {
6660
7272
  const offClose = sink.onClose(() => {
6661
7273
  clearInterval(keepaliveInterval);
6662
7274
  offClose();
6663
- resolve4();
7275
+ resolve5();
6664
7276
  });
6665
7277
  if (signal.aborted) {
6666
7278
  clearInterval(keepaliveInterval);
6667
- server.close().finally(() => resolve4());
7279
+ server.close().finally(() => resolve5());
6668
7280
  }
6669
7281
  signal.addEventListener("abort", () => {
6670
7282
  clearInterval(keepaliveInterval);
6671
- server.close().finally(() => resolve4());
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((resolve4) => {
6679
- if (signal.aborted) return resolve4();
6680
- const timer = setTimeout(resolve4, ms);
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
- resolve4();
7295
+ resolve5();
6684
7296
  });
6685
7297
  });
6686
7298
  }