weacpx 0.6.1 → 0.8.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 +10 -564
- package/package.json +10 -72
- package/plugin-api.d.ts +2 -0
- package/plugin-api.js +5 -0
- package/LICENSE +0 -21
- package/config.example.json +0 -44
- package/dist/bridge/bridge-main.js +0 -2120
- package/dist/channels/channel-scope.d.ts +0 -17
- package/dist/channels/cli/provider.d.ts +0 -73
- package/dist/channels/cli/registry.d.ts +0 -7
- package/dist/channels/cli/weixin-provider.d.ts +0 -2
- package/dist/channels/create-channel.d.ts +0 -16
- package/dist/channels/media-store.d.ts +0 -29
- package/dist/channels/media-types.d.ts +0 -28
- package/dist/channels/outbound-media-safety.d.ts +0 -7
- package/dist/channels/plugin.d.ts +0 -9
- package/dist/channels/types.d.ts +0 -115
- package/dist/channels/weixin-channel.d.ts +0 -25
- package/dist/cli.js +0 -46971
- package/dist/commands/command-hints.d.ts +0 -11
- package/dist/commands/command-list.d.ts +0 -3
- package/dist/commands/config-clone.d.ts +0 -2
- package/dist/commands/handlers/agent-handler.d.ts +0 -6
- package/dist/commands/handlers/config-handler.d.ts +0 -5
- package/dist/commands/handlers/later-handler.d.ts +0 -21
- package/dist/commands/handlers/orchestration-handler.d.ts +0 -16
- package/dist/commands/handlers/permission-handler.d.ts +0 -9
- package/dist/commands/handlers/session-handler.d.ts +0 -37
- package/dist/commands/handlers/workspace-handler.d.ts +0 -8
- package/dist/commands/help/help-registry.d.ts +0 -4
- package/dist/commands/help/help-types.d.ts +0 -12
- package/dist/commands/parse-command.d.ts +0 -175
- package/dist/commands/router-types.d.ts +0 -144
- package/dist/commands/workspace-name.d.ts +0 -4
- package/dist/commands/workspace-path.d.ts +0 -4
- package/dist/config/agent-templates.d.ts +0 -4
- package/dist/config/config-store.d.ts +0 -13
- package/dist/config/load-config.d.ts +0 -10
- package/dist/config/resolve-agent-command.d.ts +0 -2
- package/dist/config/types.d.ts +0 -85
- package/dist/formatting/render-text.d.ts +0 -23
- package/dist/logging/app-logger.d.ts +0 -23
- package/dist/logging/rotating-file-writer.d.ts +0 -2
- package/dist/orchestration/async-mutex.d.ts +0 -4
- package/dist/orchestration/build-coordinator-prompt.d.ts +0 -66
- package/dist/orchestration/orchestration-service.d.ts +0 -471
- package/dist/orchestration/orchestration-types.d.ts +0 -181
- package/dist/orchestration/progress-line-parser.d.ts +0 -19
- package/dist/orchestration/render-delegate-group-result.d.ts +0 -6
- package/dist/orchestration/render-delegate-question-package.d.ts +0 -21
- package/dist/orchestration/render-delegate-result.d.ts +0 -2
- package/dist/orchestration/task-watch-timeouts.d.ts +0 -5
- package/dist/perf/perf-log-writer.d.ts +0 -25
- package/dist/perf/perf-tracer.d.ts +0 -54
- package/dist/plugin-api.d.ts +0 -9
- package/dist/plugin-api.js +0 -180
- package/dist/plugins/compatibility.d.ts +0 -16
- package/dist/plugins/known-plugins.d.ts +0 -9
- package/dist/plugins/types.d.ts +0 -18
- package/dist/scheduled/parse-later-time.d.ts +0 -11
- package/dist/scheduled/scheduled-render.d.ts +0 -7
- package/dist/scheduled/scheduled-service.d.ts +0 -41
- package/dist/scheduled/scheduled-types.d.ts +0 -29
- package/dist/sessions/session-service.d.ts +0 -83
- package/dist/state/state-store.d.ts +0 -8
- package/dist/state/types.d.ts +0 -44
- package/dist/transport/tool-event-mode.d.ts +0 -14
- package/dist/transport/types.d.ts +0 -129
- package/dist/util/path.d.ts +0 -4
- package/dist/util/private-file.d.ts +0 -26
- package/dist/util/sanitize.d.ts +0 -10
- package/dist/util/text.d.ts +0 -3
- package/dist/version.d.ts +0 -3
- package/dist/weixin/agent/interface.d.ts +0 -78
- package/dist/weixin/api/api.d.ts +0 -76
- package/dist/weixin/api/config-cache.d.ts +0 -18
- package/dist/weixin/api/session-guard.d.ts +0 -17
- package/dist/weixin/api/types.d.ts +0 -203
- package/dist/weixin/auth/accounts.d.ts +0 -69
- package/dist/weixin/auth/login-qr.d.ts +0 -37
- package/dist/weixin/bot.d.ts +0 -56
- package/dist/weixin/cdn/aes-ecb.d.ts +0 -6
- package/dist/weixin/cdn/cdn-upload.d.ts +0 -17
- package/dist/weixin/cdn/cdn-url.d.ts +0 -11
- package/dist/weixin/cdn/pic-decrypt.d.ts +0 -9
- package/dist/weixin/cdn/upload.d.ts +0 -42
- package/dist/weixin/index.d.ts +0 -6
- package/dist/weixin/media/media-download.d.ts +0 -18
- package/dist/weixin/media/mime.d.ts +0 -6
- package/dist/weixin/media/silk-transcode.d.ts +0 -8
- package/dist/weixin/messaging/conversation-executor.d.ts +0 -7
- package/dist/weixin/messaging/debug-mode.d.ts +0 -9
- package/dist/weixin/messaging/deliver-coordinator-message.d.ts +0 -22
- package/dist/weixin/messaging/deliver-orchestration-task-notice.d.ts +0 -18
- package/dist/weixin/messaging/deliver-orchestration-task-progress.d.ts +0 -16
- package/dist/weixin/messaging/error-notice.d.ts +0 -13
- package/dist/weixin/messaging/execute-chat-turn.d.ts +0 -12
- package/dist/weixin/messaging/final-heads-up.d.ts +0 -5
- package/dist/weixin/messaging/handle-weixin-message-turn.d.ts +0 -32
- package/dist/weixin/messaging/inbound.d.ts +0 -80
- package/dist/weixin/messaging/markdown-filter.d.ts +0 -45
- package/dist/weixin/messaging/orchestration-notice-accounts.d.ts +0 -2
- package/dist/weixin/messaging/quota-errors.d.ts +0 -8
- package/dist/weixin/messaging/quota-manager.d.ts +0 -44
- package/dist/weixin/messaging/scheduled-turn.d.ts +0 -22
- package/dist/weixin/messaging/send-errors.d.ts +0 -39
- package/dist/weixin/messaging/send-media.d.ts +0 -23
- package/dist/weixin/messaging/send-orchestration-notice.d.ts +0 -10
- package/dist/weixin/messaging/send.d.ts +0 -73
- package/dist/weixin/messaging/slash-commands.d.ts +0 -40
- package/dist/weixin/monitor/consumer-lock.d.ts +0 -24
- package/dist/weixin/monitor/monitor.d.ts +0 -30
- package/dist/weixin/storage/ensure-dir.d.ts +0 -1
- package/dist/weixin/storage/state-dir.d.ts +0 -2
- package/dist/weixin/storage/sync-buf.d.ts +0 -20
- package/dist/weixin/util/logger.d.ts +0 -14
- package/dist/weixin/util/random.d.ts +0 -10
- package/dist/weixin/util/redact.d.ts +0 -21
|
@@ -1,2120 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
function __accessProp(key) {
|
|
8
|
-
return this[key];
|
|
9
|
-
}
|
|
10
|
-
var __toESMCache_node;
|
|
11
|
-
var __toESMCache_esm;
|
|
12
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
-
var canCache = mod != null && typeof mod === "object";
|
|
14
|
-
if (canCache) {
|
|
15
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
-
var cached = cache.get(mod);
|
|
17
|
-
if (cached)
|
|
18
|
-
return cached;
|
|
19
|
-
}
|
|
20
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
-
for (let key of __getOwnPropNames(mod))
|
|
23
|
-
if (!__hasOwnProp.call(to, key))
|
|
24
|
-
__defProp(to, key, {
|
|
25
|
-
get: __accessProp.bind(mod, key),
|
|
26
|
-
enumerable: true
|
|
27
|
-
});
|
|
28
|
-
if (canCache)
|
|
29
|
-
cache.set(mod, to);
|
|
30
|
-
return to;
|
|
31
|
-
};
|
|
32
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
-
var __returnValue = (v) => v;
|
|
34
|
-
function __exportSetter(name, newValue) {
|
|
35
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
-
}
|
|
37
|
-
var __export = (target, all) => {
|
|
38
|
-
for (var name in all)
|
|
39
|
-
__defProp(target, name, {
|
|
40
|
-
get: all[name],
|
|
41
|
-
enumerable: true,
|
|
42
|
-
configurable: true,
|
|
43
|
-
set: __exportSetter.bind(all, name)
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
|
-
var __promiseAll = (args) => Promise.all(args);
|
|
48
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
|
-
|
|
50
|
-
// src/transport/acpx-bridge/acpx-bridge-protocol.ts
|
|
51
|
-
function encodeBridgeRequest(request) {
|
|
52
|
-
return `${JSON.stringify(request)}
|
|
53
|
-
`;
|
|
54
|
-
}
|
|
55
|
-
function encodeBridgePromptSegmentEvent(event) {
|
|
56
|
-
return `${JSON.stringify(event)}
|
|
57
|
-
`;
|
|
58
|
-
}
|
|
59
|
-
function encodeBridgePromptToolEvent(event) {
|
|
60
|
-
return `${JSON.stringify(event)}
|
|
61
|
-
`;
|
|
62
|
-
}
|
|
63
|
-
function encodeBridgePromptThoughtEvent(event) {
|
|
64
|
-
return `${JSON.stringify(event)}
|
|
65
|
-
`;
|
|
66
|
-
}
|
|
67
|
-
function encodeBridgeSessionProgressEvent(event) {
|
|
68
|
-
return `${JSON.stringify(event)}
|
|
69
|
-
`;
|
|
70
|
-
}
|
|
71
|
-
function encodeBridgeSessionNoteEvent(event) {
|
|
72
|
-
return `${JSON.stringify(event)}
|
|
73
|
-
`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// src/transport/prompt-output.ts
|
|
77
|
-
function getPromptText(result) {
|
|
78
|
-
const stdoutOutput = extractPromptOutput(result.stdout);
|
|
79
|
-
if (result.code === 0) {
|
|
80
|
-
return sanitizePromptText(stdoutOutput.text);
|
|
81
|
-
}
|
|
82
|
-
const preferredError = extractPromptFailureMessage(result);
|
|
83
|
-
if (preferredError) {
|
|
84
|
-
throw new PromptCommandError(preferredError, result);
|
|
85
|
-
}
|
|
86
|
-
const stderrOutput = extractPromptOutput(result.stderr);
|
|
87
|
-
const partialReply = [stdoutOutput, stderrOutput].filter((output) => output.hasAgentMessage && output.text.length > 0).map((output) => sanitizePromptText(output.text)).find((text) => text.length > 0);
|
|
88
|
-
if (partialReply) {
|
|
89
|
-
return partialReply;
|
|
90
|
-
}
|
|
91
|
-
throw new PromptCommandError(`command failed with exit code ${result.code}`, result);
|
|
92
|
-
}
|
|
93
|
-
function normalizeCommandError(result) {
|
|
94
|
-
const preferredError = extractPromptFailureMessage(result);
|
|
95
|
-
if (preferredError) {
|
|
96
|
-
return preferredError;
|
|
97
|
-
}
|
|
98
|
-
return result.stdout.trim() || null;
|
|
99
|
-
}
|
|
100
|
-
function extractPromptFailureMessage(result) {
|
|
101
|
-
const rpcMessages = extractJsonRpcErrorMessages(result.stderr).concat(extractJsonRpcErrorMessages(result.stdout)).filter((message) => message.length > 0);
|
|
102
|
-
const preferredMessage = [...rpcMessages].reverse().find((message) => message !== "Resource not found");
|
|
103
|
-
if (preferredMessage) {
|
|
104
|
-
return preferredMessage;
|
|
105
|
-
}
|
|
106
|
-
if (rpcMessages.length > 0) {
|
|
107
|
-
return rpcMessages[rpcMessages.length - 1] ?? null;
|
|
108
|
-
}
|
|
109
|
-
const stderrText = result.stderr.trim();
|
|
110
|
-
if (stderrText.length > 0) {
|
|
111
|
-
return stderrText;
|
|
112
|
-
}
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
function extractPromptOutput(output) {
|
|
116
|
-
const lines = output.split(`
|
|
117
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
118
|
-
let text = "";
|
|
119
|
-
let hasAgentMessage = false;
|
|
120
|
-
for (const line of lines) {
|
|
121
|
-
let event;
|
|
122
|
-
try {
|
|
123
|
-
event = JSON.parse(line);
|
|
124
|
-
} catch {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const isMessageChunk = event.method === "session/update" && event.params?.update?.sessionUpdate === "agent_message_chunk" && event.params.update.content?.type === "text" && typeof event.params.update.content.text === "string";
|
|
128
|
-
if (isMessageChunk) {
|
|
129
|
-
hasAgentMessage = true;
|
|
130
|
-
text += event.params.update.content.text ?? "";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (hasAgentMessage && text.trim().length > 0) {
|
|
134
|
-
return {
|
|
135
|
-
text: text.trim(),
|
|
136
|
-
hasAgentMessage
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
text: output.trim(),
|
|
141
|
-
hasAgentMessage
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
function sanitizePromptText(text) {
|
|
145
|
-
const trimmed = text.trim();
|
|
146
|
-
const paragraphs = trimmed.split(/\n\s*\n/);
|
|
147
|
-
if (paragraphs.length < 2) {
|
|
148
|
-
return trimmed;
|
|
149
|
-
}
|
|
150
|
-
const firstParagraph = paragraphs[0].trim().replace(/\s+/g, " ").toLowerCase();
|
|
151
|
-
if (!looksLikeWorkflowPreamble(firstParagraph)) {
|
|
152
|
-
return trimmed;
|
|
153
|
-
}
|
|
154
|
-
return paragraphs.slice(1).join(`
|
|
155
|
-
|
|
156
|
-
`).trim();
|
|
157
|
-
}
|
|
158
|
-
function looksLikeWorkflowPreamble(paragraph) {
|
|
159
|
-
if (!paragraph.startsWith("using ")) {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
return paragraph.includes("using-superpowers") || paragraph.includes("repo workflow requirement") || paragraph.includes("workflow requirement") || paragraph.includes("before responding") || paragraph.includes("skill check");
|
|
163
|
-
}
|
|
164
|
-
function extractJsonRpcErrorMessages(output) {
|
|
165
|
-
return output.split(`
|
|
166
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
|
|
167
|
-
try {
|
|
168
|
-
const payload = JSON.parse(line);
|
|
169
|
-
const err = payload.error;
|
|
170
|
-
const dataMsg = typeof err?.data?.message === "string" && err.data.message.length > 0 ? err.data.message : undefined;
|
|
171
|
-
const baseMsg = typeof err?.message === "string" && err.message.length > 0 ? err.message : undefined;
|
|
172
|
-
const chosen = dataMsg && dataMsg !== baseMsg ? dataMsg : baseMsg;
|
|
173
|
-
if (chosen) {
|
|
174
|
-
return [chosen];
|
|
175
|
-
}
|
|
176
|
-
} catch {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
return [];
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
var PromptCommandError;
|
|
183
|
-
var init_prompt_output = __esm(() => {
|
|
184
|
-
PromptCommandError = class PromptCommandError extends Error {
|
|
185
|
-
exitCode;
|
|
186
|
-
stdout;
|
|
187
|
-
stderr;
|
|
188
|
-
constructor(message, result) {
|
|
189
|
-
super(message);
|
|
190
|
-
this.name = "PromptCommandError";
|
|
191
|
-
this.exitCode = result.code;
|
|
192
|
-
this.stdout = result.stdout;
|
|
193
|
-
this.stderr = result.stderr;
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// src/process/spawn-command.ts
|
|
199
|
-
function resolveSpawnCommand(command, args) {
|
|
200
|
-
if (SCRIPT_FILE_PATTERN.test(command)) {
|
|
201
|
-
return {
|
|
202
|
-
command: process.execPath,
|
|
203
|
-
args: [command, ...args]
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
return { command, args };
|
|
207
|
-
}
|
|
208
|
-
var SCRIPT_FILE_PATTERN;
|
|
209
|
-
var init_spawn_command = __esm(() => {
|
|
210
|
-
SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// src/transport/prompt-media.ts
|
|
214
|
-
import { mkdtemp, open, rm, writeFile } from "node:fs/promises";
|
|
215
|
-
import { tmpdir as defaultTmpdir } from "node:os";
|
|
216
|
-
import path from "node:path";
|
|
217
|
-
import { pathToFileURL } from "node:url";
|
|
218
|
-
async function createStructuredPromptFile(text, media, deps = defaultStructuredPromptFileDeps) {
|
|
219
|
-
const mediaList = normalizePromptMedia(media);
|
|
220
|
-
if (mediaList.length === 0) {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
const blocks = [];
|
|
224
|
-
if (text.trim().length > 0) {
|
|
225
|
-
blocks.push({ type: "text", text });
|
|
226
|
-
}
|
|
227
|
-
const nonImages = mediaList.filter((item) => item.type !== "image");
|
|
228
|
-
if (nonImages.length > 0) {
|
|
229
|
-
blocks.push({ type: "text", text: buildAttachmentSummary(nonImages) });
|
|
230
|
-
}
|
|
231
|
-
for (const item of mediaList) {
|
|
232
|
-
if (item.type === "image") {
|
|
233
|
-
const imageData = await deps.readImageFile(item.filePath, MAX_STRUCTURED_IMAGE_BYTES);
|
|
234
|
-
if (imageData.byteLength === 0)
|
|
235
|
-
throw new Error("image prompt must not be empty");
|
|
236
|
-
if (imageData.byteLength > MAX_STRUCTURED_IMAGE_BYTES) {
|
|
237
|
-
throw new Error(`image prompt exceeds ${MAX_STRUCTURED_IMAGE_BYTES} bytes`);
|
|
238
|
-
}
|
|
239
|
-
blocks.push({
|
|
240
|
-
type: "image",
|
|
241
|
-
mimeType: resolveImageMimeType(imageData, item.mimeType),
|
|
242
|
-
data: imageData.toString("base64")
|
|
243
|
-
});
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
blocks.push({
|
|
247
|
-
type: "resource",
|
|
248
|
-
resource: {
|
|
249
|
-
uri: pathToFileURL(item.filePath).toString(),
|
|
250
|
-
text: `${item.fileName ?? path.basename(item.filePath)} ${item.mimeType} ${item.type}`
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
return await writeStructuredPromptBlocks(blocks, deps);
|
|
255
|
-
}
|
|
256
|
-
function normalizePromptMedia(media) {
|
|
257
|
-
if (!media)
|
|
258
|
-
return [];
|
|
259
|
-
return Array.isArray(media) ? media : [media];
|
|
260
|
-
}
|
|
261
|
-
function buildAttachmentSummary(items) {
|
|
262
|
-
const lines = ["Attachments available as local files:"];
|
|
263
|
-
for (const [index, item] of items.entries()) {
|
|
264
|
-
lines.push(`${index + 1}. ${item.type} ${item.fileName ?? path.basename(item.filePath)} ${item.mimeType} ${item.filePath}`);
|
|
265
|
-
}
|
|
266
|
-
return lines.join(`
|
|
267
|
-
`);
|
|
268
|
-
}
|
|
269
|
-
async function writeStructuredPromptBlocks(blocks, deps) {
|
|
270
|
-
let dir = "";
|
|
271
|
-
try {
|
|
272
|
-
dir = await deps.mkdtemp(path.join(deps.tmpdir(), "weacpx-acp-prompt-"));
|
|
273
|
-
const filePath = path.join(dir, "prompt.json");
|
|
274
|
-
await deps.writeFile(filePath, JSON.stringify(blocks), "utf8");
|
|
275
|
-
return { filePath, cleanup: async () => deps.rm(dir, { recursive: true, force: true }) };
|
|
276
|
-
} catch (error) {
|
|
277
|
-
if (dir)
|
|
278
|
-
await deps.rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
async function readImageFileBounded(filePath, maxBytes) {
|
|
283
|
-
const handle = await open(filePath, "r");
|
|
284
|
-
try {
|
|
285
|
-
const imageStats = await handle.stat();
|
|
286
|
-
if (!imageStats.isFile()) {
|
|
287
|
-
throw new Error("image prompt path must be a regular file");
|
|
288
|
-
}
|
|
289
|
-
if (imageStats.size > maxBytes) {
|
|
290
|
-
throw new Error(`image prompt exceeds ${maxBytes} bytes`);
|
|
291
|
-
}
|
|
292
|
-
const chunks = [];
|
|
293
|
-
let total = 0;
|
|
294
|
-
let position = 0;
|
|
295
|
-
const chunkSize = 1024 * 1024;
|
|
296
|
-
while (total <= maxBytes) {
|
|
297
|
-
const buffer = Buffer.allocUnsafe(Math.min(chunkSize, maxBytes + 1 - total));
|
|
298
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
299
|
-
if (bytesRead === 0)
|
|
300
|
-
break;
|
|
301
|
-
chunks.push(buffer.subarray(0, bytesRead));
|
|
302
|
-
total += bytesRead;
|
|
303
|
-
position += bytesRead;
|
|
304
|
-
}
|
|
305
|
-
return Buffer.concat(chunks, total);
|
|
306
|
-
} finally {
|
|
307
|
-
await handle.close();
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
function resolveImageMimeType(buffer, declaredMimeType) {
|
|
311
|
-
if (/^image\/[A-Za-z0-9.+-]+$/.test(declaredMimeType) && declaredMimeType !== "image/*") {
|
|
312
|
-
return declaredMimeType;
|
|
313
|
-
}
|
|
314
|
-
if (buffer.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"))) {
|
|
315
|
-
return "image/png";
|
|
316
|
-
}
|
|
317
|
-
if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
|
|
318
|
-
return "image/jpeg";
|
|
319
|
-
}
|
|
320
|
-
const header6 = buffer.subarray(0, 6).toString("ascii");
|
|
321
|
-
if (header6 === "GIF87a" || header6 === "GIF89a") {
|
|
322
|
-
return "image/gif";
|
|
323
|
-
}
|
|
324
|
-
if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
325
|
-
return "image/webp";
|
|
326
|
-
}
|
|
327
|
-
if (buffer.length >= 2 && buffer.subarray(0, 2).toString("ascii") === "BM") {
|
|
328
|
-
return "image/bmp";
|
|
329
|
-
}
|
|
330
|
-
return "image/png";
|
|
331
|
-
}
|
|
332
|
-
var MAX_STRUCTURED_IMAGE_BYTES, defaultStructuredPromptFileDeps;
|
|
333
|
-
var init_prompt_media = __esm(() => {
|
|
334
|
-
MAX_STRUCTURED_IMAGE_BYTES = 100 * 1024 * 1024;
|
|
335
|
-
defaultStructuredPromptFileDeps = {
|
|
336
|
-
readImageFile: readImageFileBounded,
|
|
337
|
-
mkdtemp,
|
|
338
|
-
writeFile,
|
|
339
|
-
rm,
|
|
340
|
-
tmpdir: defaultTmpdir
|
|
341
|
-
};
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// src/transport/tool-event-mode.ts
|
|
345
|
-
function resolveToolEventMode(input) {
|
|
346
|
-
if (input?.toolEventMode !== undefined) {
|
|
347
|
-
return input.toolEventMode;
|
|
348
|
-
}
|
|
349
|
-
if (input?.onToolEvent !== undefined) {
|
|
350
|
-
return "structured";
|
|
351
|
-
}
|
|
352
|
-
return "text";
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// src/transport/tool-kind-emoji.ts
|
|
356
|
-
var TOOL_KIND_EMOJI, DEFAULT_TOOL_EMOJI;
|
|
357
|
-
var init_tool_kind_emoji = __esm(() => {
|
|
358
|
-
TOOL_KIND_EMOJI = {
|
|
359
|
-
read: "\uD83D\uDCD6",
|
|
360
|
-
search: "\uD83D\uDD0D",
|
|
361
|
-
execute: "\uD83D\uDCBB",
|
|
362
|
-
edit: "✏️",
|
|
363
|
-
think: "\uD83E\uDDE0",
|
|
364
|
-
other: "\uD83D\uDD27"
|
|
365
|
-
};
|
|
366
|
-
DEFAULT_TOOL_EMOJI = TOOL_KIND_EMOJI.other;
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
// src/transport/streaming-prompt.ts
|
|
370
|
-
function createStreamingPromptState(formatToolCalls = false, options) {
|
|
371
|
-
let toolEventMode;
|
|
372
|
-
let onToolEvent;
|
|
373
|
-
let onThought;
|
|
374
|
-
if (options === undefined) {
|
|
375
|
-
toolEventMode = "text";
|
|
376
|
-
onToolEvent = undefined;
|
|
377
|
-
} else if (typeof options === "function") {
|
|
378
|
-
onToolEvent = options;
|
|
379
|
-
toolEventMode = "structured";
|
|
380
|
-
} else {
|
|
381
|
-
onToolEvent = options.onToolEvent;
|
|
382
|
-
onThought = options.onThought;
|
|
383
|
-
toolEventMode = resolveToolEventMode({
|
|
384
|
-
toolEventMode: options.mode,
|
|
385
|
-
onToolEvent
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
return {
|
|
389
|
-
buffer: "",
|
|
390
|
-
segments: [],
|
|
391
|
-
hasAgentMessage: false,
|
|
392
|
-
pendingLine: "",
|
|
393
|
-
formatToolCalls,
|
|
394
|
-
emittedToolCallIds: new Set,
|
|
395
|
-
toolEventMode,
|
|
396
|
-
onToolEvent,
|
|
397
|
-
onThought,
|
|
398
|
-
finalize() {
|
|
399
|
-
if (this.pendingLine.trim().length > 0) {
|
|
400
|
-
parseStreamingChunks(this, this.pendingLine);
|
|
401
|
-
}
|
|
402
|
-
const remaining = this.buffer.trim();
|
|
403
|
-
this.buffer = "";
|
|
404
|
-
this.pendingLine = "";
|
|
405
|
-
return remaining;
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
function parseStreamingDataChunk(state, chunk) {
|
|
410
|
-
state.pendingLine += chunk;
|
|
411
|
-
let boundary;
|
|
412
|
-
while ((boundary = state.pendingLine.indexOf(`
|
|
413
|
-
`)) !== -1) {
|
|
414
|
-
const line = state.pendingLine.slice(0, boundary);
|
|
415
|
-
state.pendingLine = state.pendingLine.slice(boundary + 1);
|
|
416
|
-
parseStreamingChunks(state, line);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
function parseStreamingChunks(state, line) {
|
|
420
|
-
const trimmed = line.trim();
|
|
421
|
-
if (trimmed.length === 0)
|
|
422
|
-
return;
|
|
423
|
-
let event;
|
|
424
|
-
try {
|
|
425
|
-
event = JSON.parse(trimmed);
|
|
426
|
-
} catch {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
if (event.method !== "session/update")
|
|
430
|
-
return;
|
|
431
|
-
const update = event.params?.update;
|
|
432
|
-
if (!update)
|
|
433
|
-
return;
|
|
434
|
-
if (state.formatToolCalls && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
|
|
435
|
-
const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
|
|
436
|
-
const wantsText = state.toolEventMode === "text" || state.toolEventMode === "both";
|
|
437
|
-
if (wantsStructured && state.onToolEvent) {
|
|
438
|
-
const toolEvent = buildToolUseEvent(update);
|
|
439
|
-
if (toolEvent)
|
|
440
|
-
state.onToolEvent(toolEvent);
|
|
441
|
-
}
|
|
442
|
-
if (wantsText) {
|
|
443
|
-
const formatted = formatToolCallEvent(update, update.sessionUpdate);
|
|
444
|
-
if (formatted) {
|
|
445
|
-
const toolCallId = update.toolCallId;
|
|
446
|
-
if (toolCallId) {
|
|
447
|
-
if (state.emittedToolCallIds.has(toolCallId))
|
|
448
|
-
return;
|
|
449
|
-
state.emittedToolCallIds.add(toolCallId);
|
|
450
|
-
}
|
|
451
|
-
state.segments.push(formatted);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
|
|
457
|
-
if (isThoughtChunk) {
|
|
458
|
-
const chunk2 = update.content.text;
|
|
459
|
-
if (chunk2.length > 0) {
|
|
460
|
-
state.onThought?.(chunk2);
|
|
461
|
-
}
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
const isMessageChunk = update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
|
|
465
|
-
if (!isMessageChunk)
|
|
466
|
-
return;
|
|
467
|
-
state.hasAgentMessage = true;
|
|
468
|
-
const chunk = update.content.text ?? "";
|
|
469
|
-
if (chunk.length === 0)
|
|
470
|
-
return;
|
|
471
|
-
state.buffer += chunk;
|
|
472
|
-
let boundary;
|
|
473
|
-
while ((boundary = state.buffer.indexOf(`
|
|
474
|
-
|
|
475
|
-
`)) !== -1) {
|
|
476
|
-
const segment = state.buffer.slice(0, boundary).trim();
|
|
477
|
-
state.buffer = state.buffer.slice(boundary + 2);
|
|
478
|
-
if (segment.length > 0) {
|
|
479
|
-
state.segments.push(segment);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
function formatToolCallEvent(update, sessionUpdate) {
|
|
484
|
-
if (!update)
|
|
485
|
-
return null;
|
|
486
|
-
const kind = update.kind ?? "";
|
|
487
|
-
const title = update.title ?? "";
|
|
488
|
-
if (title.length === 0)
|
|
489
|
-
return null;
|
|
490
|
-
const emoji = TOOL_KIND_EMOJI[kind] ?? DEFAULT_TOOL_EMOJI;
|
|
491
|
-
const inputSummary = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
|
|
492
|
-
const status = readString(update, "status");
|
|
493
|
-
if (!inputSummary && status === "pending")
|
|
494
|
-
return null;
|
|
495
|
-
if (!inputSummary && isGenericToolTitle(kind, title))
|
|
496
|
-
return null;
|
|
497
|
-
const summaryText = inputSummary && inputSummary !== title ? `: ${truncateToolDisplay(inputSummary)}` : "";
|
|
498
|
-
const statusText = status ? ` (${status})` : "";
|
|
499
|
-
return `${emoji} ${title}${statusText}${summaryText}`;
|
|
500
|
-
}
|
|
501
|
-
function buildToolUseEvent(update) {
|
|
502
|
-
if (!update)
|
|
503
|
-
return null;
|
|
504
|
-
const toolCallId = update.toolCallId;
|
|
505
|
-
if (!toolCallId)
|
|
506
|
-
return null;
|
|
507
|
-
const kindRaw = update.kind ?? "";
|
|
508
|
-
const kind = (() => {
|
|
509
|
-
switch (kindRaw) {
|
|
510
|
-
case "read":
|
|
511
|
-
case "search":
|
|
512
|
-
case "execute":
|
|
513
|
-
case "edit":
|
|
514
|
-
case "think":
|
|
515
|
-
return kindRaw;
|
|
516
|
-
default:
|
|
517
|
-
return "other";
|
|
518
|
-
}
|
|
519
|
-
})();
|
|
520
|
-
const title = (update.title ?? "").trim();
|
|
521
|
-
const toolName = title || "Tool";
|
|
522
|
-
const summaryRaw = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
|
|
523
|
-
const summary = summaryRaw && summaryRaw !== title ? summaryRaw : undefined;
|
|
524
|
-
const statusRaw = readString(update, "status");
|
|
525
|
-
const status = statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : "running";
|
|
526
|
-
const rawInput = update.rawInput;
|
|
527
|
-
const content = update.content;
|
|
528
|
-
const rawOutput = update.rawOutput;
|
|
529
|
-
const locations = update.locations;
|
|
530
|
-
return {
|
|
531
|
-
toolCallId,
|
|
532
|
-
toolName,
|
|
533
|
-
kind,
|
|
534
|
-
...summary ? { summary } : {},
|
|
535
|
-
...rawInput !== undefined ? { rawInput } : {},
|
|
536
|
-
...content !== undefined ? { content } : {},
|
|
537
|
-
...rawOutput !== undefined ? { rawOutput } : {},
|
|
538
|
-
...locations !== undefined ? { locations } : {},
|
|
539
|
-
status
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
function summarizeToolInput(rawInput, title = "") {
|
|
543
|
-
if (rawInput == null)
|
|
544
|
-
return;
|
|
545
|
-
if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
|
|
546
|
-
return String(rawInput);
|
|
547
|
-
}
|
|
548
|
-
if (!isRecord(rawInput))
|
|
549
|
-
return;
|
|
550
|
-
const taskSummary = summarizeTaskInput(rawInput, title);
|
|
551
|
-
if (taskSummary)
|
|
552
|
-
return taskSummary;
|
|
553
|
-
const command = readFirstString(rawInput, ["command", "cmd", "program"]);
|
|
554
|
-
const args = readFirstStringArray(rawInput, ["args", "arguments"]);
|
|
555
|
-
if (command) {
|
|
556
|
-
return [command, ...args ?? []].join(" ");
|
|
557
|
-
}
|
|
558
|
-
const parsedCmd = rawInput.parsed_cmd;
|
|
559
|
-
if (Array.isArray(parsedCmd) && parsedCmd.length > 0) {
|
|
560
|
-
const parts = [];
|
|
561
|
-
for (const entry of parsedCmd) {
|
|
562
|
-
if (isRecord(entry) && typeof entry.cmd === "string" && entry.cmd.length > 0) {
|
|
563
|
-
parts.push(entry.cmd);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (parts.length > 0) {
|
|
567
|
-
return parts.join(" ");
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
return readFirstString(rawInput, [
|
|
571
|
-
"path",
|
|
572
|
-
"file",
|
|
573
|
-
"filePath",
|
|
574
|
-
"filepath",
|
|
575
|
-
"file_path",
|
|
576
|
-
"target",
|
|
577
|
-
"uri",
|
|
578
|
-
"url",
|
|
579
|
-
"query",
|
|
580
|
-
"pattern",
|
|
581
|
-
"text",
|
|
582
|
-
"search",
|
|
583
|
-
"name",
|
|
584
|
-
"description"
|
|
585
|
-
]);
|
|
586
|
-
}
|
|
587
|
-
function summarizeTaskInput(rawInput, title) {
|
|
588
|
-
const subagentType = readFirstString(rawInput, ["subagent_type", "subagentType", "agent", "agentType"]);
|
|
589
|
-
const description = readFirstString(rawInput, ["description", "task", "summary"]);
|
|
590
|
-
if (subagentType && description) {
|
|
591
|
-
return description === title ? subagentType : `${subagentType}: ${description}`;
|
|
592
|
-
}
|
|
593
|
-
if (subagentType)
|
|
594
|
-
return subagentType;
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
function readFirstString(record, keys) {
|
|
598
|
-
for (const key of keys) {
|
|
599
|
-
const value = record[key];
|
|
600
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
601
|
-
return value.trim();
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
function readFirstStringArray(record, keys) {
|
|
607
|
-
for (const key of keys) {
|
|
608
|
-
const value = record[key];
|
|
609
|
-
if (!Array.isArray(value))
|
|
610
|
-
continue;
|
|
611
|
-
const entries = value.map((entry) => typeof entry === "string" && entry.trim().length > 0 ? entry.trim() : undefined).filter((entry) => entry !== undefined);
|
|
612
|
-
if (entries.length > 0) {
|
|
613
|
-
return entries;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
function isRecord(value) {
|
|
619
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
620
|
-
}
|
|
621
|
-
function readString(rawInput, key) {
|
|
622
|
-
if (!isRecord(rawInput))
|
|
623
|
-
return;
|
|
624
|
-
const value = rawInput[key];
|
|
625
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
626
|
-
}
|
|
627
|
-
function truncateToolDisplay(text) {
|
|
628
|
-
return text.length > 60 ? `${text.slice(0, 57)}...` : text;
|
|
629
|
-
}
|
|
630
|
-
function isGenericToolTitle(kind, title) {
|
|
631
|
-
const normalizedTitle = title.trim().toLowerCase();
|
|
632
|
-
if (kind === "execute" && ["bash", "shell", "sh", "powershell", "cmd"].includes(normalizedTitle)) {
|
|
633
|
-
return true;
|
|
634
|
-
}
|
|
635
|
-
if (kind === "search" && ["grep", "rg", "search"].includes(normalizedTitle)) {
|
|
636
|
-
return true;
|
|
637
|
-
}
|
|
638
|
-
if (kind === "read" && ["read", "cat"].includes(normalizedTitle)) {
|
|
639
|
-
return true;
|
|
640
|
-
}
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
var init_streaming_prompt = __esm(() => {
|
|
644
|
-
init_tool_kind_emoji();
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
// src/recovery/discover-parent-package-paths.ts
|
|
648
|
-
import { spawn } from "node:child_process";
|
|
649
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
650
|
-
import { access } from "node:fs/promises";
|
|
651
|
-
import { homedir } from "node:os";
|
|
652
|
-
import { dirname, join } from "node:path";
|
|
653
|
-
function deriveParentPackageName(platformPackage) {
|
|
654
|
-
return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
|
|
655
|
-
}
|
|
656
|
-
async function discoverParentPackagePaths(platformPackage, seedPath, deps = {}) {
|
|
657
|
-
const env = deps.env ?? process.env;
|
|
658
|
-
const home = deps.home ?? homedir();
|
|
659
|
-
const cwd = deps.cwd ?? process.cwd();
|
|
660
|
-
const fsExists = deps.fsExists ?? defaultFsExists;
|
|
661
|
-
const resolveFromCwd = deps.resolveFromCwd ?? defaultResolveFromCwd;
|
|
662
|
-
const queryRoot = deps.queryPackageManagerRoot ?? defaultQueryPackageManagerRoot;
|
|
663
|
-
const parentName = deriveParentPackageName(platformPackage);
|
|
664
|
-
const rawCandidates = [];
|
|
665
|
-
const bunGlobalRoot = env.BUN_INSTALL ? join(env.BUN_INSTALL, "install", "global", "node_modules") : join(home, ".bun", "install", "global", "node_modules");
|
|
666
|
-
const [npmRoot, pnpmRoot, yarnRoot] = await Promise.all([
|
|
667
|
-
queryRoot("npm"),
|
|
668
|
-
queryRoot("pnpm"),
|
|
669
|
-
queryRoot("yarn")
|
|
670
|
-
]);
|
|
671
|
-
const classify = (p) => {
|
|
672
|
-
if (isUnder(p, bunGlobalRoot))
|
|
673
|
-
return "bun";
|
|
674
|
-
if (pnpmRoot && isUnder(p, pnpmRoot))
|
|
675
|
-
return "pnpm";
|
|
676
|
-
if (yarnRoot && isUnder(p, yarnRoot))
|
|
677
|
-
return "yarn";
|
|
678
|
-
return "npm";
|
|
679
|
-
};
|
|
680
|
-
if (seedPath) {
|
|
681
|
-
rawCandidates.push({ path: seedPath, manager: classify(seedPath) });
|
|
682
|
-
}
|
|
683
|
-
for (const name of [parentName, platformPackage]) {
|
|
684
|
-
const resolved = resolveFromCwd(name, cwd);
|
|
685
|
-
if (resolved)
|
|
686
|
-
rawCandidates.push({ path: resolved, manager: classify(resolved) });
|
|
687
|
-
}
|
|
688
|
-
rawCandidates.push({ path: join(bunGlobalRoot, parentName), manager: "bun" });
|
|
689
|
-
if (npmRoot)
|
|
690
|
-
rawCandidates.push({ path: join(npmRoot, parentName), manager: "npm" });
|
|
691
|
-
if (pnpmRoot)
|
|
692
|
-
rawCandidates.push({ path: join(pnpmRoot, parentName), manager: "pnpm" });
|
|
693
|
-
if (yarnRoot)
|
|
694
|
-
rawCandidates.push({ path: join(yarnRoot, parentName), manager: "yarn" });
|
|
695
|
-
const seen = new Set;
|
|
696
|
-
const verified = [];
|
|
697
|
-
for (const candidate of rawCandidates) {
|
|
698
|
-
if (seen.has(candidate.path))
|
|
699
|
-
continue;
|
|
700
|
-
seen.add(candidate.path);
|
|
701
|
-
if (await fsExists(join(candidate.path, "package.json"))) {
|
|
702
|
-
verified.push(candidate);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
return verified;
|
|
706
|
-
}
|
|
707
|
-
function isUnder(child, parent) {
|
|
708
|
-
const c = child.replace(/[\\/]+$/, "");
|
|
709
|
-
const p = parent.replace(/[\\/]+$/, "");
|
|
710
|
-
return c === p || c.startsWith(p + "/") || c.startsWith(p + "\\");
|
|
711
|
-
}
|
|
712
|
-
async function defaultFsExists(path2) {
|
|
713
|
-
try {
|
|
714
|
-
await access(path2);
|
|
715
|
-
return true;
|
|
716
|
-
} catch {
|
|
717
|
-
return false;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
function defaultResolveFromCwd(name, cwd) {
|
|
721
|
-
try {
|
|
722
|
-
const pkgJson = require2.resolve(`${name}/package.json`, {
|
|
723
|
-
paths: [cwd, ...require2.resolve.paths(name) ?? []]
|
|
724
|
-
});
|
|
725
|
-
return dirname(pkgJson);
|
|
726
|
-
} catch {
|
|
727
|
-
return null;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
async function defaultQueryPackageManagerRoot(tool) {
|
|
731
|
-
const spec = tool === "yarn" ? { cmd: "yarn", args: ["global", "dir"], postfix: "node_modules" } : { cmd: tool, args: ["root", "-g"], postfix: null };
|
|
732
|
-
return await new Promise((resolve) => {
|
|
733
|
-
let settled = false;
|
|
734
|
-
const done = (value) => {
|
|
735
|
-
if (settled)
|
|
736
|
-
return;
|
|
737
|
-
settled = true;
|
|
738
|
-
resolve(value);
|
|
739
|
-
};
|
|
740
|
-
let child;
|
|
741
|
-
try {
|
|
742
|
-
child = spawn(spec.cmd, spec.args, {
|
|
743
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
744
|
-
shell: process.platform === "win32"
|
|
745
|
-
});
|
|
746
|
-
} catch {
|
|
747
|
-
done(null);
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
let stdout = "";
|
|
751
|
-
const timer = setTimeout(() => {
|
|
752
|
-
try {
|
|
753
|
-
child.kill();
|
|
754
|
-
} catch {}
|
|
755
|
-
done(null);
|
|
756
|
-
}, 2000);
|
|
757
|
-
timer.unref?.();
|
|
758
|
-
child.stdout.on("data", (chunk) => {
|
|
759
|
-
stdout += String(chunk);
|
|
760
|
-
});
|
|
761
|
-
child.on("error", () => {
|
|
762
|
-
clearTimeout(timer);
|
|
763
|
-
done(null);
|
|
764
|
-
});
|
|
765
|
-
child.on("close", (code) => {
|
|
766
|
-
clearTimeout(timer);
|
|
767
|
-
if (code !== 0)
|
|
768
|
-
return done(null);
|
|
769
|
-
const trimmed = stdout.trim().split(/\r?\n/).pop()?.trim() ?? "";
|
|
770
|
-
if (!trimmed)
|
|
771
|
-
return done(null);
|
|
772
|
-
done(spec.postfix ? join(trimmed, spec.postfix) : trimmed);
|
|
773
|
-
});
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
var require2;
|
|
777
|
-
var init_discover_parent_package_paths = __esm(() => {
|
|
778
|
-
require2 = createRequire2(import.meta.url);
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
// src/process/terminate-process-tree.ts
|
|
782
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
783
|
-
async function terminateProcessTree(pid, options = {}, platform = process.platform, runCommand = defaultRunProcessCommand, killProcess = (targetPid, signal) => {
|
|
784
|
-
process.kill(targetPid, signal);
|
|
785
|
-
}, isProcessRunning = defaultIsProcessRunning) {
|
|
786
|
-
if (pid <= 0) {
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
if (platform === "win32") {
|
|
790
|
-
try {
|
|
791
|
-
await runCommand("taskkill", ["/PID", String(pid), "/T", "/F"]);
|
|
792
|
-
} catch {}
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
const targetPid = options.detachedProcessGroup ? -pid : pid;
|
|
796
|
-
try {
|
|
797
|
-
killProcess(targetPid, "SIGTERM");
|
|
798
|
-
} catch {
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
const deadline = Date.now() + 5000;
|
|
802
|
-
while (Date.now() < deadline) {
|
|
803
|
-
if (!isProcessRunning(targetPid)) {
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
807
|
-
}
|
|
808
|
-
try {
|
|
809
|
-
killProcess(targetPid, "SIGKILL");
|
|
810
|
-
} catch {}
|
|
811
|
-
}
|
|
812
|
-
function defaultIsProcessRunning(pid) {
|
|
813
|
-
try {
|
|
814
|
-
process.kill(pid, 0);
|
|
815
|
-
return true;
|
|
816
|
-
} catch {
|
|
817
|
-
return false;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
async function defaultRunProcessCommand(command, args) {
|
|
821
|
-
return await new Promise((resolve, reject) => {
|
|
822
|
-
const child = spawn2(command, args, { stdio: "ignore" });
|
|
823
|
-
child.on("error", reject);
|
|
824
|
-
child.on("close", (code) => resolve(code ?? 1));
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
var init_terminate_process_tree = () => {};
|
|
828
|
-
|
|
829
|
-
// src/util/text.ts
|
|
830
|
-
function truncateText(text, maxLength, ellipsis = "…") {
|
|
831
|
-
if (text.length <= maxLength)
|
|
832
|
-
return text;
|
|
833
|
-
return text.slice(0, maxLength - ellipsis.length) + ellipsis;
|
|
834
|
-
}
|
|
835
|
-
function escapeForDoubleQuotes(input) {
|
|
836
|
-
return input.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
837
|
-
}
|
|
838
|
-
function quoteIfNeeded(input) {
|
|
839
|
-
return `"${escapeForDoubleQuotes(input)}"`;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// src/transport/acpx-queue-owner-launcher.ts
|
|
843
|
-
import { createHash } from "node:crypto";
|
|
844
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
845
|
-
import { readFile, unlink } from "node:fs/promises";
|
|
846
|
-
import { homedir as homedir2 } from "node:os";
|
|
847
|
-
import { join as join2 } from "node:path";
|
|
848
|
-
function buildWeacpxMcpServerSpec(input) {
|
|
849
|
-
const { command, args } = splitCommandLine(input.weacpxCommand);
|
|
850
|
-
return {
|
|
851
|
-
name: "weacpx",
|
|
852
|
-
type: "stdio",
|
|
853
|
-
command,
|
|
854
|
-
args: [
|
|
855
|
-
...args,
|
|
856
|
-
"mcp-stdio",
|
|
857
|
-
"--coordinator-session",
|
|
858
|
-
input.coordinatorSession,
|
|
859
|
-
...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
|
|
860
|
-
]
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
function buildQueueOwnerPayload(input) {
|
|
864
|
-
return {
|
|
865
|
-
sessionId: input.sessionId,
|
|
866
|
-
permissionMode: input.permissionMode,
|
|
867
|
-
nonInteractivePermissions: input.nonInteractivePermissions,
|
|
868
|
-
ttlMs: input.ttlMs ?? 300000,
|
|
869
|
-
maxQueueDepth: input.maxQueueDepth ?? 16,
|
|
870
|
-
...Number.isFinite(input.promptRetries) ? { promptRetries: input.promptRetries } : {},
|
|
871
|
-
...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
|
|
872
|
-
mcpServers: input.mcpServers
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
class AcpxQueueOwnerLauncher {
|
|
877
|
-
acpxCommand;
|
|
878
|
-
weacpxCommand;
|
|
879
|
-
spawnOwner;
|
|
880
|
-
terminateOwner;
|
|
881
|
-
baseEnv;
|
|
882
|
-
ttlMs;
|
|
883
|
-
maxQueueDepth;
|
|
884
|
-
launchLocks = new Map;
|
|
885
|
-
constructor(options) {
|
|
886
|
-
this.acpxCommand = options.acpxCommand;
|
|
887
|
-
this.weacpxCommand = options.weacpxCommand ?? resolveDefaultWeacpxCommand(options.baseEnv ?? process.env);
|
|
888
|
-
this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
|
|
889
|
-
this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
|
|
890
|
-
this.baseEnv = options.baseEnv ?? process.env;
|
|
891
|
-
this.ttlMs = options.ttlMs;
|
|
892
|
-
this.maxQueueDepth = options.maxQueueDepth;
|
|
893
|
-
}
|
|
894
|
-
async launch(input) {
|
|
895
|
-
const key = input.acpxRecordId;
|
|
896
|
-
const previous = this.launchLocks.get(key) ?? Promise.resolve();
|
|
897
|
-
const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
|
|
898
|
-
this.launchLocks.set(key, next.catch(() => {}));
|
|
899
|
-
return next;
|
|
900
|
-
}
|
|
901
|
-
async doLaunch(input) {
|
|
902
|
-
await this.terminateOwner(input.acpxRecordId);
|
|
903
|
-
const payload = buildQueueOwnerPayload({
|
|
904
|
-
sessionId: input.acpxRecordId,
|
|
905
|
-
permissionMode: input.permissionMode,
|
|
906
|
-
nonInteractivePermissions: input.nonInteractivePermissions,
|
|
907
|
-
ttlMs: this.ttlMs,
|
|
908
|
-
maxQueueDepth: this.maxQueueDepth,
|
|
909
|
-
mcpServers: [buildWeacpxMcpServerSpec({
|
|
910
|
-
weacpxCommand: this.weacpxCommand,
|
|
911
|
-
coordinatorSession: input.coordinatorSession,
|
|
912
|
-
...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
|
|
913
|
-
})]
|
|
914
|
-
});
|
|
915
|
-
const spawnSpec = resolveSpawnCommand(this.acpxCommand, ["__queue-owner"]);
|
|
916
|
-
await this.spawnOwner(spawnSpec.command, spawnSpec.args, {
|
|
917
|
-
env: {
|
|
918
|
-
...stringEnv(this.baseEnv),
|
|
919
|
-
ACPX_QUEUE_OWNER_PAYLOAD: JSON.stringify(payload)
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
function splitCommandLine(value) {
|
|
925
|
-
const parts = [];
|
|
926
|
-
let current = "";
|
|
927
|
-
let quote = null;
|
|
928
|
-
let escaping = false;
|
|
929
|
-
for (const char of value) {
|
|
930
|
-
if (escaping) {
|
|
931
|
-
current += char;
|
|
932
|
-
escaping = false;
|
|
933
|
-
continue;
|
|
934
|
-
}
|
|
935
|
-
if (char === "\\" && quote !== "'") {
|
|
936
|
-
escaping = true;
|
|
937
|
-
continue;
|
|
938
|
-
}
|
|
939
|
-
if (quote) {
|
|
940
|
-
if (char === quote) {
|
|
941
|
-
quote = null;
|
|
942
|
-
} else {
|
|
943
|
-
current += char;
|
|
944
|
-
}
|
|
945
|
-
continue;
|
|
946
|
-
}
|
|
947
|
-
if (char === "'" || char === '"') {
|
|
948
|
-
quote = char;
|
|
949
|
-
continue;
|
|
950
|
-
}
|
|
951
|
-
if (/\s/.test(char)) {
|
|
952
|
-
if (current.length > 0) {
|
|
953
|
-
parts.push(current);
|
|
954
|
-
current = "";
|
|
955
|
-
}
|
|
956
|
-
continue;
|
|
957
|
-
}
|
|
958
|
-
current += char;
|
|
959
|
-
}
|
|
960
|
-
if (escaping) {
|
|
961
|
-
current += "\\";
|
|
962
|
-
}
|
|
963
|
-
if (quote) {
|
|
964
|
-
throw new Error("weacpx MCP command has an unterminated quote");
|
|
965
|
-
}
|
|
966
|
-
if (current.length > 0) {
|
|
967
|
-
parts.push(current);
|
|
968
|
-
}
|
|
969
|
-
if (parts.length === 0) {
|
|
970
|
-
throw new Error("weacpx MCP command must not be empty");
|
|
971
|
-
}
|
|
972
|
-
return { command: parts[0], args: parts.slice(1) };
|
|
973
|
-
}
|
|
974
|
-
function stringEnv(env) {
|
|
975
|
-
return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
|
|
976
|
-
}
|
|
977
|
-
async function defaultQueueOwnerSpawner(command, args, options) {
|
|
978
|
-
await new Promise((resolve, reject) => {
|
|
979
|
-
const child = spawn3(command, args, {
|
|
980
|
-
detached: true,
|
|
981
|
-
stdio: "ignore",
|
|
982
|
-
env: options.env,
|
|
983
|
-
windowsHide: true
|
|
984
|
-
});
|
|
985
|
-
child.once("error", reject);
|
|
986
|
-
child.once("spawn", () => {
|
|
987
|
-
child.unref();
|
|
988
|
-
resolve();
|
|
989
|
-
});
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
function createDefaultQueueOwnerTerminator(_acpxCommand) {
|
|
993
|
-
return async (sessionId) => {
|
|
994
|
-
await terminateAcpxQueueOwner(sessionId);
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
async function terminateAcpxQueueOwner(sessionId) {
|
|
998
|
-
const lockPath = queueLockFilePath(sessionId);
|
|
999
|
-
let owner;
|
|
1000
|
-
try {
|
|
1001
|
-
owner = JSON.parse(await readFile(lockPath, "utf8"));
|
|
1002
|
-
} catch {
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
1006
|
-
await terminateProcessTree(owner.pid, { detachedProcessGroup: true });
|
|
1007
|
-
}
|
|
1008
|
-
await unlink(lockPath).catch(() => {});
|
|
1009
|
-
}
|
|
1010
|
-
function queueLockFilePath(sessionId) {
|
|
1011
|
-
return join2(homedir2(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
|
|
1012
|
-
}
|
|
1013
|
-
function shortHash(value, length) {
|
|
1014
|
-
return createHash("sha256").update(value).digest("hex").slice(0, length);
|
|
1015
|
-
}
|
|
1016
|
-
function resolveDefaultWeacpxCommand(env) {
|
|
1017
|
-
if (env.WEACPX_CLI_COMMAND?.trim()) {
|
|
1018
|
-
return env.WEACPX_CLI_COMMAND.trim();
|
|
1019
|
-
}
|
|
1020
|
-
if (env.WEACPX_DAEMON_ARG0?.trim()) {
|
|
1021
|
-
return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(env.WEACPX_DAEMON_ARG0.trim())}`;
|
|
1022
|
-
}
|
|
1023
|
-
if (process.argv[1]) {
|
|
1024
|
-
return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(process.argv[1])}`;
|
|
1025
|
-
}
|
|
1026
|
-
return "weacpx";
|
|
1027
|
-
}
|
|
1028
|
-
function quoteCommandPart(value) {
|
|
1029
|
-
return quoteIfNeeded(value);
|
|
1030
|
-
}
|
|
1031
|
-
var init_acpx_queue_owner_launcher = __esm(() => {
|
|
1032
|
-
init_spawn_command();
|
|
1033
|
-
init_terminate_process_tree();
|
|
1034
|
-
});
|
|
1035
|
-
|
|
1036
|
-
// src/transport/permission-mode-flag.ts
|
|
1037
|
-
function permissionModeToFlag(permissionMode) {
|
|
1038
|
-
switch (permissionMode) {
|
|
1039
|
-
case "approve-reads":
|
|
1040
|
-
return "--approve-reads";
|
|
1041
|
-
case "deny-all":
|
|
1042
|
-
return "--deny-all";
|
|
1043
|
-
case "approve-all":
|
|
1044
|
-
return "--approve-all";
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
// src/util/path.ts
|
|
1049
|
-
import path2 from "node:path";
|
|
1050
|
-
import { homedir as homedir3 } from "node:os";
|
|
1051
|
-
function normalizePath(input) {
|
|
1052
|
-
const expanded = expandHome(input);
|
|
1053
|
-
if (isWindowsLikePath(expanded)) {
|
|
1054
|
-
return path2.win32.normalize(expanded).replace(/\\/g, "/");
|
|
1055
|
-
}
|
|
1056
|
-
return path2.posix.normalize(expanded.replace(/\\/g, "/"));
|
|
1057
|
-
}
|
|
1058
|
-
function basenameForPath(input) {
|
|
1059
|
-
const normalized = normalizePath(input);
|
|
1060
|
-
if (ROOT_PATH_RE.test(normalized)) {
|
|
1061
|
-
return normalized;
|
|
1062
|
-
}
|
|
1063
|
-
const base = path2.posix.basename(normalized);
|
|
1064
|
-
return base || normalized;
|
|
1065
|
-
}
|
|
1066
|
-
function isSamePath(left, right) {
|
|
1067
|
-
const normalizedLeft = normalizePath(left);
|
|
1068
|
-
const normalizedRight = normalizePath(right);
|
|
1069
|
-
if (isWindowsLikePath(normalizedLeft) || isWindowsLikePath(normalizedRight)) {
|
|
1070
|
-
return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
|
|
1071
|
-
}
|
|
1072
|
-
return normalizedLeft === normalizedRight;
|
|
1073
|
-
}
|
|
1074
|
-
function isWindowsLikePath(input) {
|
|
1075
|
-
return WINDOWS_DRIVE_PATH_RE.test(input) || WINDOWS_UNC_PATH_RE.test(input);
|
|
1076
|
-
}
|
|
1077
|
-
function expandHome(input) {
|
|
1078
|
-
return input.startsWith("~") ? homedir3() + input.slice(1) : input;
|
|
1079
|
-
}
|
|
1080
|
-
var WINDOWS_DRIVE_PATH_RE, WINDOWS_UNC_PATH_RE, ROOT_PATH_RE;
|
|
1081
|
-
var init_path = __esm(() => {
|
|
1082
|
-
WINDOWS_DRIVE_PATH_RE = /^[a-zA-Z]:[\\/]/;
|
|
1083
|
-
WINDOWS_UNC_PATH_RE = /^\\\\/;
|
|
1084
|
-
ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
|
|
1085
|
-
});
|
|
1086
|
-
|
|
1087
|
-
// src/transport/agent-session-list.ts
|
|
1088
|
-
function isUnknownFilterCwdOption(output) {
|
|
1089
|
-
return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
|
|
1090
|
-
}
|
|
1091
|
-
async function runAgentSessionList(options) {
|
|
1092
|
-
let result = await options.runList(true);
|
|
1093
|
-
let filterLocally = false;
|
|
1094
|
-
if (result.code !== 0 && options.filterCwd && isUnknownFilterCwdOption(result.stdout + result.stderr)) {
|
|
1095
|
-
result = await options.runList(false);
|
|
1096
|
-
filterLocally = true;
|
|
1097
|
-
}
|
|
1098
|
-
if (result.code !== 0) {
|
|
1099
|
-
if ((result.stdout + result.stderr).includes("sessionCapabilities.list")) {
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
throw new Error(options.formatError(result));
|
|
1103
|
-
}
|
|
1104
|
-
return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
|
|
1105
|
-
}
|
|
1106
|
-
function parseAgentSessionListOutput(stdout, filterCwd) {
|
|
1107
|
-
let parsed;
|
|
1108
|
-
try {
|
|
1109
|
-
parsed = JSON.parse(stdout);
|
|
1110
|
-
} catch {
|
|
1111
|
-
throw new Error("failed to parse acpx sessions list output");
|
|
1112
|
-
}
|
|
1113
|
-
if (!isAgentSessionListResult(parsed)) {
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
return filterCwd ? filterAgentSessionListByCwd(parsed, filterCwd) : parsed;
|
|
1117
|
-
}
|
|
1118
|
-
function isAgentSessionListResult(value) {
|
|
1119
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1120
|
-
return false;
|
|
1121
|
-
const record = value;
|
|
1122
|
-
if (record.source !== "agent" || !Array.isArray(record.sessions))
|
|
1123
|
-
return false;
|
|
1124
|
-
return record.sessions.every((session) => {
|
|
1125
|
-
if (!session || typeof session !== "object" || Array.isArray(session))
|
|
1126
|
-
return false;
|
|
1127
|
-
const item = session;
|
|
1128
|
-
return typeof item.sessionId === "string";
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
function filterAgentSessionListByCwd(result, cwd) {
|
|
1132
|
-
return {
|
|
1133
|
-
...result,
|
|
1134
|
-
sessions: result.sessions.filter((session) => session.cwd && isSamePath(session.cwd, cwd))
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
var init_agent_session_list = __esm(() => {
|
|
1138
|
-
init_path();
|
|
1139
|
-
});
|
|
1140
|
-
|
|
1141
|
-
// src/bridge/bridge-main.ts
|
|
1142
|
-
import { createInterface } from "node:readline";
|
|
1143
|
-
|
|
1144
|
-
// src/bridge/bridge-env.ts
|
|
1145
|
-
function normalizeBridgePermissionMode(value) {
|
|
1146
|
-
return value === "approve-reads" || value === "deny-all" || value === "approve-all" ? value : "approve-all";
|
|
1147
|
-
}
|
|
1148
|
-
function normalizeBridgeNonInteractivePermissions(value) {
|
|
1149
|
-
return value === "deny" || value === "fail" ? value : "deny";
|
|
1150
|
-
}
|
|
1151
|
-
function normalizeBridgeQueueOwnerTtlSeconds(value) {
|
|
1152
|
-
if (value === undefined) {
|
|
1153
|
-
return;
|
|
1154
|
-
}
|
|
1155
|
-
const parsed = Number(value);
|
|
1156
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// src/bridge/bridge-server.ts
|
|
1160
|
-
init_prompt_output();
|
|
1161
|
-
|
|
1162
|
-
// src/bridge/bridge-request-scheduler.ts
|
|
1163
|
-
class BridgeRequestScheduler {
|
|
1164
|
-
sessions = new Map;
|
|
1165
|
-
run(sessionName, lane, task) {
|
|
1166
|
-
if (lane === "control") {
|
|
1167
|
-
return Promise.resolve().then(task);
|
|
1168
|
-
}
|
|
1169
|
-
const state = this.sessions.get(sessionName) ?? this.createSessionState(sessionName);
|
|
1170
|
-
state.pendingNormals += 1;
|
|
1171
|
-
const result = state.tail.then(() => task());
|
|
1172
|
-
state.tail = result.then(() => {
|
|
1173
|
-
return;
|
|
1174
|
-
}, () => {
|
|
1175
|
-
return;
|
|
1176
|
-
});
|
|
1177
|
-
return result.finally(() => {
|
|
1178
|
-
state.pendingNormals -= 1;
|
|
1179
|
-
if (state.pendingNormals === 0 && this.sessions.get(sessionName) === state) {
|
|
1180
|
-
this.sessions.delete(sessionName);
|
|
1181
|
-
}
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
createSessionState(sessionName) {
|
|
1185
|
-
const state = {
|
|
1186
|
-
pendingNormals: 0,
|
|
1187
|
-
tail: Promise.resolve()
|
|
1188
|
-
};
|
|
1189
|
-
this.sessions.set(sessionName, state);
|
|
1190
|
-
return state;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// src/bridge/bridge-runtime.ts
|
|
1195
|
-
init_spawn_command();
|
|
1196
|
-
init_prompt_output();
|
|
1197
|
-
init_prompt_media();
|
|
1198
|
-
init_streaming_prompt();
|
|
1199
|
-
import { copyFile, readdir } from "node:fs/promises";
|
|
1200
|
-
import { homedir as homedir4 } from "node:os";
|
|
1201
|
-
import { dirname as dirname2, join as join3, win32 } from "node:path";
|
|
1202
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
1203
|
-
|
|
1204
|
-
// src/bridge/parse-missing-optional-dep.ts
|
|
1205
|
-
var PATTERN = /You can try manually installing ["']([^"']+)["']/;
|
|
1206
|
-
var VALID_NAME = /^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
1207
|
-
function parseMissingOptionalDep(text) {
|
|
1208
|
-
const match = PATTERN.exec(text);
|
|
1209
|
-
if (!match || !match[1])
|
|
1210
|
-
return null;
|
|
1211
|
-
const pkg = match[1];
|
|
1212
|
-
if (!VALID_NAME.test(pkg))
|
|
1213
|
-
return null;
|
|
1214
|
-
return { package: pkg };
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
// src/bridge/bridge-runtime.ts
|
|
1218
|
-
init_discover_parent_package_paths();
|
|
1219
|
-
init_acpx_queue_owner_launcher();
|
|
1220
|
-
init_agent_session_list();
|
|
1221
|
-
class EnsureSessionFailedError extends Error {
|
|
1222
|
-
kind;
|
|
1223
|
-
data;
|
|
1224
|
-
constructor(message, kind, data) {
|
|
1225
|
-
super(message);
|
|
1226
|
-
this.name = "EnsureSessionFailedError";
|
|
1227
|
-
this.kind = kind;
|
|
1228
|
-
this.data = data;
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
class BridgeRuntime {
|
|
1233
|
-
command;
|
|
1234
|
-
run;
|
|
1235
|
-
runSessionCreate;
|
|
1236
|
-
options;
|
|
1237
|
-
runPromptCommand;
|
|
1238
|
-
repairSessionIndex;
|
|
1239
|
-
queueOwnerLauncher;
|
|
1240
|
-
acpxVerboseSupported = undefined;
|
|
1241
|
-
constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner, options = {}, runPromptCommand = defaultPromptRunner, repairSessionIndex = tryRepairAcpxSessionIndex, queueOwnerLauncher = new AcpxQueueOwnerLauncher({
|
|
1242
|
-
acpxCommand: command,
|
|
1243
|
-
...typeof options.queueOwnerTtlSeconds === "number" && Number.isFinite(options.queueOwnerTtlSeconds) ? { ttlMs: options.queueOwnerTtlSeconds * 1000 } : {}
|
|
1244
|
-
})) {
|
|
1245
|
-
this.command = command;
|
|
1246
|
-
this.run = run;
|
|
1247
|
-
this.runSessionCreate = runSessionCreate;
|
|
1248
|
-
this.options = options;
|
|
1249
|
-
this.runPromptCommand = runPromptCommand;
|
|
1250
|
-
this.repairSessionIndex = repairSessionIndex;
|
|
1251
|
-
this.queueOwnerLauncher = queueOwnerLauncher;
|
|
1252
|
-
}
|
|
1253
|
-
async updatePermissionPolicy(policy) {
|
|
1254
|
-
this.options.permissionMode = policy.permissionMode;
|
|
1255
|
-
this.options.nonInteractivePermissions = policy.nonInteractivePermissions;
|
|
1256
|
-
this.options.permissionPolicy = policy.permissionPolicy;
|
|
1257
|
-
return {};
|
|
1258
|
-
}
|
|
1259
|
-
async listAgentSessions(input) {
|
|
1260
|
-
return await runAgentSessionList({
|
|
1261
|
-
filterCwd: input.filterCwd,
|
|
1262
|
-
runList: async (includeFilterCwd) => {
|
|
1263
|
-
const spec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1264
|
-
"sessions",
|
|
1265
|
-
"list",
|
|
1266
|
-
...includeFilterCwd && input.filterCwd ? ["--filter-cwd", input.filterCwd] : [],
|
|
1267
|
-
...input.cursor ? ["--cursor", input.cursor] : []
|
|
1268
|
-
], { format: "json" }));
|
|
1269
|
-
return await this.run(spec.command, spec.args);
|
|
1270
|
-
},
|
|
1271
|
-
formatError: (result) => result.stderr || result.stdout || `sessions list failed with exit code ${result.code}`
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
async resumeAgentSession(input) {
|
|
1275
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1276
|
-
"sessions",
|
|
1277
|
-
"new",
|
|
1278
|
-
"--name",
|
|
1279
|
-
input.name,
|
|
1280
|
-
"--resume-session",
|
|
1281
|
-
input.agentSessionId
|
|
1282
|
-
], { format: "quiet" }));
|
|
1283
|
-
const result = await this.runSessionCreate(spawnSpec.command, spawnSpec.args, input.cwd);
|
|
1284
|
-
if (result.code !== 0) {
|
|
1285
|
-
throw new Error(result.stderr || result.stdout || "sessions resume failed");
|
|
1286
|
-
}
|
|
1287
|
-
return {};
|
|
1288
|
-
}
|
|
1289
|
-
async hasSession(input) {
|
|
1290
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1291
|
-
"sessions",
|
|
1292
|
-
"show",
|
|
1293
|
-
input.name
|
|
1294
|
-
]));
|
|
1295
|
-
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
1296
|
-
return { exists: result.code === 0 };
|
|
1297
|
-
}
|
|
1298
|
-
async tailSessionHistory(input) {
|
|
1299
|
-
const candidates = [
|
|
1300
|
-
["sessions", "history", "quiet", "-s", input.name, String(input.lines)],
|
|
1301
|
-
["sessions", "history", "quiet", input.name, String(input.lines)],
|
|
1302
|
-
["sessions", "history", "-s", input.name, "--tail", String(input.lines)],
|
|
1303
|
-
["sessions", "history", input.name, "--tail", String(input.lines)],
|
|
1304
|
-
["sessions", "history", "--name", input.name, "--tail", String(input.lines)]
|
|
1305
|
-
];
|
|
1306
|
-
let lastResult;
|
|
1307
|
-
for (const tailArgs of candidates) {
|
|
1308
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, tailArgs));
|
|
1309
|
-
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
1310
|
-
if (result.code === 0) {
|
|
1311
|
-
return { text: result.stdout.trimEnd() };
|
|
1312
|
-
}
|
|
1313
|
-
lastResult = result;
|
|
1314
|
-
}
|
|
1315
|
-
const message = lastResult?.stderr || lastResult?.stdout || "sessions history failed";
|
|
1316
|
-
throw new Error(message);
|
|
1317
|
-
}
|
|
1318
|
-
async ensureSession(input, onProgress) {
|
|
1319
|
-
onProgress?.("spawn");
|
|
1320
|
-
const onStderrLine = onProgress ? (line) => {
|
|
1321
|
-
const trimmed = line.replace(/\r$/, "").trimEnd();
|
|
1322
|
-
if (trimmed.length === 0)
|
|
1323
|
-
return;
|
|
1324
|
-
onProgress({ kind: "note", text: trimmed });
|
|
1325
|
-
} : undefined;
|
|
1326
|
-
const runWithVerboseFallback = async (tailArgs, runner) => {
|
|
1327
|
-
const useVerbose = this.acpxVerboseSupported !== false;
|
|
1328
|
-
const spec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, tailArgs, { verbose: useVerbose }));
|
|
1329
|
-
const result = await runner(spec.command, spec.args);
|
|
1330
|
-
if (result.code === 0) {
|
|
1331
|
-
if (useVerbose)
|
|
1332
|
-
this.acpxVerboseSupported = true;
|
|
1333
|
-
return result;
|
|
1334
|
-
}
|
|
1335
|
-
if (useVerbose && isUnknownVerboseOption(result.stderr, result.stdout)) {
|
|
1336
|
-
this.acpxVerboseSupported = false;
|
|
1337
|
-
const retrySpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, tailArgs, { verbose: false }));
|
|
1338
|
-
return await runner(retrySpec.command, retrySpec.args);
|
|
1339
|
-
}
|
|
1340
|
-
return result;
|
|
1341
|
-
};
|
|
1342
|
-
const ensured = await runWithVerboseFallback(["sessions", "ensure", "--name", input.name], (command, args) => this.run(command, args, { onStderrLine }));
|
|
1343
|
-
if (ensured.code === 0) {
|
|
1344
|
-
onProgress?.("ready");
|
|
1345
|
-
return {};
|
|
1346
|
-
}
|
|
1347
|
-
const existingSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, ["sessions", "show", input.name]));
|
|
1348
|
-
const existing = await this.run(existingSpec.command, existingSpec.args);
|
|
1349
|
-
if (existing.code === 0) {
|
|
1350
|
-
onProgress?.("ready");
|
|
1351
|
-
return {};
|
|
1352
|
-
}
|
|
1353
|
-
onProgress?.("initializing");
|
|
1354
|
-
const created = await runWithVerboseFallback(["sessions", "new", "--name", input.name], (command, args) => this.runSessionCreate(command, args, input.cwd, { onStderrLine }));
|
|
1355
|
-
if (created.code === 0) {
|
|
1356
|
-
onProgress?.("ready");
|
|
1357
|
-
return {};
|
|
1358
|
-
}
|
|
1359
|
-
const output = created.stderr || created.stdout || "";
|
|
1360
|
-
if (output.includes("EPERM") && await this.repairSessionIndex()) {
|
|
1361
|
-
const repaired = await this.run(existingSpec.command, existingSpec.args);
|
|
1362
|
-
if (repaired.code === 0) {
|
|
1363
|
-
onProgress?.("ready");
|
|
1364
|
-
return {};
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
const rawMessage = output || ensured.stderr || ensured.stdout || "failed to create session";
|
|
1368
|
-
const parseInput = rawMessage.split(/\r\n|\r|\n/).filter((line) => !/^\s*\[acpx\]/.test(line)).join(`
|
|
1369
|
-
`);
|
|
1370
|
-
const parsed = parseMissingOptionalDep(parseInput);
|
|
1371
|
-
if (parsed) {
|
|
1372
|
-
const parentPackagePath = this.resolveParentPackagePath(input, parsed.package);
|
|
1373
|
-
throw new EnsureSessionFailedError(rawMessage, "missing_optional_dep", {
|
|
1374
|
-
package: parsed.package,
|
|
1375
|
-
parentPackagePath
|
|
1376
|
-
});
|
|
1377
|
-
}
|
|
1378
|
-
throw new EnsureSessionFailedError(rawMessage, "generic");
|
|
1379
|
-
}
|
|
1380
|
-
resolveParentPackagePath(input, platformPackage) {
|
|
1381
|
-
const guess = deriveParentPackageName(platformPackage);
|
|
1382
|
-
const candidates = [input.agentCommand, input.agent, guess].filter((c) => Boolean(c));
|
|
1383
|
-
for (const candidate of candidates) {
|
|
1384
|
-
try {
|
|
1385
|
-
const resolved = __require.resolve(`${candidate}/package.json`, {
|
|
1386
|
-
paths: [process.cwd(), ...__require.resolve.paths(candidate) ?? []]
|
|
1387
|
-
});
|
|
1388
|
-
return dirname2(resolved);
|
|
1389
|
-
} catch {
|
|
1390
|
-
continue;
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
return null;
|
|
1394
|
-
}
|
|
1395
|
-
async prompt(input, onEvent) {
|
|
1396
|
-
await this.launchMcpQueueOwnerIfNeeded(input);
|
|
1397
|
-
const structuredPrompt = await createStructuredPromptFile(input.text, input.media);
|
|
1398
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildPromptArgs(input, [
|
|
1399
|
-
"prompt",
|
|
1400
|
-
"-s",
|
|
1401
|
-
input.name,
|
|
1402
|
-
...structuredPrompt ? ["--file", structuredPrompt.filePath] : [input.text]
|
|
1403
|
-
]));
|
|
1404
|
-
const formatToolCalls = (input.replyMode ?? "verbose") === "verbose";
|
|
1405
|
-
const toolEventMode = input.toolEventMode ?? (input.toolEvents === true ? "structured" : "text");
|
|
1406
|
-
try {
|
|
1407
|
-
const result = onEvent ? await this.runPromptCommand(spawnSpec.command, spawnSpec.args, onEvent, {
|
|
1408
|
-
formatToolCalls,
|
|
1409
|
-
toolEventMode
|
|
1410
|
-
}) : await this.run(spawnSpec.command, spawnSpec.args);
|
|
1411
|
-
return { text: getPromptText(result) };
|
|
1412
|
-
} finally {
|
|
1413
|
-
try {
|
|
1414
|
-
await structuredPrompt?.cleanup();
|
|
1415
|
-
} catch {}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
async launchMcpQueueOwnerIfNeeded(input) {
|
|
1419
|
-
if (!input.mcpCoordinatorSession) {
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1422
|
-
const record = await this.readSessionRecord(input);
|
|
1423
|
-
await this.queueOwnerLauncher.launch({
|
|
1424
|
-
acpxRecordId: record.acpxRecordId,
|
|
1425
|
-
coordinatorSession: input.mcpCoordinatorSession,
|
|
1426
|
-
...input.mcpSourceHandle ? { sourceHandle: input.mcpSourceHandle } : {},
|
|
1427
|
-
permissionMode: this.options.permissionMode ?? "approve-all",
|
|
1428
|
-
nonInteractivePermissions: this.options.nonInteractivePermissions ?? "deny"
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
async readSessionRecord(input) {
|
|
1432
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1433
|
-
"sessions",
|
|
1434
|
-
"show",
|
|
1435
|
-
input.name
|
|
1436
|
-
]));
|
|
1437
|
-
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
1438
|
-
if (result.code !== 0) {
|
|
1439
|
-
throw new Error(result.stderr || result.stdout || "sessions show failed");
|
|
1440
|
-
}
|
|
1441
|
-
try {
|
|
1442
|
-
const parsed = JSON.parse(result.stdout);
|
|
1443
|
-
let acpxRecordId;
|
|
1444
|
-
if (typeof parsed.acpxRecordId === "string") {
|
|
1445
|
-
acpxRecordId = parsed.acpxRecordId;
|
|
1446
|
-
} else if (typeof parsed.id === "string") {
|
|
1447
|
-
acpxRecordId = parsed.id;
|
|
1448
|
-
}
|
|
1449
|
-
if (acpxRecordId) {
|
|
1450
|
-
return { acpxRecordId };
|
|
1451
|
-
}
|
|
1452
|
-
} catch {
|
|
1453
|
-
const firstLine = result.stdout.trim().split(/\r?\n/, 1)[0];
|
|
1454
|
-
if (firstLine && /^[\w.:-]+$/.test(firstLine) && firstLine.length >= 8) {
|
|
1455
|
-
return { acpxRecordId: firstLine };
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
throw new Error("failed to resolve acpx session record id");
|
|
1459
|
-
}
|
|
1460
|
-
async setMode(input) {
|
|
1461
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1462
|
-
"set-mode",
|
|
1463
|
-
"-s",
|
|
1464
|
-
input.name,
|
|
1465
|
-
input.modeId
|
|
1466
|
-
]));
|
|
1467
|
-
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
1468
|
-
if (result.code !== 0) {
|
|
1469
|
-
throw new Error(result.stderr || result.stdout || "set-mode failed");
|
|
1470
|
-
}
|
|
1471
|
-
return {};
|
|
1472
|
-
}
|
|
1473
|
-
async cancel(input) {
|
|
1474
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1475
|
-
"cancel",
|
|
1476
|
-
"-s",
|
|
1477
|
-
input.name
|
|
1478
|
-
]));
|
|
1479
|
-
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
1480
|
-
if (result.code !== 0) {
|
|
1481
|
-
throw new Error(result.stderr || result.stdout || "cancel failed");
|
|
1482
|
-
}
|
|
1483
|
-
return {
|
|
1484
|
-
cancelled: true,
|
|
1485
|
-
message: result.stdout.trim()
|
|
1486
|
-
};
|
|
1487
|
-
}
|
|
1488
|
-
async removeSession(input) {
|
|
1489
|
-
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1490
|
-
"sessions",
|
|
1491
|
-
"close",
|
|
1492
|
-
input.name
|
|
1493
|
-
]));
|
|
1494
|
-
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
1495
|
-
if (result.code === 0) {
|
|
1496
|
-
return {};
|
|
1497
|
-
}
|
|
1498
|
-
if (isMissingBridgeSessionError(result.stderr, result.stdout)) {
|
|
1499
|
-
return {};
|
|
1500
|
-
}
|
|
1501
|
-
throw new Error(result.stderr || result.stdout || "sessions close failed");
|
|
1502
|
-
}
|
|
1503
|
-
async shutdown() {
|
|
1504
|
-
return {};
|
|
1505
|
-
}
|
|
1506
|
-
buildSessionArgs(input, tail, options = {}) {
|
|
1507
|
-
const prefix = [
|
|
1508
|
-
"--format",
|
|
1509
|
-
options.format ?? "quiet",
|
|
1510
|
-
"--cwd",
|
|
1511
|
-
input.cwd,
|
|
1512
|
-
...this.buildPermissionArgs()
|
|
1513
|
-
];
|
|
1514
|
-
if (options.verbose) {
|
|
1515
|
-
prefix.push("--verbose");
|
|
1516
|
-
}
|
|
1517
|
-
if (input.agentCommand) {
|
|
1518
|
-
return [...prefix, "--agent", input.agentCommand, ...tail];
|
|
1519
|
-
}
|
|
1520
|
-
return [...prefix, input.agent, ...tail];
|
|
1521
|
-
}
|
|
1522
|
-
buildPromptArgs(input, tail) {
|
|
1523
|
-
const prefix = [
|
|
1524
|
-
"--format",
|
|
1525
|
-
"json",
|
|
1526
|
-
"--json-strict",
|
|
1527
|
-
"--cwd",
|
|
1528
|
-
input.cwd,
|
|
1529
|
-
...this.buildPermissionArgs(),
|
|
1530
|
-
...this.buildQueueOwnerTtlArgs()
|
|
1531
|
-
];
|
|
1532
|
-
if (input.agentCommand) {
|
|
1533
|
-
return [...prefix, "--agent", input.agentCommand, ...tail];
|
|
1534
|
-
}
|
|
1535
|
-
return [...prefix, input.agent, ...tail];
|
|
1536
|
-
}
|
|
1537
|
-
buildQueueOwnerTtlArgs() {
|
|
1538
|
-
const ttl = this.options.queueOwnerTtlSeconds;
|
|
1539
|
-
if (typeof ttl !== "number" || !Number.isFinite(ttl)) {
|
|
1540
|
-
return [];
|
|
1541
|
-
}
|
|
1542
|
-
return ["--ttl", String(ttl)];
|
|
1543
|
-
}
|
|
1544
|
-
buildPermissionArgs() {
|
|
1545
|
-
const permissionMode = this.options.permissionMode ?? "approve-all";
|
|
1546
|
-
const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "deny";
|
|
1547
|
-
const modeFlag = permissionModeToFlag(permissionMode);
|
|
1548
|
-
const args = [modeFlag, "--non-interactive-permissions", nonInteractivePermissions];
|
|
1549
|
-
if (typeof this.options.permissionPolicy === "string" && this.options.permissionPolicy.trim().length > 0) {
|
|
1550
|
-
args.push("--permission-policy", this.options.permissionPolicy);
|
|
1551
|
-
}
|
|
1552
|
-
return args;
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
function spawnCapture(command, args, options) {
|
|
1556
|
-
return new Promise((resolve, reject) => {
|
|
1557
|
-
const child = spawn4(command, args, { cwd: options?.cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
1558
|
-
child.stdout.setEncoding("utf8");
|
|
1559
|
-
child.stderr.setEncoding("utf8");
|
|
1560
|
-
let stdout = "";
|
|
1561
|
-
let stderr = "";
|
|
1562
|
-
let stderrTail = "";
|
|
1563
|
-
child.stdout.on("data", (chunk) => {
|
|
1564
|
-
stdout += String(chunk);
|
|
1565
|
-
});
|
|
1566
|
-
child.stderr.on("data", (chunk) => {
|
|
1567
|
-
const text = String(chunk);
|
|
1568
|
-
stderr += text;
|
|
1569
|
-
if (!options?.onStderrLine)
|
|
1570
|
-
return;
|
|
1571
|
-
stderrTail += text;
|
|
1572
|
-
const matches = stderrTail.split(/\r\n|\r|\n/);
|
|
1573
|
-
stderrTail = matches.pop() ?? "";
|
|
1574
|
-
for (const line of matches) {
|
|
1575
|
-
options.onStderrLine(line);
|
|
1576
|
-
}
|
|
1577
|
-
});
|
|
1578
|
-
child.on("error", reject);
|
|
1579
|
-
child.on("close", (code) => {
|
|
1580
|
-
if (options?.onStderrLine && stderrTail.length > 0) {
|
|
1581
|
-
options.onStderrLine(stderrTail);
|
|
1582
|
-
}
|
|
1583
|
-
resolve({ code: code ?? 1, stdout, stderr });
|
|
1584
|
-
});
|
|
1585
|
-
});
|
|
1586
|
-
}
|
|
1587
|
-
async function defaultRunner(command, args, options) {
|
|
1588
|
-
return await spawnCapture(command, args, options);
|
|
1589
|
-
}
|
|
1590
|
-
async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
1591
|
-
const spawnPrompt = options.spawnPrompt ?? ((spawnCommand, spawnArgs) => spawn4(spawnCommand, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
|
|
1592
|
-
const setIntervalFn = options.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
|
|
1593
|
-
const clearIntervalFn = options.clearIntervalFn ?? ((timer) => clearInterval(timer));
|
|
1594
|
-
const maxSegmentWaitMs = options.maxSegmentWaitMs ?? 30000;
|
|
1595
|
-
const flushCheckIntervalMs = options.flushCheckIntervalMs ?? 5000;
|
|
1596
|
-
const now = options.now ?? (() => Date.now());
|
|
1597
|
-
return await new Promise((resolve, reject) => {
|
|
1598
|
-
const child = spawnPrompt(command, args);
|
|
1599
|
-
let stdout = "";
|
|
1600
|
-
let stderr = "";
|
|
1601
|
-
const toolEventMode = options.toolEventMode ?? "text";
|
|
1602
|
-
const state = createStreamingPromptState(options.formatToolCalls ?? false, {
|
|
1603
|
-
mode: toolEventMode,
|
|
1604
|
-
...onEvent && (toolEventMode === "structured" || toolEventMode === "both") ? { onToolEvent: (toolEvent) => onEvent({ type: "prompt.tool_event", event: toolEvent }) } : {},
|
|
1605
|
-
...onEvent ? { onThought: (chunk) => onEvent({ type: "prompt.thought", text: chunk }) } : {}
|
|
1606
|
-
});
|
|
1607
|
-
let lastReplyAt = now();
|
|
1608
|
-
const flushBuffer = () => {
|
|
1609
|
-
const remaining = state.buffer.trim();
|
|
1610
|
-
if (remaining.length > 0) {
|
|
1611
|
-
state.buffer = "";
|
|
1612
|
-
onEvent?.({ type: "prompt.segment", text: remaining });
|
|
1613
|
-
lastReplyAt = now();
|
|
1614
|
-
}
|
|
1615
|
-
};
|
|
1616
|
-
const timer = setIntervalFn(() => {
|
|
1617
|
-
if (state.buffer.trim().length > 0 && now() - lastReplyAt >= maxSegmentWaitMs) {
|
|
1618
|
-
flushBuffer();
|
|
1619
|
-
}
|
|
1620
|
-
}, flushCheckIntervalMs);
|
|
1621
|
-
child.stdout.setEncoding("utf8");
|
|
1622
|
-
child.stdout.on("data", (chunk) => {
|
|
1623
|
-
const text = String(chunk);
|
|
1624
|
-
stdout += text;
|
|
1625
|
-
parseStreamingDataChunk(state, text);
|
|
1626
|
-
for (const segment of state.segments.splice(0)) {
|
|
1627
|
-
onEvent?.({ type: "prompt.segment", text: segment });
|
|
1628
|
-
lastReplyAt = now();
|
|
1629
|
-
}
|
|
1630
|
-
});
|
|
1631
|
-
child.stderr.on("data", (chunk) => {
|
|
1632
|
-
stderr += String(chunk);
|
|
1633
|
-
});
|
|
1634
|
-
child.on("error", (error) => {
|
|
1635
|
-
clearIntervalFn(timer);
|
|
1636
|
-
reject(error);
|
|
1637
|
-
});
|
|
1638
|
-
child.on("close", (code) => {
|
|
1639
|
-
clearIntervalFn(timer);
|
|
1640
|
-
const remaining = state.finalize();
|
|
1641
|
-
if (remaining.length > 0) {
|
|
1642
|
-
onEvent?.({ type: "prompt.segment", text: remaining });
|
|
1643
|
-
}
|
|
1644
|
-
resolve({ code: code ?? 1, stdout, stderr });
|
|
1645
|
-
});
|
|
1646
|
-
});
|
|
1647
|
-
}
|
|
1648
|
-
async function defaultPromptRunner(command, args, onEvent, options) {
|
|
1649
|
-
return await runStreamingPrompt(command, args, onEvent, options);
|
|
1650
|
-
}
|
|
1651
|
-
async function shellSessionCreateRunner(command, args, cwd, options) {
|
|
1652
|
-
return await spawnCapture(command, args, { cwd, onStderrLine: options?.onStderrLine });
|
|
1653
|
-
}
|
|
1654
|
-
function selectLatestAcpxSessionIndexTmp(files) {
|
|
1655
|
-
let latestTmp = null;
|
|
1656
|
-
let latestTime = 0;
|
|
1657
|
-
for (const file of files) {
|
|
1658
|
-
const match = file.match(/^index\.json\.\d+\.(\d+)\.tmp$/);
|
|
1659
|
-
if (!match) {
|
|
1660
|
-
continue;
|
|
1661
|
-
}
|
|
1662
|
-
const timestamp = Number(match[1]);
|
|
1663
|
-
if (timestamp > latestTime) {
|
|
1664
|
-
latestTime = timestamp;
|
|
1665
|
-
latestTmp = file;
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
return latestTmp;
|
|
1669
|
-
}
|
|
1670
|
-
async function tryRepairAcpxSessionIndex(deps = {}) {
|
|
1671
|
-
const platform = deps.platform ?? process.platform;
|
|
1672
|
-
if (platform !== "win32") {
|
|
1673
|
-
return false;
|
|
1674
|
-
}
|
|
1675
|
-
const home = deps.home ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir4();
|
|
1676
|
-
if (!home) {
|
|
1677
|
-
return false;
|
|
1678
|
-
}
|
|
1679
|
-
const pathJoin = platform === "win32" ? win32.join : join3;
|
|
1680
|
-
const sessionsDir = pathJoin(home, ".acpx", "sessions");
|
|
1681
|
-
const indexPath = pathJoin(sessionsDir, "index.json");
|
|
1682
|
-
const readdirFn = deps.readdirFn ?? readdir;
|
|
1683
|
-
const copyFileFn = deps.copyFileFn ?? copyFile;
|
|
1684
|
-
let files;
|
|
1685
|
-
try {
|
|
1686
|
-
files = await readdirFn(sessionsDir);
|
|
1687
|
-
} catch {
|
|
1688
|
-
return false;
|
|
1689
|
-
}
|
|
1690
|
-
const latestTmp = selectLatestAcpxSessionIndexTmp(files);
|
|
1691
|
-
if (!latestTmp) {
|
|
1692
|
-
return false;
|
|
1693
|
-
}
|
|
1694
|
-
try {
|
|
1695
|
-
await copyFileFn(pathJoin(sessionsDir, latestTmp), indexPath);
|
|
1696
|
-
return true;
|
|
1697
|
-
} catch {
|
|
1698
|
-
return false;
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
function isUnknownVerboseOption(stderr, stdout) {
|
|
1702
|
-
const combined = `${stderr}
|
|
1703
|
-
${stdout}`;
|
|
1704
|
-
return /(unknown|unrecognized)\b[^\n]*--verbose/i.test(combined);
|
|
1705
|
-
}
|
|
1706
|
-
function isMissingBridgeSessionError(stderr, stdout) {
|
|
1707
|
-
const combined = `${stderr}
|
|
1708
|
-
${stdout}`.toLowerCase();
|
|
1709
|
-
return combined.includes("no named session") || combined.includes("no cwd session") || combined.includes("session not found") || combined.includes("unknown session") || combined.includes("no acpx session found");
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
// src/bridge/bridge-server.ts
|
|
1713
|
-
class BridgeInvalidRequestError extends Error {
|
|
1714
|
-
}
|
|
1715
|
-
var BRIDGE_METHODS = new Set([
|
|
1716
|
-
"ping",
|
|
1717
|
-
"shutdown",
|
|
1718
|
-
"updatePermissionPolicy",
|
|
1719
|
-
"hasSession",
|
|
1720
|
-
"ensureSession",
|
|
1721
|
-
"tailSessionHistory",
|
|
1722
|
-
"listAgentSessions",
|
|
1723
|
-
"resumeAgentSession",
|
|
1724
|
-
"prompt",
|
|
1725
|
-
"setMode",
|
|
1726
|
-
"cancel",
|
|
1727
|
-
"removeSession"
|
|
1728
|
-
]);
|
|
1729
|
-
var SESSION_SCOPED_METHODS = new Set([
|
|
1730
|
-
"hasSession",
|
|
1731
|
-
"ensureSession",
|
|
1732
|
-
"tailSessionHistory",
|
|
1733
|
-
"resumeAgentSession",
|
|
1734
|
-
"prompt",
|
|
1735
|
-
"setMode",
|
|
1736
|
-
"cancel",
|
|
1737
|
-
"removeSession"
|
|
1738
|
-
]);
|
|
1739
|
-
|
|
1740
|
-
class BridgeServer {
|
|
1741
|
-
runtime;
|
|
1742
|
-
scheduler = new BridgeRequestScheduler;
|
|
1743
|
-
constructor(runtime) {
|
|
1744
|
-
this.runtime = runtime;
|
|
1745
|
-
}
|
|
1746
|
-
async handleLine(line, writeLine) {
|
|
1747
|
-
let requestId = extractRequestId(line);
|
|
1748
|
-
try {
|
|
1749
|
-
const request = parseBridgeRequest(line);
|
|
1750
|
-
requestId = request.id;
|
|
1751
|
-
const result = await this.dispatchRequest(request.id, request.method, request.params, writeLine);
|
|
1752
|
-
return `${JSON.stringify({
|
|
1753
|
-
id: request.id,
|
|
1754
|
-
ok: true,
|
|
1755
|
-
result
|
|
1756
|
-
})}
|
|
1757
|
-
`;
|
|
1758
|
-
} catch (error) {
|
|
1759
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1760
|
-
const ensureSessionFields = error instanceof EnsureSessionFailedError ? { kind: error.kind, ...error.data ? { data: error.data } : {} } : {};
|
|
1761
|
-
const promptDetails = error instanceof PromptCommandError ? { details: { exitCode: error.exitCode, stdout: error.stdout, stderr: error.stderr } } : {};
|
|
1762
|
-
return `${JSON.stringify({
|
|
1763
|
-
id: requestId,
|
|
1764
|
-
ok: false,
|
|
1765
|
-
error: {
|
|
1766
|
-
code: error instanceof BridgeInvalidRequestError ? "BRIDGE_INVALID_REQUEST" : "BRIDGE_INTERNAL_ERROR",
|
|
1767
|
-
message,
|
|
1768
|
-
...ensureSessionFields,
|
|
1769
|
-
...promptDetails
|
|
1770
|
-
}
|
|
1771
|
-
})}
|
|
1772
|
-
`;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
async dispatchRequest(requestId, method, params, writeLine) {
|
|
1776
|
-
if (!SESSION_SCOPED_METHODS.has(method)) {
|
|
1777
|
-
return await this.dispatch(requestId, method, params, writeLine);
|
|
1778
|
-
}
|
|
1779
|
-
const sessionName = getSessionName(params);
|
|
1780
|
-
if (!sessionName) {
|
|
1781
|
-
return await this.dispatch(requestId, method, params, writeLine);
|
|
1782
|
-
}
|
|
1783
|
-
const sessionKey = getSessionScheduleKey(params);
|
|
1784
|
-
if (!sessionKey) {
|
|
1785
|
-
return await this.dispatch(requestId, method, params, writeLine);
|
|
1786
|
-
}
|
|
1787
|
-
const lane = method === "cancel" ? "control" : "normal";
|
|
1788
|
-
return await this.scheduler.run(sessionKey, lane, () => this.dispatch(requestId, method, params, writeLine));
|
|
1789
|
-
}
|
|
1790
|
-
async dispatch(requestId, method, params, writeLine) {
|
|
1791
|
-
switch (method) {
|
|
1792
|
-
case "ping":
|
|
1793
|
-
return {};
|
|
1794
|
-
case "shutdown":
|
|
1795
|
-
return await this.runtime.shutdown();
|
|
1796
|
-
case "updatePermissionPolicy":
|
|
1797
|
-
return await this.runtime.updatePermissionPolicy({
|
|
1798
|
-
permissionMode: requirePermissionMode(params, "permissionMode"),
|
|
1799
|
-
nonInteractivePermissions: requireNonInteractivePermissions(params, "nonInteractivePermissions")
|
|
1800
|
-
});
|
|
1801
|
-
case "hasSession":
|
|
1802
|
-
return await this.runtime.hasSession({
|
|
1803
|
-
agent: requireString(params, "agent"),
|
|
1804
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1805
|
-
cwd: requireString(params, "cwd"),
|
|
1806
|
-
name: requireString(params, "name")
|
|
1807
|
-
});
|
|
1808
|
-
case "tailSessionHistory":
|
|
1809
|
-
return await this.runtime.tailSessionHistory({
|
|
1810
|
-
agent: requireString(params, "agent"),
|
|
1811
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1812
|
-
cwd: requireString(params, "cwd"),
|
|
1813
|
-
name: requireString(params, "name"),
|
|
1814
|
-
lines: requirePositiveInt(params, "lines")
|
|
1815
|
-
});
|
|
1816
|
-
case "listAgentSessions":
|
|
1817
|
-
return await this.runtime.listAgentSessions({
|
|
1818
|
-
agent: requireString(params, "agent"),
|
|
1819
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1820
|
-
cwd: requireString(params, "cwd"),
|
|
1821
|
-
cursor: asOptionalString(params.cursor),
|
|
1822
|
-
filterCwd: asOptionalString(params.filterCwd)
|
|
1823
|
-
});
|
|
1824
|
-
case "ensureSession":
|
|
1825
|
-
return await this.runtime.ensureSession({
|
|
1826
|
-
agent: requireString(params, "agent"),
|
|
1827
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1828
|
-
cwd: requireString(params, "cwd"),
|
|
1829
|
-
name: requireString(params, "name"),
|
|
1830
|
-
mcpCoordinatorSession: asOptionalString(params.mcpCoordinatorSession),
|
|
1831
|
-
mcpSourceHandle: asOptionalString(params.mcpSourceHandle)
|
|
1832
|
-
}, (progress) => {
|
|
1833
|
-
if (typeof progress === "string") {
|
|
1834
|
-
writeLine?.(encodeBridgeSessionProgressEvent({
|
|
1835
|
-
id: requestId,
|
|
1836
|
-
event: "session.progress",
|
|
1837
|
-
stage: progress
|
|
1838
|
-
}));
|
|
1839
|
-
} else if (progress.kind === "note") {
|
|
1840
|
-
writeLine?.(encodeBridgeSessionNoteEvent({
|
|
1841
|
-
id: requestId,
|
|
1842
|
-
event: "session.note",
|
|
1843
|
-
text: progress.text
|
|
1844
|
-
}));
|
|
1845
|
-
}
|
|
1846
|
-
});
|
|
1847
|
-
case "prompt":
|
|
1848
|
-
const media = asOptionalPromptMediaInput(params.media);
|
|
1849
|
-
const resolvedToolEventMode = asOptionalToolEventMode(params.toolEventMode);
|
|
1850
|
-
return await this.runtime.prompt({
|
|
1851
|
-
agent: requireString(params, "agent"),
|
|
1852
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1853
|
-
cwd: requireString(params, "cwd"),
|
|
1854
|
-
name: requireString(params, "name"),
|
|
1855
|
-
mcpCoordinatorSession: asOptionalString(params.mcpCoordinatorSession),
|
|
1856
|
-
mcpSourceHandle: asOptionalString(params.mcpSourceHandle),
|
|
1857
|
-
text: requirePromptText(params, media),
|
|
1858
|
-
replyMode: asOptionalReplyMode(params.replyMode),
|
|
1859
|
-
toolEvents: params.toolEvents === true,
|
|
1860
|
-
...resolvedToolEventMode ? { toolEventMode: resolvedToolEventMode } : {},
|
|
1861
|
-
media
|
|
1862
|
-
}, (event) => {
|
|
1863
|
-
if (event.type === "prompt.segment") {
|
|
1864
|
-
writeLine?.(encodeBridgePromptSegmentEvent({
|
|
1865
|
-
id: requestId,
|
|
1866
|
-
event: "prompt.segment",
|
|
1867
|
-
text: event.text
|
|
1868
|
-
}));
|
|
1869
|
-
} else if (event.type === "prompt.tool_event") {
|
|
1870
|
-
writeLine?.(encodeBridgePromptToolEvent({
|
|
1871
|
-
id: requestId,
|
|
1872
|
-
event: "prompt.tool_event",
|
|
1873
|
-
toolEvent: event.event
|
|
1874
|
-
}));
|
|
1875
|
-
} else if (event.type === "prompt.thought") {
|
|
1876
|
-
writeLine?.(encodeBridgePromptThoughtEvent({
|
|
1877
|
-
id: requestId,
|
|
1878
|
-
event: "prompt.thought",
|
|
1879
|
-
text: event.text
|
|
1880
|
-
}));
|
|
1881
|
-
}
|
|
1882
|
-
});
|
|
1883
|
-
case "resumeAgentSession":
|
|
1884
|
-
return await this.runtime.resumeAgentSession({
|
|
1885
|
-
agent: requireString(params, "agent"),
|
|
1886
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1887
|
-
cwd: requireString(params, "cwd"),
|
|
1888
|
-
name: requireString(params, "name"),
|
|
1889
|
-
agentSessionId: requireString(params, "agentSessionId")
|
|
1890
|
-
});
|
|
1891
|
-
case "setMode":
|
|
1892
|
-
return await this.runtime.setMode({
|
|
1893
|
-
agent: requireString(params, "agent"),
|
|
1894
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1895
|
-
cwd: requireString(params, "cwd"),
|
|
1896
|
-
name: requireString(params, "name"),
|
|
1897
|
-
modeId: requireString(params, "modeId")
|
|
1898
|
-
});
|
|
1899
|
-
case "cancel":
|
|
1900
|
-
return await this.runtime.cancel({
|
|
1901
|
-
agent: requireString(params, "agent"),
|
|
1902
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1903
|
-
cwd: requireString(params, "cwd"),
|
|
1904
|
-
name: requireString(params, "name")
|
|
1905
|
-
});
|
|
1906
|
-
case "removeSession":
|
|
1907
|
-
return await this.runtime.removeSession({
|
|
1908
|
-
agent: requireString(params, "agent"),
|
|
1909
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
1910
|
-
cwd: requireString(params, "cwd"),
|
|
1911
|
-
name: requireString(params, "name")
|
|
1912
|
-
});
|
|
1913
|
-
default:
|
|
1914
|
-
throw new Error(`unsupported bridge method: ${method}`);
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
function extractRequestId(line) {
|
|
1919
|
-
try {
|
|
1920
|
-
const raw = JSON.parse(line);
|
|
1921
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1922
|
-
return "unknown";
|
|
1923
|
-
}
|
|
1924
|
-
const id = raw.id;
|
|
1925
|
-
return typeof id === "string" && id.length > 0 ? id : "unknown";
|
|
1926
|
-
} catch {
|
|
1927
|
-
return "unknown";
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
function parseBridgeRequest(line) {
|
|
1931
|
-
let raw;
|
|
1932
|
-
try {
|
|
1933
|
-
raw = JSON.parse(line);
|
|
1934
|
-
} catch {
|
|
1935
|
-
throw new BridgeInvalidRequestError("request must be valid JSON");
|
|
1936
|
-
}
|
|
1937
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1938
|
-
throw new BridgeInvalidRequestError("request must be a JSON object");
|
|
1939
|
-
}
|
|
1940
|
-
const request = raw;
|
|
1941
|
-
const id = request.id;
|
|
1942
|
-
const method = request.method;
|
|
1943
|
-
const params = request.params;
|
|
1944
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
1945
|
-
throw new BridgeInvalidRequestError("id must be a non-empty string");
|
|
1946
|
-
}
|
|
1947
|
-
if (typeof method !== "string" || method.length === 0) {
|
|
1948
|
-
throw new BridgeInvalidRequestError("method must be a non-empty string");
|
|
1949
|
-
}
|
|
1950
|
-
if (!BRIDGE_METHODS.has(method)) {
|
|
1951
|
-
throw new BridgeInvalidRequestError(`unsupported bridge method: ${method}`);
|
|
1952
|
-
}
|
|
1953
|
-
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
1954
|
-
throw new BridgeInvalidRequestError("params must be an object");
|
|
1955
|
-
}
|
|
1956
|
-
return {
|
|
1957
|
-
id,
|
|
1958
|
-
method,
|
|
1959
|
-
params
|
|
1960
|
-
};
|
|
1961
|
-
}
|
|
1962
|
-
function getSessionName(params) {
|
|
1963
|
-
return asNonEmptyString(params.name);
|
|
1964
|
-
}
|
|
1965
|
-
function getSessionScheduleKey(params) {
|
|
1966
|
-
const name = asNonEmptyString(params.name);
|
|
1967
|
-
const cwd = asNonEmptyString(params.cwd);
|
|
1968
|
-
const agentIdentity = asNonEmptyString(params.agentCommand) ?? asNonEmptyString(params.agent);
|
|
1969
|
-
if (!name || !cwd || !agentIdentity) {
|
|
1970
|
-
return;
|
|
1971
|
-
}
|
|
1972
|
-
return JSON.stringify([agentIdentity, cwd, name]);
|
|
1973
|
-
}
|
|
1974
|
-
function asNonEmptyString(value) {
|
|
1975
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
1976
|
-
return;
|
|
1977
|
-
}
|
|
1978
|
-
return value;
|
|
1979
|
-
}
|
|
1980
|
-
function requireString(params, key) {
|
|
1981
|
-
const value = params[key];
|
|
1982
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
1983
|
-
throw new BridgeInvalidRequestError(`${key} must be a non-empty string`);
|
|
1984
|
-
}
|
|
1985
|
-
return value;
|
|
1986
|
-
}
|
|
1987
|
-
function requirePositiveInt(params, key) {
|
|
1988
|
-
const value = params[key];
|
|
1989
|
-
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
1990
|
-
throw new BridgeInvalidRequestError(`${key} must be a positive integer`);
|
|
1991
|
-
}
|
|
1992
|
-
return value;
|
|
1993
|
-
}
|
|
1994
|
-
function requirePromptText(params, media) {
|
|
1995
|
-
const value = params.text;
|
|
1996
|
-
if (typeof value !== "string") {
|
|
1997
|
-
throw new BridgeInvalidRequestError("text must be a non-empty string");
|
|
1998
|
-
}
|
|
1999
|
-
const hasMedia = Array.isArray(media) ? media.length > 0 : Boolean(media);
|
|
2000
|
-
if (value.length === 0 && !hasMedia) {
|
|
2001
|
-
throw new BridgeInvalidRequestError("text must be a non-empty string unless media is provided");
|
|
2002
|
-
}
|
|
2003
|
-
return value;
|
|
2004
|
-
}
|
|
2005
|
-
function requirePermissionMode(params, key) {
|
|
2006
|
-
const value = params[key];
|
|
2007
|
-
if (value === "approve-all" || value === "approve-reads" || value === "deny-all") {
|
|
2008
|
-
return value;
|
|
2009
|
-
}
|
|
2010
|
-
throw new BridgeInvalidRequestError(`${key} must be approve-all, approve-reads, or deny-all`);
|
|
2011
|
-
}
|
|
2012
|
-
function requireNonInteractivePermissions(params, key) {
|
|
2013
|
-
const value = params[key];
|
|
2014
|
-
if (value === "deny" || value === "fail") {
|
|
2015
|
-
return value;
|
|
2016
|
-
}
|
|
2017
|
-
throw new BridgeInvalidRequestError(`${key} must be deny or fail`);
|
|
2018
|
-
}
|
|
2019
|
-
function asOptionalString(value) {
|
|
2020
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
return value;
|
|
2024
|
-
}
|
|
2025
|
-
function asOptionalPromptMediaInput(value) {
|
|
2026
|
-
if (value === undefined)
|
|
2027
|
-
return;
|
|
2028
|
-
if (Array.isArray(value))
|
|
2029
|
-
return value.map(asPromptMedia);
|
|
2030
|
-
return asPromptMedia(value);
|
|
2031
|
-
}
|
|
2032
|
-
function asPromptMedia(value) {
|
|
2033
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2034
|
-
throw new BridgeInvalidRequestError("media must be an object or array of objects when provided");
|
|
2035
|
-
}
|
|
2036
|
-
const record = value;
|
|
2037
|
-
const type = record.type;
|
|
2038
|
-
if (type !== "image" && type !== "audio" && type !== "video" && type !== "file") {
|
|
2039
|
-
throw new BridgeInvalidRequestError("media.type must be image, audio, video, or file");
|
|
2040
|
-
}
|
|
2041
|
-
if (typeof record.filePath !== "string" || record.filePath.trim().length === 0) {
|
|
2042
|
-
throw new BridgeInvalidRequestError("media.filePath must be a non-empty string");
|
|
2043
|
-
}
|
|
2044
|
-
if (typeof record.mimeType !== "string" || record.mimeType.trim().length === 0) {
|
|
2045
|
-
throw new BridgeInvalidRequestError("media.mimeType must be a non-empty string");
|
|
2046
|
-
}
|
|
2047
|
-
return {
|
|
2048
|
-
type,
|
|
2049
|
-
filePath: record.filePath,
|
|
2050
|
-
mimeType: record.mimeType,
|
|
2051
|
-
...typeof record.fileName === "string" && record.fileName ? { fileName: record.fileName } : {}
|
|
2052
|
-
};
|
|
2053
|
-
}
|
|
2054
|
-
var VALID_REPLY_MODES = new Set(["stream", "final", "verbose"]);
|
|
2055
|
-
function asOptionalReplyMode(value) {
|
|
2056
|
-
if (typeof value !== "string" || !VALID_REPLY_MODES.has(value)) {
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
return value;
|
|
2060
|
-
}
|
|
2061
|
-
var VALID_TOOL_EVENT_MODES = new Set(["text", "structured", "both"]);
|
|
2062
|
-
function asOptionalToolEventMode(value) {
|
|
2063
|
-
if (typeof value !== "string" || !VALID_TOOL_EVENT_MODES.has(value)) {
|
|
2064
|
-
return;
|
|
2065
|
-
}
|
|
2066
|
-
return value;
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
// src/bridge/bridge-main.ts
|
|
2070
|
-
async function processBridgeInput(options) {
|
|
2071
|
-
const pendingWrites = new Set;
|
|
2072
|
-
let firstError;
|
|
2073
|
-
for await (const line of options.input) {
|
|
2074
|
-
const pendingWrite = (async () => {
|
|
2075
|
-
const response = await options.server.handleLine(line, (chunk) => {
|
|
2076
|
-
options.write(chunk);
|
|
2077
|
-
});
|
|
2078
|
-
options.write(response);
|
|
2079
|
-
})();
|
|
2080
|
-
const observedPendingWrite = pendingWrite.catch((error) => {
|
|
2081
|
-
if (firstError === undefined) {
|
|
2082
|
-
firstError = error;
|
|
2083
|
-
options.input.close();
|
|
2084
|
-
}
|
|
2085
|
-
});
|
|
2086
|
-
pendingWrites.add(pendingWrite);
|
|
2087
|
-
observedPendingWrite.finally(() => {
|
|
2088
|
-
pendingWrites.delete(pendingWrite);
|
|
2089
|
-
});
|
|
2090
|
-
}
|
|
2091
|
-
await Promise.allSettled(pendingWrites);
|
|
2092
|
-
if (firstError !== undefined) {
|
|
2093
|
-
throw firstError;
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
async function runBridgeMain() {
|
|
2097
|
-
const server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx", undefined, undefined, {
|
|
2098
|
-
permissionMode: normalizeBridgePermissionMode(process.env.WEACPX_BRIDGE_PERMISSION_MODE),
|
|
2099
|
-
nonInteractivePermissions: normalizeBridgeNonInteractivePermissions(process.env.WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS),
|
|
2100
|
-
queueOwnerTtlSeconds: normalizeBridgeQueueOwnerTtlSeconds(process.env.WEACPX_BRIDGE_QUEUE_OWNER_TTL_SECONDS)
|
|
2101
|
-
}));
|
|
2102
|
-
const input = createInterface({
|
|
2103
|
-
input: process.stdin,
|
|
2104
|
-
crlfDelay: Infinity
|
|
2105
|
-
});
|
|
2106
|
-
await processBridgeInput({
|
|
2107
|
-
input,
|
|
2108
|
-
server,
|
|
2109
|
-
write: (chunk) => {
|
|
2110
|
-
process.stdout.write(chunk);
|
|
2111
|
-
}
|
|
2112
|
-
});
|
|
2113
|
-
}
|
|
2114
|
-
if (__require.main == __require.module) {
|
|
2115
|
-
await runBridgeMain();
|
|
2116
|
-
}
|
|
2117
|
-
export {
|
|
2118
|
-
runBridgeMain,
|
|
2119
|
-
processBridgeInput
|
|
2120
|
-
};
|