@agentproto/runtime 5.2.1 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/bundles.d.ts +67 -0
- package/dist/bundles.mjs +173 -0
- package/dist/bundles.mjs.map +1 -0
- package/dist/catalog-models.d.ts +2 -3
- package/dist/config-Gz7q9yfJ.d.ts +761 -0
- package/dist/config-schema.d.ts +9 -3
- package/dist/config-schema.mjs +162 -12
- package/dist/config-schema.mjs.map +1 -1
- package/dist/config.d.ts +2 -611
- package/dist/config.mjs +169 -14
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +674 -541
- package/dist/index.mjs +10647 -4135
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-imports.d.ts +73 -1
- package/dist/mcp-imports.mjs +146 -4
- package/dist/mcp-imports.mjs.map +1 -1
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/{session-config-RyRL2AbD.d.ts → session-config-DxIIuJFY.d.ts} +1 -1
- package/dist/session-presence.mjs +160 -11
- package/dist/session-presence.mjs.map +1 -1
- package/dist/session-story.mjs +1 -0
- package/dist/session-story.mjs.map +1 -1
- package/dist/{sessions-g4fw_zKx.d.ts → sessions-DMzCgTVy.d.ts} +647 -12
- package/dist/{spawn-defaults-CHvz85DD.d.ts → spawn-defaults-Cp9SGZex.d.ts} +103 -2
- package/dist/user-presets.d.ts +5 -3
- package/package.json +43 -30
- package/dist/context-continuity-BfNVbX5W.d.ts +0 -104
package/README.md
CHANGED
|
@@ -221,6 +221,24 @@ We use a `data-*` custom part rather than the `ai` v6 native `tool-approval-requ
|
|
|
221
221
|
|
|
222
222
|
Only `permission-resolved` (the final decision) transits the chat stream today. PENDING permission requests (the same class of thing `ai` models as `tool-approval-request`, and Mastra as `tool-call-approval`) are **not** yet surfaced by this route — there is no per-turn "request is waiting for a human" event in this stream. That is a known gap, out of scope for this work package, not an oversight; closing it (emitting a pending-approval data part when the daemon holds on a permission) is future work.
|
|
223
223
|
|
|
224
|
+
## Browser adapters and page tools
|
|
225
|
+
|
|
226
|
+
The daemon, `POST /sessions/browser` and the CLI resolve browser adapters (`camofox`, `bureau`, `chromium`) through one table, `defaultBrowserAdapterResolution()`, backed by the `@agentproto/adapter-browser` facade.
|
|
227
|
+
|
|
228
|
+
`projectBrowserTools(instance, { provider })` projects a `@agentproto/driver-browser` `BrowserInstance` onto page-level MCP tools (MCP only; the SDK stays the typed client):
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { projectBrowserTools } from "@agentproto/runtime"
|
|
232
|
+
|
|
233
|
+
const projected = projectBrowserTools(instance, { provider: { id: "camofox", capabilities: provider.manifest.capabilities } })
|
|
234
|
+
projected.register(mcpServer) // browser_navigate, browser_evaluate, browser_click, browser_fill,
|
|
235
|
+
// browser_screenshot, browser_get_dom, browser_list_requests,
|
|
236
|
+
// browser_get_request_body, browser_cdp_send
|
|
237
|
+
await projected.close()
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Tools are gated by capability before any driver is attached. A tool the provider cannot serve returns an error result with `structuredContent.error.code === "browser:unsupported"` and `error.cause.capability` naming the missing capability (for example `cdp` for `browser_list_requests` on a `cdp:false` provider). `browser_download` and `browser_act` are Bureau session-pool features, not driver-port verbs, so they are not projected. `path` on `browser_screenshot` and `browser_get_dom` needs the `writeArtifact` option; without it the call fails with a clear message.
|
|
241
|
+
|
|
224
242
|
## License
|
|
225
243
|
|
|
226
244
|
MIT — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `~/.agentproto/bundles.json` — named sets of imported MCPs + skills (+
|
|
5
|
+
* optionally the daemon's own `/mcp`), attachable to any harness in one
|
|
6
|
+
* `agent_start({bundles:["research"]})` call.
|
|
7
|
+
*
|
|
8
|
+
* Same load/save/atomic-write pattern as `user-presets.ts`; validation
|
|
9
|
+
* against the live imported-MCP set is the single boundary every writer
|
|
10
|
+
* (MCP tools, HTTP routes) goes through (`saveBundle`).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface Bundle {
|
|
14
|
+
/** Stable machine-local id, e.g. `research`. */
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
/** Imported-MCP ids (`mcp_imported_list`) to mount as native MCP servers
|
|
19
|
+
* on a spawn carrying this bundle. `"*"` expands at spawn time to the
|
|
20
|
+
* CURRENT import set (never validated at save time, never dangling).
|
|
21
|
+
* Opt-in only: no default config ships a bundle with `"*"` (dozens of
|
|
22
|
+
* tools per import would flood every harness palette). */
|
|
23
|
+
mcpImports: string[] | "*";
|
|
24
|
+
/** Also mount the daemon's own scoped `/mcp` — the same entry the
|
|
25
|
+
* claude-code/hermes self-mount default builds. */
|
|
26
|
+
includeDaemon?: boolean;
|
|
27
|
+
/** Skills unioned into the spawn's resolved skill list. */
|
|
28
|
+
skills: string[];
|
|
29
|
+
}
|
|
30
|
+
declare const bundlesFileSchema: z.ZodObject<{
|
|
31
|
+
version: z.ZodLiteral<1>;
|
|
32
|
+
bundles: z.ZodArray<z.ZodObject<{
|
|
33
|
+
id: z.ZodString;
|
|
34
|
+
label: z.ZodString;
|
|
35
|
+
description: z.ZodOptional<z.ZodString>;
|
|
36
|
+
mcpImports: z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodLiteral<"*">]>;
|
|
37
|
+
includeDaemon: z.ZodOptional<z.ZodBoolean>;
|
|
38
|
+
skills: z.ZodArray<z.ZodString>;
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
type BundlesFile = z.infer<typeof bundlesFileSchema>;
|
|
42
|
+
declare const BUNDLES_PATH: () => string;
|
|
43
|
+
/** Missing or malformed user config is treated as empty — a bad bundle must
|
|
44
|
+
* never prevent the daemon from starting. Writes always restore valid JSON. */
|
|
45
|
+
declare function loadBundles(path?: string): Promise<BundlesFile>;
|
|
46
|
+
declare function listBundles(): Promise<Bundle[]>;
|
|
47
|
+
declare function getBundle(id: string): Promise<Bundle | undefined>;
|
|
48
|
+
/** Thrown by `saveBundle` when `mcpImports` names an id not present in the
|
|
49
|
+
* live imported-MCP set — the create/update validation boundary. */
|
|
50
|
+
declare class BundleValidationError extends Error {
|
|
51
|
+
}
|
|
52
|
+
/** `bundle.mcpImports` ids that are no longer in the live imported-MCP set —
|
|
53
|
+
* the id was valid at save time but the import was later removed
|
|
54
|
+
* (`mcp_imported_remove`). Used by `bundle_list` to flag dangling refs and
|
|
55
|
+
* by the spawn-expansion path to skip them with a warning. */
|
|
56
|
+
declare function danglingImports(bundle: Bundle, importedIds: ReadonlySet<string>): string[];
|
|
57
|
+
/** Create a new bundle. Throws (Zod) on a malformed shape, or
|
|
58
|
+
* `BundleValidationError` when `mcpImports` names an unknown import id.
|
|
59
|
+
* Rejects when `id` already exists — use `updateBundle` to modify one. */
|
|
60
|
+
declare function createBundle(bundle: Bundle): Promise<Bundle>;
|
|
61
|
+
/** Merge `patch` onto the existing bundle named `id` and persist it. Throws
|
|
62
|
+
* `BundleValidationError` when `id` doesn't exist or the merged
|
|
63
|
+
* `mcpImports` names an unknown import id. */
|
|
64
|
+
declare function updateBundle(id: string, patch: Partial<Omit<Bundle, "id">>): Promise<Bundle>;
|
|
65
|
+
declare function deleteBundle(id: string): Promise<boolean>;
|
|
66
|
+
|
|
67
|
+
export { BUNDLES_PATH, type Bundle, BundleValidationError, type BundlesFile, createBundle, danglingImports, deleteBundle, getBundle, listBundles, loadBundles, updateBundle };
|
package/dist/bundles.mjs
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { promises } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { resolve, dirname } from 'path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @agentproto/runtime v0.1.0-alpha
|
|
8
|
+
* Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var IMPORTED_MCPS_PATH = () => resolve(homedir(), ".agentproto", "imported-mcps.json");
|
|
12
|
+
var IMPORTED_MCPS_VERSION = 1;
|
|
13
|
+
var EMPTY = {
|
|
14
|
+
version: IMPORTED_MCPS_VERSION,
|
|
15
|
+
imports: []
|
|
16
|
+
};
|
|
17
|
+
async function loadImportedMcps(path = IMPORTED_MCPS_PATH()) {
|
|
18
|
+
let raw;
|
|
19
|
+
try {
|
|
20
|
+
raw = await promises.readFile(path, "utf8");
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (err.code === "ENOENT") return EMPTY;
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(raw);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`agentproto: ${path} is not valid JSON (${err instanceof Error ? err.message : String(err)}). Delete or fix manually.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return normalize(parsed);
|
|
34
|
+
}
|
|
35
|
+
function normalize(parsed) {
|
|
36
|
+
if (!parsed || typeof parsed !== "object") return EMPTY;
|
|
37
|
+
const obj = parsed;
|
|
38
|
+
const imports = [];
|
|
39
|
+
if (Array.isArray(obj.imports)) {
|
|
40
|
+
for (const entry of obj.imports) {
|
|
41
|
+
if (!entry || typeof entry !== "object") continue;
|
|
42
|
+
const e = entry;
|
|
43
|
+
const id = typeof e.id === "string" ? e.id : "";
|
|
44
|
+
const alias = typeof e.alias === "string" ? e.alias : id;
|
|
45
|
+
const addedAt = typeof e.addedAt === "string" ? e.addedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
46
|
+
const snapshot = e.snapshot;
|
|
47
|
+
if (!id || !snapshot) continue;
|
|
48
|
+
const extra = {};
|
|
49
|
+
const origin = parseOrigin(e.origin);
|
|
50
|
+
if (origin) extra.origin = origin;
|
|
51
|
+
if (e.resolve === "live" || e.resolve === "snapshot") extra.resolve = e.resolve;
|
|
52
|
+
const secretRefs = parseSecretRefs(e.secretRefs);
|
|
53
|
+
if (secretRefs) extra.secretRefs = secretRefs;
|
|
54
|
+
imports.push({ id, alias, addedAt, snapshot, ...extra });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { version: IMPORTED_MCPS_VERSION, imports };
|
|
58
|
+
}
|
|
59
|
+
function parseOrigin(v) {
|
|
60
|
+
if (!v || typeof v !== "object") return void 0;
|
|
61
|
+
const o = v;
|
|
62
|
+
if (typeof o.kind !== "string" || typeof o.scope !== "string" || typeof o.name !== "string")
|
|
63
|
+
return void 0;
|
|
64
|
+
return { kind: o.kind, scope: o.scope, name: o.name };
|
|
65
|
+
}
|
|
66
|
+
function parseStringMap(v) {
|
|
67
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return void 0;
|
|
68
|
+
const out = {};
|
|
69
|
+
for (const [k, val] of Object.entries(v)) {
|
|
70
|
+
if (typeof val === "string") out[k] = val;
|
|
71
|
+
}
|
|
72
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
73
|
+
}
|
|
74
|
+
function parseSecretRefs(v) {
|
|
75
|
+
if (!v || typeof v !== "object") return void 0;
|
|
76
|
+
const o = v;
|
|
77
|
+
const headers = parseStringMap(o.headers);
|
|
78
|
+
const env = parseStringMap(o.env);
|
|
79
|
+
if (!headers && !env) return void 0;
|
|
80
|
+
return { ...headers ? { headers } : {}, ...env ? { env } : {} };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/bundles.ts
|
|
84
|
+
var bundleIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "id must be lowercase kebab-case (letters, digits, hyphens)");
|
|
85
|
+
var bundleSchema = z.object({
|
|
86
|
+
id: bundleIdSchema,
|
|
87
|
+
label: z.string().min(1),
|
|
88
|
+
description: z.string().min(1).optional(),
|
|
89
|
+
mcpImports: z.union([z.array(z.string().min(1)), z.literal("*")]),
|
|
90
|
+
includeDaemon: z.boolean().optional(),
|
|
91
|
+
skills: z.array(z.string().min(1))
|
|
92
|
+
});
|
|
93
|
+
var bundlesFileSchema = z.object({
|
|
94
|
+
version: z.literal(1),
|
|
95
|
+
bundles: z.array(bundleSchema)
|
|
96
|
+
});
|
|
97
|
+
var BUNDLES_PATH = () => resolve(homedir(), ".agentproto", "bundles.json");
|
|
98
|
+
function emptyFile() {
|
|
99
|
+
return { version: 1, bundles: [] };
|
|
100
|
+
}
|
|
101
|
+
async function loadBundles(path = BUNDLES_PATH()) {
|
|
102
|
+
try {
|
|
103
|
+
return bundlesFileSchema.parse(JSON.parse(await promises.readFile(path, "utf8")));
|
|
104
|
+
} catch {
|
|
105
|
+
return emptyFile();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function writeBundles(file, path = BUNDLES_PATH()) {
|
|
109
|
+
const dir = dirname(path);
|
|
110
|
+
await promises.mkdir(dir, { recursive: true });
|
|
111
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
112
|
+
await promises.writeFile(tmp, JSON.stringify(file, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
113
|
+
await promises.rename(tmp, path);
|
|
114
|
+
}
|
|
115
|
+
async function listBundles() {
|
|
116
|
+
return (await loadBundles()).bundles;
|
|
117
|
+
}
|
|
118
|
+
async function getBundle(id) {
|
|
119
|
+
return (await loadBundles()).bundles.find((b) => b.id === id);
|
|
120
|
+
}
|
|
121
|
+
var BundleValidationError = class extends Error {
|
|
122
|
+
};
|
|
123
|
+
function danglingImports(bundle, importedIds) {
|
|
124
|
+
if (bundle.mcpImports === "*") return [];
|
|
125
|
+
return bundle.mcpImports.filter((id) => !importedIds.has(id));
|
|
126
|
+
}
|
|
127
|
+
async function assertImportsKnown(mcpImports) {
|
|
128
|
+
if (mcpImports === "*" || mcpImports.length === 0) return;
|
|
129
|
+
const imported = await loadImportedMcps();
|
|
130
|
+
const validIds = new Set(imported.imports.map((e) => e.id));
|
|
131
|
+
const unknown = mcpImports.filter((id) => !validIds.has(id));
|
|
132
|
+
if (unknown.length > 0) {
|
|
133
|
+
throw new BundleValidationError(
|
|
134
|
+
`unknown mcpImports id(s): ${unknown.join(", ")}. Valid ids: ${[...validIds].join(", ") || "(none imported \u2014 run mcp_import first)"}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function createBundle(bundle) {
|
|
139
|
+
const validated = bundleSchema.parse(bundle);
|
|
140
|
+
await assertImportsKnown(validated.mcpImports);
|
|
141
|
+
const file = await loadBundles();
|
|
142
|
+
if (file.bundles.some((b) => b.id === validated.id)) {
|
|
143
|
+
throw new BundleValidationError(`bundle "${validated.id}" already exists \u2014 use bundle_update.`);
|
|
144
|
+
}
|
|
145
|
+
file.bundles.push(validated);
|
|
146
|
+
await writeBundles(file);
|
|
147
|
+
return validated;
|
|
148
|
+
}
|
|
149
|
+
async function updateBundle(id, patch) {
|
|
150
|
+
const file = await loadBundles();
|
|
151
|
+
const index = file.bundles.findIndex((b) => b.id === id);
|
|
152
|
+
if (index === -1) {
|
|
153
|
+
throw new BundleValidationError(`bundle "${id}" not found \u2014 use bundle_create.`);
|
|
154
|
+
}
|
|
155
|
+
const existing = file.bundles[index];
|
|
156
|
+
const merged = bundleSchema.parse({ ...existing, ...patch, id });
|
|
157
|
+
await assertImportsKnown(merged.mcpImports);
|
|
158
|
+
file.bundles[index] = merged;
|
|
159
|
+
await writeBundles(file);
|
|
160
|
+
return merged;
|
|
161
|
+
}
|
|
162
|
+
async function deleteBundle(id) {
|
|
163
|
+
const file = await loadBundles();
|
|
164
|
+
const index = file.bundles.findIndex((b) => b.id === id);
|
|
165
|
+
if (index === -1) return false;
|
|
166
|
+
file.bundles.splice(index, 1);
|
|
167
|
+
await writeBundles(file);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { BUNDLES_PATH, BundleValidationError, createBundle, danglingImports, deleteBundle, getBundle, listBundles, loadBundles, updateBundle };
|
|
172
|
+
//# sourceMappingURL=bundles.mjs.map
|
|
173
|
+
//# sourceMappingURL=bundles.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp-imports.ts","../src/bundles.ts"],"names":["resolvePath","fs","homedir","dirname"],"mappings":"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","file":"bundles.mjs","sourcesContent":["/**\n * `~/.agentproto/imported-mcps.json` — the user's curated set of\n * discovered MCPs they want the daemon to know about. v1 is just\n * persistence + read-side; the actual MCP-proxy implementation\n * (where the daemon's /mcp endpoint aggregates the imported\n * servers' tools under namespace prefixes) is v2 work.\n *\n * Purpose: today, \"I see you have chrome-devtools in claude\" is\n * read-only. Once a user imports it, the operator agent can refer\n * to it (\"the user said it's enabled — call it\") and a future\n * proxy layer can actually expose it. We persist the choice now so\n * the data is in place when the proxy lands.\n *\n * File shape:\n * {\n * \"version\": 1,\n * \"imports\": [\n * {\n * \"id\": \"claude-code:project:/path:chrome-devtools\",\n * \"alias\": \"chrome-devtools\",\n * \"addedAt\": \"2026-05-10T...\",\n * \"snapshot\": { ...the DiscoveredMcp at import time }\n * }\n * ]\n * }\n *\n * Snapshotting at import time keeps the daemon resilient to the\n * source config getting deleted (user runs `claude mcp remove ...`).\n * The imported entry stays usable; only the next discovery pass\n * stops listing it as \"available to import.\"\n */\n\nimport { promises as fs } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { dirname, resolve as resolvePath } from \"node:path\"\nimport type { DiscoveredMcp } from \"./mcp-discovery.js\"\n\nexport const IMPORTED_MCPS_PATH = (): string =>\n resolvePath(homedir(), \".agentproto\", \"imported-mcps.json\")\n\nexport const IMPORTED_MCPS_VERSION = 1 as const\n\n/** Where an import came from. `plugin` (written by plugin-local-browser) and\n * `manual` have no discovery source; `kind` otherwise mirrors\n * `DiscoveredMcp[\"source\"]`. */\nexport interface ImportedMcpOrigin {\n kind: DiscoveredMcp[\"source\"] | \"plugin\" | \"manual\"\n scope: string\n name: string\n}\n\n/** `live` = re-read the source harness config at connect time (the snapshot is\n * only a fallback); `snapshot` = the stored copy is authoritative. */\nexport type ImportedMcpResolve = \"live\" | \"snapshot\"\n\n/** Opaque secret refs (resolved via `resolveMcpSecret`), keyed by the header\n * / env key they fill. Values are refs, NEVER secret material. */\nexport interface ImportedMcpSecretRefs {\n headers?: Record<string, string>\n env?: Record<string, string>\n}\n\nexport interface ImportedMcpEntry {\n id: string\n alias: string\n addedAt: string\n snapshot: DiscoveredMcp\n /** Additive (no file-version bump): link back to the source config. */\n origin?: ImportedMcpOrigin\n resolve?: ImportedMcpResolve\n secretRefs?: ImportedMcpSecretRefs\n}\n\n/** Value written into `snapshot.headers[K]` / `snapshot.env[K]` in place of a\n * literal secret that now lives behind `secretRefs`. */\nexport const SECRET_REF_MARKER = \"<secretRef>\"\n\n/** Discovery sources whose config can be re-read live. */\nconst LIVE_SOURCES: ReadonlySet<string> = new Set([\n \"claude-code\",\n \"cursor\",\n \"codex\",\n \"workspace\",\n])\n\n/** `live` only when discovery can actually re-find the entry: a discovery\n * source AND a scope discovery scans (`global` / `project:*` / `workspace:*`).\n * claude-code `local`-scope and plugin/manual entries stay `snapshot`. */\nexport function deriveResolve(snapshot: DiscoveredMcp): ImportedMcpResolve {\n if (!LIVE_SOURCES.has(snapshot.source)) return \"snapshot\"\n const s = snapshot.scope\n return s === \"global\" || s.startsWith(\"project:\") || s.startsWith(\"workspace:\")\n ? \"live\"\n : \"snapshot\"\n}\n\n/** Keychain path + account for one secret slot of an import. Ref format\n * `agentproto/mcp-import/<importId>#<header|env>:<KEY>` (split on LAST `#`). */\nexport function mcpSecretRef(\n importId: string,\n kind: \"header\" | \"env\",\n key: string\n): string {\n return `agentproto/mcp-import/${importId}#${kind}:${key}`\n}\n\nconst PLACEHOLDER_RE = /\\$\\{[A-Z_][A-Z0-9_]*(?::-[^}]*)?\\}/i\n\n/** A value worth moving to the keychain: non-empty, not already the marker,\n * and not a `${VAR}` placeholder (those expand at connect, no secret at rest). */\nexport function isLiteralSecretValue(v: unknown): v is string {\n return (\n typeof v === \"string\" &&\n v.length > 0 &&\n v !== SECRET_REF_MARKER &&\n !PLACEHOLDER_RE.test(v)\n )\n}\n\nexport interface MoveSecretsResult {\n entry: ImportedMcpEntry\n /** Header/env KEY names moved this call (never values). */\n moved: string[]\n /** Human warnings (key names only). */\n warnings: string[]\n}\n\n/**\n * Move every literal header/env value of `entry.snapshot` behind a keychain\n * ref. Keychain write happens FIRST per key; only on success is the snapshot\n * value replaced by the marker (a failed write keeps the literal + warns).\n * Read-back verification when `resolve` is supplied.\n */\nexport async function moveEntrySecrets(\n entry: ImportedMcpEntry,\n hooks: {\n storeMcpSecret?: (ref: string, value: string) => Promise<void>\n resolveMcpSecret?: (ref: string) => Promise<string | undefined>\n }\n): Promise<MoveSecretsResult> {\n if (!hooks.storeMcpSecret) {\n const has =\n Object.values(entry.snapshot.headers ?? {}).some(isLiteralSecretValue) ||\n Object.values(entry.snapshot.env ?? {}).some(isLiteralSecretValue)\n return {\n entry,\n moved: [],\n warnings: has\n ? [\n \"no secret store available: header/env values were kept as literals in imported-mcps.json (mode 0600)\",\n ]\n : [],\n }\n }\n const moved: string[] = []\n const warnings: string[] = []\n const secretRefs: ImportedMcpSecretRefs = {\n ...(entry.secretRefs?.headers ? { headers: { ...entry.secretRefs.headers } } : {}),\n ...(entry.secretRefs?.env ? { env: { ...entry.secretRefs.env } } : {}),\n }\n const headers = entry.snapshot.headers ? { ...entry.snapshot.headers } : undefined\n const env = entry.snapshot.env ? { ...entry.snapshot.env } : undefined\n const passes: Array<[\"header\" | \"env\", Record<string, string> | undefined]> = [\n [\"header\", headers],\n [\"env\", env],\n ]\n for (const [kind, map] of passes) {\n if (!map) continue\n for (const [key, value] of Object.entries(map)) {\n if (!isLiteralSecretValue(value)) continue\n const ref = mcpSecretRef(entry.id, kind, key)\n try {\n await hooks.storeMcpSecret(ref, value)\n if (hooks.resolveMcpSecret) {\n const back = await hooks.resolveMcpSecret(ref)\n if (back !== value) throw new Error(\"read-back mismatch\")\n }\n } catch (err) {\n warnings.push(\n `could not store ${kind} \"${key}\" in the secret store (${\n err instanceof Error ? err.message : \"error\"\n }); kept as a literal`\n )\n continue\n }\n map[key] = SECRET_REF_MARKER\n const bucket = kind === \"header\" ? \"headers\" : \"env\"\n secretRefs[bucket] = { ...(secretRefs[bucket] ?? {}), [key]: ref }\n moved.push(`${kind}:${key}`)\n }\n }\n if (moved.length === 0) return { entry, moved, warnings }\n return {\n entry: {\n ...entry,\n snapshot: {\n ...entry.snapshot,\n ...(headers ? { headers } : {}),\n ...(env ? { env } : {}),\n },\n secretRefs,\n },\n moved,\n warnings,\n }\n}\n\nexport interface ImportedMcpsConfig {\n version: typeof IMPORTED_MCPS_VERSION\n imports: ImportedMcpEntry[]\n}\n\nconst EMPTY: ImportedMcpsConfig = {\n version: IMPORTED_MCPS_VERSION,\n imports: [],\n}\n\nexport async function loadImportedMcps(\n path: string = IMPORTED_MCPS_PATH()\n): Promise<ImportedMcpsConfig> {\n let raw: string\n try {\n raw = await fs.readFile(path, \"utf8\")\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return EMPTY\n throw err\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (err) {\n throw new Error(\n `agentproto: ${path} is not valid JSON (${\n err instanceof Error ? err.message : String(err)\n }). Delete or fix manually.`\n )\n }\n return normalize(parsed)\n}\n\nexport async function saveImportedMcps(\n config: ImportedMcpsConfig,\n path: string = IMPORTED_MCPS_PATH()\n): Promise<void> {\n await fs.mkdir(dirname(path), { recursive: true })\n const tmp = `${path}.tmp.${process.pid}`\n // 0600: entries may hold literal upstream credentials (parity with bundles.ts).\n await fs.writeFile(tmp, JSON.stringify(config, null, 2) + \"\\n\", {\n encoding: \"utf8\",\n mode: 0o600,\n })\n await fs.rename(tmp, path)\n}\n\n/**\n * Add (or replace) an import. Snapshots the discovered MCP at\n * import time so the entry stays usable when the source config\n * disappears.\n */\nexport function addImport(\n config: ImportedMcpsConfig,\n input: { snapshot: DiscoveredMcp; alias?: string }\n): ImportedMcpsConfig {\n const alias = (input.alias ?? input.snapshot.name).trim()\n if (!alias) {\n throw new Error(\"addImport: alias resolves to empty string\")\n }\n const addedAt = new Date().toISOString()\n const next: ImportedMcpEntry = {\n id: input.snapshot.id,\n alias,\n addedAt,\n snapshot: input.snapshot,\n origin: {\n kind: input.snapshot.source,\n scope: input.snapshot.scope,\n name: input.snapshot.name,\n },\n resolve: deriveResolve(input.snapshot),\n }\n const others = config.imports.filter(e => e.id !== input.snapshot.id)\n return { ...config, imports: [...others, next] }\n}\n\n/**\n * `addImport` + move literal header/env values behind keychain refs (via the\n * `storeMcpSecret` hook). Hook absent → literals kept and a warning returned.\n * Callers persist `config` and surface `warnings` in their reply.\n */\nexport async function addImportWithSecrets(\n config: ImportedMcpsConfig,\n input: { snapshot: DiscoveredMcp; alias?: string },\n hooks: {\n storeMcpSecret?: (ref: string, value: string) => Promise<void>\n resolveMcpSecret?: (ref: string) => Promise<string | undefined>\n }\n): Promise<{ config: ImportedMcpsConfig; entry: ImportedMcpEntry; warnings: string[] }> {\n const added = addImport(config, input)\n const entry = added.imports.find(e => e.id === input.snapshot.id)!\n const moved = await moveEntrySecrets(entry, hooks)\n return {\n config: {\n ...added,\n imports: added.imports.map(e => (e.id === entry.id ? moved.entry : e)),\n },\n entry: moved.entry,\n warnings: moved.warnings,\n }\n}\n\nexport function removeImport(\n config: ImportedMcpsConfig,\n id: string\n): ImportedMcpsConfig {\n return { ...config, imports: config.imports.filter(e => e.id !== id) }\n}\n\nexport function findImport(\n config: ImportedMcpsConfig,\n id: string\n): ImportedMcpEntry | undefined {\n return config.imports.find(e => e.id === id)\n}\n\nfunction normalize(parsed: unknown): ImportedMcpsConfig {\n if (!parsed || typeof parsed !== \"object\") return EMPTY\n const obj = parsed as Record<string, unknown>\n const imports: ImportedMcpEntry[] = []\n if (Array.isArray(obj.imports)) {\n for (const entry of obj.imports) {\n if (!entry || typeof entry !== \"object\") continue\n const e = entry as Record<string, unknown>\n const id = typeof e.id === \"string\" ? e.id : \"\"\n const alias = typeof e.alias === \"string\" ? e.alias : id\n const addedAt =\n typeof e.addedAt === \"string\" ? e.addedAt : new Date().toISOString()\n const snapshot = e.snapshot as DiscoveredMcp | undefined\n if (!id || !snapshot) continue\n const extra: Partial<ImportedMcpEntry> = {}\n const origin = parseOrigin(e.origin)\n if (origin) extra.origin = origin\n if (e.resolve === \"live\" || e.resolve === \"snapshot\") extra.resolve = e.resolve\n const secretRefs = parseSecretRefs(e.secretRefs)\n if (secretRefs) extra.secretRefs = secretRefs\n imports.push({ id, alias, addedAt, snapshot, ...extra })\n }\n }\n return { version: IMPORTED_MCPS_VERSION, imports }\n}\n\nfunction parseOrigin(v: unknown): ImportedMcpOrigin | undefined {\n if (!v || typeof v !== \"object\") return undefined\n const o = v as Record<string, unknown>\n if (\n typeof o.kind !== \"string\" ||\n typeof o.scope !== \"string\" ||\n typeof o.name !== \"string\"\n )\n return undefined\n return { kind: o.kind as ImportedMcpOrigin[\"kind\"], scope: o.scope, name: o.name }\n}\n\nfunction parseStringMap(v: unknown): Record<string, string> | undefined {\n if (!v || typeof v !== \"object\" || Array.isArray(v)) return undefined\n const out: Record<string, string> = {}\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (typeof val === \"string\") out[k] = val\n }\n return Object.keys(out).length > 0 ? out : undefined\n}\n\nfunction parseSecretRefs(v: unknown): ImportedMcpSecretRefs | undefined {\n if (!v || typeof v !== \"object\") return undefined\n const o = v as Record<string, unknown>\n const headers = parseStringMap(o.headers)\n const env = parseStringMap(o.env)\n if (!headers && !env) return undefined\n return { ...(headers ? { headers } : {}), ...(env ? { env } : {}) }\n}\n\n/** KEY names only (never refs' targets or values) — for status surfaces. */\nexport function secretRefKeys(\n entry: Pick<ImportedMcpEntry, \"secretRefs\">\n): { headers?: string[]; env?: string[] } | undefined {\n const r = entry.secretRefs\n if (!r) return undefined\n const headers = r.headers ? Object.keys(r.headers) : []\n const env = r.env ? Object.keys(r.env) : []\n if (headers.length === 0 && env.length === 0) return undefined\n return {\n ...(headers.length ? { headers } : {}),\n ...(env.length ? { env } : {}),\n }\n}\n","/**\n * `~/.agentproto/bundles.json` — named sets of imported MCPs + skills (+\n * optionally the daemon's own `/mcp`), attachable to any harness in one\n * `agent_start({bundles:[\"research\"]})` call.\n *\n * Same load/save/atomic-write pattern as `user-presets.ts`; validation\n * against the live imported-MCP set is the single boundary every writer\n * (MCP tools, HTTP routes) goes through (`saveBundle`).\n */\n\nimport { promises as fs } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { dirname, resolve as resolvePath } from \"node:path\"\nimport { z } from \"zod\"\nimport { loadImportedMcps } from \"./mcp-imports.js\"\n\nexport interface Bundle {\n /** Stable machine-local id, e.g. `research`. */\n id: string\n label: string\n description?: string\n /** Imported-MCP ids (`mcp_imported_list`) to mount as native MCP servers\n * on a spawn carrying this bundle. `\"*\"` expands at spawn time to the\n * CURRENT import set (never validated at save time, never dangling).\n * Opt-in only: no default config ships a bundle with `\"*\"` (dozens of\n * tools per import would flood every harness palette). */\n mcpImports: string[] | \"*\"\n /** Also mount the daemon's own scoped `/mcp` — the same entry the\n * claude-code/hermes self-mount default builds. */\n includeDaemon?: boolean\n /** Skills unioned into the spawn's resolved skill list. */\n skills: string[]\n}\n\nconst bundleIdSchema = z\n .string()\n .regex(/^[a-z0-9][a-z0-9-]*$/, \"id must be lowercase kebab-case (letters, digits, hyphens)\")\n\nconst bundleSchema = z.object({\n id: bundleIdSchema,\n label: z.string().min(1),\n description: z.string().min(1).optional(),\n mcpImports: z.union([z.array(z.string().min(1)), z.literal(\"*\")]),\n includeDaemon: z.boolean().optional(),\n skills: z.array(z.string().min(1)),\n}) satisfies z.ZodType<Bundle>\n\nconst bundlesFileSchema = z.object({\n version: z.literal(1),\n bundles: z.array(bundleSchema),\n})\n\nexport type BundlesFile = z.infer<typeof bundlesFileSchema>\n\nexport const BUNDLES_PATH = (): string => resolvePath(homedir(), \".agentproto\", \"bundles.json\")\n\nfunction emptyFile(): BundlesFile {\n return { version: 1, bundles: [] }\n}\n\n/** Missing or malformed user config is treated as empty — a bad bundle must\n * never prevent the daemon from starting. Writes always restore valid JSON. */\nexport async function loadBundles(path: string = BUNDLES_PATH()): Promise<BundlesFile> {\n try {\n return bundlesFileSchema.parse(JSON.parse(await fs.readFile(path, \"utf8\")))\n } catch {\n return emptyFile()\n }\n}\n\nasync function writeBundles(file: BundlesFile, path: string = BUNDLES_PATH()): Promise<void> {\n const dir = dirname(path)\n await fs.mkdir(dir, { recursive: true })\n const tmp = `${path}.tmp.${process.pid}`\n await fs.writeFile(tmp, JSON.stringify(file, null, 2) + \"\\n\", { encoding: \"utf8\", mode: 0o600 })\n await fs.rename(tmp, path)\n}\n\nexport async function listBundles(): Promise<Bundle[]> {\n return (await loadBundles()).bundles\n}\n\nexport async function getBundle(id: string): Promise<Bundle | undefined> {\n return (await loadBundles()).bundles.find(b => b.id === id)\n}\n\n/** Thrown by `saveBundle` when `mcpImports` names an id not present in the\n * live imported-MCP set — the create/update validation boundary. */\nexport class BundleValidationError extends Error {}\n\n/** `bundle.mcpImports` ids that are no longer in the live imported-MCP set —\n * the id was valid at save time but the import was later removed\n * (`mcp_imported_remove`). Used by `bundle_list` to flag dangling refs and\n * by the spawn-expansion path to skip them with a warning. */\nexport function danglingImports(bundle: Bundle, importedIds: ReadonlySet<string>): string[] {\n if (bundle.mcpImports === \"*\") return []\n return bundle.mcpImports.filter(id => !importedIds.has(id))\n}\n\nasync function assertImportsKnown(mcpImports: string[] | \"*\"): Promise<void> {\n if (mcpImports === \"*\" || mcpImports.length === 0) return\n const imported = await loadImportedMcps()\n const validIds = new Set(imported.imports.map(e => e.id))\n const unknown = mcpImports.filter(id => !validIds.has(id))\n if (unknown.length > 0) {\n throw new BundleValidationError(\n `unknown mcpImports id(s): ${unknown.join(\", \")}. Valid ids: ${\n [...validIds].join(\", \") || \"(none imported — run mcp_import first)\"\n }`,\n )\n }\n}\n\n/** Create a new bundle. Throws (Zod) on a malformed shape, or\n * `BundleValidationError` when `mcpImports` names an unknown import id.\n * Rejects when `id` already exists — use `updateBundle` to modify one. */\nexport async function createBundle(bundle: Bundle): Promise<Bundle> {\n const validated = bundleSchema.parse(bundle)\n await assertImportsKnown(validated.mcpImports)\n const file = await loadBundles()\n if (file.bundles.some(b => b.id === validated.id)) {\n throw new BundleValidationError(`bundle \"${validated.id}\" already exists — use bundle_update.`)\n }\n file.bundles.push(validated)\n await writeBundles(file)\n return validated\n}\n\n/** Merge `patch` onto the existing bundle named `id` and persist it. Throws\n * `BundleValidationError` when `id` doesn't exist or the merged\n * `mcpImports` names an unknown import id. */\nexport async function updateBundle(\n id: string,\n patch: Partial<Omit<Bundle, \"id\">>,\n): Promise<Bundle> {\n const file = await loadBundles()\n const index = file.bundles.findIndex(b => b.id === id)\n if (index === -1) {\n throw new BundleValidationError(`bundle \"${id}\" not found — use bundle_create.`)\n }\n const existing = file.bundles[index]!\n const merged = bundleSchema.parse({ ...existing, ...patch, id })\n await assertImportsKnown(merged.mcpImports)\n file.bundles[index] = merged\n await writeBundles(file)\n return merged\n}\n\nexport async function deleteBundle(id: string): Promise<boolean> {\n const file = await loadBundles()\n const index = file.bundles.findIndex(b => b.id === id)\n if (index === -1) return false\n file.bundles.splice(index, 1)\n await writeBundles(file)\n return true\n}\n"]}
|
package/dist/catalog-models.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AuthProfile } from '@agentproto/auth';
|
|
2
|
-
import { A as AdapterAuthDescriptor } from './spawn-defaults-
|
|
3
|
-
import { R as RouteSpec } from './session-config-
|
|
4
|
-
import './context-continuity-BfNVbX5W.js';
|
|
2
|
+
import { A as AdapterAuthDescriptor } from './spawn-defaults-Cp9SGZex.js';
|
|
3
|
+
import { R as RouteSpec } from './session-config-DxIIuJFY.js';
|
|
5
4
|
import '@agentproto/acp';
|
|
6
5
|
import '@agentproto/command-sandbox';
|
|
7
6
|
import '@agentproto/model-catalog';
|