@agentproto/cli 0.3.0 → 0.5.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 +17 -17
- package/dist/cli.mjs +28301 -48608
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.mjs +18141 -6231
- package/dist/index.mjs.map +1 -1
- package/dist/registry/builtins.mjs +25 -17
- package/dist/registry/builtins.mjs.map +1 -1
- package/dist/registry/manifest.mjs +4 -4
- package/dist/registry/plugins.mjs +4 -4
- package/dist/registry/runtime.mjs +4 -4
- package/dist/util/credentials.d.ts +4 -2
- package/dist/util/credentials.mjs +6 -4
- package/dist/util/credentials.mjs.map +1 -1
- package/package.json +21 -23
- package/skill/agentproto/SKILL.md +26 -26
|
@@ -6,12 +6,12 @@ import matter from 'gray-matter';
|
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @agentproto/cli v0.
|
|
9
|
+
* @agentproto/cli v0.5.0
|
|
10
10
|
* The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
|
|
11
11
|
*/
|
|
12
|
-
// Provide a real `require` in the ESM bundle.
|
|
13
|
-
//
|
|
14
|
-
// esbuild's interop shim throws "Dynamic require is not supported".
|
|
12
|
+
// Provide a real `require` in the ESM bundle. Some bundled deps (e.g.
|
|
13
|
+
// gray-matter, node-pty) are CJS and call `require("assert")` or similar;
|
|
14
|
+
// without this esbuild's interop shim throws "Dynamic require is not supported".
|
|
15
15
|
createRequire(import.meta.url);
|
|
16
16
|
var HEADER_RE = /^=== TURN id=([^\s]+) participant=([^\s]+) ts=([^\s]+) ===$/;
|
|
17
17
|
var FileSubstrate = class {
|
|
@@ -190,7 +190,7 @@ function spawnWithStdin(opts) {
|
|
|
190
190
|
signal,
|
|
191
191
|
killSignal = "SIGTERM"
|
|
192
192
|
} = opts;
|
|
193
|
-
return new Promise((
|
|
193
|
+
return new Promise((resolve3, reject) => {
|
|
194
194
|
if (signal?.aborted) {
|
|
195
195
|
reject(new Error(`${command} aborted before start`));
|
|
196
196
|
return;
|
|
@@ -227,7 +227,7 @@ function spawnWithStdin(opts) {
|
|
|
227
227
|
child.on(
|
|
228
228
|
"close",
|
|
229
229
|
(code) => finish(() => {
|
|
230
|
-
if (code === 0)
|
|
230
|
+
if (code === 0) resolve3(out);
|
|
231
231
|
else
|
|
232
232
|
reject(
|
|
233
233
|
new Error(`${command} exited with code ${code}: ${err.trim().slice(0, 200) || "(no stderr)"}`)
|
|
@@ -260,7 +260,7 @@ var AgentCliParticipant = class {
|
|
|
260
260
|
opts;
|
|
261
261
|
kind = "agent-cli";
|
|
262
262
|
async executeTurn(input) {
|
|
263
|
-
const prompt = await assemblePrompt(input);
|
|
263
|
+
const prompt = await assemblePrompt(input, this.opts.baseDir);
|
|
264
264
|
const stdout = await spawnWithStdin({
|
|
265
265
|
command: this.opts.command,
|
|
266
266
|
args: this.opts.args ?? [],
|
|
@@ -274,8 +274,8 @@ var AgentCliParticipant = class {
|
|
|
274
274
|
return { content };
|
|
275
275
|
}
|
|
276
276
|
};
|
|
277
|
-
async function assemblePrompt(input) {
|
|
278
|
-
const roleText = input.participant.role ? await loadRole(input.participant.role) : "";
|
|
277
|
+
async function assemblePrompt(input, baseDir) {
|
|
278
|
+
const roleText = input.participant.role ? await loadRole(input.participant.role, baseDir) : "";
|
|
279
279
|
const transcript = input.recentTurns.map((t) => `[${t.participantId}] ${t.content}`).join("\n\n");
|
|
280
280
|
return [
|
|
281
281
|
roleText && `# Your role
|
|
@@ -290,16 +290,17 @@ You are ${input.participant.displayName}. The latest message in the conversation
|
|
|
290
290
|
].filter(Boolean).join("\n\n");
|
|
291
291
|
}
|
|
292
292
|
var ROLE_FILE_EXTENSIONS = [".md", ".markdown", ".txt"];
|
|
293
|
-
async function loadRole(roleField) {
|
|
293
|
+
async function loadRole(roleField, baseDir) {
|
|
294
294
|
if (!looksLikeRoleFile(roleField)) return roleField;
|
|
295
|
+
const path = baseDir ? resolve(baseDir, roleField) : roleField;
|
|
295
296
|
try {
|
|
296
|
-
const raw = await readFile(
|
|
297
|
+
const raw = await readFile(path, "utf8");
|
|
297
298
|
const parsed = matter(raw);
|
|
298
299
|
return parsed.content.trim();
|
|
299
300
|
} catch (err) {
|
|
300
301
|
const code = err.code;
|
|
301
302
|
process.stderr.write(
|
|
302
|
-
`agent-cli participant: role path '${roleField}' not readable (${code ?? "unknown"}); using the literal string as inline role text.
|
|
303
|
+
`agent-cli participant: role path '${roleField}' (resolved to ${path}) not readable (${code ?? "unknown"}); using the literal string as inline role text.
|
|
303
304
|
`
|
|
304
305
|
);
|
|
305
306
|
return roleField;
|
|
@@ -339,18 +340,25 @@ function registerBuiltins() {
|
|
|
339
340
|
const dir = typeof cfg.dir === "string" ? cfg.dir : ".runtime/state";
|
|
340
341
|
return new FileStateStore({ dir: resolve(ctx.baseDir, dir) });
|
|
341
342
|
});
|
|
342
|
-
registerExecutor("agent-cli", (cfg) => buildAgentCli(cfg));
|
|
343
|
+
registerExecutor("agent-cli", (cfg, ctx) => buildAgentCli(cfg, ctx));
|
|
343
344
|
}
|
|
344
|
-
function buildAgentCli(cfg) {
|
|
345
|
+
function buildAgentCli(cfg, ctx) {
|
|
345
346
|
const command = typeof cfg.command === "string" ? cfg.command : "claude";
|
|
346
|
-
const
|
|
347
|
+
const defaultArgs = command === "claude" ? ["--print", "--output-format=json", "--permission-mode", "bypassPermissions"] : ["--print", "--output-format=json"];
|
|
348
|
+
const args = Array.isArray(cfg.args) ? cfg.args.filter((a) => typeof a === "string") : defaultArgs;
|
|
347
349
|
const useClaudeJson = cfg.parseJson !== false && command === "claude";
|
|
350
|
+
const model = typeof cfg.model === "string" ? cfg.model : void 0;
|
|
351
|
+
const finalArgs = model && command === "claude" && !hasModelFlag(args) ? [...args, "--model", model] : args;
|
|
348
352
|
return new AgentCliParticipant({
|
|
349
353
|
command,
|
|
350
|
-
args,
|
|
351
|
-
parseOutput: useClaudeJson ? parseClaudeJsonOutput : void 0
|
|
354
|
+
args: finalArgs,
|
|
355
|
+
parseOutput: useClaudeJson ? parseClaudeJsonOutput : void 0,
|
|
356
|
+
baseDir: ctx.baseDir
|
|
352
357
|
});
|
|
353
358
|
}
|
|
359
|
+
function hasModelFlag(args) {
|
|
360
|
+
return args.some((a) => a === "--model" || a.startsWith("--model="));
|
|
361
|
+
}
|
|
354
362
|
|
|
355
363
|
export { registerBuiltins };
|
|
356
364
|
//# sourceMappingURL=builtins.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../agent-runtime/src/adapters/substrate-file.ts","../../../agent-runtime/src/util/mention-parser.mjs","../../../agent-runtime/src/adapters/dispatcher-mention.ts","../../../agent-runtime/src/adapters/state-fs.ts","../../../cli-exec/src/spawn-stdin.ts","../../../cli-exec/src/parse.ts","../../../agent-runtime/src/adapters/participant-agent-cli.ts","../../src/registry/runtime.ts","../../src/registry/builtins.ts"],"names":["readFile","isNotFound","ensureDir","writeFile","resolvePath","stat","mkdir","resolve"],"mappings":"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","file":"builtins.mjs","sourcesContent":["/**\n * File substrate — append-only markdown journal.\n *\n * Each turn is delimited by a header line:\n * === TURN id=<id> participant=<pid> ts=<iso> ===\n * <body lines…>\n *\n * Ids are content-hashed, so re-running an append with the same body\n * is idempotent at the row level.\n *\n * Caveats:\n * - `appendFile` does not fsync. A power-loss between buffered write\n * and disk flush can lose the most recent turn(s); callers that\n * need durability across crashes should layer their own fsync.\n * - The parser splits on lines matching `=== TURN ... ===`. Turn\n * content containing such a line will corrupt subsequent parses.\n * Treat turn content as untrusted? Sanitize before append.\n */\n\nimport { appendFile, readFile, writeFile, mkdir, stat } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\nimport { createHash } from \"node:crypto\"\nimport type { Substrate, Turn, TurnInput, TurnId } from \"../ports.js\"\n\nexport type FileSubstrateOptions = {\n /** Absolute path to the journal file. */\n readonly path: string\n}\n\nconst HEADER_RE =\n /^=== TURN id=([^\\s]+) participant=([^\\s]+) ts=([^\\s]+) ===$/\n\nexport class FileSubstrate implements Substrate {\n readonly kind = \"file\"\n\n constructor(private readonly opts: FileSubstrateOptions) {}\n\n async append(input: TurnInput): Promise<Turn> {\n const timestamp = input.timestamp ?? new Date().toISOString()\n const id = hashTurnId(input.participantId, timestamp, input.content)\n const turn: Turn = {\n id,\n participantId: input.participantId,\n timestamp,\n content: input.content,\n meta: input.meta,\n }\n await ensureDir(this.opts.path)\n const block = formatTurnBlock(turn)\n await appendFile(this.opts.path, block, \"utf8\")\n return turn\n }\n\n async read(since?: TurnId): Promise<readonly Turn[]> {\n let raw: string\n try {\n raw = await readFile(this.opts.path, \"utf8\")\n } catch (err) {\n if (isNotFound(err)) return []\n throw err\n }\n const turns = parseJournal(raw)\n if (since === undefined) return turns\n const cutoff = turns.findIndex((t) => t.id === since)\n if (cutoff === -1) return turns\n return turns.slice(cutoff + 1)\n }\n}\n\n/**\n * Reset the journal by truncating it. Useful for tests. Not part of the\n * Substrate interface — callers must reach for the adapter directly.\n */\nexport async function resetFileSubstrate(path: string): Promise<void> {\n await ensureDir(path)\n await writeFile(path, \"\", \"utf8\")\n}\n\nfunction formatTurnBlock(turn: Turn): string {\n const header = `=== TURN id=${turn.id} participant=${turn.participantId} ts=${turn.timestamp} ===\\n`\n const body = turn.content.endsWith(\"\\n\") ? turn.content : `${turn.content}\\n`\n return `${header}${body}`\n}\n\nfunction parseJournal(raw: string): Turn[] {\n const lines = raw.split(\"\\n\")\n const turns: Turn[] = []\n let current: { id: string; pid: string; ts: string; buf: string[] } | null = null\n\n for (const line of lines) {\n const match = HEADER_RE.exec(line)\n if (match && match[1] && match[2] && match[3]) {\n if (current) turns.push(materialise(current))\n current = { id: match[1], pid: match[2], ts: match[3], buf: [] }\n continue\n }\n if (current) current.buf.push(line)\n }\n if (current) turns.push(materialise(current))\n return turns\n}\n\nfunction materialise(c: {\n id: string\n pid: string\n ts: string\n buf: string[]\n}): Turn {\n // Drop the trailing empty line that appendFile leaves between blocks.\n let body = c.buf.join(\"\\n\")\n while (body.endsWith(\"\\n\")) body = body.slice(0, -1)\n return {\n id: c.id,\n participantId: c.pid,\n timestamp: c.ts,\n content: body,\n }\n}\n\nfunction hashTurnId(pid: string, ts: string, content: string): string {\n const h = createHash(\"sha256\")\n h.update(pid)\n h.update(\"\\x00\")\n h.update(ts)\n h.update(\"\\x00\")\n h.update(content)\n return `t_${h.digest(\"hex\").slice(0, 12)}`\n}\n\nasync function ensureDir(path: string): Promise<void> {\n const dir = dirname(path)\n try {\n await stat(dir)\n } catch (err) {\n if (isNotFound(err)) {\n await mkdir(dir, { recursive: true })\n return\n }\n throw err\n }\n}\n\nfunction isNotFound(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code: string }).code === \"ENOENT\"\n )\n}\n","/**\n * Canonical @-mention parser.\n *\n * Lives as raw JavaScript (no types) because it's consumed in two\n * places that don't share a build:\n *\n * 1. The kernel imports `textContainsMention` from this file as a\n * normal ESM module (the dispatcher in `adapters/dispatcher-mention.ts`).\n *\n * 2. Runtime profiles inline this file's source into Claude Code\n * hooks at profile-build time, because `.claude/` has no\n * module-resolution at hook-execution time. The build script\n * reads this file as text and stamps it into the hook template.\n *\n * Two cases:\n * 1. Literal `@<name>` substring (case-sensitive, full name).\n * 2. For multi-word names, also `@<firstName>` with non-word\n * boundary (case-insensitive).\n *\n * Edit here only — both consumers stay in sync via build pipelines.\n */\n\n/** @param {string} text @param {string} name @returns {boolean} */\nexport function textContainsMention(text, name) {\n if (text.includes(`@${name}`)) return true\n if (name.includes(\" \")) {\n const firstName = name.split(\" \")[0]?.trim()\n if (!firstName) return false\n const regex = new RegExp(`@${firstName}(?!\\\\w)`, \"i\")\n return regex.test(text)\n }\n return false\n}\n","/**\n * Mention dispatcher — selects participants whose displayName is\n * @-mentioned in the most recent turn.\n *\n * Skip rules:\n * - never re-select the participant who authored the trigger turn\n * (otherwise they'd reply to themselves indefinitely)\n * - never dispatch on a trigger we already processed (in-memory cursor —\n * prevents the loop from re-firing on the same @mention every poll\n * when the kernel's own reply isn't yet visible in the next read).\n * Per-dispatcher-instance state; a fresh swarm process starts with\n * no cursor and naturally responds to the latest unhandled mention\n * exactly once.\n * - if no mentions match any known participant, return [] (idle)\n *\n * The parser itself lives in `../util/mention-parser.mjs` (vanilla JS)\n * so runtime profiles can stamp the same implementation into Claude\n * Code hooks at build time without depending on the TS build pipeline.\n */\n\nimport type {\n Dispatcher,\n DispatcherInput,\n ParticipantId,\n TurnId,\n} from \"../ports.js\"\nimport { textContainsMention } from \"../util/mention-parser.mjs\"\n\nexport class MentionDispatcher implements Dispatcher {\n readonly kind = \"mention\"\n private lastProcessedTriggerId: TurnId | undefined\n\n async selectNext(input: DispatcherInput): Promise<readonly ParticipantId[]> {\n const trigger = input.recentTurns[input.recentTurns.length - 1]\n if (!trigger) return []\n if (trigger.id === this.lastProcessedTriggerId) return []\n const text = trigger.content\n\n const selected: ParticipantId[] = []\n for (const p of input.participants) {\n if (p.id === trigger.participantId) continue\n if (textContainsMention(text, p.displayName)) {\n selected.push(p.id)\n }\n }\n\n if (selected.length > 0) {\n this.lastProcessedTriggerId = trigger.id\n }\n return selected\n }\n}\n\n// Re-exported so callers writing their own adapters can detect\n// mentions with identical semantics.\nexport { textContainsMention }\n","/**\n * Filesystem state store — one JSON file per participant.\n *\n * <stateDir>/<participantId>.json\n *\n * Missing files read as {}. Writes are full overwrites; callers\n * pass the merged state, the adapter doesn't merge on its own.\n */\n\nimport { readFile, writeFile, mkdir, stat } from \"node:fs/promises\"\nimport { resolve as resolvePath } from \"node:path\"\nimport type { ParticipantId, StateStore } from \"../ports.js\"\n\nexport type FileStateStoreOptions = {\n /** Directory to hold the per-participant JSON files. */\n readonly dir: string\n}\n\nexport class FileStateStore implements StateStore {\n readonly kind = \"fs\"\n\n constructor(private readonly opts: FileStateStoreOptions) {}\n\n async read(participantId: ParticipantId): Promise<Readonly<Record<string, unknown>>> {\n const path = this.pathFor(participantId)\n try {\n const raw = await readFile(path, \"utf8\")\n return JSON.parse(raw) as Record<string, unknown>\n } catch (err) {\n if (isNotFound(err)) return Object.freeze({})\n throw err\n }\n }\n\n async write(\n participantId: ParticipantId,\n state: Readonly<Record<string, unknown>>\n ): Promise<void> {\n await ensureDir(this.opts.dir)\n const path = this.pathFor(participantId)\n await writeFile(path, JSON.stringify(state, null, 2), \"utf8\")\n }\n\n private pathFor(participantId: ParticipantId): string {\n // Sanitize: participant ids are user-controlled. Disallow path traversal.\n const safe = participantId.replace(/[^a-zA-Z0-9._-]/g, \"_\")\n return resolvePath(this.opts.dir, `${safe}.json`)\n }\n}\n\nasync function ensureDir(dir: string): Promise<void> {\n try {\n await stat(dir)\n } catch (err) {\n if (isNotFound(err)) {\n await mkdir(dir, { recursive: true })\n return\n }\n throw err\n }\n}\n\nfunction isNotFound(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code: string }).code === \"ENOENT\"\n )\n}\n","import { spawn } from \"node:child_process\"\n\nexport interface SpawnWithStdinOptions {\n /** Executable to invoke (must be on PATH). */\n readonly command: string\n /** Static argv. The prompt is fed over stdin, not as an argument. */\n readonly args?: readonly string[]\n /** Text written to the child's stdin, then closed. */\n readonly stdin: string\n /** Working directory. Defaults to process.cwd(). */\n readonly cwd?: string\n /** Hard timeout in ms; the child is killed and the call rejects. Default 90000. */\n readonly timeoutMs?: number\n /** Abort the call — kills the child and rejects. */\n readonly signal?: AbortSignal\n /** Signal used to kill on timeout/abort. Default \"SIGTERM\". */\n readonly killSignal?: NodeJS.Signals\n}\n\n/**\n * Spawn a command in one-shot mode: write `stdin`, close it, capture stdout.\n *\n * Resolves with captured stdout on exit code 0; rejects on non-zero exit (with\n * trimmed stderr), spawn error, timeout, or abort. A single settle guard makes\n * the late `close` event after a kill a no-op.\n */\nexport function spawnWithStdin(opts: SpawnWithStdinOptions): Promise<string> {\n const {\n command,\n args = [],\n stdin,\n cwd,\n timeoutMs = 90_000,\n signal,\n killSignal = \"SIGTERM\",\n } = opts\n\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error(`${command} aborted before start`))\n return\n }\n\n const child = spawn(command, [...args], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n ...(cwd ? { cwd } : {}),\n })\n\n let out = \"\"\n let err = \"\"\n let settled = false\n\n const finish = (fn: () => void) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n signal?.removeEventListener(\"abort\", onAbort)\n fn()\n }\n\n const onAbort = () => {\n child.kill(killSignal)\n finish(() => reject(new Error(`${command} aborted`)))\n }\n const timer = setTimeout(() => {\n child.kill(killSignal)\n finish(() => reject(new Error(`${command} timed out after ${timeoutMs}ms`)))\n }, timeoutMs)\n\n signal?.addEventListener(\"abort\", onAbort)\n\n child.stdout.on(\"data\", d => (out += d))\n child.stderr.on(\"data\", d => (err += d))\n child.on(\"error\", e =>\n finish(() => reject(new Error(`${command} not runnable: ${e.message}`)))\n )\n child.on(\"close\", code =>\n finish(() => {\n if (code === 0) resolve(out)\n else\n reject(\n new Error(`${command} exited with code ${code}: ${err.trim().slice(0, 200) || \"(no stderr)\"}`)\n )\n })\n )\n\n // child may exit before stdin drains → EPIPE on the stream; the close\n // handler already drives resolve/reject, so this write error is benign\n child.stdin.on(\"error\", () => {})\n child.stdin.write(stdin)\n child.stdin.end()\n })\n}\n","/**\n * Parse a `claude --output-format json` response: return the `result` field as\n * the model's text, or null if the JSON isn't that shape (caller falls back to\n * raw stdout). The same envelope is emitted by several agent CLIs in JSON mode.\n */\nexport function parseClaudeJsonOutput(stdout: string): string | null {\n try {\n const parsed = JSON.parse(stdout) as unknown\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"result\" in parsed &&\n typeof (parsed as { result: unknown }).result === \"string\"\n ) {\n return (parsed as { result: string }).result\n }\n return null\n } catch {\n return null\n }\n}\n","/**\n * Agent-CLI participant — spawns a CLI binary, pipes the assembled prompt\n * over stdin, and uses captured stdout as the turn content.\n *\n * Works for any CLI that supports a one-shot \"read prompt from stdin,\n * print response to stdout\" mode (claude-code's `--print`, hermes' `-p`,\n * etc.).\n */\n\nimport { readFile } from \"node:fs/promises\"\nimport matter from \"gray-matter\"\nimport { spawnWithStdin } from \"@agentproto/cli-exec\"\nimport type {\n ParticipantExecuteInput,\n ParticipantExecuteOutput,\n ParticipantExecutor,\n} from \"../ports.js\"\n\n// Re-exported for back-compat: the JSON-envelope parser now lives in the shared\n// @agentproto/cli-exec package alongside the spawn helper.\nexport { parseClaudeJsonOutput } from \"@agentproto/cli-exec\"\n\nexport type AgentCliParticipantOptions = {\n /** Executable to invoke. Examples: \"claude\", \"hermes\", \"goose\". */\n readonly command: string\n /** Static args. The prompt is fed over stdin, not as an argument. */\n readonly args?: readonly string[]\n /** Working directory. Defaults to process.cwd(). */\n readonly cwd?: string\n /** Hard timeout in ms. Default 90000. */\n readonly timeoutMs?: number\n /** Optional output parser — if returns null, content falls back to raw stdout. */\n readonly parseOutput?: (stdout: string) => string | null\n}\n\nexport class AgentCliParticipant implements ParticipantExecutor {\n readonly kind = \"agent-cli\"\n\n constructor(private readonly opts: AgentCliParticipantOptions) {}\n\n async executeTurn(\n input: ParticipantExecuteInput\n ): Promise<ParticipantExecuteOutput> {\n const prompt = await assemblePrompt(input)\n const stdout = await spawnWithStdin({\n command: this.opts.command,\n args: this.opts.args ?? [],\n cwd: this.opts.cwd ?? process.cwd(),\n timeoutMs: this.opts.timeoutMs ?? 90000,\n stdin: prompt,\n signal: input.signal,\n })\n const parsed = this.opts.parseOutput?.(stdout) ?? null\n const content = parsed ?? stdout.trimEnd()\n return { content }\n }\n}\n\nasync function assemblePrompt(\n input: ParticipantExecuteInput\n): Promise<string> {\n const roleText = input.participant.role\n ? await loadRole(input.participant.role)\n : \"\"\n\n const transcript = input.recentTurns\n .map((t) => `[${t.participantId}] ${t.content}`)\n .join(\"\\n\\n\")\n\n return [\n roleText && `# Your role\\n\\n${roleText}`,\n `# Recent conversation\\n\\n${transcript}`,\n `# Your turn\\n\\nYou are ${input.participant.displayName}. The latest message in the conversation triggered you (most likely because it mentions you). Read the transcript above and reply in character. Keep it conversational unless the trigger asks for detailed work. Output only your reply — no preamble, no role labels, no quotes around the response.`,\n ]\n .filter(Boolean)\n .join(\"\\n\\n\")\n}\n\n// File extensions we treat as a path-to-role-file. Anything else is\n// inline role text — even strings that contain `/`, which are common\n// in normal sentences (\"I am an AI/ML reviewer\").\nconst ROLE_FILE_EXTENSIONS = [\".md\", \".markdown\", \".txt\"]\n\nasync function loadRole(roleField: string): Promise<string> {\n if (!looksLikeRoleFile(roleField)) return roleField\n try {\n const raw = await readFile(roleField, \"utf8\")\n // Strip optional YAML frontmatter — lets a Claude Code agent\n // definition file (.claude/agents/*.md) double as a swarm role\n // without ferrying the agent's metadata into the prompt.\n const parsed = matter(raw)\n return parsed.content.trim()\n } catch (err) {\n // The string had a role-file extension but the file isn't readable\n // — surface a hint on stderr so authors don't silently get the\n // literal path as a prompt. Still fall back to inline so the swarm\n // doesn't crash on a typo.\n const code = (err as NodeJS.ErrnoException).code\n process.stderr.write(\n `agent-cli participant: role path '${roleField}' not readable (${code ?? \"unknown\"}); using the literal string as inline role text.\\n`\n )\n return roleField\n }\n}\n\nfunction looksLikeRoleFile(s: string): boolean {\n const lower = s.toLowerCase()\n return ROLE_FILE_EXTENSIONS.some((ext) => lower.endsWith(ext))\n}\n","/**\n * MultiAgentRuntime adapter registry — the seam third-party packages\n * use to plug substrates, dispatchers, participant executors, and\n * state stores into `agentproto run-swarm` without forking the CLI.\n *\n * A plugin module exports nothing required by name — it just imports\n * `register*` helpers from `@agentproto/cli/registry/runtime` and\n * calls them at module load. The CLI discovers plugins via:\n *\n * 1. `--plugin <module-id>` flags on `run-swarm`\n * 2. The `plugins[]` array in `~/.agentproto/config.json`\n * 3. Auto-registered built-ins (file substrate, mention dispatcher,\n * fs state, agent-cli participant) — registered by\n * `registerBuiltins()` at startup.\n *\n * Manifest `kind` strings are resolved through the registry: the kind\n * is the lookup key; the factory builds the concrete adapter from the\n * (loose) manifest config + shared context.\n */\n\nimport type {\n Dispatcher,\n ParticipantExecutor,\n StateStore,\n Substrate,\n} from \"@agentproto/agent-runtime\"\n\n// ── Context passed to every factory ──\n\n/**\n * Shared context every factory receives. Carries the manifest's base\n * directory (for resolving relative paths declared in adapter configs)\n * and a `cleanup` collector so adapters that hold disposable resources\n * (MCP clients, sockets, child processes) can register teardown callbacks\n * that the CLI runs on shutdown.\n */\nexport interface AdapterContext {\n /** Absolute directory of the manifest file. */\n readonly baseDir: string\n /** Register a teardown callback to run when the swarm shuts down. */\n registerCleanup(fn: () => Promise<void> | void): void\n}\n\n// ── Loose config shape ──\n\n/**\n * Manifest adapter blocks are loose: `{ kind: string, ...host-extension }`.\n * Each factory pulls its own typed fields off the config and validates\n * them inline.\n */\nexport interface AdapterConfig {\n readonly kind: string\n readonly [extension: string]: unknown\n}\n\n// ── Factory signatures ──\n\nexport type SubstrateFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<Substrate> | Substrate\n\nexport type DispatcherFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<Dispatcher> | Dispatcher\n\nexport type ExecutorFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<ParticipantExecutor> | ParticipantExecutor\n\nexport type StateStoreFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<StateStore> | StateStore\n\n// ── Registry state ──\n\nconst substrates = new Map<string, SubstrateFactory>()\nconst dispatchers = new Map<string, DispatcherFactory>()\nconst executors = new Map<string, ExecutorFactory>()\nconst stateStores = new Map<string, StateStoreFactory>()\n\n// ── Public registration API ──\n\nexport function registerSubstrate(kind: string, factory: SubstrateFactory): void {\n substrates.set(kind, factory)\n}\n\nexport function registerDispatcher(\n kind: string,\n factory: DispatcherFactory\n): void {\n dispatchers.set(kind, factory)\n}\n\nexport function registerExecutor(kind: string, factory: ExecutorFactory): void {\n executors.set(kind, factory)\n}\n\nexport function registerStateStore(\n kind: string,\n factory: StateStoreFactory\n): void {\n stateStores.set(kind, factory)\n}\n\n// ── Lookup API (used by run-swarm wiring) ──\n\nexport function getSubstrateFactory(kind: string): SubstrateFactory | undefined {\n return substrates.get(kind)\n}\n\nexport function getDispatcherFactory(\n kind: string\n): DispatcherFactory | undefined {\n return dispatchers.get(kind)\n}\n\nexport function getExecutorFactory(kind: string): ExecutorFactory | undefined {\n return executors.get(kind)\n}\n\nexport function getStateStoreFactory(\n kind: string\n): StateStoreFactory | undefined {\n return stateStores.get(kind)\n}\n\nexport function listRegisteredKinds(): {\n substrates: readonly string[]\n dispatchers: readonly string[]\n executors: readonly string[]\n stateStores: readonly string[]\n} {\n return {\n substrates: [...substrates.keys()],\n dispatchers: [...dispatchers.keys()],\n executors: [...executors.keys()],\n stateStores: [...stateStores.keys()],\n }\n}\n\n/**\n * Test-only: drop every registration. Plugin registrations persist for\n * the lifetime of the process, so tests that load + unload plugins use\n * this to start from a clean slate.\n */\nexport function _resetRegistryForTests(): void {\n substrates.clear()\n dispatchers.clear()\n executors.clear()\n stateStores.clear()\n}\n","/**\n * Built-in MultiAgentRuntime adapters. Registers reference adapters\n * shipped by `@agentproto/agent-runtime` against the runtime registry\n * so a vanilla `agentproto run-swarm` works without any plugins.\n *\n * Built-in `kind` strings: `file`, `mention`, `fs`, `agent-cli`.\n */\n\nimport { resolve as resolvePath } from \"node:path\"\nimport { FileSubstrate } from \"@agentproto/agent-runtime/adapters/substrate-file\"\nimport { MentionDispatcher } from \"@agentproto/agent-runtime/adapters/dispatcher-mention\"\nimport { FileStateStore } from \"@agentproto/agent-runtime/adapters/state-fs\"\nimport {\n AgentCliParticipant,\n parseClaudeJsonOutput,\n} from \"@agentproto/agent-runtime/adapters/participant-agent-cli\"\nimport {\n registerDispatcher,\n registerExecutor,\n registerStateStore,\n registerSubstrate,\n type AdapterConfig,\n type AdapterContext,\n} from \"./runtime.js\"\n\nexport function registerBuiltins(): void {\n registerSubstrate(\"file\", (cfg, ctx) => {\n const path = typeof cfg.path === \"string\" ? cfg.path : \".runtime/conversation.md\"\n return new FileSubstrate({ path: resolvePath(ctx.baseDir, path) })\n })\n\n registerDispatcher(\"mention\", () => new MentionDispatcher())\n\n registerStateStore(\"fs\", (cfg, ctx) => {\n const dir = typeof cfg.dir === \"string\" ? cfg.dir : \".runtime/state\"\n return new FileStateStore({ dir: resolvePath(ctx.baseDir, dir) })\n })\n\n registerExecutor(\"agent-cli\", (cfg) => buildAgentCli(cfg))\n}\n\nfunction buildAgentCli(cfg: AdapterConfig): AgentCliParticipant {\n const command = typeof cfg.command === \"string\" ? cfg.command : \"claude\"\n const args = Array.isArray(cfg.args)\n ? cfg.args.filter((a): a is string => typeof a === \"string\")\n : [\"--print\", \"--output-format=json\"]\n const useClaudeJson = cfg.parseJson !== false && command === \"claude\"\n return new AgentCliParticipant({\n command,\n args,\n parseOutput: useClaudeJson ? parseClaudeJsonOutput : undefined,\n })\n}\n\n// Suppress unused-context warning on registrations that don't read it.\nexport type { AdapterContext }\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../agent-runtime/src/adapters/substrate-file.ts","../../../agent-runtime/src/util/mention-parser.mjs","../../../agent-runtime/src/adapters/dispatcher-mention.ts","../../../agent-runtime/src/adapters/state-fs.ts","../../../cli-exec/src/spawn-stdin.ts","../../../cli-exec/src/parse.ts","../../../agent-runtime/src/adapters/participant-agent-cli.ts","../../src/registry/runtime.ts","../../src/registry/builtins.ts"],"names":["readFile","isNotFound","ensureDir","writeFile","resolvePath","stat","mkdir","resolve"],"mappings":"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","file":"builtins.mjs","sourcesContent":["/**\n * File substrate — append-only markdown journal.\n *\n * Each turn is delimited by a header line:\n * === TURN id=<id> participant=<pid> ts=<iso> ===\n * <body lines…>\n *\n * Ids are content-hashed, so re-running an append with the same body\n * is idempotent at the row level.\n *\n * Caveats:\n * - `appendFile` does not fsync. A power-loss between buffered write\n * and disk flush can lose the most recent turn(s); callers that\n * need durability across crashes should layer their own fsync.\n * - The parser splits on lines matching `=== TURN ... ===`. Turn\n * content containing such a line will corrupt subsequent parses.\n * Treat turn content as untrusted? Sanitize before append.\n */\n\nimport { appendFile, readFile, writeFile, mkdir, stat } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\nimport { createHash } from \"node:crypto\"\nimport type { Substrate, Turn, TurnInput, TurnId } from \"../ports.js\"\n\nexport type FileSubstrateOptions = {\n /** Absolute path to the journal file. */\n readonly path: string\n}\n\nconst HEADER_RE =\n /^=== TURN id=([^\\s]+) participant=([^\\s]+) ts=([^\\s]+) ===$/\n\nexport class FileSubstrate implements Substrate {\n readonly kind = \"file\"\n\n constructor(private readonly opts: FileSubstrateOptions) {}\n\n async append(input: TurnInput): Promise<Turn> {\n const timestamp = input.timestamp ?? new Date().toISOString()\n const id = hashTurnId(input.participantId, timestamp, input.content)\n const turn: Turn = {\n id,\n participantId: input.participantId,\n timestamp,\n content: input.content,\n meta: input.meta,\n }\n await ensureDir(this.opts.path)\n const block = formatTurnBlock(turn)\n await appendFile(this.opts.path, block, \"utf8\")\n return turn\n }\n\n async read(since?: TurnId): Promise<readonly Turn[]> {\n let raw: string\n try {\n raw = await readFile(this.opts.path, \"utf8\")\n } catch (err) {\n if (isNotFound(err)) return []\n throw err\n }\n const turns = parseJournal(raw)\n if (since === undefined) return turns\n const cutoff = turns.findIndex((t) => t.id === since)\n if (cutoff === -1) return turns\n return turns.slice(cutoff + 1)\n }\n}\n\n/**\n * Reset the journal by truncating it. Useful for tests. Not part of the\n * Substrate interface — callers must reach for the adapter directly.\n */\nexport async function resetFileSubstrate(path: string): Promise<void> {\n await ensureDir(path)\n await writeFile(path, \"\", \"utf8\")\n}\n\nfunction formatTurnBlock(turn: Turn): string {\n const header = `=== TURN id=${turn.id} participant=${turn.participantId} ts=${turn.timestamp} ===\\n`\n const body = turn.content.endsWith(\"\\n\") ? turn.content : `${turn.content}\\n`\n return `${header}${body}`\n}\n\nfunction parseJournal(raw: string): Turn[] {\n const lines = raw.split(\"\\n\")\n const turns: Turn[] = []\n let current: { id: string; pid: string; ts: string; buf: string[] } | null = null\n\n for (const line of lines) {\n const match = HEADER_RE.exec(line)\n if (match && match[1] && match[2] && match[3]) {\n if (current) turns.push(materialise(current))\n current = { id: match[1], pid: match[2], ts: match[3], buf: [] }\n continue\n }\n if (current) current.buf.push(line)\n }\n if (current) turns.push(materialise(current))\n return turns\n}\n\nfunction materialise(c: {\n id: string\n pid: string\n ts: string\n buf: string[]\n}): Turn {\n // Drop the trailing empty line that appendFile leaves between blocks.\n let body = c.buf.join(\"\\n\")\n while (body.endsWith(\"\\n\")) body = body.slice(0, -1)\n return {\n id: c.id,\n participantId: c.pid,\n timestamp: c.ts,\n content: body,\n }\n}\n\nfunction hashTurnId(pid: string, ts: string, content: string): string {\n const h = createHash(\"sha256\")\n h.update(pid)\n h.update(\"\\x00\")\n h.update(ts)\n h.update(\"\\x00\")\n h.update(content)\n return `t_${h.digest(\"hex\").slice(0, 12)}`\n}\n\nasync function ensureDir(path: string): Promise<void> {\n const dir = dirname(path)\n try {\n await stat(dir)\n } catch (err) {\n if (isNotFound(err)) {\n await mkdir(dir, { recursive: true })\n return\n }\n throw err\n }\n}\n\nfunction isNotFound(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code: string }).code === \"ENOENT\"\n )\n}\n","/**\n * Canonical @-mention parser.\n *\n * Lives as raw JavaScript (no types) because it's consumed in two\n * places that don't share a build:\n *\n * 1. The kernel imports `textContainsMention` from this file as a\n * normal ESM module (the dispatcher in `adapters/dispatcher-mention.ts`).\n *\n * 2. Runtime profiles inline this file's source into Claude Code\n * hooks at profile-build time, because `.claude/` has no\n * module-resolution at hook-execution time. The build script\n * reads this file as text and stamps it into the hook template.\n *\n * Two cases:\n * 1. Literal `@<name>` substring (case-sensitive, full name).\n * 2. For multi-word names, also `@<firstName>` with non-word\n * boundary (case-insensitive).\n *\n * Edit here only — both consumers stay in sync via build pipelines.\n */\n\n/** @param {string} text @param {string} name @returns {boolean} */\nexport function textContainsMention(text, name) {\n if (text.includes(`@${name}`)) return true\n if (name.includes(\" \")) {\n const firstName = name.split(\" \")[0]?.trim()\n if (!firstName) return false\n const regex = new RegExp(`@${firstName}(?!\\\\w)`, \"i\")\n return regex.test(text)\n }\n return false\n}\n","/**\n * Mention dispatcher — selects participants whose displayName is\n * @-mentioned in the most recent turn.\n *\n * Skip rules:\n * - never re-select the participant who authored the trigger turn\n * (otherwise they'd reply to themselves indefinitely)\n * - never dispatch on a trigger we already processed (in-memory cursor —\n * prevents the loop from re-firing on the same @mention every poll\n * when the kernel's own reply isn't yet visible in the next read).\n * Per-dispatcher-instance state; a fresh swarm process starts with\n * no cursor and naturally responds to the latest unhandled mention\n * exactly once.\n * - if no mentions match any known participant, return [] (idle)\n *\n * The parser itself lives in `../util/mention-parser.mjs` (vanilla JS)\n * so runtime profiles can stamp the same implementation into Claude\n * Code hooks at build time without depending on the TS build pipeline.\n */\n\nimport type {\n Dispatcher,\n DispatcherInput,\n ParticipantId,\n TurnId,\n} from \"../ports.js\"\nimport { textContainsMention } from \"../util/mention-parser.mjs\"\n\nexport class MentionDispatcher implements Dispatcher {\n readonly kind = \"mention\"\n private lastProcessedTriggerId: TurnId | undefined\n\n async selectNext(input: DispatcherInput): Promise<readonly ParticipantId[]> {\n const trigger = input.recentTurns[input.recentTurns.length - 1]\n if (!trigger) return []\n if (trigger.id === this.lastProcessedTriggerId) return []\n const text = trigger.content\n\n const selected: ParticipantId[] = []\n for (const p of input.participants) {\n if (p.id === trigger.participantId) continue\n if (textContainsMention(text, p.displayName)) {\n selected.push(p.id)\n }\n }\n\n if (selected.length > 0) {\n this.lastProcessedTriggerId = trigger.id\n }\n return selected\n }\n}\n\n// Re-exported so callers writing their own adapters can detect\n// mentions with identical semantics.\nexport { textContainsMention }\n","/**\n * Filesystem state store — one JSON file per participant.\n *\n * <stateDir>/<participantId>.json\n *\n * Missing files read as {}. Writes are full overwrites; callers\n * pass the merged state, the adapter doesn't merge on its own.\n */\n\nimport { readFile, writeFile, mkdir, stat } from \"node:fs/promises\"\nimport { resolve as resolvePath } from \"node:path\"\nimport type { ParticipantId, StateStore } from \"../ports.js\"\n\nexport type FileStateStoreOptions = {\n /** Directory to hold the per-participant JSON files. */\n readonly dir: string\n}\n\nexport class FileStateStore implements StateStore {\n readonly kind = \"fs\"\n\n constructor(private readonly opts: FileStateStoreOptions) {}\n\n async read(participantId: ParticipantId): Promise<Readonly<Record<string, unknown>>> {\n const path = this.pathFor(participantId)\n try {\n const raw = await readFile(path, \"utf8\")\n return JSON.parse(raw) as Record<string, unknown>\n } catch (err) {\n if (isNotFound(err)) return Object.freeze({})\n throw err\n }\n }\n\n async write(\n participantId: ParticipantId,\n state: Readonly<Record<string, unknown>>\n ): Promise<void> {\n await ensureDir(this.opts.dir)\n const path = this.pathFor(participantId)\n await writeFile(path, JSON.stringify(state, null, 2), \"utf8\")\n }\n\n private pathFor(participantId: ParticipantId): string {\n // Sanitize: participant ids are user-controlled. Disallow path traversal.\n const safe = participantId.replace(/[^a-zA-Z0-9._-]/g, \"_\")\n return resolvePath(this.opts.dir, `${safe}.json`)\n }\n}\n\nasync function ensureDir(dir: string): Promise<void> {\n try {\n await stat(dir)\n } catch (err) {\n if (isNotFound(err)) {\n await mkdir(dir, { recursive: true })\n return\n }\n throw err\n }\n}\n\nfunction isNotFound(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code: string }).code === \"ENOENT\"\n )\n}\n","import { spawn } from \"node:child_process\"\n\nexport interface SpawnWithStdinOptions {\n /** Executable to invoke (must be on PATH). */\n readonly command: string\n /** Static argv. The prompt is fed over stdin, not as an argument. */\n readonly args?: readonly string[]\n /** Text written to the child's stdin, then closed. */\n readonly stdin: string\n /** Working directory. Defaults to process.cwd(). */\n readonly cwd?: string\n /** Hard timeout in ms; the child is killed and the call rejects. Default 90000. */\n readonly timeoutMs?: number\n /** Abort the call — kills the child and rejects. */\n readonly signal?: AbortSignal\n /** Signal used to kill on timeout/abort. Default \"SIGTERM\". */\n readonly killSignal?: NodeJS.Signals\n}\n\n/**\n * Spawn a command in one-shot mode: write `stdin`, close it, capture stdout.\n *\n * Resolves with captured stdout on exit code 0; rejects on non-zero exit (with\n * trimmed stderr), spawn error, timeout, or abort. A single settle guard makes\n * the late `close` event after a kill a no-op.\n */\nexport function spawnWithStdin(opts: SpawnWithStdinOptions): Promise<string> {\n const {\n command,\n args = [],\n stdin,\n cwd,\n timeoutMs = 90_000,\n signal,\n killSignal = \"SIGTERM\",\n } = opts\n\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error(`${command} aborted before start`))\n return\n }\n\n const child = spawn(command, [...args], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n ...(cwd ? { cwd } : {}),\n })\n\n let out = \"\"\n let err = \"\"\n let settled = false\n\n const finish = (fn: () => void) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n signal?.removeEventListener(\"abort\", onAbort)\n fn()\n }\n\n const onAbort = () => {\n child.kill(killSignal)\n finish(() => reject(new Error(`${command} aborted`)))\n }\n const timer = setTimeout(() => {\n child.kill(killSignal)\n finish(() => reject(new Error(`${command} timed out after ${timeoutMs}ms`)))\n }, timeoutMs)\n\n signal?.addEventListener(\"abort\", onAbort)\n\n child.stdout.on(\"data\", d => (out += d))\n child.stderr.on(\"data\", d => (err += d))\n child.on(\"error\", e =>\n finish(() => reject(new Error(`${command} not runnable: ${e.message}`)))\n )\n child.on(\"close\", code =>\n finish(() => {\n if (code === 0) resolve(out)\n else\n reject(\n new Error(`${command} exited with code ${code}: ${err.trim().slice(0, 200) || \"(no stderr)\"}`)\n )\n })\n )\n\n // child may exit before stdin drains → EPIPE on the stream; the close\n // handler already drives resolve/reject, so this write error is benign\n child.stdin.on(\"error\", () => {})\n child.stdin.write(stdin)\n child.stdin.end()\n })\n}\n","/**\n * Parse a `claude --output-format json` response: return the `result` field as\n * the model's text, or null if the JSON isn't that shape (caller falls back to\n * raw stdout). The same envelope is emitted by several agent CLIs in JSON mode.\n */\nexport function parseClaudeJsonOutput(stdout: string): string | null {\n try {\n const parsed = JSON.parse(stdout) as unknown\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"result\" in parsed &&\n typeof (parsed as { result: unknown }).result === \"string\"\n ) {\n return (parsed as { result: string }).result\n }\n return null\n } catch {\n return null\n }\n}\n","/**\n * Agent-CLI participant — spawns a CLI binary, pipes the assembled prompt\n * over stdin, and uses captured stdout as the turn content.\n *\n * Works for any CLI that supports a one-shot \"read prompt from stdin,\n * print response to stdout\" mode (claude-code's `--print`, hermes' `-p`,\n * etc.).\n */\n\nimport { readFile } from \"node:fs/promises\"\nimport { resolve as resolvePath } from \"node:path\"\nimport matter from \"gray-matter\"\nimport { spawnWithStdin } from \"@agentproto/cli-exec\"\nimport type {\n ParticipantExecuteInput,\n ParticipantExecuteOutput,\n ParticipantExecutor,\n} from \"../ports.js\"\n\n// Re-exported for back-compat: the JSON-envelope parser now lives in the shared\n// @agentproto/cli-exec package alongside the spawn helper.\nexport { parseClaudeJsonOutput } from \"@agentproto/cli-exec\"\n\nexport type AgentCliParticipantOptions = {\n /** Executable to invoke. Examples: \"claude\", \"hermes\", \"goose\". */\n readonly command: string\n /** Static args. The prompt is fed over stdin, not as an argument. */\n readonly args?: readonly string[]\n /** Working directory. Defaults to process.cwd(). */\n readonly cwd?: string\n /** Manifest base directory; relative role paths resolve against this. */\n readonly baseDir?: string\n /** Hard timeout in ms. Default 90000. */\n readonly timeoutMs?: number\n /** Optional output parser — if returns null, content falls back to raw stdout. */\n readonly parseOutput?: (stdout: string) => string | null\n}\n\nexport class AgentCliParticipant implements ParticipantExecutor {\n readonly kind = \"agent-cli\"\n\n constructor(private readonly opts: AgentCliParticipantOptions) {}\n\n async executeTurn(\n input: ParticipantExecuteInput\n ): Promise<ParticipantExecuteOutput> {\n const prompt = await assemblePrompt(input, this.opts.baseDir)\n const stdout = await spawnWithStdin({\n command: this.opts.command,\n args: this.opts.args ?? [],\n cwd: this.opts.cwd ?? process.cwd(),\n timeoutMs: this.opts.timeoutMs ?? 90000,\n stdin: prompt,\n signal: input.signal,\n })\n const parsed = this.opts.parseOutput?.(stdout) ?? null\n const content = parsed ?? stdout.trimEnd()\n return { content }\n }\n}\n\nasync function assemblePrompt(\n input: ParticipantExecuteInput,\n baseDir?: string\n): Promise<string> {\n const roleText = input.participant.role\n ? await loadRole(input.participant.role, baseDir)\n : \"\"\n\n const transcript = input.recentTurns\n .map((t) => `[${t.participantId}] ${t.content}`)\n .join(\"\\n\\n\")\n\n return [\n roleText && `# Your role\\n\\n${roleText}`,\n `# Recent conversation\\n\\n${transcript}`,\n `# Your turn\\n\\nYou are ${input.participant.displayName}. The latest message in the conversation triggered you (most likely because it mentions you). Read the transcript above and reply in character. Keep it conversational unless the trigger asks for detailed work. Output only your reply — no preamble, no role labels, no quotes around the response.`,\n ]\n .filter(Boolean)\n .join(\"\\n\\n\")\n}\n\n// File extensions we treat as a path-to-role-file. Anything else is\n// inline role text — even strings that contain `/`, which are common\n// in normal sentences (\"I am an AI/ML reviewer\").\nconst ROLE_FILE_EXTENSIONS = [\".md\", \".markdown\", \".txt\"]\n\nasync function loadRole(roleField: string, baseDir?: string): Promise<string> {\n if (!looksLikeRoleFile(roleField)) return roleField\n const path = baseDir ? resolvePath(baseDir, roleField) : roleField\n try {\n const raw = await readFile(path, \"utf8\")\n // Strip optional YAML frontmatter — lets a Claude Code agent\n // definition file (.claude/agents/*.md) double as a swarm role\n // without ferrying the agent's metadata into the prompt.\n const parsed = matter(raw)\n return parsed.content.trim()\n } catch (err) {\n // The string had a role-file extension but the file isn't readable\n // — surface a hint on stderr so authors don't silently get the\n // literal path as a prompt. Still fall back to inline so the swarm\n // doesn't crash on a typo.\n const code = (err as NodeJS.ErrnoException).code\n process.stderr.write(\n `agent-cli participant: role path '${roleField}' (resolved to ${path}) not readable (${code ?? \"unknown\"}); using the literal string as inline role text.\\n`\n )\n return roleField\n }\n}\n\nfunction looksLikeRoleFile(s: string): boolean {\n const lower = s.toLowerCase()\n return ROLE_FILE_EXTENSIONS.some((ext) => lower.endsWith(ext))\n}\n","/**\n * MultiAgentRuntime adapter registry — the seam third-party packages\n * use to plug substrates, dispatchers, participant executors, and\n * state stores into `agentproto run-swarm` without forking the CLI.\n *\n * A plugin module exports nothing required by name — it just imports\n * `register*` helpers from `@agentproto/cli/registry/runtime` and\n * calls them at module load. The CLI discovers plugins via:\n *\n * 1. `--plugin <module-id>` flags on `run-swarm`\n * 2. The `plugins[]` array in `~/.agentproto/config.json`\n * 3. Auto-registered built-ins (file substrate, mention dispatcher,\n * fs state, agent-cli participant) — registered by\n * `registerBuiltins()` at startup.\n *\n * Manifest `kind` strings are resolved through the registry: the kind\n * is the lookup key; the factory builds the concrete adapter from the\n * (loose) manifest config + shared context.\n */\n\nimport type {\n Dispatcher,\n ParticipantExecutor,\n StateStore,\n Substrate,\n} from \"@agentproto/agent-runtime\"\n\n// ── Context passed to every factory ──\n\n/**\n * Shared context every factory receives. Carries the manifest's base\n * directory (for resolving relative paths declared in adapter configs)\n * and a `cleanup` collector so adapters that hold disposable resources\n * (MCP clients, sockets, child processes) can register teardown callbacks\n * that the CLI runs on shutdown.\n */\nexport interface AdapterContext {\n /** Absolute directory of the manifest file. */\n readonly baseDir: string\n /** Register a teardown callback to run when the swarm shuts down. */\n registerCleanup(fn: () => Promise<void> | void): void\n}\n\n// ── Loose config shape ──\n\n/**\n * Manifest adapter blocks are loose: `{ kind: string, ...host-extension }`.\n * Each factory pulls its own typed fields off the config and validates\n * them inline.\n */\nexport interface AdapterConfig {\n readonly kind: string\n readonly [extension: string]: unknown\n}\n\n// ── Factory signatures ──\n\nexport type SubstrateFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<Substrate> | Substrate\n\nexport type DispatcherFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<Dispatcher> | Dispatcher\n\nexport type ExecutorFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<ParticipantExecutor> | ParticipantExecutor\n\nexport type StateStoreFactory = (\n config: AdapterConfig,\n ctx: AdapterContext\n) => Promise<StateStore> | StateStore\n\n// ── Registry state ──\n\nconst substrates = new Map<string, SubstrateFactory>()\nconst dispatchers = new Map<string, DispatcherFactory>()\nconst executors = new Map<string, ExecutorFactory>()\nconst stateStores = new Map<string, StateStoreFactory>()\n\n// ── Public registration API ──\n\nexport function registerSubstrate(kind: string, factory: SubstrateFactory): void {\n substrates.set(kind, factory)\n}\n\nexport function registerDispatcher(\n kind: string,\n factory: DispatcherFactory\n): void {\n dispatchers.set(kind, factory)\n}\n\nexport function registerExecutor(kind: string, factory: ExecutorFactory): void {\n executors.set(kind, factory)\n}\n\nexport function registerStateStore(\n kind: string,\n factory: StateStoreFactory\n): void {\n stateStores.set(kind, factory)\n}\n\n// ── Lookup API (used by run-swarm wiring) ──\n\nexport function getSubstrateFactory(kind: string): SubstrateFactory | undefined {\n return substrates.get(kind)\n}\n\nexport function getDispatcherFactory(\n kind: string\n): DispatcherFactory | undefined {\n return dispatchers.get(kind)\n}\n\nexport function getExecutorFactory(kind: string): ExecutorFactory | undefined {\n return executors.get(kind)\n}\n\nexport function getStateStoreFactory(\n kind: string\n): StateStoreFactory | undefined {\n return stateStores.get(kind)\n}\n\nexport function listRegisteredKinds(): {\n substrates: readonly string[]\n dispatchers: readonly string[]\n executors: readonly string[]\n stateStores: readonly string[]\n} {\n return {\n substrates: [...substrates.keys()],\n dispatchers: [...dispatchers.keys()],\n executors: [...executors.keys()],\n stateStores: [...stateStores.keys()],\n }\n}\n\n/**\n * Test-only: drop every registration. Plugin registrations persist for\n * the lifetime of the process, so tests that load + unload plugins use\n * this to start from a clean slate.\n */\nexport function _resetRegistryForTests(): void {\n substrates.clear()\n dispatchers.clear()\n executors.clear()\n stateStores.clear()\n}\n","/**\n * Built-in MultiAgentRuntime adapters. Registers reference adapters\n * shipped by `@agentproto/agent-runtime` against the runtime registry\n * so a vanilla `agentproto run-swarm` works without any plugins.\n *\n * Built-in `kind` strings: `file`, `mention`, `fs`, `agent-cli`.\n */\n\nimport { resolve as resolvePath } from \"node:path\"\nimport { FileSubstrate } from \"@agentproto/agent-runtime/adapters/substrate-file\"\nimport { MentionDispatcher } from \"@agentproto/agent-runtime/adapters/dispatcher-mention\"\nimport { FileStateStore } from \"@agentproto/agent-runtime/adapters/state-fs\"\nimport {\n AgentCliParticipant,\n parseClaudeJsonOutput,\n} from \"@agentproto/agent-runtime/adapters/participant-agent-cli\"\nimport {\n registerDispatcher,\n registerExecutor,\n registerStateStore,\n registerSubstrate,\n type AdapterConfig,\n type AdapterContext,\n} from \"./runtime.js\"\n\nexport function registerBuiltins(): void {\n registerSubstrate(\"file\", (cfg, ctx) => {\n const path = typeof cfg.path === \"string\" ? cfg.path : \".runtime/conversation.md\"\n return new FileSubstrate({ path: resolvePath(ctx.baseDir, path) })\n })\n\n registerDispatcher(\"mention\", () => new MentionDispatcher())\n\n registerStateStore(\"fs\", (cfg, ctx) => {\n const dir = typeof cfg.dir === \"string\" ? cfg.dir : \".runtime/state\"\n return new FileStateStore({ dir: resolvePath(ctx.baseDir, dir) })\n })\n\n registerExecutor(\"agent-cli\", (cfg, ctx) => buildAgentCli(cfg, ctx))\n}\n\nfunction buildAgentCli(cfg: AdapterConfig, ctx: AdapterContext): AgentCliParticipant {\n const command = typeof cfg.command === \"string\" ? cfg.command : \"claude\"\n const defaultArgs =\n command === \"claude\"\n ? [\"--print\", \"--output-format=json\", \"--permission-mode\", \"bypassPermissions\"]\n : [\"--print\", \"--output-format=json\"]\n const args = Array.isArray(cfg.args)\n ? cfg.args.filter((a): a is string => typeof a === \"string\")\n : defaultArgs\n const useClaudeJson = cfg.parseJson !== false && command === \"claude\"\n const model = typeof cfg.model === \"string\" ? cfg.model : undefined\n const finalArgs =\n model && command === \"claude\" && !hasModelFlag(args)\n ? [...args, \"--model\", model]\n : args\n return new AgentCliParticipant({\n command,\n args: finalArgs,\n parseOutput: useClaudeJson ? parseClaudeJsonOutput : undefined,\n baseDir: ctx.baseDir,\n })\n}\n\nfunction hasModelFlag(args: readonly string[]): boolean {\n return args.some((a) => a === \"--model\" || a.startsWith(\"--model=\"))\n}\n\n// Suppress unused-context warning on registrations that don't read it.\nexport type { AdapterContext }\n"]}
|
|
@@ -2,12 +2,12 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @agentproto/cli v0.
|
|
5
|
+
* @agentproto/cli v0.5.0
|
|
6
6
|
* The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
|
|
7
7
|
*/
|
|
8
|
-
// Provide a real `require` in the ESM bundle.
|
|
9
|
-
//
|
|
10
|
-
// esbuild's interop shim throws "Dynamic require is not supported".
|
|
8
|
+
// Provide a real `require` in the ESM bundle. Some bundled deps (e.g.
|
|
9
|
+
// gray-matter, node-pty) are CJS and call `require("assert")` or similar;
|
|
10
|
+
// without this esbuild's interop shim throws "Dynamic require is not supported".
|
|
11
11
|
createRequire(import.meta.url);
|
|
12
12
|
var PLUGIN_MANIFEST_SCHEMA = "agentproto/plugin/v1";
|
|
13
13
|
var AdapterEntrySchema = z.object({
|
|
@@ -7,12 +7,12 @@ import { pathToFileURL } from 'url';
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @agentproto/cli v0.
|
|
10
|
+
* @agentproto/cli v0.5.0
|
|
11
11
|
* The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
|
|
12
12
|
*/
|
|
13
|
-
// Provide a real `require` in the ESM bundle.
|
|
14
|
-
//
|
|
15
|
-
// esbuild's interop shim throws "Dynamic require is not supported".
|
|
13
|
+
// Provide a real `require` in the ESM bundle. Some bundled deps (e.g.
|
|
14
|
+
// gray-matter, node-pty) are CJS and call `require("assert")` or similar;
|
|
15
|
+
// without this esbuild's interop shim throws "Dynamic require is not supported".
|
|
16
16
|
createRequire(import.meta.url);
|
|
17
17
|
var PLUGIN_MANIFEST_SCHEMA = "agentproto/plugin/v1";
|
|
18
18
|
var AdapterEntrySchema = z.object({
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @agentproto/cli v0.
|
|
4
|
+
* @agentproto/cli v0.5.0
|
|
5
5
|
* The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
|
|
6
6
|
*/
|
|
7
|
-
// Provide a real `require` in the ESM bundle.
|
|
8
|
-
//
|
|
9
|
-
// esbuild's interop shim throws "Dynamic require is not supported".
|
|
7
|
+
// Provide a real `require` in the ESM bundle. Some bundled deps (e.g.
|
|
8
|
+
// gray-matter, node-pty) are CJS and call `require("assert")` or similar;
|
|
9
|
+
// without this esbuild's interop shim throws "Dynamic require is not supported".
|
|
10
10
|
createRequire(import.meta.url);
|
|
11
11
|
|
|
12
12
|
// src/registry/runtime.ts
|
|
@@ -37,8 +37,10 @@ interface HostCredential {
|
|
|
37
37
|
token: string;
|
|
38
38
|
/** Always "Bearer" today; reserved for future schemes. */
|
|
39
39
|
tokenType: "Bearer";
|
|
40
|
-
/** ISO-8601. May be in the past — callers must check `isExpired`.
|
|
41
|
-
|
|
40
|
+
/** ISO-8601. May be in the past — callers must check `isExpired`. Absent
|
|
41
|
+
* when the issuing host didn't report an `expires_in` — treated as a
|
|
42
|
+
* non-expiring credential. */
|
|
43
|
+
expiresAt?: string;
|
|
42
44
|
/** OAuth 2.0 refresh token. Present when the host issues one. */
|
|
43
45
|
refreshToken?: string;
|
|
44
46
|
/** Space-separated scopes the token was issued with. */
|
|
@@ -4,12 +4,12 @@ import { homedir } from 'os';
|
|
|
4
4
|
import { join, dirname } from 'path';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @agentproto/cli v0.
|
|
7
|
+
* @agentproto/cli v0.5.0
|
|
8
8
|
* The `agentproto` binary — install / run / serve AIP-45 agent CLIs.
|
|
9
9
|
*/
|
|
10
|
-
// Provide a real `require` in the ESM bundle.
|
|
11
|
-
//
|
|
12
|
-
// esbuild's interop shim throws "Dynamic require is not supported".
|
|
10
|
+
// Provide a real `require` in the ESM bundle. Some bundled deps (e.g.
|
|
11
|
+
// gray-matter, node-pty) are CJS and call `require("assert")` or similar;
|
|
12
|
+
// without this esbuild's interop shim throws "Dynamic require is not supported".
|
|
13
13
|
createRequire(import.meta.url);
|
|
14
14
|
var FILE_VERSION = 1;
|
|
15
15
|
function credentialsPath() {
|
|
@@ -69,11 +69,13 @@ async function deleteHost(host) {
|
|
|
69
69
|
return prev;
|
|
70
70
|
}
|
|
71
71
|
function isExpired(cred, gracePeriodMs = 3e4) {
|
|
72
|
+
if (!cred.expiresAt) return false;
|
|
72
73
|
const exp = Date.parse(cred.expiresAt);
|
|
73
74
|
if (!Number.isFinite(exp)) return false;
|
|
74
75
|
return exp - gracePeriodMs <= Date.now();
|
|
75
76
|
}
|
|
76
77
|
function formatExpiry(cred) {
|
|
78
|
+
if (!cred.expiresAt) return "no expiry reported";
|
|
77
79
|
const exp = new Date(cred.expiresAt);
|
|
78
80
|
if (Number.isNaN(exp.getTime())) return "unknown";
|
|
79
81
|
const ms = exp.getTime() - Date.now();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/credentials.ts"],"names":[],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"sources":["../../src/util/credentials.ts"],"names":[],"mappings":"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","file":"credentials.mjs","sourcesContent":["/**\n * Credential store for `agentproto auth login`.\n *\n * Format on disk (JSON, mode 0600):\n *\n * {\n * \"version\": 1,\n * \"hosts\": {\n * \"wss://guilde.work/api/v1/agentproto/tunnel\": {\n * \"token\": \"eyJ…\",\n * \"tokenType\": \"Bearer\",\n * \"expiresAt\": \"2026-08-08T12:34:56.000Z\",\n * \"refreshToken\": \"rt_…\", // optional\n * \"scope\": \"tunnel:connect\",\n * \"subject\": \"user_abc\",\n * \"obtainedAt\": \"2026-05-10T08:21:11.000Z\",\n * \"deviceLabel\": \"jeremy@laptop\"\n * }\n * }\n * }\n *\n * One file per *user*, multiple hosts under the same file. Hosts are\n * keyed by the URL the user passed to `agentproto auth login --host`,\n * stripped of trailing slash. `agentproto serve --connect <url>` looks\n * up the same key when no `--token` is passed.\n *\n * Storage location:\n * - `$AGENTPROTO_HOME/credentials.json`, falling back to\n * - `~/.agentproto/credentials.json`\n *\n * The file holds bearer tokens, so it's chmod'd 0600 on every write.\n * On Windows we rely on the per-user profile path being already\n * private and skip chmod. Never log the token contents.\n */\n\nimport { mkdir, readFile, writeFile, chmod, unlink } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join, dirname } from \"node:path\"\n\nexport interface HostCredential {\n /** Bearer token. Bearer-form is the only one supported for v1. */\n token: string\n /** Always \"Bearer\" today; reserved for future schemes. */\n tokenType: \"Bearer\"\n /** ISO-8601. May be in the past — callers must check `isExpired`. Absent\n * when the issuing host didn't report an `expires_in` — treated as a\n * non-expiring credential. */\n expiresAt?: string\n /** OAuth 2.0 refresh token. Present when the host issues one. */\n refreshToken?: string\n /** Space-separated scopes the token was issued with. */\n scope?: string\n /** Human-readable subject — usually the user id, surfaced in `auth status`. */\n subject?: string\n /** ISO-8601 of when this credential was minted. */\n obtainedAt: string\n /** Friendly device label shown on the host's Machine-tokens page. */\n deviceLabel?: string\n /** Opaque revocation hint surfaced by the host (e.g. JTI). The CLI\n * passes it back on `auth logout` so the host can revoke the right\n * row server-side, not just delete the local copy. */\n revocationId?: string\n}\n\ninterface CredentialsFile {\n version: 1\n hosts: Record<string, HostCredential>\n}\n\nconst FILE_VERSION = 1 as const\n\nexport function credentialsPath(): string {\n const base = process.env[\"AGENTPROTO_HOME\"] ?? join(homedir(), \".agentproto\")\n return join(base, \"credentials.json\")\n}\n\nexport function normaliseHost(host: string): string {\n return host.replace(/\\/+$/, \"\")\n}\n\nexport async function loadCredentials(): Promise<CredentialsFile> {\n const path = credentialsPath()\n try {\n const raw = await readFile(path, \"utf8\")\n const parsed = JSON.parse(raw) as CredentialsFile\n if (parsed.version !== FILE_VERSION) {\n throw new Error(\n `credentials.json: unknown version ${parsed.version}; expected ${FILE_VERSION}. ` +\n `Delete the file and re-run \\`agentproto auth login\\`.`\n )\n }\n return parsed\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { version: FILE_VERSION, hosts: {} }\n }\n throw err\n }\n}\n\nexport async function saveCredentials(file: CredentialsFile): Promise<void> {\n const path = credentialsPath()\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, JSON.stringify(file, null, 2))\n await chmod(path, 0o600).catch(() => {\n // Windows + some sandboxed FSes (e.g. WSL on a Windows mount) don't\n // honour chmod. The file lives under the user profile in those\n // cases, which is already user-private; not worth refusing.\n })\n}\n\nexport async function readHost(host: string): Promise<HostCredential | null> {\n const f = await loadCredentials()\n return f.hosts[normaliseHost(host)] ?? null\n}\n\nexport async function writeHost(\n host: string,\n cred: HostCredential\n): Promise<void> {\n const f = await loadCredentials()\n f.hosts[normaliseHost(host)] = cred\n await saveCredentials(f)\n}\n\nexport async function deleteHost(host: string): Promise<HostCredential | null> {\n const f = await loadCredentials()\n const key = normaliseHost(host)\n const prev = f.hosts[key] ?? null\n if (prev) {\n delete f.hosts[key]\n if (Object.keys(f.hosts).length === 0) {\n // Empty file is a valid state (`auth login` re-creates), but\n // unlinking is cleaner — `auth status` then prints \"no credentials\".\n await unlink(credentialsPath()).catch(() => {})\n } else {\n await saveCredentials(f)\n }\n }\n return prev\n}\n\nexport function isExpired(cred: HostCredential, gracePeriodMs = 30_000): boolean {\n if (!cred.expiresAt) return false // no expiry reported → treat as durable\n const exp = Date.parse(cred.expiresAt)\n if (!Number.isFinite(exp)) return false // unparseable expiry → trust the host\n return exp - gracePeriodMs <= Date.now()\n}\n\nexport function formatExpiry(cred: HostCredential): string {\n if (!cred.expiresAt) return \"no expiry reported\"\n const exp = new Date(cred.expiresAt)\n if (Number.isNaN(exp.getTime())) return \"unknown\"\n const ms = exp.getTime() - Date.now()\n if (ms < 0) return `expired ${formatRelative(-ms)} ago`\n return `expires in ${formatRelative(ms)}`\n}\n\nfunction formatRelative(ms: number): string {\n if (ms < 60_000) return `${Math.round(ms / 1000)}s`\n if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`\n if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h`\n return `${Math.round(ms / 86_400_000)}d`\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "@agentproto/cli — the `agentproto` binary. Install AIP-45 agent CLI adapters, run them locally, or expose them over a tunnel as a long-running daemon. Reference host for hermes / claude-code / opencode / gemini-cli / goose, all driven through @agentproto/driver-agent-cli.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -72,44 +72,42 @@
|
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@clack/prompts": "^1.5.0",
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
75
|
+
"@earendil-works/pi-tui": "^0.80.0",
|
|
76
|
+
"chalk": "^5.0.0",
|
|
75
77
|
"cli-highlight": "^2.1.11",
|
|
76
78
|
"gray-matter": "^4.0.3",
|
|
77
|
-
"ink": "^5.0.1",
|
|
78
|
-
"ink-spinner": "^5.0.0",
|
|
79
|
-
"ink-text-input": "^5.0.1",
|
|
80
|
-
"marked": "^14.0.0",
|
|
81
|
-
"marked-terminal": "^7.2.1",
|
|
82
|
-
"react": "^18.3.1",
|
|
83
79
|
"ws": "^8.20.0",
|
|
84
80
|
"zod": "^4.4.3",
|
|
85
|
-
"@agentproto/acp": "0.
|
|
81
|
+
"@agentproto/acp": "0.4.0",
|
|
86
82
|
"@agentproto/adapter-browser": "0.1.0",
|
|
87
|
-
"@agentproto/
|
|
83
|
+
"@agentproto/auth": "0.1.0",
|
|
84
|
+
"@agentproto/provider-kit": "0.2.0",
|
|
88
85
|
"@agentproto/driver": "0.1.2",
|
|
89
|
-
"@agentproto/driver-agent-cli": "0.
|
|
90
|
-
"@agentproto/
|
|
91
|
-
"@agentproto/
|
|
86
|
+
"@agentproto/driver-agent-cli": "0.4.0",
|
|
87
|
+
"@agentproto/runtime-profile-standard": "0.1.1",
|
|
88
|
+
"@agentproto/model-catalog": "0.2.0"
|
|
92
89
|
},
|
|
93
90
|
"optionalDependencies": {
|
|
94
91
|
"node-pty": "^1.0.0"
|
|
95
92
|
},
|
|
96
93
|
"devDependencies": {
|
|
97
|
-
"@types/marked-terminal": "^6.1.1",
|
|
98
94
|
"@types/node": "^25.6.2",
|
|
99
|
-
"@types/react": "^18.3.0",
|
|
100
95
|
"@types/ws": "^8.18.1",
|
|
101
96
|
"tsup": "^8.5.1",
|
|
102
97
|
"typescript": "^5.9.3",
|
|
103
98
|
"vitest": "^3.2.4",
|
|
104
|
-
"@agentproto/adapter-claude-code": "0.
|
|
105
|
-
"@agentproto/adapter-
|
|
106
|
-
"@agentproto/adapter-
|
|
107
|
-
"@agentproto/adapter-mastra-agent": "0.1.
|
|
108
|
-
"@agentproto/adapter-
|
|
109
|
-
"@agentproto/adapter-
|
|
110
|
-
"@agentproto/
|
|
111
|
-
"@agentproto/
|
|
112
|
-
"@agentproto/
|
|
99
|
+
"@agentproto/adapter-claude-code": "0.2.0",
|
|
100
|
+
"@agentproto/adapter-claude-sdk": "0.2.0",
|
|
101
|
+
"@agentproto/adapter-codex": "0.1.2",
|
|
102
|
+
"@agentproto/adapter-mastra-agent": "0.1.2",
|
|
103
|
+
"@agentproto/adapter-hermes": "0.2.0",
|
|
104
|
+
"@agentproto/adapter-mastracode-inprocess": "0.2.1",
|
|
105
|
+
"@agentproto/adapter-mastracode": "0.2.1",
|
|
106
|
+
"@agentproto/adapter-openclaw": "0.1.2",
|
|
107
|
+
"@agentproto/adapter-opencode": "0.1.2",
|
|
108
|
+
"@agentproto/tooling": "0.1.0-alpha.0",
|
|
109
|
+
"@agentproto/runtime": "0.5.0",
|
|
110
|
+
"@agentproto/agent-runtime": "0.1.0"
|
|
113
111
|
},
|
|
114
112
|
"engines": {
|
|
115
113
|
"node": ">=20.9.0"
|