@rivus/agent 0.16.2 → 0.16.6
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 +0 -1
- package/README.md +3 -1079
- package/dist/acp.d.ts +1 -143
- package/dist/acp.js +1 -767
- package/dist/bootstrap/pi-feishu.d.ts +1 -19
- package/dist/bootstrap/pi-feishu.js +1 -4340
- package/dist/chunks/rivus-plugin-testkit.d.ts +2 -189
- package/dist/chunks/rivus-plugin-testkit.js +4 -19
- package/dist/cli.js +12 -1234
- package/dist/index.d.ts +12 -7
- package/dist/index.js +4 -7
- package/dist/mcp.d.ts +1 -91
- package/dist/mcp.js +2 -450
- package/dist/pi.d.ts +1 -128
- package/dist/pi.js +1 -2
- package/dist/testing/index.d.ts +1 -1
- package/examples/pi-feishu-deployment.bootstrap.ts +3 -894
- package/package.json +50 -87
- package/dist/chunks/agent-loop.d.ts +0 -446
- package/dist/chunks/agent-loop.js +0 -158
- package/dist/chunks/background-session-authority.js +0 -230
- package/dist/chunks/background-session-control-input.js +0 -51
- package/dist/chunks/background-session-service.d.ts +0 -382
- package/dist/chunks/index.d.ts +0 -4448
- package/dist/chunks/pi-tool-proxy.d.ts +0 -120
- package/dist/chunks/pi.js +0 -406
- package/dist/chunks/rivus-agent-definition-resolver.js +0 -508
- package/dist/chunks/rivus-daemon-cli.js +0 -3839
- package/dist/chunks/rivus-model-management-wire.js +0 -344
- package/dist/chunks/rivus-skill.d.ts +0 -95
- package/dist/chunks/rivus-tool.js +0 -158
- package/dist/chunks/sha256-digest.js +0 -7
- package/dist/chunks/src.js +0 -15313
package/dist/acp.js
CHANGED
|
@@ -1,768 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as acp from "@agentclientprotocol/sdk";
|
|
3
|
-
import { Effect, Stream } from "effect";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
-
import { Readable, Writable } from "node:stream";
|
|
6
|
-
import { spawn } from "node:child_process";
|
|
7
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
8
|
-
import { dirname } from "node:path";
|
|
9
|
-
//#region src/adapters/acp/runtime/acp-agent-loop.ts
|
|
10
|
-
function createAcpAgentLoop$1(options) {
|
|
11
|
-
return {
|
|
12
|
-
run: (input) => Stream.fromAsyncIterable(runAcpSession(input, options), (error) => error),
|
|
13
|
-
supportsSteering: true
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
async function* runAcpSession(input, options) {
|
|
17
|
-
const session = await options.resolveSession(input);
|
|
18
|
-
const updates = [];
|
|
19
|
-
const tools = /* @__PURE__ */ new Map();
|
|
20
|
-
let wake;
|
|
21
|
-
let abortCancellation;
|
|
22
|
-
let abortFailure;
|
|
23
|
-
let abortSettled = false;
|
|
24
|
-
let aborted = false;
|
|
25
|
-
let currentTurn = startAcpTurn(input.text, session, input, updates, tools, () => {
|
|
26
|
-
wake?.();
|
|
27
|
-
wake = void 0;
|
|
28
|
-
});
|
|
29
|
-
const onAbort = () => {
|
|
30
|
-
aborted = true;
|
|
31
|
-
abortCancellation ??= settleCancelledTurn(session, currentTurn.promise, options.cancellationSettleTimeoutMs ?? 5e3);
|
|
32
|
-
abortCancellation.then(() => {
|
|
33
|
-
abortSettled = true;
|
|
34
|
-
wake?.();
|
|
35
|
-
wake = void 0;
|
|
36
|
-
}, (error) => {
|
|
37
|
-
abortFailure = error;
|
|
38
|
-
wake?.();
|
|
39
|
-
wake = void 0;
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
input.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
43
|
-
const takeSteering = () => input.steering ? Effect.runPromise(input.steering.next()) : void 0;
|
|
44
|
-
let steering = takeSteering();
|
|
45
|
-
try {
|
|
46
|
-
if (input.abortSignal.aborted) onAbort();
|
|
47
|
-
while (!currentTurn.completed || updates.length > 0) {
|
|
48
|
-
if (abortFailure !== void 0) throw abortFailure;
|
|
49
|
-
if (aborted && abortSettled) break;
|
|
50
|
-
const event = updates.shift();
|
|
51
|
-
if (event) yield event;
|
|
52
|
-
else {
|
|
53
|
-
const signal = await Promise.race([
|
|
54
|
-
new Promise((resolve) => {
|
|
55
|
-
wake = () => resolve({ type: "wake" });
|
|
56
|
-
}),
|
|
57
|
-
...steering ? [steering.then((text) => ({
|
|
58
|
-
text,
|
|
59
|
-
type: "steer"
|
|
60
|
-
}))] : [],
|
|
61
|
-
currentTurn.promise.then(() => ({ type: "turn_completed" }), () => ({ type: "turn_completed" }))
|
|
62
|
-
]);
|
|
63
|
-
if (signal.type === "steer") {
|
|
64
|
-
await Promise.resolve(session.cancel({ preserveSession: true }));
|
|
65
|
-
await currentTurn.promise;
|
|
66
|
-
updates.push(createAgentLoopTurnStart());
|
|
67
|
-
currentTurn = startAcpTurn(createSteeringTurnPrompt(signal.text), session, input, updates, tools, () => {
|
|
68
|
-
wake?.();
|
|
69
|
-
wake = void 0;
|
|
70
|
-
});
|
|
71
|
-
steering = takeSteering();
|
|
72
|
-
}
|
|
73
|
-
if (signal.type === "turn_completed" && currentTurn.completed) break;
|
|
74
|
-
if (signal.type === "wake") wake = void 0;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (!aborted) {
|
|
78
|
-
await currentTurn.promise;
|
|
79
|
-
if (currentTurn.failure !== void 0) throw currentTurn.failure;
|
|
80
|
-
}
|
|
81
|
-
} finally {
|
|
82
|
-
input.abortSignal.removeEventListener("abort", onAbort);
|
|
83
|
-
if (abortCancellation) await abortCancellation;
|
|
84
|
-
if (options.disposeSessionAfterRun !== false) await session.dispose?.();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function settleCancelledTurn(session, currentTurn, timeoutMs) {
|
|
88
|
-
await Promise.resolve(session.cancel({ preserveSession: false }));
|
|
89
|
-
if (await settlesWithin(currentTurn, timeoutMs)) return;
|
|
90
|
-
if (session.invalidate) await session.invalidate();
|
|
91
|
-
else await session.dispose?.();
|
|
92
|
-
}
|
|
93
|
-
async function settlesWithin(promise, timeoutMs) {
|
|
94
|
-
let timeout;
|
|
95
|
-
try {
|
|
96
|
-
return await Promise.race([promise.then(() => true), new Promise((resolve) => {
|
|
97
|
-
timeout = setTimeout(() => resolve(false), timeoutMs);
|
|
98
|
-
})]);
|
|
99
|
-
} finally {
|
|
100
|
-
if (timeout) clearTimeout(timeout);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function createSteeringTurnPrompt(text) {
|
|
104
|
-
return [
|
|
105
|
-
"用户刚刚在当前任务执行期间发送了一条 steering 消息。",
|
|
106
|
-
"请先直接回答这条消息;如果它是关于当前任务的进度或概念问题,先给出清晰解释。",
|
|
107
|
-
"只有消息明确要求改变任务时才执行动作;不要把用户自然语言当作 shell 命令。",
|
|
108
|
-
"回答后基于当前会话已有状态继续任务;不得重放最初指令或重复已经完成的步骤。",
|
|
109
|
-
"用户消息:",
|
|
110
|
-
text
|
|
111
|
-
].join("\n");
|
|
112
|
-
}
|
|
113
|
-
function startAcpTurn(text, session, input, updates, tools, notify) {
|
|
114
|
-
let completed = false;
|
|
115
|
-
let failure;
|
|
116
|
-
return {
|
|
117
|
-
promise: session.prompt(text, (update) => {
|
|
118
|
-
updates.push(...mapAcpSessionUpdate(update, tools));
|
|
119
|
-
notify();
|
|
120
|
-
}).then(() => void 0).catch((error) => {
|
|
121
|
-
if (!input.abortSignal.aborted) failure = error;
|
|
122
|
-
}).finally(() => {
|
|
123
|
-
completed = true;
|
|
124
|
-
notify();
|
|
125
|
-
}),
|
|
126
|
-
get completed() {
|
|
127
|
-
return completed;
|
|
128
|
-
},
|
|
129
|
-
get failure() {
|
|
130
|
-
return failure;
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
function mapAcpSessionUpdate(update, tools) {
|
|
135
|
-
if (update.sessionUpdate === "agent_message_chunk") {
|
|
136
|
-
const text = readTextContent(update.content);
|
|
137
|
-
return text === void 0 ? [] : [createAgentLoopTextDelta(text)];
|
|
138
|
-
}
|
|
139
|
-
if (update.sessionUpdate === "agent_thought_chunk") {
|
|
140
|
-
const text = readTextContent(update.content);
|
|
141
|
-
return text === void 0 ? [] : [createAgentLoopThinkingDelta(text)];
|
|
142
|
-
}
|
|
143
|
-
if (update.sessionUpdate === "tool_call") {
|
|
144
|
-
if (!update.toolCallId || !update.title) return [];
|
|
145
|
-
const name = update.name?.trim() || update.title;
|
|
146
|
-
const state = {
|
|
147
|
-
input: update.rawInput,
|
|
148
|
-
name
|
|
149
|
-
};
|
|
150
|
-
tools.set(update.toolCallId, state);
|
|
151
|
-
const start = createAgentLoopToolExecutionStart({
|
|
152
|
-
input: state.input,
|
|
153
|
-
toolCallId: update.toolCallId,
|
|
154
|
-
toolName: state.name
|
|
155
|
-
});
|
|
156
|
-
if (update.status === "completed" || update.status === "failed") {
|
|
157
|
-
tools.delete(update.toolCallId);
|
|
158
|
-
return [start, createAgentLoopToolExecutionEnd({
|
|
159
|
-
isError: update.status === "failed",
|
|
160
|
-
result: update.rawOutput,
|
|
161
|
-
toolCallId: update.toolCallId,
|
|
162
|
-
toolName: state.name
|
|
163
|
-
})];
|
|
164
|
-
}
|
|
165
|
-
return [start];
|
|
166
|
-
}
|
|
167
|
-
if (update.sessionUpdate !== "tool_call_update") return [];
|
|
168
|
-
if (!update.toolCallId) return [];
|
|
169
|
-
const prior = tools.get(update.toolCallId);
|
|
170
|
-
const state = {
|
|
171
|
-
input: update.rawInput ?? prior?.input,
|
|
172
|
-
name: update.name?.trim() || update.title?.trim() || prior?.name || update.toolCallId
|
|
173
|
-
};
|
|
174
|
-
tools.set(update.toolCallId, state);
|
|
175
|
-
if (update.status === "completed" || update.status === "failed") {
|
|
176
|
-
tools.delete(update.toolCallId);
|
|
177
|
-
return [createAgentLoopToolExecutionEnd({
|
|
178
|
-
isError: update.status === "failed",
|
|
179
|
-
result: update.rawOutput,
|
|
180
|
-
toolCallId: update.toolCallId,
|
|
181
|
-
toolName: state.name
|
|
182
|
-
})];
|
|
183
|
-
}
|
|
184
|
-
return [createAgentLoopToolExecutionUpdate({
|
|
185
|
-
input: state.input,
|
|
186
|
-
partialResult: update.rawOutput,
|
|
187
|
-
toolCallId: update.toolCallId,
|
|
188
|
-
toolName: state.name
|
|
189
|
-
})];
|
|
190
|
-
}
|
|
191
|
-
function readTextContent(content) {
|
|
192
|
-
if (!isRecord$1(content) || content.type !== "text" || typeof content.text !== "string") return void 0;
|
|
193
|
-
return content.text;
|
|
194
|
-
}
|
|
195
|
-
function isRecord$1(value) {
|
|
196
|
-
return typeof value === "object" && value !== null;
|
|
197
|
-
}
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region src/adapters/acp/runtime/acp-agent-server.ts
|
|
200
|
-
function createAcpAgentServer$1(options) {
|
|
201
|
-
const sessions = /* @__PURE__ */ new Set();
|
|
202
|
-
const active = /* @__PURE__ */ new Map();
|
|
203
|
-
return acp.agent({ name: options.agentName ?? "rivus" }).onRequest(acp.methods.agent.initialize, ({ params }) => ({
|
|
204
|
-
agentCapabilities: {},
|
|
205
|
-
agentInfo: {
|
|
206
|
-
name: options.agentName ?? "rivus",
|
|
207
|
-
version: "1"
|
|
208
|
-
},
|
|
209
|
-
protocolVersion: params.protocolVersion === acp.PROTOCOL_VERSION ? params.protocolVersion : acp.PROTOCOL_VERSION
|
|
210
|
-
})).onRequest(acp.methods.agent.session.new, () => {
|
|
211
|
-
const sessionId = randomUUID();
|
|
212
|
-
sessions.add(sessionId);
|
|
213
|
-
return { sessionId };
|
|
214
|
-
}).onRequest(acp.methods.agent.session.prompt, async ({ client, params }) => {
|
|
215
|
-
if (!sessions.has(params.sessionId)) throw new Error(`Unknown ACP session: ${params.sessionId}`);
|
|
216
|
-
if (active.has(params.sessionId)) throw new Error(`ACP session is already active: ${params.sessionId}`);
|
|
217
|
-
const controller = new AbortController();
|
|
218
|
-
active.set(params.sessionId, controller);
|
|
219
|
-
const sessionKey = `acp:${params.sessionId}`;
|
|
220
|
-
const releasePermissionBridge = options.permissionBridge?.bind(sessionKey, async (request) => {
|
|
221
|
-
const response = await client.request(acp.methods.client.session.requestPermission, {
|
|
222
|
-
options: request.options.map((option) => ({ ...option })),
|
|
223
|
-
sessionId: params.sessionId,
|
|
224
|
-
toolCall: {
|
|
225
|
-
...request.toolCall.rawInput === void 0 ? {} : { rawInput: request.toolCall.rawInput },
|
|
226
|
-
...request.toolCall.title === void 0 ? {} : { title: request.toolCall.title },
|
|
227
|
-
toolCallId: request.toolCall.toolCallId
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
return response.outcome.outcome === "selected" ? { optionId: response.outcome.optionId } : void 0;
|
|
231
|
-
});
|
|
232
|
-
try {
|
|
233
|
-
await Effect.runPromise(options.loop.run({
|
|
234
|
-
abortSignal: controller.signal,
|
|
235
|
-
runId: randomUUID(),
|
|
236
|
-
sessionKey,
|
|
237
|
-
text: readPromptText(params.prompt)
|
|
238
|
-
}).pipe(Stream.runForEach((event) => Effect.tryPromise({
|
|
239
|
-
catch: (cause) => cause,
|
|
240
|
-
try: async () => {
|
|
241
|
-
const update = mapAgentLoopEvent(event);
|
|
242
|
-
if (update) await client.notify(acp.methods.client.session.update, {
|
|
243
|
-
sessionId: params.sessionId,
|
|
244
|
-
update
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}))));
|
|
248
|
-
return { stopReason: controller.signal.aborted ? "cancelled" : "end_turn" };
|
|
249
|
-
} finally {
|
|
250
|
-
releasePermissionBridge?.();
|
|
251
|
-
active.delete(params.sessionId);
|
|
252
|
-
}
|
|
253
|
-
}).onNotification(acp.methods.agent.session.cancel, ({ params }) => {
|
|
254
|
-
active.get(params.sessionId)?.abort();
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
async function serveAcpAgentOnStdio$1(app) {
|
|
258
|
-
const stream = acp.ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
|
|
259
|
-
await app.connect(stream).closed;
|
|
260
|
-
}
|
|
261
|
-
function readPromptText(prompt) {
|
|
262
|
-
const parts = prompt.flatMap((content) => content.type === "text" ? [content.text] : []);
|
|
263
|
-
if (parts.length !== prompt.length) throw new Error("Rivus ACP facade currently accepts text prompt content only");
|
|
264
|
-
return parts.join("\n");
|
|
265
|
-
}
|
|
266
|
-
function mapAgentLoopEvent(event) {
|
|
267
|
-
if (event.type === "assistant_text_delta") return {
|
|
268
|
-
content: {
|
|
269
|
-
text: event.delta,
|
|
270
|
-
type: "text"
|
|
271
|
-
},
|
|
272
|
-
sessionUpdate: "agent_message_chunk"
|
|
273
|
-
};
|
|
274
|
-
if (event.type === "assistant_thinking_delta") return {
|
|
275
|
-
content: {
|
|
276
|
-
text: event.delta,
|
|
277
|
-
type: "text"
|
|
278
|
-
},
|
|
279
|
-
sessionUpdate: "agent_thought_chunk"
|
|
280
|
-
};
|
|
281
|
-
if (event.type === "tool_execution_start") return {
|
|
282
|
-
name: event.toolName,
|
|
283
|
-
rawInput: event.input,
|
|
284
|
-
sessionUpdate: "tool_call",
|
|
285
|
-
status: "in_progress",
|
|
286
|
-
title: event.toolName,
|
|
287
|
-
toolCallId: event.toolCallId
|
|
288
|
-
};
|
|
289
|
-
if (event.type === "tool_execution_update") return {
|
|
290
|
-
name: event.toolName,
|
|
291
|
-
rawInput: event.input,
|
|
292
|
-
rawOutput: event.partialResult,
|
|
293
|
-
sessionUpdate: "tool_call_update",
|
|
294
|
-
status: "in_progress",
|
|
295
|
-
title: event.toolName,
|
|
296
|
-
toolCallId: event.toolCallId
|
|
297
|
-
};
|
|
298
|
-
if (event.type === "tool_execution_end") return {
|
|
299
|
-
name: event.toolName,
|
|
300
|
-
rawOutput: event.result,
|
|
301
|
-
sessionUpdate: "tool_call_update",
|
|
302
|
-
status: event.isError ? "failed" : "completed",
|
|
303
|
-
title: event.toolName,
|
|
304
|
-
toolCallId: event.toolCallId
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
//#endregion
|
|
308
|
-
//#region src/adapters/acp/runtime/acp-permission-policy.ts
|
|
309
|
-
async function decideAcpPermission(request, policy) {
|
|
310
|
-
const selection = await policy?.(request);
|
|
311
|
-
if (!selection || !request.options.some((option) => option.optionId === selection.optionId)) return { outcome: "cancelled" };
|
|
312
|
-
return {
|
|
313
|
-
optionId: selection.optionId,
|
|
314
|
-
outcome: "selected"
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
//#endregion
|
|
318
|
-
//#region src/adapters/acp/runtime/acp-stdio-agent-loop.ts
|
|
319
|
-
async function buildAcpSession(processConnection, options, input) {
|
|
320
|
-
const mcpServers = createMcpServers(options, input);
|
|
321
|
-
const persisted = await options.sessionStore?.load(input.sessionKey);
|
|
322
|
-
if (persisted) return restoreAcpSession(processConnection, options, persisted.sessionId, mcpServers);
|
|
323
|
-
const session = await processConnection.connection.agent.buildSession({
|
|
324
|
-
cwd: options.workingDirectory,
|
|
325
|
-
mcpServers
|
|
326
|
-
}).start();
|
|
327
|
-
await options.sessionStore?.save(input.sessionKey, { sessionId: session.sessionId });
|
|
328
|
-
return session;
|
|
329
|
-
}
|
|
330
|
-
function createMcpServers(options, input) {
|
|
331
|
-
return (options.mcpServers?.(input) ?? []).map((server) => ({
|
|
332
|
-
args: [...server.args],
|
|
333
|
-
command: server.command,
|
|
334
|
-
env: server.env.map(({ name, value }) => ({
|
|
335
|
-
name,
|
|
336
|
-
value
|
|
337
|
-
})),
|
|
338
|
-
name: server.name,
|
|
339
|
-
type: "stdio"
|
|
340
|
-
}));
|
|
341
|
-
}
|
|
342
|
-
async function restoreAcpSession(processConnection, options, sessionId, mcpServers) {
|
|
343
|
-
const session = new ResumedAcpSession(processConnection, sessionId);
|
|
344
|
-
try {
|
|
345
|
-
if (processConnection.agentCapabilities?.loadSession) await processConnection.connection.agent.request(acp.methods.agent.session.load, {
|
|
346
|
-
cwd: options.workingDirectory,
|
|
347
|
-
mcpServers,
|
|
348
|
-
sessionId
|
|
349
|
-
});
|
|
350
|
-
else if (processConnection.agentCapabilities?.sessionCapabilities?.resume) await processConnection.connection.agent.request(acp.methods.agent.session.resume, {
|
|
351
|
-
cwd: options.workingDirectory,
|
|
352
|
-
mcpServers,
|
|
353
|
-
sessionId
|
|
354
|
-
});
|
|
355
|
-
else throw new AcpSessionResumeUnavailable(sessionId);
|
|
356
|
-
session.clearReplay();
|
|
357
|
-
return session;
|
|
358
|
-
} catch (error) {
|
|
359
|
-
session.dispose();
|
|
360
|
-
throw new AcpSessionResumeFailed(sessionId, error);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
function createAcpStdioAgentLoop$1(options) {
|
|
364
|
-
let pendingConnection;
|
|
365
|
-
let activeConnection;
|
|
366
|
-
let ownedChild;
|
|
367
|
-
const sessionKeys = /* @__PURE__ */ new Map();
|
|
368
|
-
const resolveConnection = () => {
|
|
369
|
-
if (pendingConnection) return pendingConnection;
|
|
370
|
-
let current;
|
|
371
|
-
current = openConnection(options, sessionKeys, (child) => {
|
|
372
|
-
ownedChild = child;
|
|
373
|
-
});
|
|
374
|
-
pendingConnection = current;
|
|
375
|
-
current.then((processConnection) => {
|
|
376
|
-
if (pendingConnection !== current) {
|
|
377
|
-
processConnection.connection.close();
|
|
378
|
-
terminateChild(processConnection.child, options.terminationTimeoutMs);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
activeConnection = processConnection;
|
|
382
|
-
processConnection.connection.closed.finally(() => {
|
|
383
|
-
if (pendingConnection !== current) return;
|
|
384
|
-
pendingConnection = void 0;
|
|
385
|
-
activeConnection = void 0;
|
|
386
|
-
if (ownedChild === processConnection.child) ownedChild = void 0;
|
|
387
|
-
sessionKeys.clear();
|
|
388
|
-
for (const session of processConnection.sessions.values()) session.dispose();
|
|
389
|
-
processConnection.sessions.clear();
|
|
390
|
-
processConnection.sessionUpdateHandlers.clear();
|
|
391
|
-
terminateChild(processConnection.child, options.terminationTimeoutMs);
|
|
392
|
-
});
|
|
393
|
-
}, () => {
|
|
394
|
-
if (pendingConnection === current) {
|
|
395
|
-
pendingConnection = void 0;
|
|
396
|
-
activeConnection = void 0;
|
|
397
|
-
ownedChild = void 0;
|
|
398
|
-
sessionKeys.clear();
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
return current;
|
|
402
|
-
};
|
|
403
|
-
return {
|
|
404
|
-
dispose: async () => {
|
|
405
|
-
const current = pendingConnection;
|
|
406
|
-
pendingConnection = void 0;
|
|
407
|
-
const processConnection = activeConnection;
|
|
408
|
-
activeConnection = void 0;
|
|
409
|
-
if (processConnection) {
|
|
410
|
-
for (const session of processConnection.sessions.values()) session.dispose();
|
|
411
|
-
processConnection.sessions.clear();
|
|
412
|
-
processConnection.sessionUpdateHandlers.clear();
|
|
413
|
-
processConnection.connection.close();
|
|
414
|
-
}
|
|
415
|
-
sessionKeys.clear();
|
|
416
|
-
const child = ownedChild;
|
|
417
|
-
ownedChild = void 0;
|
|
418
|
-
if (child) await terminateChild(child, options.terminationTimeoutMs);
|
|
419
|
-
await current?.catch(() => void 0);
|
|
420
|
-
},
|
|
421
|
-
loop: createAcpAgentLoop$1({
|
|
422
|
-
disposeSessionAfterRun: false,
|
|
423
|
-
resolveSession: async (input) => {
|
|
424
|
-
const processConnection = await resolveConnection();
|
|
425
|
-
const current = processConnection.sessions.get(input.sessionKey);
|
|
426
|
-
if (current?.isReusable) return current;
|
|
427
|
-
if (current) {
|
|
428
|
-
current.dispose();
|
|
429
|
-
processConnection.sessions.delete(input.sessionKey);
|
|
430
|
-
}
|
|
431
|
-
const session = new SdkAcpAgentSession(await buildAcpSession(processConnection, options, input), processConnection.connection.agent, input.sessionKey, options.sessionStore);
|
|
432
|
-
sessionKeys.set(session.sessionId, input.sessionKey);
|
|
433
|
-
processConnection.sessions.set(input.sessionKey, session);
|
|
434
|
-
return session;
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
var SdkAcpAgentSession = class {
|
|
440
|
-
session;
|
|
441
|
-
agent;
|
|
442
|
-
sessionKey;
|
|
443
|
-
sessionStore;
|
|
444
|
-
reusable = true;
|
|
445
|
-
constructor(session, agent, sessionKey, sessionStore) {
|
|
446
|
-
this.session = session;
|
|
447
|
-
this.agent = agent;
|
|
448
|
-
this.sessionKey = sessionKey;
|
|
449
|
-
this.sessionStore = sessionStore;
|
|
450
|
-
}
|
|
451
|
-
get sessionId() {
|
|
452
|
-
return this.session.sessionId;
|
|
453
|
-
}
|
|
454
|
-
get isReusable() {
|
|
455
|
-
return this.reusable;
|
|
456
|
-
}
|
|
457
|
-
cancel(options) {
|
|
458
|
-
if (options?.preserveSession !== true) this.reusable = false;
|
|
459
|
-
return this.agent.notify(acp.methods.agent.session.cancel, { sessionId: this.session.sessionId });
|
|
460
|
-
}
|
|
461
|
-
dispose() {
|
|
462
|
-
this.reusable = false;
|
|
463
|
-
this.session.dispose();
|
|
464
|
-
}
|
|
465
|
-
async invalidate() {
|
|
466
|
-
this.reusable = false;
|
|
467
|
-
this.session.dispose();
|
|
468
|
-
await this.sessionStore?.delete(this.sessionKey);
|
|
469
|
-
}
|
|
470
|
-
async prompt(text, onUpdate) {
|
|
471
|
-
const failure = this.session.prompt(text).then(() => new Promise(() => void 0), (error) => Promise.reject(error));
|
|
472
|
-
for (;;) {
|
|
473
|
-
const message = await Promise.race([this.session.nextUpdate(), failure]);
|
|
474
|
-
if (message.kind === "stop") return { stopReason: message.stopReason };
|
|
475
|
-
onUpdate(message.update);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
var ResumedAcpSession = class {
|
|
480
|
-
processConnection;
|
|
481
|
-
sessionId;
|
|
482
|
-
updates = [];
|
|
483
|
-
waiters = [];
|
|
484
|
-
disposed = false;
|
|
485
|
-
failure;
|
|
486
|
-
constructor(processConnection, sessionId) {
|
|
487
|
-
this.processConnection = processConnection;
|
|
488
|
-
this.sessionId = sessionId;
|
|
489
|
-
processConnection.sessionUpdateHandlers.set(sessionId, (notification) => {
|
|
490
|
-
this.enqueue({
|
|
491
|
-
kind: "session_update",
|
|
492
|
-
update: notification.update
|
|
493
|
-
});
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
clearReplay() {
|
|
497
|
-
this.updates.splice(0);
|
|
498
|
-
}
|
|
499
|
-
dispose() {
|
|
500
|
-
if (this.disposed) return;
|
|
501
|
-
this.disposed = true;
|
|
502
|
-
this.processConnection.sessionUpdateHandlers.delete(this.sessionId);
|
|
503
|
-
const error = /* @__PURE__ */ new Error(`ACP session ${this.sessionId} observer disposed`);
|
|
504
|
-
for (const waiter of this.waiters.splice(0)) waiter.reject(error);
|
|
505
|
-
this.updates.splice(0);
|
|
506
|
-
}
|
|
507
|
-
nextUpdate() {
|
|
508
|
-
if (this.updates.length > 0) return Promise.resolve(this.updates.shift());
|
|
509
|
-
if (this.failure !== void 0) return Promise.reject(this.failure);
|
|
510
|
-
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error(`ACP session ${this.sessionId} observer disposed`));
|
|
511
|
-
return new Promise((resolve, reject) => this.waiters.push({
|
|
512
|
-
reject,
|
|
513
|
-
resolve
|
|
514
|
-
}));
|
|
515
|
-
}
|
|
516
|
-
prompt(text) {
|
|
517
|
-
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error(`ACP session ${this.sessionId} observer disposed`));
|
|
518
|
-
const response = this.processConnection.connection.agent.request(acp.methods.agent.session.prompt, {
|
|
519
|
-
prompt: [{
|
|
520
|
-
text,
|
|
521
|
-
type: "text"
|
|
522
|
-
}],
|
|
523
|
-
sessionId: this.sessionId
|
|
524
|
-
});
|
|
525
|
-
response.then((result) => this.enqueue({
|
|
526
|
-
kind: "stop",
|
|
527
|
-
stopReason: result.stopReason
|
|
528
|
-
}), (error) => this.fail(error));
|
|
529
|
-
return response;
|
|
530
|
-
}
|
|
531
|
-
enqueue(message) {
|
|
532
|
-
if (this.disposed) return;
|
|
533
|
-
const waiter = this.waiters.shift();
|
|
534
|
-
if (waiter) waiter.resolve(message);
|
|
535
|
-
else this.updates.push(message);
|
|
536
|
-
}
|
|
537
|
-
fail(error) {
|
|
538
|
-
if (this.failure !== void 0 || this.disposed) return;
|
|
539
|
-
this.failure = error;
|
|
540
|
-
for (const waiter of this.waiters.splice(0)) waiter.reject(error);
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
var AcpSessionResumeUnavailable = class extends Error {
|
|
544
|
-
sessionId;
|
|
545
|
-
name = "AcpSessionResumeUnavailable";
|
|
546
|
-
constructor(sessionId) {
|
|
547
|
-
super(`ACP Agent cannot load or resume persisted session ${sessionId}`);
|
|
548
|
-
this.sessionId = sessionId;
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
var AcpSessionResumeFailed = class extends Error {
|
|
552
|
-
sessionId;
|
|
553
|
-
cause;
|
|
554
|
-
name = "AcpSessionResumeFailed";
|
|
555
|
-
constructor(sessionId, cause) {
|
|
556
|
-
super(`ACP Agent failed to restore persisted session ${sessionId}`);
|
|
557
|
-
this.sessionId = sessionId;
|
|
558
|
-
this.cause = cause;
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
async function openConnection(options, sessionKeys, onSpawn) {
|
|
562
|
-
const child = spawn(options.command, [...options.arguments ?? []], {
|
|
563
|
-
cwd: options.workingDirectory,
|
|
564
|
-
env: { ...options.environment },
|
|
565
|
-
stdio: [
|
|
566
|
-
"pipe",
|
|
567
|
-
"pipe",
|
|
568
|
-
"pipe"
|
|
569
|
-
]
|
|
570
|
-
});
|
|
571
|
-
onSpawn(child);
|
|
572
|
-
child.stderr.setEncoding("utf8");
|
|
573
|
-
if (options.onStderr) child.stderr.on("data", options.onStderr);
|
|
574
|
-
else child.stderr.resume();
|
|
575
|
-
const sessionUpdateHandlers = /* @__PURE__ */ new Map();
|
|
576
|
-
const app = acp.client({ name: options.clientName ?? "rivus" }).onRequest(acp.methods.client.session.requestPermission, async ({ params }) => {
|
|
577
|
-
const sessionKey = sessionKeys.get(params.sessionId);
|
|
578
|
-
return { outcome: await decideAcpPermission(toPermissionRequest(params), sessionKey && options.permissionPolicy ? (request) => options.permissionPolicy?.(request, { sessionKey }) : void 0) };
|
|
579
|
-
}).onNotification(acp.methods.client.session.update, ({ params }) => {
|
|
580
|
-
sessionUpdateHandlers.get(params.sessionId)?.(params);
|
|
581
|
-
});
|
|
582
|
-
const stream = acp.ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
583
|
-
const connection = app.connect(stream);
|
|
584
|
-
try {
|
|
585
|
-
const initialized = await withTimeout(connection.agent.request(acp.methods.agent.initialize, {
|
|
586
|
-
clientCapabilities: {},
|
|
587
|
-
clientInfo: {
|
|
588
|
-
name: options.clientName ?? "rivus",
|
|
589
|
-
version: "1"
|
|
590
|
-
},
|
|
591
|
-
protocolVersion: acp.PROTOCOL_VERSION
|
|
592
|
-
}), options.initializationTimeoutMs ?? 1e4, "ACP initialization timed out");
|
|
593
|
-
if (initialized.protocolVersion !== acp.PROTOCOL_VERSION) throw new Error(`ACP protocol version ${initialized.protocolVersion} is not supported`);
|
|
594
|
-
return {
|
|
595
|
-
...initialized.agentCapabilities ? { agentCapabilities: initialized.agentCapabilities } : {},
|
|
596
|
-
child,
|
|
597
|
-
connection,
|
|
598
|
-
sessionUpdateHandlers,
|
|
599
|
-
sessions: /* @__PURE__ */ new Map()
|
|
600
|
-
};
|
|
601
|
-
} catch (error) {
|
|
602
|
-
connection.close(error);
|
|
603
|
-
await terminateChild(child, options.terminationTimeoutMs);
|
|
604
|
-
throw error;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
function toPermissionRequest(request) {
|
|
608
|
-
return {
|
|
609
|
-
options: request.options,
|
|
610
|
-
sessionId: request.sessionId,
|
|
611
|
-
toolCall: {
|
|
612
|
-
...request.toolCall.rawInput === void 0 ? {} : { rawInput: request.toolCall.rawInput },
|
|
613
|
-
...request.toolCall.title == null ? {} : { title: request.toolCall.title },
|
|
614
|
-
toolCallId: request.toolCall.toolCallId
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
async function terminateChild(child, timeoutMs = 2e3) {
|
|
619
|
-
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
620
|
-
child.kill("SIGTERM");
|
|
621
|
-
if (await waitForChildExit(child, timeoutMs)) return;
|
|
622
|
-
child.kill("SIGKILL");
|
|
623
|
-
if (!await waitForChildExit(child, timeoutMs)) throw new Error(`ACP child did not exit after SIGKILL within ${timeoutMs}ms`);
|
|
624
|
-
}
|
|
625
|
-
function waitForChildExit(child, timeoutMs) {
|
|
626
|
-
if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true);
|
|
627
|
-
return new Promise((resolve) => {
|
|
628
|
-
const onExit = () => {
|
|
629
|
-
clearTimeout(timeout);
|
|
630
|
-
resolve(true);
|
|
631
|
-
};
|
|
632
|
-
const timeout = setTimeout(() => {
|
|
633
|
-
child.removeListener("exit", onExit);
|
|
634
|
-
resolve(false);
|
|
635
|
-
}, timeoutMs);
|
|
636
|
-
timeout.unref();
|
|
637
|
-
child.once("exit", onExit);
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
function withTimeout(promise, timeoutMs, message) {
|
|
641
|
-
return new Promise((resolve, reject) => {
|
|
642
|
-
const timeout = setTimeout(() => reject(/* @__PURE__ */ new Error(`${message} after ${timeoutMs}ms`)), timeoutMs);
|
|
643
|
-
promise.then((value) => {
|
|
644
|
-
clearTimeout(timeout);
|
|
645
|
-
resolve(value);
|
|
646
|
-
}, (error) => {
|
|
647
|
-
clearTimeout(timeout);
|
|
648
|
-
reject(error);
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
//#endregion
|
|
653
|
-
//#region src/adapters/compatibility/agent-execution/acp/acp-runtime.ts
|
|
654
|
-
function createAcpAgentLoop(options) {
|
|
655
|
-
return fromEffectAgentLoop(createAcpAgentLoop$1({
|
|
656
|
-
...options.cancellationSettleTimeoutMs === void 0 ? {} : { cancellationSettleTimeoutMs: options.cancellationSettleTimeoutMs },
|
|
657
|
-
...options.disposeSessionAfterRun === void 0 ? {} : { disposeSessionAfterRun: options.disposeSessionAfterRun },
|
|
658
|
-
resolveSession: (input) => options.resolveSession(toCompatibilityAgentLoopInput(input))
|
|
659
|
-
}));
|
|
660
|
-
}
|
|
661
|
-
function createAcpAgentServer(options) {
|
|
662
|
-
return createAcpAgentServer$1({
|
|
663
|
-
...options.agentName === void 0 ? {} : { agentName: options.agentName },
|
|
664
|
-
loop: toEffectAgentLoop(options.loop),
|
|
665
|
-
...options.permissionBridge ? { permissionBridge: options.permissionBridge } : {},
|
|
666
|
-
workingDirectory: options.workingDirectory
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
function serveAcpAgentOnStdio(app) {
|
|
670
|
-
return serveAcpAgentOnStdio$1(app);
|
|
671
|
-
}
|
|
672
|
-
function createAcpStdioAgentLoop(options) {
|
|
673
|
-
const handle = createAcpStdioAgentLoop$1({
|
|
674
|
-
...options.arguments ? { arguments: options.arguments } : {},
|
|
675
|
-
...options.clientName === void 0 ? {} : { clientName: options.clientName },
|
|
676
|
-
command: options.command,
|
|
677
|
-
...options.environment ? { environment: options.environment } : {},
|
|
678
|
-
...options.initializationTimeoutMs === void 0 ? {} : { initializationTimeoutMs: options.initializationTimeoutMs },
|
|
679
|
-
...options.mcpServers ? { mcpServers: (input) => options.mcpServers(toCompatibilityAgentLoopInput(input)) } : {},
|
|
680
|
-
...options.onStderr ? { onStderr: options.onStderr } : {},
|
|
681
|
-
...options.permissionPolicy ? { permissionPolicy: options.permissionPolicy } : {},
|
|
682
|
-
...options.sessionStore ? { sessionStore: options.sessionStore } : {},
|
|
683
|
-
...options.terminationTimeoutMs === void 0 ? {} : { terminationTimeoutMs: options.terminationTimeoutMs },
|
|
684
|
-
workingDirectory: options.workingDirectory
|
|
685
|
-
});
|
|
686
|
-
return {
|
|
687
|
-
dispose: () => handle.dispose(),
|
|
688
|
-
loop: fromEffectAgentLoop(handle.loop)
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
//#endregion
|
|
692
|
-
//#region src/adapters/acp/runtime/acp-permission-bridge.ts
|
|
693
|
-
function createAcpPermissionBridge() {
|
|
694
|
-
const policies = /* @__PURE__ */ new Map();
|
|
695
|
-
return {
|
|
696
|
-
bind: (sessionKey, policy) => {
|
|
697
|
-
if (policies.has(sessionKey)) throw new Error(`ACP permission bridge is already bound: ${sessionKey}`);
|
|
698
|
-
policies.set(sessionKey, policy);
|
|
699
|
-
return () => {
|
|
700
|
-
if (policies.get(sessionKey) === policy) policies.delete(sessionKey);
|
|
701
|
-
};
|
|
702
|
-
},
|
|
703
|
-
policy: async (request, context) => policies.get(context.sessionKey)?.(request)
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
//#endregion
|
|
707
|
-
//#region src/adapters/acp/runtime/acp-session-store.ts
|
|
708
|
-
/**
|
|
709
|
-
* A small deployment-owned store for ACP provider session identities.
|
|
710
|
-
* The file contains no prompts or credentials, only session-key bindings.
|
|
711
|
-
*/
|
|
712
|
-
function createJsonAcpSessionStore(options) {
|
|
713
|
-
let recordsPromise;
|
|
714
|
-
let writeChain = Promise.resolve();
|
|
715
|
-
const readRecords = async () => {
|
|
716
|
-
try {
|
|
717
|
-
const raw = await readFile(options.filePath, "utf8");
|
|
718
|
-
const parsed = JSON.parse(raw);
|
|
719
|
-
if (!isRecord(parsed)) throw new Error("ACP session store must contain an object");
|
|
720
|
-
const records = /* @__PURE__ */ new Map();
|
|
721
|
-
for (const [sessionKey, value] of Object.entries(parsed)) {
|
|
722
|
-
if (!isRecord(value) || typeof value.sessionId !== "string" || value.sessionId.trim() === "") throw new Error(`invalid ACP session record for ${sessionKey}`);
|
|
723
|
-
records.set(sessionKey, { sessionId: value.sessionId });
|
|
724
|
-
}
|
|
725
|
-
return records;
|
|
726
|
-
} catch (error) {
|
|
727
|
-
if (isMissingFile(error)) return /* @__PURE__ */ new Map();
|
|
728
|
-
throw error;
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
const records = async () => {
|
|
732
|
-
recordsPromise ??= readRecords();
|
|
733
|
-
return recordsPromise;
|
|
734
|
-
};
|
|
735
|
-
const persist = async (value) => {
|
|
736
|
-
const temporaryPath = `${options.filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
737
|
-
await mkdir(dirname(options.filePath), { recursive: true });
|
|
738
|
-
await writeFile(temporaryPath, `${JSON.stringify(Object.fromEntries(value), null, 2)}\n`, "utf8");
|
|
739
|
-
await rename(temporaryPath, options.filePath);
|
|
740
|
-
};
|
|
741
|
-
return {
|
|
742
|
-
delete: async (sessionKey) => {
|
|
743
|
-
writeChain = writeChain.then(async () => {
|
|
744
|
-
const current = await records();
|
|
745
|
-
if (!current.delete(sessionKey)) return;
|
|
746
|
-
await persist(current);
|
|
747
|
-
});
|
|
748
|
-
await writeChain;
|
|
749
|
-
},
|
|
750
|
-
load: async (sessionKey) => (await records()).get(sessionKey),
|
|
751
|
-
save: async (sessionKey, record) => {
|
|
752
|
-
writeChain = writeChain.then(async () => {
|
|
753
|
-
const current = await records();
|
|
754
|
-
current.set(sessionKey, { sessionId: record.sessionId });
|
|
755
|
-
await persist(current);
|
|
756
|
-
});
|
|
757
|
-
await writeChain;
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
function isMissingFile(error) {
|
|
762
|
-
return isRecord(error) && error.code === "ENOENT";
|
|
763
|
-
}
|
|
764
|
-
function isRecord(value) {
|
|
765
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
766
|
-
}
|
|
767
|
-
//#endregion
|
|
1
|
+
import { AcpSessionResumeFailed, AcpSessionResumeUnavailable, createAcpPermissionBridge, createJsonAcpSessionStore, createLegacyAcpAgentLoop as createAcpAgentLoop, createLegacyAcpAgentServer as createAcpAgentServer, createLegacyAcpStdioAgentLoop as createAcpStdioAgentLoop, decideAcpPermission, serveLegacyAcpAgentOnStdio as serveAcpAgentOnStdio } from "@rivus/runtime/acp";
|
|
768
2
|
export { AcpSessionResumeFailed, AcpSessionResumeUnavailable, createAcpAgentLoop, createAcpAgentServer, createAcpPermissionBridge, createAcpStdioAgentLoop, createJsonAcpSessionStore, decideAcpPermission, serveAcpAgentOnStdio };
|