@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 CHANGED
@@ -1,22 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import { promises, existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
3
3
  import { homedir, userInfo, hostname, platform, tmpdir } from 'os';
4
- import { resolve, basename, join, dirname, isAbsolute, normalize, relative } from 'path';
4
+ import { resolve, isAbsolute, join, dirname, basename, normalize as normalize$1, relative } from 'path';
5
5
  import * as childProc from 'child_process';
6
6
  import { execFile, spawn } from 'child_process';
7
7
  import { promisify, parseArgs } from 'util';
8
- import { unlink, readFile, chmod, readdir, writeFile, mkdir, stat, mkdtemp, rm, appendFile } from 'fs/promises';
8
+ import { rm, readFile, mkdir, writeFile, readdir, unlink, chmod, stat, mkdtemp, appendFile } from 'fs/promises';
9
9
  import { randomUUID, randomBytes, createHash } from 'crypto';
10
+ import { fileURLToPath, pathToFileURL } from 'url';
10
11
  import { createInterface } from 'readline/promises';
11
12
  import { stdout, stdin } from 'process';
12
13
  import { createRequire } from 'module';
13
- import { pathToFileURL } from 'url';
14
14
  import { z } from 'zod';
15
15
  import { createAgentCliRuntime } from '@agentproto/driver-agent-cli';
16
16
  import matter3 from 'gray-matter';
17
- import { wrapWebSocket, createTunnelServer } from '@agentproto/acp/tunnel';
17
+ import { wrapWebSocket, createTunnelServer, DEFAULT_WS_DIAL_TIMEOUT_MS, DEFAULT_HTTP_FORWARD_TIMEOUT_MS } from '@agentproto/acp/tunnel';
18
18
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
19
19
  import '@modelcontextprotocol/sdk/server/stdio.js';
20
+ import { driverSpec } from '@agentproto/driver';
20
21
  import { EventEmitter } from 'events';
21
22
  import http, { createServer } from 'http';
22
23
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
@@ -28,7 +29,7 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
28
29
  import https from 'https';
29
30
 
30
31
  /**
31
- * @agentproto/cli v0.1.0-alpha
32
+ * @agentproto/cli v0.1.2
32
33
  * The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
33
34
  */
34
35
  var __defProp = Object.defineProperty;
@@ -389,7 +390,7 @@ function xmlEscape(s) {
389
390
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
390
391
  }
391
392
  function launchctl(args) {
392
- return new Promise((resolve9) => {
393
+ return new Promise((resolve10) => {
393
394
  const child = spawn("launchctl", args, { stdio: ["ignore", "pipe", "pipe"] });
394
395
  let stdout = "";
395
396
  let stderr = "";
@@ -397,9 +398,9 @@ function launchctl(args) {
397
398
  child.stderr?.setEncoding("utf8").on("data", (c) => stderr += c);
398
399
  child.on(
399
400
  "error",
400
- (err) => resolve9({ code: 127, stdout, stderr: err.message })
401
+ (err) => resolve10({ code: 127, stdout, stderr: err.message })
401
402
  );
402
- child.on("exit", (code) => resolve9({ code: code ?? 1, stdout, stderr }));
403
+ child.on("exit", (code) => resolve10({ code: code ?? 1, stdout, stderr }));
403
404
  });
404
405
  }
405
406
  function humaniseUptime(ms) {
@@ -844,7 +845,7 @@ async function postForm(url, body) {
844
845
  return await res.json();
845
846
  }
846
847
  function sleep(ms) {
847
- return new Promise((resolve9) => setTimeout(resolve9, ms));
848
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
848
849
  }
849
850
  function formatDuration(ms) {
850
851
  if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
@@ -855,16 +856,16 @@ function openBrowser(url) {
855
856
  const p = platform();
856
857
  const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
857
858
  const args = p === "win32" ? ["/c", "start", url] : [url];
858
- return new Promise((resolve9) => {
859
+ return new Promise((resolve10) => {
859
860
  try {
860
861
  const child = spawn(cmd, args, { stdio: "ignore", detached: true });
861
- child.once("error", () => resolve9());
862
+ child.once("error", () => resolve10());
862
863
  child.once("spawn", () => {
863
864
  child.unref();
864
- resolve9();
865
+ resolve10();
865
866
  });
866
867
  } catch {
867
- resolve9();
868
+ resolve10();
868
869
  }
869
870
  });
870
871
  }
@@ -993,7 +994,7 @@ async function runEdit() {
993
994
  const cfg = await loadConfig();
994
995
  await saveConfig(cfg);
995
996
  const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
996
- return new Promise((resolve9) => {
997
+ return new Promise((resolve10) => {
997
998
  const child = spawn(editor, [CONFIG_FILE_PATH()], {
998
999
  stdio: "inherit"
999
1000
  });
@@ -1002,9 +1003,9 @@ async function runEdit() {
1002
1003
  `agentproto config edit: failed to launch ${editor}: ${err.message}
1003
1004
  `
1004
1005
  );
1005
- resolve9(1);
1006
+ resolve10(1);
1006
1007
  });
1007
- child.on("exit", (code) => resolve9(code ?? 0));
1008
+ child.on("exit", (code) => resolve10(code ?? 0));
1008
1009
  });
1009
1010
  }
1010
1011
  function parseValue(raw) {
@@ -1031,27 +1032,40 @@ async function resolveAdapter(slug) {
1031
1032
  );
1032
1033
  }
1033
1034
  const packageName = `@agentproto/adapter-${slug}`;
1035
+ const camel = slugToCamel(slug);
1034
1036
  let mod;
1037
+ let resolvedPackageName = packageName;
1035
1038
  try {
1036
1039
  mod = await import(packageName);
1037
- } catch (err) {
1038
- const cause = err instanceof Error ? err.message : String(err);
1040
+ } catch (primaryErr) {
1041
+ const hyphen = slug.lastIndexOf("-");
1042
+ if (hyphen > 0) {
1043
+ const parentPkg = `@agentproto/adapter-${slug.slice(0, hyphen)}`;
1044
+ try {
1045
+ const parentMod = await import(parentPkg);
1046
+ const parentCandidate = parentMod[camel];
1047
+ if (parentCandidate && typeof parentCandidate === "object" && "name" in parentCandidate) {
1048
+ return { slug, handle: parentCandidate, source: "npm", packageName: parentPkg };
1049
+ }
1050
+ } catch {
1051
+ }
1052
+ }
1053
+ const cause = primaryErr instanceof Error ? primaryErr.message : String(primaryErr);
1039
1054
  throw new Error(
1040
1055
  `agentproto: could not load adapter '${slug}'. Tried '${packageName}'. Install it with: npm i -g ${packageName}
1041
1056
  cause: ${cause}`
1042
1057
  );
1043
1058
  }
1044
- const camel = slugToCamel(slug);
1045
1059
  const candidate = mod[camel] ?? mod.default ?? mod.handle;
1046
1060
  if (!candidate || typeof candidate !== "object" || !("name" in candidate)) {
1047
1061
  throw new Error(
1048
- `agentproto: adapter '${packageName}' does not export an AgentCliHandle (looked for export '${camel}', 'default', or 'handle').`
1062
+ `agentproto: adapter '${resolvedPackageName}' does not export an AgentCliHandle (looked for export '${camel}', 'default', or 'handle').`
1049
1063
  );
1050
1064
  }
1051
- return { slug, handle: candidate, source: "npm", packageName };
1065
+ return { slug, handle: candidate, source: "npm", packageName: resolvedPackageName };
1052
1066
  }
1053
1067
  async function listInstalledAdapters(opts) {
1054
- const roots = await collectAgentprotoNamespaceRoots(opts?.searchRoot);
1068
+ const roots = await collectAgentprotoNamespaceRoots();
1055
1069
  const seen = /* @__PURE__ */ new Set();
1056
1070
  const out = [];
1057
1071
  for (const root of roots) {
@@ -1069,6 +1083,7 @@ async function listInstalledAdapters(opts) {
1069
1083
  try {
1070
1084
  const resolved = await resolveAdapter(slug);
1071
1085
  const handle = resolved.handle;
1086
+ const modelsField = handle.models?.allowed;
1072
1087
  const info = {
1073
1088
  slug,
1074
1089
  name: typeof handle.name === "string" ? handle.name : slug,
@@ -1076,7 +1091,9 @@ async function listInstalledAdapters(opts) {
1076
1091
  description: typeof handle.description === "string" ? handle.description : "",
1077
1092
  protocol: typeof handle.protocol === "string" ? handle.protocol : "unknown",
1078
1093
  streaming: !!handle.capabilities?.streaming,
1079
- packageName: resolved.packageName ?? `@agentproto/adapter-${slug}`
1094
+ packageName: resolved.packageName ?? `@agentproto/adapter-${slug}`,
1095
+ commands: Array.isArray(handle.commands) ? handle.commands : [],
1096
+ models: Array.isArray(modelsField) ? modelsField.filter((m) => typeof m === "string") : []
1080
1097
  };
1081
1098
  out.push(info);
1082
1099
  } catch (err) {
@@ -1092,26 +1109,95 @@ async function listInstalledAdapters(opts) {
1092
1109
  return out.sort((a, b) => a.slug.localeCompare(b.slug));
1093
1110
  }
1094
1111
  async function collectAgentprotoNamespaceRoots(start) {
1112
+ const seen = /* @__PURE__ */ new Set();
1095
1113
  const roots = [];
1096
- let cur = start ?? process.cwd();
1097
1114
  const candidatesAt = (dir) => [
1098
1115
  resolve(dir, "node_modules", "@agentproto"),
1099
1116
  resolve(dir, "node_modules", ".pnpm", "node_modules", "@agentproto")
1100
1117
  ];
1101
- for (let depth = 0; depth < 16; depth++) {
1102
- for (const candidate of candidatesAt(cur)) {
1103
- try {
1104
- const stat5 = await promises.stat(candidate);
1105
- if (stat5.isDirectory()) roots.push(candidate);
1106
- } catch {
1118
+ async function walkUp(from) {
1119
+ let cur = from;
1120
+ for (let depth = 0; depth < 20; depth++) {
1121
+ if (seen.has(cur)) break;
1122
+ seen.add(cur);
1123
+ for (const candidate of candidatesAt(cur)) {
1124
+ try {
1125
+ const s = await promises.stat(candidate);
1126
+ if (s.isDirectory() && !roots.includes(candidate)) roots.push(candidate);
1127
+ } catch {
1128
+ }
1107
1129
  }
1130
+ const parent = resolve(cur, "..");
1131
+ if (parent === cur) break;
1132
+ cur = parent;
1108
1133
  }
1109
- const parent = resolve(cur, "..");
1110
- if (parent === cur) break;
1111
- cur = parent;
1134
+ }
1135
+ await walkUp(process.cwd());
1136
+ try {
1137
+ await walkUp(dirname(fileURLToPath(import.meta.url)));
1138
+ } catch {
1112
1139
  }
1113
1140
  return roots;
1114
1141
  }
1142
+ async function listAdaptersWithCatalog(catalog) {
1143
+ const seenSlugs = /* @__PURE__ */ new Set();
1144
+ const out = [];
1145
+ const agentprotoHome = process.env["AGENTPROTO_HOME"] ?? join(homedir(), ".agentproto");
1146
+ async function hasSetupLedger(slug) {
1147
+ try {
1148
+ await promises.access(join(agentprotoHome, "setup", `${slug}.json`));
1149
+ return true;
1150
+ } catch {
1151
+ return false;
1152
+ }
1153
+ }
1154
+ for (const entry of catalog) {
1155
+ seenSlugs.add(entry.slug);
1156
+ try {
1157
+ const resolved = await resolveAdapter(entry.slug);
1158
+ const handle = resolved.handle;
1159
+ const setupSteps = Array.isArray(handle.setup) ? handle.setup : [];
1160
+ const ledgerPresent = await hasSetupLedger(entry.slug);
1161
+ const status = setupSteps.length === 0 || ledgerPresent ? "ready" : "available";
1162
+ const catalogModels = handle.models?.allowed;
1163
+ out.push({
1164
+ slug: entry.slug,
1165
+ name: typeof handle.name === "string" ? handle.name : entry.name,
1166
+ version: typeof handle.version === "string" ? handle.version : "?",
1167
+ description: typeof handle.description === "string" ? handle.description : entry.description,
1168
+ protocol: typeof handle.protocol === "string" ? handle.protocol : "unknown",
1169
+ streaming: !!handle.capabilities?.streaming,
1170
+ packageName: resolved.packageName ?? entry.packageName,
1171
+ commands: Array.isArray(handle.commands) ? handle.commands : [],
1172
+ models: Array.isArray(catalogModels) ? catalogModels.filter((m) => typeof m === "string") : [],
1173
+ status,
1174
+ hint: entry.hint
1175
+ });
1176
+ } catch {
1177
+ out.push({
1178
+ slug: entry.slug,
1179
+ name: entry.name,
1180
+ version: "not installed",
1181
+ description: entry.description,
1182
+ protocol: "unknown",
1183
+ streaming: false,
1184
+ packageName: entry.packageName,
1185
+ commands: [],
1186
+ models: [],
1187
+ status: "supported",
1188
+ hint: entry.hint
1189
+ });
1190
+ }
1191
+ }
1192
+ const installed = await listInstalledAdapters();
1193
+ for (const info of installed) {
1194
+ if (seenSlugs.has(info.slug)) continue;
1195
+ seenSlugs.add(info.slug);
1196
+ const ledgerPresent = await hasSetupLedger(info.slug);
1197
+ out.push({ ...info, status: ledgerPresent ? "ready" : "available" });
1198
+ }
1199
+ return out;
1200
+ }
1115
1201
  async function runSetup(opts) {
1116
1202
  const steps = opts.handle.setup ?? [];
1117
1203
  if (steps.length === 0) {
@@ -1285,10 +1371,10 @@ async function runExternalStep(step, ledger, head) {
1285
1371
  `);
1286
1372
  const opener = platform() === "darwin" ? "open" : platform() === "win32" ? "cmd" : "xdg-open";
1287
1373
  const args = platform() === "win32" ? ["/c", "start", step.url] : [step.url];
1288
- await new Promise((resolve9) => {
1374
+ await new Promise((resolve10) => {
1289
1375
  const child = spawn(opener, args, { stdio: "ignore", detached: true });
1290
- child.once("error", () => resolve9());
1291
- child.once("spawn", () => resolve9());
1376
+ child.once("error", () => resolve10());
1377
+ child.once("spawn", () => resolve10());
1292
1378
  });
1293
1379
  let value = "";
1294
1380
  if (step.callback?.param) {
@@ -1361,7 +1447,7 @@ async function promptString(question, opts) {
1361
1447
  process.stdin.setRawMode?.(true);
1362
1448
  let buf = "";
1363
1449
  process.stdout.write(prompt);
1364
- return new Promise((resolve9) => {
1450
+ return new Promise((resolve10) => {
1365
1451
  const onData = (chunk) => {
1366
1452
  for (const code of chunk) {
1367
1453
  if (code === 13 || code === 10) {
@@ -1369,7 +1455,7 @@ async function promptString(question, opts) {
1369
1455
  process.stdin.setRawMode?.(false);
1370
1456
  process.stdout.write("\n");
1371
1457
  rl.close();
1372
- resolve9(buf || opts.defaultValue || "");
1458
+ resolve10(buf || opts.defaultValue || "");
1373
1459
  return;
1374
1460
  }
1375
1461
  if (code === 3) {
@@ -1451,7 +1537,7 @@ async function resolveSelectOptions(options) {
1451
1537
  });
1452
1538
  }
1453
1539
  async function runShellCapturing(cmd, opts) {
1454
- return new Promise((resolve9) => {
1540
+ return new Promise((resolve10) => {
1455
1541
  const child = spawn("bash", ["-lc", cmd], {
1456
1542
  stdio: opts.interactive ? "inherit" : ["ignore", "pipe", "pipe"]
1457
1543
  });
@@ -1469,11 +1555,11 @@ async function runShellCapturing(cmd, opts) {
1469
1555
  }, opts.timeoutMs);
1470
1556
  child.once("error", () => {
1471
1557
  clearTimeout(timer);
1472
- resolve9({ exitCode: 127, stdout, stderr: stderr || "spawn error" });
1558
+ resolve10({ exitCode: 127, stdout, stderr: stderr || "spawn error" });
1473
1559
  });
1474
1560
  child.once("exit", (code) => {
1475
1561
  clearTimeout(timer);
1476
- resolve9({ exitCode: code ?? 0, stdout, stderr });
1562
+ resolve10({ exitCode: code ?? 0, stdout, stderr });
1477
1563
  });
1478
1564
  });
1479
1565
  }
@@ -2108,7 +2194,7 @@ async function runDownloadInstaller(opts) {
2108
2194
  }
2109
2195
  if (extractCode !== 0) return extractCode;
2110
2196
  const srcBin = join(extractDir, opts.extractBin);
2111
- const binDir = process.env["AGENTPROTO_BIN_DIR"] ?? join(homedir5(), ".local", "bin");
2197
+ const binDir = process.env["AGENTPROTO_BIN_DIR"] ?? join(homedir6(), ".local", "bin");
2112
2198
  await mkdir(binDir, { recursive: true });
2113
2199
  const destBin = join(binDir, opts.extractBin.split("/").pop());
2114
2200
  const cpCode = await spawnInherit("cp", ["-p", srcBin, destBin]);
@@ -2134,19 +2220,19 @@ async function runDownloadInstaller(opts) {
2134
2220
  });
2135
2221
  }
2136
2222
  }
2137
- function homedir5() {
2223
+ function homedir6() {
2138
2224
  return process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/";
2139
2225
  }
2140
2226
  function spawnInherit(cmd, argv) {
2141
- return new Promise((resolve9, reject) => {
2227
+ return new Promise((resolve10, reject) => {
2142
2228
  const child = spawn(cmd, argv, { stdio: "inherit" });
2143
2229
  child.once("error", reject);
2144
- child.once("exit", (code) => resolve9(code ?? 0));
2230
+ child.once("exit", (code) => resolve10(code ?? 0));
2145
2231
  });
2146
2232
  }
2147
2233
  async function runVersionCheck(check) {
2148
2234
  if (!check) return { ok: false, message: "no version_check declared" };
2149
- return new Promise((resolve9) => {
2235
+ return new Promise((resolve10) => {
2150
2236
  const child = spawn("bash", ["-lc", check.cmd], {
2151
2237
  stdio: ["ignore", "pipe", "ignore"]
2152
2238
  });
@@ -2154,19 +2240,19 @@ async function runVersionCheck(check) {
2154
2240
  child.stdout.on("data", (c) => {
2155
2241
  buf += c.toString("utf8");
2156
2242
  });
2157
- child.once("error", () => resolve9({ ok: false, message: "check failed" }));
2243
+ child.once("error", () => resolve10({ ok: false, message: "check failed" }));
2158
2244
  child.once("exit", (code) => {
2159
2245
  if (code !== 0) {
2160
- resolve9({ ok: false, message: `check exited ${code}` });
2246
+ resolve10({ ok: false, message: `check exited ${code}` });
2161
2247
  return;
2162
2248
  }
2163
2249
  const re = new RegExp(check.parse);
2164
2250
  const m = buf.match(re);
2165
2251
  if (!m || !m[1]) {
2166
- resolve9({ ok: false, message: "could not parse version" });
2252
+ resolve10({ ok: false, message: "could not parse version" });
2167
2253
  return;
2168
2254
  }
2169
- resolve9({ ok: true, message: `version ${m[1]}` });
2255
+ resolve10({ ok: true, message: `version ${m[1]}` });
2170
2256
  });
2171
2257
  });
2172
2258
  }
@@ -3017,6 +3103,7 @@ var FileSubstrate = class {
3017
3103
  constructor(opts) {
3018
3104
  this.opts = opts;
3019
3105
  }
3106
+ opts;
3020
3107
  kind = "file";
3021
3108
  async append(input2) {
3022
3109
  const timestamp = input2.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -3142,6 +3229,7 @@ var FileStateStore = class {
3142
3229
  constructor(opts) {
3143
3230
  this.opts = opts;
3144
3231
  }
3232
+ opts;
3145
3233
  kind = "fs";
3146
3234
  async read(participantId) {
3147
3235
  const path = this.pathFor(participantId);
@@ -3177,10 +3265,84 @@ async function ensureDir3(dir) {
3177
3265
  function isNotFound2(err) {
3178
3266
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
3179
3267
  }
3268
+ function spawnWithStdin(opts) {
3269
+ const {
3270
+ command,
3271
+ args = [],
3272
+ stdin,
3273
+ cwd,
3274
+ timeoutMs = 9e4,
3275
+ signal,
3276
+ killSignal = "SIGTERM"
3277
+ } = opts;
3278
+ return new Promise((resolve10, reject) => {
3279
+ if (signal?.aborted) {
3280
+ reject(new Error(`${command} aborted before start`));
3281
+ return;
3282
+ }
3283
+ const child = spawn(command, [...args], {
3284
+ stdio: ["pipe", "pipe", "pipe"],
3285
+ ...cwd ? { cwd } : {}
3286
+ });
3287
+ let out = "";
3288
+ let err = "";
3289
+ let settled = false;
3290
+ const finish = (fn) => {
3291
+ if (settled) return;
3292
+ settled = true;
3293
+ clearTimeout(timer);
3294
+ signal?.removeEventListener("abort", onAbort);
3295
+ fn();
3296
+ };
3297
+ const onAbort = () => {
3298
+ child.kill(killSignal);
3299
+ finish(() => reject(new Error(`${command} aborted`)));
3300
+ };
3301
+ const timer = setTimeout(() => {
3302
+ child.kill(killSignal);
3303
+ finish(() => reject(new Error(`${command} timed out after ${timeoutMs}ms`)));
3304
+ }, timeoutMs);
3305
+ signal?.addEventListener("abort", onAbort);
3306
+ child.stdout.on("data", (d) => out += d);
3307
+ child.stderr.on("data", (d) => err += d);
3308
+ child.on(
3309
+ "error",
3310
+ (e) => finish(() => reject(new Error(`${command} not runnable: ${e.message}`)))
3311
+ );
3312
+ child.on(
3313
+ "close",
3314
+ (code) => finish(() => {
3315
+ if (code === 0) resolve10(out);
3316
+ else
3317
+ reject(
3318
+ new Error(`${command} exited with code ${code}: ${err.trim().slice(0, 200) || "(no stderr)"}`)
3319
+ );
3320
+ })
3321
+ );
3322
+ child.stdin.on("error", () => {
3323
+ });
3324
+ child.stdin.write(stdin);
3325
+ child.stdin.end();
3326
+ });
3327
+ }
3328
+ function parseClaudeJsonOutput(stdout) {
3329
+ try {
3330
+ const parsed = JSON.parse(stdout);
3331
+ if (typeof parsed === "object" && parsed !== null && "result" in parsed && typeof parsed.result === "string") {
3332
+ return parsed.result;
3333
+ }
3334
+ return null;
3335
+ } catch {
3336
+ return null;
3337
+ }
3338
+ }
3339
+
3340
+ // ../agent-runtime/dist/adapters/participant-agent-cli.mjs
3180
3341
  var AgentCliParticipant = class {
3181
3342
  constructor(opts) {
3182
3343
  this.opts = opts;
3183
3344
  }
3345
+ opts;
3184
3346
  kind = "agent-cli";
3185
3347
  async executeTurn(input2) {
3186
3348
  const prompt = await assemblePrompt(input2);
@@ -3232,58 +3394,6 @@ function looksLikeRoleFile(s) {
3232
3394
  const lower = s.toLowerCase();
3233
3395
  return ROLE_FILE_EXTENSIONS.some((ext) => lower.endsWith(ext));
3234
3396
  }
3235
- async function spawnWithStdin(args) {
3236
- return new Promise((resolveP, rejectP) => {
3237
- const proc = spawn(args.command, [...args.args], {
3238
- cwd: args.cwd,
3239
- stdio: ["pipe", "pipe", "pipe"]
3240
- });
3241
- let stdout = "";
3242
- let stderr = "";
3243
- const onAbort = () => proc.kill("SIGTERM");
3244
- args.signal?.addEventListener("abort", onAbort);
3245
- const timer = setTimeout(() => {
3246
- proc.kill("SIGTERM");
3247
- }, args.timeoutMs);
3248
- proc.stdout.on("data", (chunk) => {
3249
- stdout += chunk.toString("utf8");
3250
- });
3251
- proc.stderr.on("data", (chunk) => {
3252
- stderr += chunk.toString("utf8");
3253
- });
3254
- proc.on("error", (err) => {
3255
- clearTimeout(timer);
3256
- args.signal?.removeEventListener("abort", onAbort);
3257
- rejectP(err);
3258
- });
3259
- proc.on("close", (code) => {
3260
- clearTimeout(timer);
3261
- args.signal?.removeEventListener("abort", onAbort);
3262
- if (code === 0) {
3263
- resolveP(stdout);
3264
- return;
3265
- }
3266
- rejectP(
3267
- new Error(
3268
- `agent-cli participant "${args.command}" exited with code ${code}: ${stderr.trim() || "(no stderr)"}`
3269
- )
3270
- );
3271
- });
3272
- proc.stdin.write(args.stdin);
3273
- proc.stdin.end();
3274
- });
3275
- }
3276
- function parseClaudeJsonOutput(stdout) {
3277
- try {
3278
- const parsed = JSON.parse(stdout);
3279
- if (typeof parsed === "object" && parsed !== null && "result" in parsed && typeof parsed.result === "string") {
3280
- return parsed.result;
3281
- }
3282
- return null;
3283
- } catch {
3284
- return null;
3285
- }
3286
- }
3287
3397
 
3288
3398
  // src/registry/builtins.ts
3289
3399
  function registerBuiltins() {
@@ -3700,6 +3810,48 @@ function normalizeConfig(parsed) {
3700
3810
  if (active !== void 0) out.active = active;
3701
3811
  return out;
3702
3812
  }
3813
+ var IMPORTED_MCPS_PATH = () => resolve(homedir(), ".agentproto", "imported-mcps.json");
3814
+ var IMPORTED_MCPS_VERSION = 1;
3815
+ var EMPTY = {
3816
+ version: IMPORTED_MCPS_VERSION,
3817
+ imports: []
3818
+ };
3819
+ async function loadImportedMcps(path = IMPORTED_MCPS_PATH()) {
3820
+ let raw;
3821
+ try {
3822
+ raw = await promises.readFile(path, "utf8");
3823
+ } catch (err) {
3824
+ if (err.code === "ENOENT") return EMPTY;
3825
+ throw err;
3826
+ }
3827
+ let parsed;
3828
+ try {
3829
+ parsed = JSON.parse(raw);
3830
+ } catch (err) {
3831
+ throw new Error(
3832
+ `agentproto: ${path} is not valid JSON (${err instanceof Error ? err.message : String(err)}). Delete or fix manually.`
3833
+ );
3834
+ }
3835
+ return normalize(parsed);
3836
+ }
3837
+ function normalize(parsed) {
3838
+ if (!parsed || typeof parsed !== "object") return EMPTY;
3839
+ const obj = parsed;
3840
+ const imports = [];
3841
+ if (Array.isArray(obj.imports)) {
3842
+ for (const entry of obj.imports) {
3843
+ if (!entry || typeof entry !== "object") continue;
3844
+ const e = entry;
3845
+ const id = typeof e.id === "string" ? e.id : "";
3846
+ const alias = typeof e.alias === "string" ? e.alias : id;
3847
+ const addedAt = typeof e.addedAt === "string" ? e.addedAt : (/* @__PURE__ */ new Date()).toISOString();
3848
+ const snapshot = e.snapshot;
3849
+ if (!id || !snapshot) continue;
3850
+ imports.push({ id, alias, addedAt, snapshot });
3851
+ }
3852
+ }
3853
+ return { version: IMPORTED_MCPS_VERSION, imports };
3854
+ }
3703
3855
 
3704
3856
  // ../define-doctype/dist/index.mjs
3705
3857
  var DEFAULT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{1,79}$/;
@@ -3835,7 +3987,7 @@ function createVerbs(spec) {
3835
3987
  }
3836
3988
  return { path, handle: newHandle, rendered };
3837
3989
  }
3838
- async function resolve9(block, ctx = {}) {
3990
+ async function resolve10(block, ctx = {}) {
3839
3991
  if ("inline" in block) {
3840
3992
  return spec.define(block.inline);
3841
3993
  }
@@ -3861,7 +4013,7 @@ function createVerbs(spec) {
3861
4013
  async function deleteFn(path) {
3862
4014
  await rm(path, { force: true });
3863
4015
  }
3864
- return { create, load, list, update, resolve: resolve9, delete: deleteFn };
4016
+ return { create, load, list, update, resolve: resolve10, delete: deleteFn };
3865
4017
  }
3866
4018
  function defaultBody(spec, frontmatter) {
3867
4019
  const id = typeof frontmatter.name === "string" ? frontmatter.name : typeof frontmatter.id === "string" ? frontmatter.id : typeof frontmatter.slug === "string" ? frontmatter.slug : spec.name;
@@ -3999,8 +4151,374 @@ function resolvePathTemplate(template, handle, doctypeSlug) {
3999
4151
  const identity = typeof handle.id === "string" && handle.id || typeof handle.slug === "string" && handle.slug || typeof handle.name === "string" && handle.name || "unknown";
4000
4152
  return template.replace(/<slug>/g, identity).replace(/<DOCTYPE>/g, doctypeSlug.toUpperCase());
4001
4153
  }
4154
+ 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) => {
4155
+ const schemas = [z.any(), z.array(z.any())];
4156
+ const { errors, failed } = schemas.reduce(
4157
+ ({ errors: errors2, failed: failed2 }, schema) => ((result) => result.error ? {
4158
+ errors: [...errors2, ...result.error.issues],
4159
+ failed: failed2 + 1
4160
+ } : { errors: errors2, failed: failed2 })(
4161
+ schema.safeParse(x)
4162
+ ),
4163
+ { errors: [], failed: 0 }
4164
+ );
4165
+ const passed = schemas.length - failed;
4166
+ if (passed !== 1) {
4167
+ ctx.addIssue(errors.length ? {
4168
+ path: [],
4169
+ code: "invalid_union",
4170
+ errors: [errors],
4171
+ message: "Invalid input: Should pass single schema. Passed " + passed
4172
+ } : {
4173
+ path: [],
4174
+ code: "custom",
4175
+ errors: [errors],
4176
+ message: "Invalid input: Should pass single schema. Passed " + passed
4177
+ });
4178
+ }
4179
+ })).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.");
4180
+ var defineAgent = createDoctype({
4181
+ aip: 42,
4182
+ name: "agent",
4183
+ // AIP-42 ids accept an optional `@<owner>/` prefix for namespacing
4184
+ // across registries (e.g. `@agentik/writer`). Bare ids stay valid.
4185
+ idPattern: /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]{0,79}$/,
4186
+ validate(def) {
4187
+ const result = agentFrontmatterSchema.safeParse(def);
4188
+ if (!result.success) {
4189
+ throw new Error(
4190
+ `defineAgent (AIP-42): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
4191
+ );
4192
+ }
4193
+ },
4194
+ build(def) {
4195
+ return { ...def };
4196
+ }
4197
+ });
4198
+ function parseAgentManifest(source) {
4199
+ const parsed = matter3(source);
4200
+ if (Object.keys(parsed.data).length === 0) {
4201
+ throw new Error("parseAgentManifest: missing or empty frontmatter");
4202
+ }
4203
+ const result = agentFrontmatterSchema.safeParse(parsed.data);
4204
+ if (!result.success) {
4205
+ throw new Error(
4206
+ `parseAgentManifest: invalid frontmatter \u2014 ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
4207
+ );
4208
+ }
4209
+ return { frontmatter: result.data, body: parsed.content };
4210
+ }
4002
4211
 
4003
- // ../mcp-server/dist/index.mjs
4212
+ // ../agent/dist/index.mjs
4213
+ var agentSpec = {
4214
+ name: "agent",
4215
+ aip: 42,
4216
+ schemaLiteral: "agent/v1",
4217
+ pathOf: (h) => `.agents/${h.id}/AGENT.md`,
4218
+ define: defineAgent,
4219
+ parse: (source) => {
4220
+ const m = parseAgentManifest(source);
4221
+ return {
4222
+ frontmatter: m.frontmatter,
4223
+ body: m.body
4224
+ };
4225
+ }
4226
+ };
4227
+ var agentVerbs = createVerbs(agentSpec);
4228
+ var PROVIDER_KINDS = [
4229
+ "cli",
4230
+ "http",
4231
+ "mcp",
4232
+ "sdk",
4233
+ "builtin"
4234
+ ];
4235
+ var constructTool = createDoctype({
4236
+ aip: 14,
4237
+ name: "tool",
4238
+ validate(def) {
4239
+ if ("execute" in def) {
4240
+ throw new Error(
4241
+ `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.`
4242
+ );
4243
+ }
4244
+ },
4245
+ build(def) {
4246
+ return {
4247
+ id: def.id,
4248
+ name: def.name ?? def.id,
4249
+ description: def.description,
4250
+ version: def.version,
4251
+ inputSchema: def.inputSchema,
4252
+ outputSchema: def.outputSchema,
4253
+ contextSchema: def.contextSchema,
4254
+ mutates: Object.freeze([...def.mutates ?? []]),
4255
+ requires: freezeCapabilities(def.requires),
4256
+ approval: defaultApproval(def.approval, def.mutates),
4257
+ riskLevel: def.riskLevel ?? 0,
4258
+ costClass: def.costClass ?? "trivial",
4259
+ timeoutMs: def.timeoutMs ?? 3e4,
4260
+ retry: def.retry,
4261
+ tags: Object.freeze([...def.tags ?? []]),
4262
+ metadata: Object.freeze({ ...def.metadata ?? {} }),
4263
+ idempotent: def.idempotent ?? false,
4264
+ defaultImplementation: def.defaultImplementation,
4265
+ driverConstraints: freezeProviderConstraints(def.driverConstraints)
4266
+ };
4267
+ }
4268
+ });
4269
+ function defineTool(definition) {
4270
+ return constructTool(
4271
+ definition
4272
+ );
4273
+ }
4274
+ function defaultApproval(declared, mutates) {
4275
+ if (declared) return declared;
4276
+ return mutates && mutates.length > 0 ? "on-mutate" : "auto";
4277
+ }
4278
+ function freezeCapabilities(caps) {
4279
+ return Object.freeze({
4280
+ network: Object.freeze([...caps?.network ?? []]),
4281
+ secrets: Object.freeze([...caps?.secrets ?? []]),
4282
+ tools: Object.freeze([...caps?.tools ?? []])
4283
+ });
4284
+ }
4285
+ function freezeProviderConstraints(c) {
4286
+ const forbid = (c?.forbid ?? []).filter(
4287
+ (k) => PROVIDER_KINDS.includes(k)
4288
+ );
4289
+ const requireKind = (c?.requireKind ?? []).filter(
4290
+ (k) => PROVIDER_KINDS.includes(k)
4291
+ );
4292
+ return Object.freeze({
4293
+ forbid: Object.freeze(forbid),
4294
+ requireKind: Object.freeze(requireKind)
4295
+ });
4296
+ }
4297
+ var toolManifestFrontmatterSchema = z.object({
4298
+ schema: z.literal("agentproto/tool/v1").optional(),
4299
+ name: z.string().min(1).max(80),
4300
+ id: z.string().regex(/^[a-z][a-z0-9._-]{1,63}$/),
4301
+ description: z.string().min(1).max(2e3),
4302
+ version: z.string().regex(/^\d+\.\d+\.\d+/),
4303
+ // Optional metadata
4304
+ mutates: z.array(z.string()).optional(),
4305
+ requires: z.object({
4306
+ network: z.array(z.string()).optional(),
4307
+ secrets: z.array(z.string()).optional(),
4308
+ tools: z.array(z.string()).optional()
4309
+ }).optional(),
4310
+ // `ApprovalClass` is `"auto" | "always" | "on-mutate" | \`policy:${string}\``.
4311
+ // z.union widens the regex branch to plain `string` (zod can't express
4312
+ // template literal types), so we use `z.custom` to keep the inferred
4313
+ // type exact — matters because `defineTool` accepts `ApprovalClass`.
4314
+ approval: z.custom(
4315
+ (v) => typeof v === "string" && (v === "auto" || v === "always" || v === "on-mutate" || /^policy:/.test(v)),
4316
+ { message: "expected 'auto' | 'always' | 'on-mutate' | 'policy:<name>'" }
4317
+ ).optional(),
4318
+ risk_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional(),
4319
+ cost_class: z.enum(["trivial", "metered", "expensive"]).optional(),
4320
+ timeout_ms: z.number().int().positive().optional(),
4321
+ idempotent: z.boolean().optional(),
4322
+ tags: z.array(z.string()).optional(),
4323
+ metadata: z.record(z.string(), z.unknown()).optional(),
4324
+ // AIP-26 / AIP-17 / AIP-19 references — kept loose; validated by their
4325
+ // respective AIPs' adapters when consumed.
4326
+ code: z.unknown().optional(),
4327
+ run: z.unknown().optional(),
4328
+ runner: z.unknown().optional(),
4329
+ secrets: z.unknown().optional(),
4330
+ network: z.unknown().optional()
4331
+ });
4332
+ function parseToolManifest(source) {
4333
+ const parsed = matter3(source);
4334
+ if (Object.keys(parsed.data).length === 0) {
4335
+ throw new Error("parseToolManifest: missing or empty frontmatter");
4336
+ }
4337
+ const result = toolManifestFrontmatterSchema.safeParse(parsed.data);
4338
+ if (!result.success) {
4339
+ throw new Error(
4340
+ `parseToolManifest: invalid frontmatter \u2014 ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
4341
+ );
4342
+ }
4343
+ return { frontmatter: result.data, body: parsed.content };
4344
+ }
4345
+ var toolSpec = {
4346
+ name: "tool",
4347
+ aip: 14,
4348
+ schemaLiteral: "agentproto/tool/v1",
4349
+ pathOf: (h) => `${h.id}/TOOL.md`,
4350
+ define: (params) => defineTool(params),
4351
+ parse: (source) => {
4352
+ const m = parseToolManifest(source);
4353
+ return {
4354
+ frontmatter: m.frontmatter,
4355
+ body: m.body
4356
+ };
4357
+ }
4358
+ };
4359
+ var toolVerbs = createVerbs(toolSpec);
4360
+ 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).");
4361
+ var defineRoutine = createDoctype({
4362
+ aip: 41,
4363
+ name: "routine",
4364
+ validate(def) {
4365
+ const result = routineFrontmatterSchema.safeParse(def);
4366
+ if (!result.success) {
4367
+ throw new Error(
4368
+ `defineRoutine (AIP-41): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
4369
+ );
4370
+ }
4371
+ },
4372
+ build(def) {
4373
+ return { ...def };
4374
+ }
4375
+ });
4376
+ function parseRoutineManifest(source) {
4377
+ const parsed = matter3(source);
4378
+ if (Object.keys(parsed.data).length === 0) {
4379
+ throw new Error("parseRoutineManifest: missing or empty frontmatter");
4380
+ }
4381
+ const result = routineFrontmatterSchema.safeParse(parsed.data);
4382
+ if (!result.success) {
4383
+ throw new Error(
4384
+ `parseRoutineManifest: invalid frontmatter \u2014 ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
4385
+ );
4386
+ }
4387
+ return { frontmatter: result.data, body: parsed.content };
4388
+ }
4389
+
4390
+ // ../routine/dist/index.mjs
4391
+ var routineSpec = {
4392
+ name: "routine",
4393
+ aip: 41,
4394
+ schemaLiteral: "routine/v1",
4395
+ pathOf: (h) => `.routines/${h.id}/ROUTINE.md`,
4396
+ define: defineRoutine,
4397
+ parse: (source) => {
4398
+ const m = parseRoutineManifest(source);
4399
+ return {
4400
+ frontmatter: m.frontmatter,
4401
+ body: m.body
4402
+ };
4403
+ }
4404
+ };
4405
+ var routineVerbs = createVerbs(routineSpec);
4406
+ async function summarizeToolRef(raw, workspace) {
4407
+ if (typeof raw === "string") {
4408
+ return { id: raw, description: "(string ref \u2014 not locally resolved)" };
4409
+ }
4410
+ if (typeof raw !== "object" || raw === null) {
4411
+ return { id: String(raw), description: "(unknown ref shape)" };
4412
+ }
4413
+ const block = raw;
4414
+ if (block.file) {
4415
+ try {
4416
+ const handle = await toolVerbs.resolve(
4417
+ { file: block.file },
4418
+ { baseDir: workspace }
4419
+ );
4420
+ return { id: handle.id, description: handle.description ?? "" };
4421
+ } catch {
4422
+ return { id: block.file, description: "(file ref \u2014 could not load)" };
4423
+ }
4424
+ }
4425
+ if (block.inline) {
4426
+ return {
4427
+ id: String(block.inline.id ?? ""),
4428
+ description: String(block.inline.description ?? "")
4429
+ };
4430
+ }
4431
+ if (block.ref) {
4432
+ return { id: block.ref, description: "(ref \u2014 not locally resolved)" };
4433
+ }
4434
+ return { id: JSON.stringify(raw), description: "(unknown ref shape)" };
4435
+ }
4436
+ async function summarizeRoutineRef(raw, workspace) {
4437
+ if (typeof raw === "string") {
4438
+ return { id: raw, description: "(string ref \u2014 not locally resolved)" };
4439
+ }
4440
+ if (typeof raw !== "object" || raw === null) {
4441
+ return { id: String(raw), description: "(unknown ref shape)" };
4442
+ }
4443
+ const block = raw;
4444
+ if (block.file) {
4445
+ try {
4446
+ const handle = await routineVerbs.resolve(
4447
+ { file: block.file },
4448
+ { baseDir: workspace }
4449
+ );
4450
+ return { id: handle.id, description: handle.description ?? "" };
4451
+ } catch {
4452
+ return { id: block.file, description: "(file ref \u2014 could not load)" };
4453
+ }
4454
+ }
4455
+ if (block.inline) {
4456
+ return {
4457
+ id: String(block.inline.id ?? ""),
4458
+ description: String(block.inline.description ?? "")
4459
+ };
4460
+ }
4461
+ if (block.ref) {
4462
+ return { id: block.ref, description: "(ref \u2014 not locally resolved)" };
4463
+ }
4464
+ return { id: JSON.stringify(raw), description: "(unknown ref shape)" };
4465
+ }
4466
+ async function selfInspect(agentId, workspace) {
4467
+ const agentPath = join(workspace, ".agents", agentId, "AGENT.md");
4468
+ let handle;
4469
+ try {
4470
+ const result = await agentVerbs.load(agentPath);
4471
+ handle = result.handle;
4472
+ } catch (err) {
4473
+ const e = new Error(
4474
+ `No AGENT.md found for agent '${agentId}' at '${agentPath}'. Ensure an AGENT.md exists at <workspace>/.agents/${agentId}/AGENT.md.`
4475
+ );
4476
+ e.code = "agent_not_found";
4477
+ throw e;
4478
+ }
4479
+ const rawTools = handle.tools ?? [];
4480
+ const rawRoutines = handle.routines ?? [];
4481
+ const [tools, routines] = await Promise.all([
4482
+ Promise.all(rawTools.map((r) => summarizeToolRef(r, workspace))),
4483
+ Promise.all(rawRoutines.map((r) => summarizeRoutineRef(r, workspace)))
4484
+ ]);
4485
+ return { agentPath, tools, routines };
4486
+ }
4487
+ function registerSelfInspectTool(server, opts) {
4488
+ server.tool(
4489
+ "self_inspect",
4490
+ "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.",
4491
+ {
4492
+ agentId: z.string().min(1).describe(
4493
+ "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."
4494
+ )
4495
+ },
4496
+ async ({ agentId }) => {
4497
+ try {
4498
+ const result = await selfInspect(agentId, opts.workspace);
4499
+ return {
4500
+ content: [
4501
+ {
4502
+ type: "text",
4503
+ text: JSON.stringify(result, null, 2)
4504
+ }
4505
+ ]
4506
+ };
4507
+ } catch (err) {
4508
+ const message = err instanceof Error ? err.message : String(err);
4509
+ return {
4510
+ content: [
4511
+ {
4512
+ type: "text",
4513
+ text: JSON.stringify({ error: "agent_not_found", message }, null, 2)
4514
+ }
4515
+ ],
4516
+ isError: true
4517
+ };
4518
+ }
4519
+ }
4520
+ );
4521
+ }
4004
4522
  async function createMcpServer(opts) {
4005
4523
  const server = new McpServer({
4006
4524
  name: opts.name ?? "agentproto-mcp-server",
@@ -4015,6 +4533,9 @@ async function createMcpServer(opts) {
4015
4533
  for (const spec of allSpecs) {
4016
4534
  registerVerbs(server, spec, anchor);
4017
4535
  }
4536
+ if (opts.workspace) {
4537
+ registerSelfInspectTool(server, { workspace: opts.workspace });
4538
+ }
4018
4539
  return {
4019
4540
  server,
4020
4541
  registered: allSpecs.map((s) => s.name)
@@ -4042,7 +4563,7 @@ function registerVerbs(server, spec, anchor) {
4042
4563
  body,
4043
4564
  dryRun
4044
4565
  });
4045
- return contentText({
4566
+ return contentText2({
4046
4567
  path: result.path,
4047
4568
  rendered: result.rendered
4048
4569
  });
@@ -4056,7 +4577,7 @@ function registerVerbs(server, spec, anchor) {
4056
4577
  },
4057
4578
  async ({ path }) => {
4058
4579
  const result = await verbs.load(anchor(path));
4059
- return contentText({ path: result.path, handle: result.handle });
4580
+ return contentText2({ path: result.path, handle: result.handle });
4060
4581
  }
4061
4582
  );
4062
4583
  server.tool(
@@ -4071,7 +4592,7 @@ function registerVerbs(server, spec, anchor) {
4071
4592
  },
4072
4593
  async ({ dir, skipDirs }) => {
4073
4594
  const handles = await verbs.list(anchor(dir), { skipDirs });
4074
- return contentText({ count: handles.length, handles });
4595
+ return contentText2({ count: handles.length, handles });
4075
4596
  }
4076
4597
  );
4077
4598
  server.tool(
@@ -4091,7 +4612,7 @@ function registerVerbs(server, spec, anchor) {
4091
4612
  (existing) => ({ ...existing, ...patch }),
4092
4613
  { body }
4093
4614
  );
4094
- return contentText({ path: result.path, rendered: result.rendered });
4615
+ return contentText2({ path: result.path, rendered: result.rendered });
4095
4616
  }
4096
4617
  );
4097
4618
  server.tool(
@@ -4105,14 +4626,16 @@ function registerVerbs(server, spec, anchor) {
4105
4626
  z.object({ inline: z.record(z.string(), z.unknown()) }),
4106
4627
  z.object({ ref: z.string() }),
4107
4628
  z.object({ file: z.string() })
4108
- ]).describe("The composition block."),
4629
+ ]).describe(
4630
+ 'The composition block. Exactly one of: { "inline": {...params} } | { "ref": "@scope/id" } | { "file": "relative/path.md" }. Omitting the wrapping key causes invalid_union.'
4631
+ ),
4109
4632
  baseDir: z.string().optional().describe("Base dir for `file:` references.")
4110
4633
  },
4111
4634
  async ({ block, baseDir }) => {
4112
4635
  const handle = await verbs.resolve(block, {
4113
4636
  baseDir: baseDir ? anchor(baseDir) : void 0
4114
4637
  });
4115
- return contentText({ handle });
4638
+ return contentText2({ handle });
4116
4639
  }
4117
4640
  );
4118
4641
  server.tool(
@@ -4124,7 +4647,7 @@ function registerVerbs(server, spec, anchor) {
4124
4647
  async ({ path }) => {
4125
4648
  const target = anchor(path);
4126
4649
  await verbs.delete(target);
4127
- return contentText({ deleted: target });
4650
+ return contentText2({ deleted: target });
4128
4651
  }
4129
4652
  );
4130
4653
  }
@@ -4168,7 +4691,7 @@ async function loadExtensions(workspace, specs) {
4168
4691
  }
4169
4692
  return out;
4170
4693
  }
4171
- function contentText(payload) {
4694
+ function contentText2(payload) {
4172
4695
  return {
4173
4696
  content: [
4174
4697
  {
@@ -4270,7 +4793,7 @@ function makeCwdAnchor(workspace) {
4270
4793
  const root = resolve(workspace);
4271
4794
  return (input2) => {
4272
4795
  if (!input2 || input2.length === 0) return root;
4273
- const candidate = isAbsolute(input2) ? normalize(input2) : normalize(join(root, input2));
4796
+ const candidate = isAbsolute(input2) ? normalize$1(input2) : normalize$1(join(root, input2));
4274
4797
  const rel = relative(root, candidate);
4275
4798
  if (rel.startsWith("..") || isAbsolute(rel)) {
4276
4799
  throw new Error(
@@ -4565,7 +5088,7 @@ function makeAnchor(workspace) {
4565
5088
  if (typeof input2 !== "string" || input2.length === 0) {
4566
5089
  throw new FsPathError("path must be a non-empty string");
4567
5090
  }
4568
- const candidate = isAbsolute(input2) ? normalize(input2) : normalize(join(root, input2));
5091
+ const candidate = isAbsolute(input2) ? normalize$1(input2) : normalize$1(join(root, input2));
4569
5092
  const rel = relative(root, candidate);
4570
5093
  if (rel.startsWith("..") || isAbsolute(rel)) {
4571
5094
  throw new FsPathError(`path escapes the workspace: '${input2}'`);
@@ -4946,18 +5469,18 @@ function stringifyValues(raw) {
4946
5469
  }
4947
5470
  return out;
4948
5471
  }
4949
- var IMPORTED_MCPS_PATH = () => resolve(homedir(), ".agentproto", "imported-mcps.json");
4950
- var IMPORTED_MCPS_VERSION = 1;
4951
- var EMPTY = {
4952
- version: IMPORTED_MCPS_VERSION,
5472
+ var IMPORTED_MCPS_PATH2 = () => resolve(homedir(), ".agentproto", "imported-mcps.json");
5473
+ var IMPORTED_MCPS_VERSION2 = 1;
5474
+ var EMPTY2 = {
5475
+ version: IMPORTED_MCPS_VERSION2,
4953
5476
  imports: []
4954
5477
  };
4955
- async function loadImportedMcps(path = IMPORTED_MCPS_PATH()) {
5478
+ async function loadImportedMcps2(path = IMPORTED_MCPS_PATH2()) {
4956
5479
  let raw;
4957
5480
  try {
4958
5481
  raw = await promises.readFile(path, "utf8");
4959
5482
  } catch (err) {
4960
- if (err.code === "ENOENT") return EMPTY;
5483
+ if (err.code === "ENOENT") return EMPTY2;
4961
5484
  throw err;
4962
5485
  }
4963
5486
  let parsed;
@@ -4970,7 +5493,7 @@ async function loadImportedMcps(path = IMPORTED_MCPS_PATH()) {
4970
5493
  }
4971
5494
  return normalize3(parsed);
4972
5495
  }
4973
- async function saveImportedMcps(config, path = IMPORTED_MCPS_PATH()) {
5496
+ async function saveImportedMcps(config, path = IMPORTED_MCPS_PATH2()) {
4974
5497
  await promises.mkdir(dirname(path), { recursive: true });
4975
5498
  const tmp = `${path}.tmp.${process.pid}`;
4976
5499
  await promises.writeFile(tmp, JSON.stringify(config, null, 2) + "\n", "utf8");
@@ -4995,7 +5518,7 @@ function removeImport(config, id) {
4995
5518
  return { ...config, imports: config.imports.filter((e) => e.id !== id) };
4996
5519
  }
4997
5520
  function normalize3(parsed) {
4998
- if (!parsed || typeof parsed !== "object") return EMPTY;
5521
+ if (!parsed || typeof parsed !== "object") return EMPTY2;
4999
5522
  const obj = parsed;
5000
5523
  const imports = [];
5001
5524
  if (Array.isArray(obj.imports)) {
@@ -5010,7 +5533,7 @@ function normalize3(parsed) {
5010
5533
  imports.push({ id, alias, addedAt, snapshot });
5011
5534
  }
5012
5535
  }
5013
- return { version: IMPORTED_MCPS_VERSION, imports };
5536
+ return { version: IMPORTED_MCPS_VERSION2, imports };
5014
5537
  }
5015
5538
  function registerSessionTools(server, opts) {
5016
5539
  const { registry, resolveAgentAdapter, listAgentAdapters, mcpProxy } = opts;
@@ -5300,7 +5823,7 @@ function registerSessionTools(server, opts) {
5300
5823
  {},
5301
5824
  async () => {
5302
5825
  try {
5303
- const config = await loadImportedMcps();
5826
+ const config = await loadImportedMcps2();
5304
5827
  return {
5305
5828
  content: [
5306
5829
  { type: "text", text: JSON.stringify(config, null, 2) }
@@ -5345,7 +5868,7 @@ function registerSessionTools(server, opts) {
5345
5868
  isError: true
5346
5869
  };
5347
5870
  }
5348
- const cfg = await loadImportedMcps();
5871
+ const cfg = await loadImportedMcps2();
5349
5872
  const next = addImport(cfg, {
5350
5873
  snapshot,
5351
5874
  ...input2.alias ? { alias: input2.alias } : {}
@@ -5378,7 +5901,7 @@ function registerSessionTools(server, opts) {
5378
5901
  },
5379
5902
  async (input2) => {
5380
5903
  try {
5381
- const cfg = await loadImportedMcps();
5904
+ const cfg = await loadImportedMcps2();
5382
5905
  if (!cfg.imports.some((e) => e.id === input2.id)) {
5383
5906
  return {
5384
5907
  content: [
@@ -6260,7 +6783,7 @@ async function startHttpServer(opts) {
6260
6783
  return;
6261
6784
  }
6262
6785
  if (path === "/mcps/imports" && req.method === "GET") {
6263
- const config = await loadImportedMcps();
6786
+ const config = await loadImportedMcps2();
6264
6787
  res.writeHead(200, { "content-type": "application/json" });
6265
6788
  res.end(JSON.stringify(config));
6266
6789
  return;
@@ -6284,7 +6807,7 @@ async function startHttpServer(opts) {
6284
6807
  );
6285
6808
  return;
6286
6809
  }
6287
- const cfg = await loadImportedMcps();
6810
+ const cfg = await loadImportedMcps2();
6288
6811
  const next = addImport(cfg, {
6289
6812
  snapshot,
6290
6813
  ...body.alias ? { alias: body.alias } : {}
@@ -6297,7 +6820,7 @@ async function startHttpServer(opts) {
6297
6820
  const importMatch = path.match(/^\/mcps\/imports\/(.+)$/);
6298
6821
  if (importMatch && req.method === "DELETE") {
6299
6822
  const id = decodeURIComponent(importMatch[1] ?? "");
6300
- const cfg = await loadImportedMcps();
6823
+ const cfg = await loadImportedMcps2();
6301
6824
  if (!cfg.imports.some((e) => e.id === id)) {
6302
6825
  res.writeHead(404, { "content-type": "application/json" });
6303
6826
  res.end(JSON.stringify({ error: "import_not_found", id }));
@@ -7715,13 +8238,13 @@ var McpProxyRegistry = class {
7715
8238
  async ensureCurrent() {
7716
8239
  let mtimeMs = 0;
7717
8240
  try {
7718
- const st = await promises.stat(IMPORTED_MCPS_PATH());
8241
+ const st = await promises.stat(IMPORTED_MCPS_PATH2());
7719
8242
  mtimeMs = st.mtimeMs;
7720
8243
  } catch {
7721
8244
  mtimeMs = 0;
7722
8245
  }
7723
8246
  if (this.hasReadOnce && mtimeMs === this.lastMtimeMs) return;
7724
- const config = await loadImportedMcps().catch(() => ({
8247
+ const config = await loadImportedMcps2().catch(() => ({
7725
8248
  version: 1,
7726
8249
  imports: []
7727
8250
  }));
@@ -8290,7 +8813,7 @@ function createWorkspaceFs(opts) {
8290
8813
  "absolute paths are not allowed; use a workspace-relative path"
8291
8814
  );
8292
8815
  }
8293
- const joined = normalize(join(root, path));
8816
+ const joined = normalize$1(join(root, path));
8294
8817
  const rel = relative(root, joined);
8295
8818
  if (rel.startsWith("..") || isAbsolute(rel)) {
8296
8819
  throw new WorkspacePathError(
@@ -8494,6 +9017,51 @@ async function runBoot(workspace, opts, conversations, events) {
8494
9017
  durationMs: 0
8495
9018
  });
8496
9019
  }
9020
+
9021
+ // src/registry/catalog.ts
9022
+ var CATALOG = [
9023
+ // ── Agent CLIs ────────────────────────────────────────────────────────
9024
+ {
9025
+ type: "agent-cli",
9026
+ slug: "claude-code",
9027
+ name: "Claude Code",
9028
+ description: "Anthropic's Claude Code via @agentclientprotocol/claude-agent-acp ACP wrapper.",
9029
+ packageName: "@agentproto/adapter-claude-code",
9030
+ hint: "anthropic \xB7 ACP \xB7 resumable"
9031
+ },
9032
+ {
9033
+ type: "agent-cli",
9034
+ slug: "opencode",
9035
+ name: "OpenCode",
9036
+ description: "sst/opencode with first-party ACP mode. Multi-provider: Anthropic, OpenAI, OpenRouter, Groq.",
9037
+ packageName: "@agentproto/adapter-opencode",
9038
+ hint: "multi-provider \xB7 ACP \xB7 resumable"
9039
+ },
9040
+ {
9041
+ type: "agent-cli",
9042
+ slug: "codex",
9043
+ name: "Codex",
9044
+ description: "OpenAI Codex coding agent via Zed's @zed-industries/codex-acp ACP wrapper.",
9045
+ packageName: "@agentproto/adapter-codex",
9046
+ hint: "openai \xB7 ACP \xB7 resumable"
9047
+ },
9048
+ {
9049
+ type: "agent-cli",
9050
+ slug: "hermes",
9051
+ name: "Hermes",
9052
+ description: "Nous Research Hermes agent with skills, sandboxes, and memory plugin surface.",
9053
+ packageName: "@agentproto/adapter-hermes",
9054
+ hint: "nous \xB7 ACP \xB7 sub-agents"
9055
+ },
9056
+ {
9057
+ type: "agent-cli",
9058
+ slug: "openclaw",
9059
+ name: "OpenClaw",
9060
+ description: "OpenClaw coding-agent platform with native ACP bridge and plugin surface.",
9061
+ packageName: "@agentproto/adapter-openclaw",
9062
+ hint: "gateway \xB7 ACP \xB7 plugins"
9063
+ }
9064
+ ];
8497
9065
  async function runServe(args) {
8498
9066
  const { values } = parseArgs({
8499
9067
  args: [...args],
@@ -8600,10 +9168,11 @@ async function runServe(args) {
8600
9168
  const adapter = await resolveAdapter(slug);
8601
9169
  const runtime = createAgentCliRuntime(adapter.handle);
8602
9170
  return {
8603
- async startSession({ cwd, resumeSessionId }) {
9171
+ async startSession({ cwd, resumeSessionId, model }) {
8604
9172
  return runtime.start({
8605
9173
  cwd,
8606
- ...resumeSessionId ? { resumeSessionId } : {}
9174
+ ...resumeSessionId ? { resumeSessionId } : {},
9175
+ ...model ? { config: { options: { model } } } : {}
8607
9176
  });
8608
9177
  },
8609
9178
  commandPreview: `${adapter.handle.bin} ${(adapter.handle.bin_args ?? []).join(" ")}`.trim()
@@ -8622,15 +9191,15 @@ async function runServe(args) {
8622
9191
  workspace: opts.workspace,
8623
9192
  port: opts.port,
8624
9193
  bind: opts.bind,
8625
- specs: [],
9194
+ specs: [driverSpec],
8626
9195
  name: "agentproto-serve",
8627
9196
  // BOOT.md is silly for a tunnel daemon — skip it.
8628
9197
  boot: false,
8629
9198
  resolveAgentAdapter,
8630
9199
  // Discovery for UIs / operators — `GET /adapters` + `list_adapters`
8631
- // MCP tool. Walks node_modules @agentproto/adapter-* on each call;
8632
- // cheap enough that we don't bother caching here.
8633
- listAgentAdapters: listInstalledAdapters,
9200
+ // MCP tool. Starts from the bundled catalog so known adapters always
9201
+ // appear (with status "supported") even when not yet installed.
9202
+ listAgentAdapters: () => listAdaptersWithCatalog(CATALOG),
8634
9203
  ...spawnPty ? { spawnPty } : {},
8635
9204
  ...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
8636
9205
  ...opts.strictOrigins ? { strictOrigins: true } : {}
@@ -8642,6 +9211,10 @@ async function runServe(args) {
8642
9211
  );
8643
9212
  return 1;
8644
9213
  }
9214
+ const installedAdapterSlugs = await listAdaptersWithCatalog(CATALOG).then((list) => list.filter((a) => a.status !== "supported").map((a) => a.slug)).catch(() => []);
9215
+ const announcedTools = [
9216
+ .../* @__PURE__ */ new Set([...gateway.registered, ...installedAdapterSlugs])
9217
+ ];
8645
9218
  printBootBanner({
8646
9219
  url: gateway.url,
8647
9220
  workspace: gateway.workspace,
@@ -8699,8 +9272,8 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
8699
9272
  AGENTPROTO_DAEMON_TOKEN: gateway.token
8700
9273
  }
8701
9274
  });
8702
- await new Promise((resolve9) => {
8703
- child.once("exit", () => resolve9());
9275
+ await new Promise((resolve10) => {
9276
+ child.once("exit", () => resolve10());
8704
9277
  aborter.signal.addEventListener("abort", () => {
8705
9278
  try {
8706
9279
  child.kill("SIGTERM");
@@ -8720,8 +9293,8 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
8720
9293
  `${color.dim}Press Ctrl-C to stop.${color.reset}
8721
9294
  `
8722
9295
  );
8723
- await new Promise((resolve9) => {
8724
- aborter.signal.addEventListener("abort", () => resolve9());
9296
+ await new Promise((resolve10) => {
9297
+ aborter.signal.addEventListener("abort", () => resolve10());
8725
9298
  });
8726
9299
  return 0;
8727
9300
  }
@@ -8734,7 +9307,14 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
8734
9307
  const reconnectState = { immediate: false };
8735
9308
  while (!aborter.signal.aborted) {
8736
9309
  try {
8737
- await runOneTunnel(opts, gateway, spawnPty, aborter.signal, reconnectState);
9310
+ await runOneTunnel(
9311
+ opts,
9312
+ gateway,
9313
+ announcedTools,
9314
+ spawnPty,
9315
+ aborter.signal,
9316
+ reconnectState
9317
+ );
8738
9318
  backoffMs = opts.reconnectMinMs ?? 1e3;
8739
9319
  if (reconnectState.immediate) {
8740
9320
  reconnectState.immediate = false;
@@ -8755,17 +9335,17 @@ ${color.dim}\u2500\u2500 shutting down (${signal}) \u2500\u2500${color.reset}
8755
9335
  }
8756
9336
  return 0;
8757
9337
  }
8758
- async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
9338
+ async function runOneTunnel(opts, gateway, announcedTools, spawnPty, signal, reconnectState) {
8759
9339
  if (!opts.connect) throw new Error("runOneTunnel: --connect not set");
8760
9340
  const headers = {
8761
- "user-agent": "agentproto/0.1.0-alpha"
9341
+ "user-agent": `agentproto/${"0.1.2"}`
8762
9342
  };
8763
9343
  if (opts.token) headers.authorization = `Bearer ${opts.token}`;
8764
9344
  const ws = new WebSocket2(opts.connect, { headers });
8765
- await new Promise((resolve9, reject) => {
9345
+ await new Promise((resolve10, reject) => {
8766
9346
  const onOpen = () => {
8767
9347
  ws.off("error", onError);
8768
- resolve9();
9348
+ resolve10();
8769
9349
  };
8770
9350
  const onError = (err) => {
8771
9351
  ws.off("open", onOpen);
@@ -8796,18 +9376,34 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
8796
9376
  label: opts.label,
8797
9377
  pty: spawnPty !== null,
8798
9378
  ...spawnPty ? { spawnPty } : {},
9379
+ // Announce what this daemon serves (doctypes + installed agent adapters)
9380
+ // so a multi-daemon host can enumerate + route by capability.
9381
+ ...announcedTools.length ? { tools: announcedTools } : {},
8799
9382
  // Generic HTTP-relay upstream for tunnel `http_request` frames.
8800
9383
  // Cloud-side callers (e.g. the API's local-daemon filesystem
8801
9384
  // provider) can now route MCP JSON-RPC + any other HTTP through
8802
9385
  // the daemon without needing a public URL. We point at the local
8803
9386
  // gateway since that's where `/mcp`, `/sessions`, `/events` live.
8804
9387
  httpUpstream: gateway.url,
9388
+ // Forward bounds, stated explicitly so they're visible and tunable here
9389
+ // (the local gateway is fast, so the package defaults fit; bump these if
9390
+ // this daemon ever fronts a slower upstream):
9391
+ // - connect + buffered-body / connect + stream-headers ceiling
9392
+ httpForwardTimeoutMs: DEFAULT_HTTP_FORWARD_TIMEOUT_MS,
9393
+ // - WS upgrade dial ceiling
9394
+ wsDialTimeoutMs: DEFAULT_WS_DIAL_TIMEOUT_MS,
9395
+ // Bound a streaming forward against a silent upstream: if an SSE/ndjson
9396
+ // stream sends headers then stalls with no bytes for 2 min, end it instead
9397
+ // of holding the reqId open forever. The window resets per chunk, so a
9398
+ // well-behaved stream (events or heartbeat comments) is never cut — only a
9399
+ // genuinely dead upstream trips it.
9400
+ httpStreamIdleTimeoutMs: 12e4,
8805
9401
  // WS forwarding upstream — daemon dials the local gateway's WS
8806
9402
  // endpoints (/sessions/:id/pty, etc) and pipes frames to the host.
8807
9403
  // Used by the cloud tunnel pod so browsers on mobile can attach to
8808
9404
  // interactive PTY sessions even though the daemon is only reachable
8809
9405
  // through the host (not directly).
8810
- dialUpstreamWs: async ({ url, protocols, headers: headers2 }) => {
9406
+ dialUpstreamWs: async ({ url, protocols, headers: headers2, signal: signal2 }) => {
8811
9407
  const upstreamHeaders = {
8812
9408
  ...headers2
8813
9409
  };
@@ -8819,14 +9415,28 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
8819
9415
  } catch {
8820
9416
  }
8821
9417
  }
8822
- return await new Promise((resolve9, reject) => {
9418
+ return await new Promise((resolve10, reject) => {
8823
9419
  const sock = new WebSocket2(url, protocols ? [...protocols] : void 0, {
8824
9420
  headers: upstreamHeaders
8825
9421
  });
9422
+ const onAbort = () => {
9423
+ sock.off("open", onceOpen);
9424
+ sock.off("error", onceError);
9425
+ sock.off("unexpected-response", onceUnexpected);
9426
+ try {
9427
+ sock.terminate();
9428
+ } catch {
9429
+ }
9430
+ reject(
9431
+ signal2?.reason instanceof Error ? signal2.reason : new Error("ws dial aborted")
9432
+ );
9433
+ };
9434
+ const detachAbort = () => signal2?.removeEventListener("abort", onAbort);
8826
9435
  const onceOpen = () => {
9436
+ detachAbort();
8827
9437
  sock.off("error", onceError);
8828
9438
  sock.off("unexpected-response", onceUnexpected);
8829
- resolve9({
9439
+ resolve10({
8830
9440
  protocol: sock.protocol ?? "",
8831
9441
  send: (data, sendOpts) => {
8832
9442
  sock.send(data, { binary: sendOpts.binary });
@@ -8853,18 +9463,43 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
8853
9463
  });
8854
9464
  };
8855
9465
  const onceError = (err) => {
9466
+ detachAbort();
8856
9467
  sock.off("open", onceOpen);
8857
9468
  reject(err);
8858
9469
  };
8859
9470
  const onceUnexpected = (_req, res) => {
9471
+ detachAbort();
8860
9472
  sock.off("open", onceOpen);
8861
9473
  reject(new Error(`Unexpected server response: ${res.statusCode ?? 0}`));
8862
9474
  };
9475
+ if (signal2) {
9476
+ if (signal2.aborted) {
9477
+ onAbort();
9478
+ return;
9479
+ }
9480
+ signal2.addEventListener("abort", onAbort, { once: true });
9481
+ }
8863
9482
  sock.once("open", onceOpen);
8864
9483
  sock.once("error", onceError);
8865
9484
  sock.once("unexpected-response", onceUnexpected);
8866
9485
  });
8867
9486
  },
9487
+ // Resolve a named WS upstream to a registered import's origin. A host
9488
+ // can watch a tab on an imported capability server (e.g. a browser
9489
+ // daemon) by aliasing the import instead of the default gateway — the
9490
+ // path rides the import's own origin (`http://127.0.0.1:<port>`), the
9491
+ // daemon never accepts a raw origin from the host.
9492
+ resolveWsUpstream: async (alias) => {
9493
+ const cfg = await loadImportedMcps();
9494
+ const entry = cfg.imports.find((e) => e.alias === alias);
9495
+ const url = entry?.snapshot.url;
9496
+ if (!url) return void 0;
9497
+ try {
9498
+ return new URL(url).origin;
9499
+ } catch {
9500
+ return void 0;
9501
+ }
9502
+ },
8868
9503
  // v0 authorize hook: trust the bearer-authenticated host completely.
8869
9504
  // Token possession proves the host was provisioned for this daemon.
8870
9505
  // Per-spawn policy filtering will land alongside the policy.toml.
@@ -8900,31 +9535,31 @@ async function runOneTunnel(opts, gateway, spawnPty, signal, reconnectState) {
8900
9535
  }
8901
9536
  }
8902
9537
  });
8903
- await new Promise((resolve9) => {
9538
+ await new Promise((resolve10) => {
8904
9539
  const offClose = sink.onClose(() => {
8905
9540
  clearInterval(keepaliveInterval);
8906
9541
  offClose();
8907
- resolve9();
9542
+ resolve10();
8908
9543
  });
8909
9544
  if (signal.aborted) {
8910
9545
  clearInterval(keepaliveInterval);
8911
- server.close().finally(() => resolve9());
9546
+ server.close().finally(() => resolve10());
8912
9547
  }
8913
9548
  signal.addEventListener("abort", () => {
8914
9549
  clearInterval(keepaliveInterval);
8915
- server.close().finally(() => resolve9());
9550
+ server.close().finally(() => resolve10());
8916
9551
  });
8917
9552
  });
8918
9553
  process.stderr.write(`agentproto serve: tunnel closed.
8919
9554
  `);
8920
9555
  }
8921
9556
  function sleep3(ms, signal) {
8922
- return new Promise((resolve9) => {
8923
- if (signal.aborted) return resolve9();
8924
- const timer = setTimeout(resolve9, ms);
9557
+ return new Promise((resolve10) => {
9558
+ if (signal.aborted) return resolve10();
9559
+ const timer = setTimeout(resolve10, ms);
8925
9560
  signal.addEventListener("abort", () => {
8926
9561
  clearTimeout(timer);
8927
- resolve9();
9562
+ resolve10();
8928
9563
  });
8929
9564
  });
8930
9565
  }
@@ -10384,7 +11019,7 @@ function stripAnsi(s) {
10384
11019
  return s.replace(/\x1b\[[0-9;]*m/g, "");
10385
11020
  }
10386
11021
  function httpDelete(url, token) {
10387
- return new Promise((resolve9, reject) => {
11022
+ return new Promise((resolve10, reject) => {
10388
11023
  const u = new URL(url);
10389
11024
  const lib = u.protocol === "https:" ? https : http;
10390
11025
  const headers = {};
@@ -10400,7 +11035,7 @@ function httpDelete(url, token) {
10400
11035
  return;
10401
11036
  }
10402
11037
  try {
10403
- resolve9(raw ? JSON.parse(raw) : {});
11038
+ resolve10(raw ? JSON.parse(raw) : {});
10404
11039
  } catch (err) {
10405
11040
  reject(err instanceof Error ? err : new Error(String(err)));
10406
11041
  }
@@ -10967,21 +11602,21 @@ async function runPtyAttach(endpoint, desc, _colour) {
10967
11602
  });
10968
11603
  }
10969
11604
  async function probeDaemonHealth(baseUrl) {
10970
- return new Promise((resolve9) => {
11605
+ return new Promise((resolve10) => {
10971
11606
  try {
10972
11607
  const u = new URL(`${baseUrl}/health`);
10973
11608
  const lib = u.protocol === "https:" ? https : http;
10974
11609
  const req = lib.get(u, { timeout: 500 }, (res) => {
10975
- resolve9((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300);
11610
+ resolve10((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300);
10976
11611
  res.resume();
10977
11612
  });
10978
- req.on("error", () => resolve9(false));
11613
+ req.on("error", () => resolve10(false));
10979
11614
  req.on("timeout", () => {
10980
11615
  req.destroy();
10981
- resolve9(false);
11616
+ resolve10(false);
10982
11617
  });
10983
11618
  } catch {
10984
- resolve9(false);
11619
+ resolve10(false);
10985
11620
  }
10986
11621
  });
10987
11622
  }
@@ -11021,7 +11656,7 @@ async function explain401(endpoint, verb) {
11021
11656
  return lines.join("\n");
11022
11657
  }
11023
11658
  function httpPostJson(url, body, token) {
11024
- return new Promise((resolve9, reject) => {
11659
+ return new Promise((resolve10, reject) => {
11025
11660
  const u = new URL(url);
11026
11661
  const payload = Buffer.from(JSON.stringify(body), "utf8");
11027
11662
  const lib = u.protocol === "https:" ? https : http;
@@ -11044,7 +11679,7 @@ function httpPostJson(url, body, token) {
11044
11679
  return;
11045
11680
  }
11046
11681
  try {
11047
- resolve9(JSON.parse(raw));
11682
+ resolve10(JSON.parse(raw));
11048
11683
  } catch (err) {
11049
11684
  reject(err instanceof Error ? err : new Error(String(err)));
11050
11685
  }
@@ -11060,7 +11695,7 @@ async function httpGetJson(url) {
11060
11695
  return await httpGetJsonImpl(url);
11061
11696
  }
11062
11697
  function httpGetJsonImpl(url) {
11063
- return new Promise((resolve9, reject) => {
11698
+ return new Promise((resolve10, reject) => {
11064
11699
  const u = new URL(url);
11065
11700
  const lib = u.protocol === "https:" ? https : http;
11066
11701
  lib.get(u, (res) => {
@@ -11073,7 +11708,7 @@ function httpGetJsonImpl(url) {
11073
11708
  return;
11074
11709
  }
11075
11710
  try {
11076
- resolve9(JSON.parse(body));
11711
+ resolve10(JSON.parse(body));
11077
11712
  } catch (err) {
11078
11713
  reject(err instanceof Error ? err : new Error(String(err)));
11079
11714
  }
@@ -11146,7 +11781,8 @@ async function main(argv) {
11146
11781
  const verbIdx = argv.findIndex((a) => VERBS.has(a));
11147
11782
  if (verbIdx === -1) {
11148
11783
  if (argv.includes("--version") || argv.includes("-v")) {
11149
- process.stdout.write("agentproto 0.1.0-alpha\n");
11784
+ process.stdout.write(`agentproto ${"0.1.2"}
11785
+ `);
11150
11786
  return 0;
11151
11787
  }
11152
11788
  if (argv.includes("--help") || argv.includes("-h") || argv.length === 0) {