@parlor/agent-host 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +16 -2
- package/dist/acp.d.ts +165 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +626 -0
- package/dist/acp.js.map +1 -0
- package/dist/adapter-launch.d.ts +7 -0
- package/dist/adapter-launch.d.ts.map +1 -0
- package/dist/adapter-launch.js +35 -0
- package/dist/adapter-launch.js.map +1 -0
- package/dist/agents.d.ts +32 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +132 -0
- package/dist/agents.js.map +1 -0
- package/dist/assertion.d.ts +9 -0
- package/dist/assertion.d.ts.map +1 -0
- package/dist/assertion.js +22 -0
- package/dist/assertion.js.map +1 -0
- package/dist/control.d.ts +106 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +32 -0
- package/dist/control.js.map +1 -0
- package/dist/daemon.d.ts +50 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +543 -0
- package/dist/daemon.js.map +1 -0
- package/dist/dev-host.d.ts +5 -0
- package/dist/dev-host.d.ts.map +1 -0
- package/dist/dev-host.js +29 -0
- package/dist/dev-host.js.map +1 -0
- package/dist/device-list.d.ts +6 -0
- package/dist/device-list.d.ts.map +1 -0
- package/dist/device-list.js +30 -0
- package/dist/device-list.js.map +1 -0
- package/dist/embedded.d.ts +6 -0
- package/dist/embedded.d.ts.map +1 -0
- package/dist/embedded.js +3 -0
- package/dist/embedded.js.map +1 -0
- package/dist/fold.d.ts +38 -0
- package/dist/fold.d.ts.map +1 -0
- package/dist/fold.js +165 -0
- package/dist/fold.js.map +1 -0
- package/dist/grants.d.ts +54 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +157 -0
- package/dist/grants.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/json-file.d.ts +6 -0
- package/dist/json-file.d.ts.map +1 -0
- package/dist/json-file.js +35 -0
- package/dist/json-file.js.map +1 -0
- package/dist/lines.d.ts +5 -0
- package/dist/lines.d.ts.map +1 -0
- package/dist/lines.js +9 -0
- package/dist/lines.js.map +1 -0
- package/dist/link-device.d.ts +28 -0
- package/dist/link-device.d.ts.map +1 -0
- package/dist/link-device.js +40 -0
- package/dist/link-device.js.map +1 -0
- package/dist/link.d.ts +52 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +277 -0
- package/dist/link.js.map +1 -0
- package/dist/lock.d.ts +7 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +105 -0
- package/dist/lock.js.map +1 -0
- package/dist/log.d.ts +3 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +18 -0
- package/dist/log.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +520 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-proxy.d.ts +33 -0
- package/dist/mcp-proxy.d.ts.map +1 -0
- package/dist/mcp-proxy.js +317 -0
- package/dist/mcp-proxy.js.map +1 -0
- package/dist/naming.d.ts +7 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +42 -0
- package/dist/naming.js.map +1 -0
- package/dist/pairing.d.ts +68 -0
- package/dist/pairing.d.ts.map +1 -0
- package/dist/pairing.js +312 -0
- package/dist/pairing.js.map +1 -0
- package/dist/paths.d.ts +19 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +23 -0
- package/dist/paths.js.map +1 -0
- package/dist/policy.d.ts +37 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +60 -0
- package/dist/policy.js.map +1 -0
- package/dist/pool.d.ts +34 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +112 -0
- package/dist/pool.js.map +1 -0
- package/dist/prompter.d.ts +33 -0
- package/dist/prompter.d.ts.map +1 -0
- package/dist/prompter.js +98 -0
- package/dist/prompter.js.map +1 -0
- package/dist/release-main.d.ts +2 -0
- package/dist/release-main.d.ts.map +1 -0
- package/dist/release-main.js +50 -0
- package/dist/release-main.js.map +1 -0
- package/dist/release.d.ts +22 -0
- package/dist/release.d.ts.map +1 -0
- package/dist/release.js +81 -0
- package/dist/release.js.map +1 -0
- package/dist/revoke.d.ts +10 -0
- package/dist/revoke.d.ts.map +1 -0
- package/dist/revoke.js +23 -0
- package/dist/revoke.js.map +1 -0
- package/dist/self.d.ts +7 -0
- package/dist/self.d.ts.map +1 -0
- package/dist/self.js +10 -0
- package/dist/self.js.map +1 -0
- package/dist/service.d.ts +97 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +205 -0
- package/dist/service.js.map +1 -0
- package/dist/shell-path.d.ts +10 -0
- package/dist/shell-path.d.ts.map +1 -0
- package/dist/shell-path.js +64 -0
- package/dist/shell-path.js.map +1 -0
- package/dist/skeleton-kit.d.ts +8 -0
- package/dist/skeleton-kit.d.ts.map +1 -0
- package/dist/skeleton-kit.js +143 -0
- package/dist/skeleton-kit.js.map +1 -0
- package/dist/spec-kit.d.ts +102 -0
- package/dist/spec-kit.d.ts.map +1 -0
- package/dist/spec-kit.js +372 -0
- package/dist/spec-kit.js.map +1 -0
- package/dist/testing/fake-agent.d.ts +65 -0
- package/dist/testing/fake-agent.d.ts.map +1 -0
- package/dist/testing/fake-agent.js +238 -0
- package/dist/testing/fake-agent.js.map +1 -0
- package/dist/trust.d.ts +20 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +27 -0
- package/dist/trust.js.map +1 -0
- package/dist/turn.d.ts +32 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +650 -0
- package/dist/turn.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -3
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { serveAcpAgent, } from "../acp.js";
|
|
4
|
+
const ALLOW = "allow";
|
|
5
|
+
const REJECT = "reject";
|
|
6
|
+
const PERMISSION_OPTIONS = [
|
|
7
|
+
{ id: ALLOW, label: "Allow", effect: "allow_once" },
|
|
8
|
+
{ id: REJECT, label: "Reject", effect: "reject_once" },
|
|
9
|
+
];
|
|
10
|
+
function sleep(ms, signal) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
if (signal.aborted)
|
|
13
|
+
return resolve();
|
|
14
|
+
const timer = setTimeout(resolve, ms);
|
|
15
|
+
signal.addEventListener("abort", () => {
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
resolve();
|
|
18
|
+
}, { once: true });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function stepsFor(script, text) {
|
|
22
|
+
const trimmed = text.trim();
|
|
23
|
+
const lastLine = trimmed.split("\n").at(-1)?.trim() ?? "";
|
|
24
|
+
const named = script.steps[trimmed] ?? script.steps[lastLine];
|
|
25
|
+
if (named)
|
|
26
|
+
return named;
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
kind: "reply",
|
|
30
|
+
chunks: [
|
|
31
|
+
script.fallback === "echo" ? text : `No step list is named "${lastLine}".`,
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
class McpStdioClient {
|
|
37
|
+
#child;
|
|
38
|
+
#pending = new Map();
|
|
39
|
+
#ready;
|
|
40
|
+
#nextId = 1;
|
|
41
|
+
constructor(server) {
|
|
42
|
+
this.#child = spawn(server.command, [...server.args], {
|
|
43
|
+
env: { ...process.env, ...server.env },
|
|
44
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
45
|
+
});
|
|
46
|
+
createInterface({ input: this.#child.stdout }).on("line", (line) => {
|
|
47
|
+
let message;
|
|
48
|
+
try {
|
|
49
|
+
message = JSON.parse(line);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (typeof message.id === "number")
|
|
55
|
+
this.#pending.get(message.id)?.(message);
|
|
56
|
+
});
|
|
57
|
+
this.#ready = this.request("initialize", {
|
|
58
|
+
protocolVersion: "2025-06-18",
|
|
59
|
+
capabilities: {},
|
|
60
|
+
clientInfo: { name: "fake-agent", version: "0.0.0" },
|
|
61
|
+
}).then(() => this.#send({ jsonrpc: "2.0", method: "notifications/initialized" }));
|
|
62
|
+
}
|
|
63
|
+
#send(message) {
|
|
64
|
+
this.#child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
65
|
+
}
|
|
66
|
+
request(method, params) {
|
|
67
|
+
const id = this.#nextId++;
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
this.#pending.set(id, (message) => {
|
|
70
|
+
this.#pending.delete(id);
|
|
71
|
+
resolve(message);
|
|
72
|
+
});
|
|
73
|
+
this.#send({ jsonrpc: "2.0", id, method, params });
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async callTool(name, input) {
|
|
77
|
+
await this.#ready;
|
|
78
|
+
return this.request("tools/call", { name, arguments: input });
|
|
79
|
+
}
|
|
80
|
+
close() {
|
|
81
|
+
this.#child.kill();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function resultText(message) {
|
|
85
|
+
const error = message.error;
|
|
86
|
+
if (error)
|
|
87
|
+
return { text: error.message ?? "error", failed: true };
|
|
88
|
+
const result = message.result;
|
|
89
|
+
const text = (result?.content ?? [])
|
|
90
|
+
.flatMap((content) => (content.type === "text" && content.text ? [content.text] : []))
|
|
91
|
+
.join("\n");
|
|
92
|
+
return { text, failed: result?.isError === true };
|
|
93
|
+
}
|
|
94
|
+
/** The fixture's entry point: serves `script` on `io` (the process's stdio). */
|
|
95
|
+
export function serveFakeAgent(script, io) {
|
|
96
|
+
const sessions = new Map();
|
|
97
|
+
const clients = new Map();
|
|
98
|
+
let tools = 0;
|
|
99
|
+
const permit = async (peer, toolCallId, title, step) => {
|
|
100
|
+
const answer = await peer.ask({
|
|
101
|
+
kind: "permission",
|
|
102
|
+
toolCallId,
|
|
103
|
+
title,
|
|
104
|
+
toolKind: step.toolKind,
|
|
105
|
+
mcp: step.mcp,
|
|
106
|
+
options: PERMISSION_OPTIONS,
|
|
107
|
+
});
|
|
108
|
+
return answer.kind === "selected" && answer.optionId === ALLOW;
|
|
109
|
+
};
|
|
110
|
+
const play = async (sessionId, step, peer) => {
|
|
111
|
+
switch (step.kind) {
|
|
112
|
+
case "reply":
|
|
113
|
+
for (const chunk of step.chunks) {
|
|
114
|
+
if (peer.cancelled.aborted)
|
|
115
|
+
return "cancelled";
|
|
116
|
+
await peer.update({ kind: "text", text: `[t:${Date.now()}]${chunk}` });
|
|
117
|
+
if (step.perSecond)
|
|
118
|
+
await sleep(1000 / step.perSecond, peer.cancelled);
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
case "thought":
|
|
122
|
+
await peer.update({ kind: "thought", text: step.text });
|
|
123
|
+
return undefined;
|
|
124
|
+
case "tool": {
|
|
125
|
+
const toolCallId = `tool-${++tools}`;
|
|
126
|
+
await peer.update({
|
|
127
|
+
kind: "tool_start",
|
|
128
|
+
toolCallId,
|
|
129
|
+
title: step.title,
|
|
130
|
+
toolKind: step.toolKind,
|
|
131
|
+
mcp: step.mcp,
|
|
132
|
+
});
|
|
133
|
+
const allowed = !step.askPermission ||
|
|
134
|
+
(await permit(peer, toolCallId, step.askTitle ?? step.title, step));
|
|
135
|
+
await peer.update({
|
|
136
|
+
kind: "tool_update",
|
|
137
|
+
toolCallId,
|
|
138
|
+
status: allowed ? "completed" : "failed",
|
|
139
|
+
...(allowed ? {} : { detail: "denied" }),
|
|
140
|
+
});
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
case "call_app": {
|
|
144
|
+
const server = sessions.get(sessionId)?.[0];
|
|
145
|
+
if (!server)
|
|
146
|
+
throw new Error("The host gave this session no MCP server.");
|
|
147
|
+
const toolCallId = `tool-${++tools}`;
|
|
148
|
+
const mcp = { server: server.name, tool: step.tool };
|
|
149
|
+
await peer.update({
|
|
150
|
+
kind: "tool_start",
|
|
151
|
+
toolCallId,
|
|
152
|
+
title: step.tool,
|
|
153
|
+
toolKind: "other",
|
|
154
|
+
mcp,
|
|
155
|
+
});
|
|
156
|
+
if (!(await permit(peer, toolCallId, step.tool, { toolKind: "other", mcp }))) {
|
|
157
|
+
await peer.update({
|
|
158
|
+
kind: "tool_update",
|
|
159
|
+
toolCallId,
|
|
160
|
+
status: "failed",
|
|
161
|
+
detail: "denied",
|
|
162
|
+
});
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
let client = clients.get(sessionId);
|
|
166
|
+
if (!client) {
|
|
167
|
+
client = new McpStdioClient(server);
|
|
168
|
+
clients.set(sessionId, client);
|
|
169
|
+
}
|
|
170
|
+
const { text, failed } = resultText(await client.callTool(step.tool, step.input));
|
|
171
|
+
await peer.update({
|
|
172
|
+
kind: "tool_update",
|
|
173
|
+
toolCallId,
|
|
174
|
+
status: failed ? "failed" : "completed",
|
|
175
|
+
detail: text,
|
|
176
|
+
});
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
case "plan":
|
|
180
|
+
await peer.update({
|
|
181
|
+
kind: "plan",
|
|
182
|
+
entries: step.entries.map((content) => ({ content, status: "pending" })),
|
|
183
|
+
});
|
|
184
|
+
return undefined;
|
|
185
|
+
case "question":
|
|
186
|
+
await peer.ask({ kind: "question", message: step.message, schema: step.schema });
|
|
187
|
+
return undefined;
|
|
188
|
+
case "wait_for_cancel":
|
|
189
|
+
await new Promise((resolve) => {
|
|
190
|
+
if (peer.cancelled.aborted)
|
|
191
|
+
return resolve();
|
|
192
|
+
peer.cancelled.addEventListener("abort", () => resolve(), { once: true });
|
|
193
|
+
});
|
|
194
|
+
return "cancelled";
|
|
195
|
+
case "exit":
|
|
196
|
+
return process.exit(step.code);
|
|
197
|
+
case "stop":
|
|
198
|
+
return step.reason;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const served = serveAcpAgent({
|
|
202
|
+
capabilities: {
|
|
203
|
+
resume: script.advertise?.resume === true,
|
|
204
|
+
close: script.advertise?.close === true,
|
|
205
|
+
mcpHttp: script.advertise?.mcpHttp === true,
|
|
206
|
+
},
|
|
207
|
+
newSession: async (input) => {
|
|
208
|
+
const id = `fake-session-${sessions.size + 1}`;
|
|
209
|
+
sessions.set(id, input.mcpServers);
|
|
210
|
+
return { id, modes: script.advertise?.modes };
|
|
211
|
+
},
|
|
212
|
+
prompt: async (sessionId, blocks, peer) => {
|
|
213
|
+
const text = blocks.map((block) => block.text).join("\n");
|
|
214
|
+
for (const step of stepsFor(script, text)) {
|
|
215
|
+
if (peer.cancelled.aborted)
|
|
216
|
+
return "cancelled";
|
|
217
|
+
const stop = await play(sessionId, step, peer);
|
|
218
|
+
if (stop)
|
|
219
|
+
return stop;
|
|
220
|
+
}
|
|
221
|
+
return peer.cancelled.aborted ? "cancelled" : "end_turn";
|
|
222
|
+
},
|
|
223
|
+
}, io);
|
|
224
|
+
return served.finally(() => {
|
|
225
|
+
for (const client of clients.values())
|
|
226
|
+
client.close();
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/** A profile that launches the fixture at `entry` with `scriptFile`. */
|
|
230
|
+
export function fakeAgentProfile(entry, scriptFile) {
|
|
231
|
+
return {
|
|
232
|
+
id: "fake",
|
|
233
|
+
name: "Fake agent",
|
|
234
|
+
launch: { kind: "native", command: "node", args: [entry, scriptFile] },
|
|
235
|
+
binary: "node",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=fake-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-agent.js","sourceRoot":"","sources":["../../src/testing/fake-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\n\nimport {\n type AcpStdio,\n type AgentAsk,\n type AgentAskAnswer,\n type AgentModes,\n type AgentUpdate,\n type PromptBlock,\n serveAcpAgent,\n type StdioMcpServer,\n type StopReason,\n type ToolKind,\n} from \"../acp.js\";\nimport type { AgentProfile } from \"../agents.js\";\n\n/**\n * A scripted agent on the real ACP transport (through `serveAcpAgent`), so the adapter,\n * fold, permission, cancel and MCP proxy paths are tested as Claude Code and Codex drive\n * them. A prompt's text names the step list to play: the whole text, or its last line when\n * the host put its context block first. Every reply chunk goes out prefixed with\n * `[t:<epoch ms>]`, the time it was written.\n */\nexport interface FakeAgentScript {\n readonly advertise?:\n | {\n readonly resume?: boolean | undefined;\n readonly close?: boolean | undefined;\n readonly mcpHttp?: boolean | undefined;\n readonly modes?: AgentModes | undefined;\n }\n | undefined;\n readonly steps: Readonly<Record<string, readonly FakeStep[]>>;\n /** For a prompt no step names: reply with it. */\n readonly fallback?: \"echo\" | undefined;\n}\n\nexport type FakeStep =\n | {\n readonly kind: \"reply\";\n readonly chunks: readonly string[];\n readonly perSecond?: number;\n }\n | { readonly kind: \"thought\"; readonly text: string }\n | {\n readonly kind: \"tool\";\n readonly title: string;\n readonly toolKind: ToolKind;\n readonly mcp?: { readonly server: string; readonly tool: string } | undefined;\n readonly askPermission: boolean;\n /** The permission request's own title, when it differs from the call's. */\n readonly askTitle?: string | undefined;\n }\n /** Calls an app tool through the MCP server the host passed in `session/new`. */\n | { readonly kind: \"call_app\"; readonly tool: string; readonly input: unknown }\n | { readonly kind: \"plan\"; readonly entries: readonly string[] }\n | {\n readonly kind: \"question\";\n readonly message: string;\n readonly schema: Readonly<Record<string, unknown>>;\n }\n | { readonly kind: \"wait_for_cancel\" }\n | { readonly kind: \"exit\"; readonly code: number }\n | { readonly kind: \"stop\"; readonly reason: Exclude<StopReason, \"cancelled\"> };\n\ninterface Peer {\n update(update: AgentUpdate): Promise<void>;\n ask(ask: AgentAsk): Promise<AgentAskAnswer>;\n readonly cancelled: AbortSignal;\n}\n\nconst ALLOW = \"allow\";\nconst REJECT = \"reject\";\n\nconst PERMISSION_OPTIONS = [\n { id: ALLOW, label: \"Allow\", effect: \"allow_once\" },\n { id: REJECT, label: \"Reject\", effect: \"reject_once\" },\n] as const;\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) return resolve();\n const timer = setTimeout(resolve, ms);\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nfunction stepsFor(script: FakeAgentScript, text: string): readonly FakeStep[] {\n const trimmed = text.trim();\n const lastLine = trimmed.split(\"\\n\").at(-1)?.trim() ?? \"\";\n const named = script.steps[trimmed] ?? script.steps[lastLine];\n if (named) return named;\n return [\n {\n kind: \"reply\",\n chunks: [\n script.fallback === \"echo\" ? text : `No step list is named \"${lastLine}\".`,\n ],\n },\n ];\n}\n\nclass McpStdioClient {\n readonly #child: ChildProcessWithoutNullStreams;\n readonly #pending = new Map<number, (message: Record<string, unknown>) => void>();\n readonly #ready: Promise<void>;\n #nextId = 1;\n\n constructor(server: StdioMcpServer) {\n this.#child = spawn(server.command, [...server.args], {\n env: { ...process.env, ...server.env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n createInterface({ input: this.#child.stdout }).on(\"line\", (line) => {\n let message: Record<string, unknown>;\n try {\n message = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return;\n }\n if (typeof message.id === \"number\") this.#pending.get(message.id)?.(message);\n });\n this.#ready = this.request(\"initialize\", {\n protocolVersion: \"2025-06-18\",\n capabilities: {},\n clientInfo: { name: \"fake-agent\", version: \"0.0.0\" },\n }).then(() => this.#send({ jsonrpc: \"2.0\", method: \"notifications/initialized\" }));\n }\n\n #send(message: unknown) {\n this.#child.stdin.write(`${JSON.stringify(message)}\\n`);\n }\n\n request(method: string, params: unknown): Promise<Record<string, unknown>> {\n const id = this.#nextId++;\n return new Promise((resolve) => {\n this.#pending.set(id, (message) => {\n this.#pending.delete(id);\n resolve(message);\n });\n this.#send({ jsonrpc: \"2.0\", id, method, params });\n });\n }\n\n async callTool(name: string, input: unknown): Promise<Record<string, unknown>> {\n await this.#ready;\n return this.request(\"tools/call\", { name, arguments: input });\n }\n\n close() {\n this.#child.kill();\n }\n}\n\nfunction resultText(message: Record<string, unknown>): { text: string; failed: boolean } {\n const error = message.error as { message?: string } | undefined;\n if (error) return { text: error.message ?? \"error\", failed: true };\n const result = message.result as\n | { content?: { type: string; text?: string }[]; isError?: boolean }\n | undefined;\n const text = (result?.content ?? [])\n .flatMap((content) => (content.type === \"text\" && content.text ? [content.text] : []))\n .join(\"\\n\");\n return { text, failed: result?.isError === true };\n}\n\n/** The fixture's entry point: serves `script` on `io` (the process's stdio). */\nexport function serveFakeAgent(script: FakeAgentScript, io: AcpStdio): Promise<void> {\n const sessions = new Map<string, readonly StdioMcpServer[]>();\n const clients = new Map<string, McpStdioClient>();\n let tools = 0;\n\n const permit = async (\n peer: Peer,\n toolCallId: string,\n title: string,\n step: {\n readonly toolKind: ToolKind;\n readonly mcp?: { readonly server: string; readonly tool: string } | undefined;\n },\n ) => {\n const answer = await peer.ask({\n kind: \"permission\",\n toolCallId,\n title,\n toolKind: step.toolKind,\n mcp: step.mcp,\n options: PERMISSION_OPTIONS,\n });\n return answer.kind === \"selected\" && answer.optionId === ALLOW;\n };\n\n const play = async (\n sessionId: string,\n step: FakeStep,\n peer: Peer,\n ): Promise<StopReason | undefined> => {\n switch (step.kind) {\n case \"reply\":\n for (const chunk of step.chunks) {\n if (peer.cancelled.aborted) return \"cancelled\";\n await peer.update({ kind: \"text\", text: `[t:${Date.now()}]${chunk}` });\n if (step.perSecond) await sleep(1000 / step.perSecond, peer.cancelled);\n }\n return undefined;\n case \"thought\":\n await peer.update({ kind: \"thought\", text: step.text });\n return undefined;\n case \"tool\": {\n const toolCallId = `tool-${++tools}`;\n await peer.update({\n kind: \"tool_start\",\n toolCallId,\n title: step.title,\n toolKind: step.toolKind,\n mcp: step.mcp,\n });\n const allowed =\n !step.askPermission ||\n (await permit(peer, toolCallId, step.askTitle ?? step.title, step));\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: allowed ? \"completed\" : \"failed\",\n ...(allowed ? {} : { detail: \"denied\" }),\n });\n return undefined;\n }\n case \"call_app\": {\n const server = sessions.get(sessionId)?.[0];\n if (!server) throw new Error(\"The host gave this session no MCP server.\");\n const toolCallId = `tool-${++tools}`;\n const mcp = { server: server.name, tool: step.tool };\n await peer.update({\n kind: \"tool_start\",\n toolCallId,\n title: step.tool,\n toolKind: \"other\",\n mcp,\n });\n if (!(await permit(peer, toolCallId, step.tool, { toolKind: \"other\", mcp }))) {\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: \"failed\",\n detail: \"denied\",\n });\n return undefined;\n }\n let client = clients.get(sessionId);\n if (!client) {\n client = new McpStdioClient(server);\n clients.set(sessionId, client);\n }\n const { text, failed } = resultText(await client.callTool(step.tool, step.input));\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: failed ? \"failed\" : \"completed\",\n detail: text,\n });\n return undefined;\n }\n case \"plan\":\n await peer.update({\n kind: \"plan\",\n entries: step.entries.map((content) => ({ content, status: \"pending\" })),\n });\n return undefined;\n case \"question\":\n await peer.ask({ kind: \"question\", message: step.message, schema: step.schema });\n return undefined;\n case \"wait_for_cancel\":\n await new Promise<void>((resolve) => {\n if (peer.cancelled.aborted) return resolve();\n peer.cancelled.addEventListener(\"abort\", () => resolve(), { once: true });\n });\n return \"cancelled\";\n case \"exit\":\n return process.exit(step.code);\n case \"stop\":\n return step.reason;\n }\n };\n\n const served = serveAcpAgent(\n {\n capabilities: {\n resume: script.advertise?.resume === true,\n close: script.advertise?.close === true,\n mcpHttp: script.advertise?.mcpHttp === true,\n },\n newSession: async (input) => {\n const id = `fake-session-${sessions.size + 1}`;\n sessions.set(id, input.mcpServers);\n return { id, modes: script.advertise?.modes };\n },\n prompt: async (sessionId, blocks: readonly PromptBlock[], peer) => {\n const text = blocks.map((block) => block.text).join(\"\\n\");\n for (const step of stepsFor(script, text)) {\n if (peer.cancelled.aborted) return \"cancelled\";\n const stop = await play(sessionId, step, peer);\n if (stop) return stop;\n }\n return peer.cancelled.aborted ? \"cancelled\" : \"end_turn\";\n },\n },\n io,\n );\n return served.finally(() => {\n for (const client of clients.values()) client.close();\n });\n}\n\n/** A profile that launches the fixture at `entry` with `scriptFile`. */\nexport function fakeAgentProfile(entry: string, scriptFile: string): AgentProfile {\n return {\n id: \"fake\",\n name: \"Fake agent\",\n launch: { kind: \"native\", command: \"node\", args: [entry, scriptFile] },\n binary: \"node\",\n };\n}\n"]}
|
package/dist/trust.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PinnedDevice } from "@parlor/auth";
|
|
2
|
+
export interface TrustedDevice extends PinnedDevice {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly platform: string;
|
|
5
|
+
readonly approvedAt: number;
|
|
6
|
+
/** `pairing`: it approved `connect` here; `local`: linked on this computer; `vouched`: a linked device vouched for it. */
|
|
7
|
+
readonly via: "pairing" | "local" | "vouched";
|
|
8
|
+
/** The linked device whose vouch pinned it. */
|
|
9
|
+
readonly voucher?: string | undefined;
|
|
10
|
+
/** When it last signed here with Face ID or Touch ID; `approvedAt` until it does. */
|
|
11
|
+
readonly presenceAt?: number | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface TrustStore {
|
|
14
|
+
get(appOrigin: string, deviceId: string): Promise<TrustedDevice | undefined>;
|
|
15
|
+
list(appOrigin: string): Promise<readonly TrustedDevice[]>;
|
|
16
|
+
pin(appOrigin: string, device: TrustedDevice): Promise<void>;
|
|
17
|
+
forget(appOrigin: string, deviceId?: string): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function fileTrustStore(file: string): TrustStore;
|
|
20
|
+
//# sourceMappingURL=trust.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust.d.ts","sourceRoot":"","sources":["../src/trust.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0HAA0H;IAC1H,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,qFAAqF;IACrF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IAC7E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;IAC3D,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAID,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAwBvD"}
|
package/dist/trust.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsonFile } from "./json-file.js";
|
|
2
|
+
export function fileTrustStore(file) {
|
|
3
|
+
const pins = jsonFile(file);
|
|
4
|
+
return {
|
|
5
|
+
get: async (appOrigin, deviceId) => {
|
|
6
|
+
const devices = (await pins.read())[appOrigin];
|
|
7
|
+
return devices && Object.hasOwn(devices, deviceId) ? devices[deviceId] : undefined;
|
|
8
|
+
},
|
|
9
|
+
list: async (appOrigin) => Object.values((await pins.read())[appOrigin] ?? {}),
|
|
10
|
+
pin: (appOrigin, device) => pins.change((all) => {
|
|
11
|
+
(all[appOrigin] ??= {})[device.deviceId] = device;
|
|
12
|
+
}),
|
|
13
|
+
forget: (appOrigin, deviceId) => pins.change((all) => {
|
|
14
|
+
if (deviceId === undefined) {
|
|
15
|
+
delete all[appOrigin];
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const origin = all[appOrigin];
|
|
19
|
+
if (!origin)
|
|
20
|
+
return;
|
|
21
|
+
delete origin[deviceId];
|
|
22
|
+
if (Object.keys(origin).length === 0)
|
|
23
|
+
delete all[appOrigin];
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=trust.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust.js","sourceRoot":"","sources":["../src/trust.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAuB1C,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAO,IAAI,CAAC,CAAC;IAClC,OAAO;QACL,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE;YACjC,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;YAC/C,OAAO,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrF,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAC9E,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CACzB,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;YAClB,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QACpD,CAAC,CAAC;QACJ,MAAM,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,CAC9B,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;YAClB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC;gBACtB,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9D,CAAC,CAAC;KACL,CAAC;AACJ,CAAC","sourcesContent":["import type { PinnedDevice } from \"@parlor/auth\";\n\nimport { jsonFile } from \"./json-file.js\";\n\nexport interface TrustedDevice extends PinnedDevice {\n readonly name: string;\n readonly platform: string;\n readonly approvedAt: number;\n /** `pairing`: it approved `connect` here; `local`: linked on this computer; `vouched`: a linked device vouched for it. */\n readonly via: \"pairing\" | \"local\" | \"vouched\";\n /** The linked device whose vouch pinned it. */\n readonly voucher?: string | undefined;\n /** When it last signed here with Face ID or Touch ID; `approvedAt` until it does. */\n readonly presenceAt?: number | undefined;\n}\n\nexport interface TrustStore {\n get(appOrigin: string, deviceId: string): Promise<TrustedDevice | undefined>;\n list(appOrigin: string): Promise<readonly TrustedDevice[]>;\n pin(appOrigin: string, device: TrustedDevice): Promise<void>;\n forget(appOrigin: string, deviceId?: string): Promise<void>;\n}\n\ntype Pins = Record<string, Record<string, TrustedDevice>>;\n\nexport function fileTrustStore(file: string): TrustStore {\n const pins = jsonFile<Pins>(file);\n return {\n get: async (appOrigin, deviceId) => {\n const devices = (await pins.read())[appOrigin];\n return devices && Object.hasOwn(devices, deviceId) ? devices[deviceId] : undefined;\n },\n list: async (appOrigin) => Object.values((await pins.read())[appOrigin] ?? {}),\n pin: (appOrigin, device) =>\n pins.change((all) => {\n (all[appOrigin] ??= {})[device.deviceId] = device;\n }),\n forget: (appOrigin, deviceId) =>\n pins.change((all) => {\n if (deviceId === undefined) {\n delete all[appOrigin];\n return;\n }\n const origin = all[appOrigin];\n if (!origin) return;\n delete origin[deviceId];\n if (Object.keys(origin).length === 0) delete all[appOrigin];\n }),\n };\n}\n"]}
|
package/dist/turn.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ClaimedTurn, TurnOutcome } from "@parlor/chat";
|
|
2
|
+
import { type AgentLauncher } from "./acp.js";
|
|
3
|
+
import type { AgentProfile } from "./agents.js";
|
|
4
|
+
import type { AppLink } from "./link.js";
|
|
5
|
+
import type { McpProxy } from "./mcp-proxy.js";
|
|
6
|
+
import { type RemotePolicy } from "./policy.js";
|
|
7
|
+
import type { SessionPool } from "./pool.js";
|
|
8
|
+
import type { LocalPrompter } from "./prompter.js";
|
|
9
|
+
import type { TrustStore } from "./trust.js";
|
|
10
|
+
export interface TurnDeps {
|
|
11
|
+
readonly link: AppLink;
|
|
12
|
+
readonly profiles: readonly AgentProfile[];
|
|
13
|
+
readonly launcher: AgentLauncher;
|
|
14
|
+
readonly pool: SessionPool;
|
|
15
|
+
readonly proxy: McpProxy;
|
|
16
|
+
readonly trust: TrustStore;
|
|
17
|
+
readonly policy: RemotePolicy;
|
|
18
|
+
readonly prompter: LocalPrompter;
|
|
19
|
+
readonly now: () => number;
|
|
20
|
+
/** The secret the browser that approved this grant's `connect` proves, until it is used. */
|
|
21
|
+
readonly pairing?: {
|
|
22
|
+
secret(): Promise<string | undefined>;
|
|
23
|
+
forget(): Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare const PROMPT_MAX_AGE_MS: number;
|
|
27
|
+
/**
|
|
28
|
+
* Runs one claimed turn to its outcome and reports it with `hostFinish`: where the
|
|
29
|
+
* claimed prompt, the agent's updates, the user's answers and stop, and the local policy meet.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runTurn(claimed: ClaimedTurn, deps: TurnDeps): Promise<TurnOutcome>;
|
|
32
|
+
//# sourceMappingURL=turn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAGV,WAAW,EAIX,WAAW,EAEZ,MAAM,cAAc,CAAC;AAItB,OAAO,EAKL,KAAK,aAAa,EAInB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAyC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACtC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;KACzB,CAAC;CACH;AAED,eAAO,MAAM,iBAAiB,QAAc,CAAC;AA0G7C;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,WAAW,CAAC,CAuoBtB"}
|