@openacp/cli 0.2.5 → 0.2.12
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 +25 -25
- package/dist/chunk-5KBEVENA.js +412 -0
- package/dist/chunk-5KBEVENA.js.map +1 -0
- package/dist/chunk-TZGP3JSE.js +1818 -0
- package/dist/chunk-TZGP3JSE.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +153 -45
- package/dist/index.js +15 -10
- package/dist/index.js.map +1 -1
- package/dist/{main-66K7T7MJ.js → main-ZQ5RNL3X.js} +31 -29
- package/dist/main-ZQ5RNL3X.js.map +1 -0
- package/dist/{setup-NLOC2GFD.js → setup-4EBTX2NJ.js} +4 -33
- package/dist/setup-4EBTX2NJ.js.map +1 -0
- package/package.json +6 -3
- package/dist/chunk-6YLIH7L5.js +0 -669
- package/dist/chunk-6YLIH7L5.js.map +0 -1
- package/dist/chunk-M5ZYTPZY.js +0 -220
- package/dist/chunk-M5ZYTPZY.js.map +0 -1
- package/dist/main-66K7T7MJ.js.map +0 -1
- package/dist/setup-NLOC2GFD.js.map +0 -1
package/dist/chunk-6YLIH7L5.js
DELETED
|
@@ -1,669 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
log
|
|
3
|
-
} from "./chunk-M5ZYTPZY.js";
|
|
4
|
-
|
|
5
|
-
// packages/core/src/streams.ts
|
|
6
|
-
function nodeToWebWritable(nodeStream) {
|
|
7
|
-
return new WritableStream({
|
|
8
|
-
write(chunk) {
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
nodeStream.write(Buffer.from(chunk), (err) => {
|
|
11
|
-
if (err) reject(err);
|
|
12
|
-
else resolve();
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function nodeToWebReadable(nodeStream) {
|
|
19
|
-
return new ReadableStream({
|
|
20
|
-
start(controller) {
|
|
21
|
-
nodeStream.on("data", (chunk) => {
|
|
22
|
-
controller.enqueue(new Uint8Array(chunk));
|
|
23
|
-
});
|
|
24
|
-
nodeStream.on("end", () => controller.close());
|
|
25
|
-
nodeStream.on("error", (err) => controller.error(err));
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// packages/core/src/stderr-capture.ts
|
|
31
|
-
var StderrCapture = class {
|
|
32
|
-
constructor(maxLines = 50) {
|
|
33
|
-
this.maxLines = maxLines;
|
|
34
|
-
}
|
|
35
|
-
lines = [];
|
|
36
|
-
append(chunk) {
|
|
37
|
-
this.lines.push(...chunk.split("\n").filter(Boolean));
|
|
38
|
-
if (this.lines.length > this.maxLines) {
|
|
39
|
-
this.lines = this.lines.slice(-this.maxLines);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
getLastLines() {
|
|
43
|
-
return this.lines.join("\n");
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// packages/core/src/agent-instance.ts
|
|
48
|
-
import { spawn, execSync } from "child_process";
|
|
49
|
-
import fs from "fs";
|
|
50
|
-
import path from "path";
|
|
51
|
-
import { randomUUID } from "crypto";
|
|
52
|
-
import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
53
|
-
function resolveAgentCommand(cmd) {
|
|
54
|
-
const packageDirs = [
|
|
55
|
-
path.resolve(process.cwd(), "node_modules", "@zed-industries", cmd, "dist", "index.js"),
|
|
56
|
-
path.resolve(process.cwd(), "node_modules", cmd, "dist", "index.js")
|
|
57
|
-
];
|
|
58
|
-
for (const jsPath of packageDirs) {
|
|
59
|
-
if (fs.existsSync(jsPath)) {
|
|
60
|
-
return { command: process.execPath, args: [jsPath] };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const localBin = path.resolve(process.cwd(), "node_modules", ".bin", cmd);
|
|
64
|
-
if (fs.existsSync(localBin)) {
|
|
65
|
-
const content = fs.readFileSync(localBin, "utf-8");
|
|
66
|
-
if (content.startsWith("#!/usr/bin/env node")) {
|
|
67
|
-
return { command: process.execPath, args: [localBin] };
|
|
68
|
-
}
|
|
69
|
-
const match = content.match(/"([^"]+\.js)"/);
|
|
70
|
-
if (match) {
|
|
71
|
-
const target = path.resolve(path.dirname(localBin), match[1]);
|
|
72
|
-
if (fs.existsSync(target)) {
|
|
73
|
-
return { command: process.execPath, args: [target] };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const fullPath = execSync(`which ${cmd}`, { encoding: "utf-8" }).trim();
|
|
79
|
-
if (fullPath) {
|
|
80
|
-
const content = fs.readFileSync(fullPath, "utf-8");
|
|
81
|
-
if (content.startsWith("#!/usr/bin/env node")) {
|
|
82
|
-
return { command: process.execPath, args: [fullPath] };
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
} catch {
|
|
86
|
-
}
|
|
87
|
-
return { command: cmd, args: [] };
|
|
88
|
-
}
|
|
89
|
-
var AgentInstance = class _AgentInstance {
|
|
90
|
-
connection;
|
|
91
|
-
child;
|
|
92
|
-
stderrCapture;
|
|
93
|
-
terminals = /* @__PURE__ */ new Map();
|
|
94
|
-
sessionId;
|
|
95
|
-
agentName;
|
|
96
|
-
// Callbacks — set by core when wiring events
|
|
97
|
-
onSessionUpdate = () => {
|
|
98
|
-
};
|
|
99
|
-
onPermissionRequest = async () => "";
|
|
100
|
-
constructor(agentName) {
|
|
101
|
-
this.agentName = agentName;
|
|
102
|
-
}
|
|
103
|
-
static async spawn(agentDef, workingDirectory) {
|
|
104
|
-
const instance = new _AgentInstance(agentDef.name);
|
|
105
|
-
const resolved = resolveAgentCommand(agentDef.command);
|
|
106
|
-
log.debug(`Spawning agent "${agentDef.name}" \u2192 ${resolved.command} ${resolved.args.join(" ")}`);
|
|
107
|
-
instance.child = spawn(resolved.command, [...resolved.args, ...agentDef.args], {
|
|
108
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
109
|
-
cwd: workingDirectory,
|
|
110
|
-
env: { ...process.env, ...agentDef.env }
|
|
111
|
-
});
|
|
112
|
-
await new Promise((resolve, reject) => {
|
|
113
|
-
instance.child.on("error", (err) => {
|
|
114
|
-
reject(new Error(`Failed to spawn agent "${agentDef.name}": ${err.message}. Is "${agentDef.command}" installed?`));
|
|
115
|
-
});
|
|
116
|
-
instance.child.on("spawn", () => resolve());
|
|
117
|
-
});
|
|
118
|
-
instance.stderrCapture = new StderrCapture(50);
|
|
119
|
-
instance.child.stderr.on("data", (chunk) => {
|
|
120
|
-
instance.stderrCapture.append(chunk.toString());
|
|
121
|
-
});
|
|
122
|
-
const toAgent = nodeToWebWritable(instance.child.stdin);
|
|
123
|
-
const fromAgent = nodeToWebReadable(instance.child.stdout);
|
|
124
|
-
const stream = ndJsonStream(toAgent, fromAgent);
|
|
125
|
-
instance.connection = new ClientSideConnection(
|
|
126
|
-
(_agent) => instance.createClient(_agent),
|
|
127
|
-
stream
|
|
128
|
-
);
|
|
129
|
-
await instance.connection.initialize({
|
|
130
|
-
protocolVersion: 1,
|
|
131
|
-
clientCapabilities: {
|
|
132
|
-
fs: { readTextFile: true, writeTextFile: true },
|
|
133
|
-
terminal: true
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
const response = await instance.connection.newSession({
|
|
137
|
-
cwd: workingDirectory,
|
|
138
|
-
mcpServers: []
|
|
139
|
-
});
|
|
140
|
-
instance.sessionId = response.sessionId;
|
|
141
|
-
instance.child.on("exit", (code, signal) => {
|
|
142
|
-
if (code !== 0 && code !== null) {
|
|
143
|
-
const stderr = instance.stderrCapture.getLastLines();
|
|
144
|
-
instance.onSessionUpdate({
|
|
145
|
-
type: "error",
|
|
146
|
-
message: `Agent crashed (exit code ${code})
|
|
147
|
-
${stderr}`
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
instance.connection.closed.then(() => {
|
|
152
|
-
log.debug("ACP connection closed for", instance.agentName);
|
|
153
|
-
});
|
|
154
|
-
log.info(`Agent "${agentDef.name}" spawned with session ${response.sessionId}`);
|
|
155
|
-
return instance;
|
|
156
|
-
}
|
|
157
|
-
// createClient — implemented in Task 6b
|
|
158
|
-
createClient(_agent) {
|
|
159
|
-
const self = this;
|
|
160
|
-
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
161
|
-
return {
|
|
162
|
-
// ── Session updates ──────────────────────────────────────────────────
|
|
163
|
-
async sessionUpdate(params) {
|
|
164
|
-
const update = params.update;
|
|
165
|
-
let event = null;
|
|
166
|
-
switch (update.sessionUpdate) {
|
|
167
|
-
case "agent_message_chunk":
|
|
168
|
-
if (update.content.type === "text") {
|
|
169
|
-
event = { type: "text", content: update.content.text };
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
case "agent_thought_chunk":
|
|
173
|
-
if (update.content.type === "text") {
|
|
174
|
-
event = { type: "thought", content: update.content.text };
|
|
175
|
-
}
|
|
176
|
-
break;
|
|
177
|
-
case "tool_call":
|
|
178
|
-
event = {
|
|
179
|
-
type: "tool_call",
|
|
180
|
-
id: update.toolCallId,
|
|
181
|
-
name: update.title,
|
|
182
|
-
kind: update.kind ?? void 0,
|
|
183
|
-
status: update.status ?? "pending",
|
|
184
|
-
content: update.content ?? void 0
|
|
185
|
-
};
|
|
186
|
-
break;
|
|
187
|
-
case "tool_call_update":
|
|
188
|
-
event = {
|
|
189
|
-
type: "tool_update",
|
|
190
|
-
id: update.toolCallId,
|
|
191
|
-
status: update.status ?? "pending",
|
|
192
|
-
content: update.content ?? void 0
|
|
193
|
-
};
|
|
194
|
-
break;
|
|
195
|
-
case "plan":
|
|
196
|
-
event = { type: "plan", entries: update.entries };
|
|
197
|
-
break;
|
|
198
|
-
case "usage_update":
|
|
199
|
-
event = {
|
|
200
|
-
type: "usage",
|
|
201
|
-
tokensUsed: update.used,
|
|
202
|
-
contextSize: update.size,
|
|
203
|
-
cost: update.cost ?? void 0
|
|
204
|
-
};
|
|
205
|
-
break;
|
|
206
|
-
case "available_commands_update":
|
|
207
|
-
event = { type: "commands_update", commands: update.availableCommands };
|
|
208
|
-
break;
|
|
209
|
-
default:
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (event !== null) {
|
|
213
|
-
self.onSessionUpdate(event);
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
// ── Permission requests ──────────────────────────────────────────────
|
|
217
|
-
async requestPermission(params) {
|
|
218
|
-
const permissionRequest = {
|
|
219
|
-
id: params.toolCall.toolCallId,
|
|
220
|
-
description: params.toolCall.title ?? params.toolCall.toolCallId,
|
|
221
|
-
options: params.options.map((opt) => ({
|
|
222
|
-
id: opt.optionId,
|
|
223
|
-
label: opt.name,
|
|
224
|
-
isAllow: opt.kind === "allow_once" || opt.kind === "allow_always"
|
|
225
|
-
}))
|
|
226
|
-
};
|
|
227
|
-
const selectedOptionId = await self.onPermissionRequest(permissionRequest);
|
|
228
|
-
return {
|
|
229
|
-
outcome: { outcome: "selected", optionId: selectedOptionId }
|
|
230
|
-
};
|
|
231
|
-
},
|
|
232
|
-
// ── File operations ──────────────────────────────────────────────────
|
|
233
|
-
async readTextFile(params) {
|
|
234
|
-
const content = await fs.promises.readFile(params.path, "utf-8");
|
|
235
|
-
return { content };
|
|
236
|
-
},
|
|
237
|
-
async writeTextFile(params) {
|
|
238
|
-
await fs.promises.mkdir(path.dirname(params.path), { recursive: true });
|
|
239
|
-
await fs.promises.writeFile(params.path, params.content, "utf-8");
|
|
240
|
-
return {};
|
|
241
|
-
},
|
|
242
|
-
// ── Terminal operations ──────────────────────────────────────────────
|
|
243
|
-
async createTerminal(params) {
|
|
244
|
-
const terminalId = randomUUID();
|
|
245
|
-
const args = params.args ?? [];
|
|
246
|
-
const env = {};
|
|
247
|
-
for (const ev of params.env ?? []) {
|
|
248
|
-
env[ev.name] = ev.value;
|
|
249
|
-
}
|
|
250
|
-
const childProcess = spawn(params.command, args, {
|
|
251
|
-
cwd: params.cwd ?? void 0,
|
|
252
|
-
env: { ...process.env, ...env },
|
|
253
|
-
shell: false
|
|
254
|
-
});
|
|
255
|
-
const state = {
|
|
256
|
-
process: childProcess,
|
|
257
|
-
output: "",
|
|
258
|
-
exitStatus: null
|
|
259
|
-
};
|
|
260
|
-
self.terminals.set(terminalId, state);
|
|
261
|
-
const outputByteLimit = params.outputByteLimit ?? MAX_OUTPUT_BYTES;
|
|
262
|
-
const appendOutput = (chunk) => {
|
|
263
|
-
state.output += chunk;
|
|
264
|
-
const bytes = Buffer.byteLength(state.output, "utf-8");
|
|
265
|
-
if (bytes > outputByteLimit) {
|
|
266
|
-
const excess = bytes - outputByteLimit;
|
|
267
|
-
state.output = state.output.slice(excess);
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
childProcess.stdout?.on("data", (chunk) => appendOutput(chunk.toString()));
|
|
271
|
-
childProcess.stderr?.on("data", (chunk) => appendOutput(chunk.toString()));
|
|
272
|
-
childProcess.on("exit", (code, signal) => {
|
|
273
|
-
state.exitStatus = { exitCode: code, signal };
|
|
274
|
-
});
|
|
275
|
-
return { terminalId };
|
|
276
|
-
},
|
|
277
|
-
async terminalOutput(params) {
|
|
278
|
-
const state = self.terminals.get(params.terminalId);
|
|
279
|
-
if (!state) {
|
|
280
|
-
throw new Error(`Terminal not found: ${params.terminalId}`);
|
|
281
|
-
}
|
|
282
|
-
return {
|
|
283
|
-
output: state.output,
|
|
284
|
-
truncated: false,
|
|
285
|
-
exitStatus: state.exitStatus ? { exitCode: state.exitStatus.exitCode, signal: state.exitStatus.signal } : void 0
|
|
286
|
-
};
|
|
287
|
-
},
|
|
288
|
-
async waitForTerminalExit(params) {
|
|
289
|
-
const state = self.terminals.get(params.terminalId);
|
|
290
|
-
if (!state) {
|
|
291
|
-
throw new Error(`Terminal not found: ${params.terminalId}`);
|
|
292
|
-
}
|
|
293
|
-
if (state.exitStatus !== null) {
|
|
294
|
-
return { exitCode: state.exitStatus.exitCode, signal: state.exitStatus.signal };
|
|
295
|
-
}
|
|
296
|
-
return new Promise((resolve) => {
|
|
297
|
-
state.process.on("exit", (code, signal) => {
|
|
298
|
-
resolve({ exitCode: code, signal });
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
},
|
|
302
|
-
async killTerminal(params) {
|
|
303
|
-
const state = self.terminals.get(params.terminalId);
|
|
304
|
-
if (!state) {
|
|
305
|
-
throw new Error(`Terminal not found: ${params.terminalId}`);
|
|
306
|
-
}
|
|
307
|
-
state.process.kill("SIGTERM");
|
|
308
|
-
return {};
|
|
309
|
-
},
|
|
310
|
-
async releaseTerminal(params) {
|
|
311
|
-
const state = self.terminals.get(params.terminalId);
|
|
312
|
-
if (!state) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
state.process.kill("SIGKILL");
|
|
316
|
-
self.terminals.delete(params.terminalId);
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
async prompt(text) {
|
|
321
|
-
return this.connection.prompt({
|
|
322
|
-
sessionId: this.sessionId,
|
|
323
|
-
prompt: [{ type: "text", text }]
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
async cancel() {
|
|
327
|
-
await this.connection.cancel({ sessionId: this.sessionId });
|
|
328
|
-
}
|
|
329
|
-
async destroy() {
|
|
330
|
-
for (const [, t] of this.terminals) {
|
|
331
|
-
t.process.kill("SIGKILL");
|
|
332
|
-
}
|
|
333
|
-
this.terminals.clear();
|
|
334
|
-
this.child.kill("SIGTERM");
|
|
335
|
-
setTimeout(() => {
|
|
336
|
-
if (!this.child.killed) this.child.kill("SIGKILL");
|
|
337
|
-
}, 1e4);
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
// packages/core/src/agent-manager.ts
|
|
342
|
-
var AgentManager = class {
|
|
343
|
-
constructor(config) {
|
|
344
|
-
this.config = config;
|
|
345
|
-
}
|
|
346
|
-
getAvailableAgents() {
|
|
347
|
-
return Object.entries(this.config.agents).map(([name, cfg]) => ({
|
|
348
|
-
name,
|
|
349
|
-
command: cfg.command,
|
|
350
|
-
args: cfg.args,
|
|
351
|
-
workingDirectory: cfg.workingDirectory,
|
|
352
|
-
env: cfg.env
|
|
353
|
-
}));
|
|
354
|
-
}
|
|
355
|
-
getAgent(name) {
|
|
356
|
-
const cfg = this.config.agents[name];
|
|
357
|
-
if (!cfg) return void 0;
|
|
358
|
-
return { name, ...cfg };
|
|
359
|
-
}
|
|
360
|
-
async spawn(agentName, workingDirectory) {
|
|
361
|
-
const agentDef = this.getAgent(agentName);
|
|
362
|
-
if (!agentDef) throw new Error(`Agent "${agentName}" not found in config`);
|
|
363
|
-
return AgentInstance.spawn(agentDef, workingDirectory);
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
// packages/core/src/session.ts
|
|
368
|
-
import { nanoid } from "nanoid";
|
|
369
|
-
var Session = class {
|
|
370
|
-
id;
|
|
371
|
-
channelId;
|
|
372
|
-
threadId = "";
|
|
373
|
-
agentName;
|
|
374
|
-
workingDirectory;
|
|
375
|
-
agentInstance;
|
|
376
|
-
status = "initializing";
|
|
377
|
-
name;
|
|
378
|
-
promptQueue = [];
|
|
379
|
-
promptRunning = false;
|
|
380
|
-
createdAt = /* @__PURE__ */ new Date();
|
|
381
|
-
adapter;
|
|
382
|
-
// Set by wireSessionEvents for renaming
|
|
383
|
-
pendingPermission;
|
|
384
|
-
constructor(opts) {
|
|
385
|
-
this.id = opts.id || nanoid(12);
|
|
386
|
-
this.channelId = opts.channelId;
|
|
387
|
-
this.agentName = opts.agentName;
|
|
388
|
-
this.workingDirectory = opts.workingDirectory;
|
|
389
|
-
this.agentInstance = opts.agentInstance;
|
|
390
|
-
}
|
|
391
|
-
async enqueuePrompt(text) {
|
|
392
|
-
if (this.promptRunning) {
|
|
393
|
-
this.promptQueue.push(text);
|
|
394
|
-
log.debug(`Prompt queued for session ${this.id} (${this.promptQueue.length} in queue)`);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
await this.runPrompt(text);
|
|
398
|
-
}
|
|
399
|
-
async runPrompt(text) {
|
|
400
|
-
this.promptRunning = true;
|
|
401
|
-
this.status = "active";
|
|
402
|
-
try {
|
|
403
|
-
await this.agentInstance.prompt(text);
|
|
404
|
-
if (!this.name) {
|
|
405
|
-
await this.autoName();
|
|
406
|
-
}
|
|
407
|
-
} catch (err) {
|
|
408
|
-
this.status = "error";
|
|
409
|
-
log.error(`Prompt failed for session ${this.id}:`, err);
|
|
410
|
-
} finally {
|
|
411
|
-
this.promptRunning = false;
|
|
412
|
-
if (this.promptQueue.length > 0) {
|
|
413
|
-
const next = this.promptQueue.shift();
|
|
414
|
-
await this.runPrompt(next);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
// NOTE: This injects a summary prompt into the agent's conversation history.
|
|
419
|
-
// Known Phase 1 limitation — the agent sees this prompt in its context.
|
|
420
|
-
async autoName() {
|
|
421
|
-
let title = "";
|
|
422
|
-
const prevHandler = this.agentInstance.onSessionUpdate;
|
|
423
|
-
this.agentInstance.onSessionUpdate = (event) => {
|
|
424
|
-
if (event.type === "text") title += event.content;
|
|
425
|
-
};
|
|
426
|
-
try {
|
|
427
|
-
await this.agentInstance.prompt(
|
|
428
|
-
"Summarize this conversation in max 5 words for a topic title. Reply ONLY with the title, nothing else."
|
|
429
|
-
);
|
|
430
|
-
this.name = title.trim().slice(0, 50);
|
|
431
|
-
if (this.adapter && this.name) {
|
|
432
|
-
await this.adapter.renameSessionThread(this.id, this.name);
|
|
433
|
-
}
|
|
434
|
-
} catch {
|
|
435
|
-
this.name = `Session ${this.id.slice(0, 6)}`;
|
|
436
|
-
} finally {
|
|
437
|
-
this.agentInstance.onSessionUpdate = prevHandler;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
async cancel() {
|
|
441
|
-
this.status = "cancelled";
|
|
442
|
-
await this.agentInstance.cancel();
|
|
443
|
-
}
|
|
444
|
-
async destroy() {
|
|
445
|
-
await this.agentInstance.destroy();
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
// packages/core/src/session-manager.ts
|
|
450
|
-
var SessionManager = class {
|
|
451
|
-
sessions = /* @__PURE__ */ new Map();
|
|
452
|
-
async createSession(channelId, agentName, workingDirectory, agentManager) {
|
|
453
|
-
const agentInstance = await agentManager.spawn(agentName, workingDirectory);
|
|
454
|
-
const session = new Session({ channelId, agentName, workingDirectory, agentInstance });
|
|
455
|
-
this.sessions.set(session.id, session);
|
|
456
|
-
return session;
|
|
457
|
-
}
|
|
458
|
-
getSession(sessionId) {
|
|
459
|
-
return this.sessions.get(sessionId);
|
|
460
|
-
}
|
|
461
|
-
getSessionByThread(channelId, threadId) {
|
|
462
|
-
for (const session of this.sessions.values()) {
|
|
463
|
-
if (session.channelId === channelId && session.threadId === threadId) {
|
|
464
|
-
return session;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return void 0;
|
|
468
|
-
}
|
|
469
|
-
async cancelSession(sessionId) {
|
|
470
|
-
const session = this.sessions.get(sessionId);
|
|
471
|
-
if (session) await session.cancel();
|
|
472
|
-
}
|
|
473
|
-
listSessions(channelId) {
|
|
474
|
-
const all = Array.from(this.sessions.values());
|
|
475
|
-
if (channelId) return all.filter((s) => s.channelId === channelId);
|
|
476
|
-
return all;
|
|
477
|
-
}
|
|
478
|
-
async destroyAll() {
|
|
479
|
-
for (const session of this.sessions.values()) {
|
|
480
|
-
await session.destroy();
|
|
481
|
-
}
|
|
482
|
-
this.sessions.clear();
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
// packages/core/src/notification.ts
|
|
487
|
-
var NotificationManager = class {
|
|
488
|
-
constructor(adapters) {
|
|
489
|
-
this.adapters = adapters;
|
|
490
|
-
}
|
|
491
|
-
async notify(channelId, notification) {
|
|
492
|
-
const adapter = this.adapters.get(channelId);
|
|
493
|
-
if (adapter) {
|
|
494
|
-
await adapter.sendNotification(notification);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
async notifyAll(notification) {
|
|
498
|
-
for (const adapter of this.adapters.values()) {
|
|
499
|
-
await adapter.sendNotification(notification);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
// packages/core/src/core.ts
|
|
505
|
-
var OpenACPCore = class {
|
|
506
|
-
configManager;
|
|
507
|
-
agentManager;
|
|
508
|
-
sessionManager;
|
|
509
|
-
notificationManager;
|
|
510
|
-
adapters = /* @__PURE__ */ new Map();
|
|
511
|
-
constructor(configManager) {
|
|
512
|
-
this.configManager = configManager;
|
|
513
|
-
const config = configManager.get();
|
|
514
|
-
this.agentManager = new AgentManager(config);
|
|
515
|
-
this.sessionManager = new SessionManager();
|
|
516
|
-
this.notificationManager = new NotificationManager(this.adapters);
|
|
517
|
-
}
|
|
518
|
-
registerAdapter(name, adapter) {
|
|
519
|
-
this.adapters.set(name, adapter);
|
|
520
|
-
}
|
|
521
|
-
async start() {
|
|
522
|
-
for (const adapter of this.adapters.values()) {
|
|
523
|
-
await adapter.start();
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
async stop() {
|
|
527
|
-
try {
|
|
528
|
-
await this.notificationManager.notifyAll({
|
|
529
|
-
sessionId: "system",
|
|
530
|
-
type: "error",
|
|
531
|
-
summary: "OpenACP is shutting down"
|
|
532
|
-
});
|
|
533
|
-
} catch {
|
|
534
|
-
}
|
|
535
|
-
await this.sessionManager.destroyAll();
|
|
536
|
-
for (const adapter of this.adapters.values()) {
|
|
537
|
-
await adapter.stop();
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
// --- Message Routing ---
|
|
541
|
-
async handleMessage(message) {
|
|
542
|
-
const config = this.configManager.get();
|
|
543
|
-
if (config.security.allowedUserIds.length > 0) {
|
|
544
|
-
if (!config.security.allowedUserIds.includes(message.userId)) return;
|
|
545
|
-
}
|
|
546
|
-
const activeSessions = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
|
|
547
|
-
if (activeSessions.length >= config.security.maxConcurrentSessions) {
|
|
548
|
-
const adapter = this.adapters.get(message.channelId);
|
|
549
|
-
if (adapter) {
|
|
550
|
-
await adapter.sendMessage("system", {
|
|
551
|
-
type: "error",
|
|
552
|
-
text: `Max concurrent sessions (${config.security.maxConcurrentSessions}) reached. Cancel a session first.`
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
const session = this.sessionManager.getSessionByThread(message.channelId, message.threadId);
|
|
558
|
-
if (!session) return;
|
|
559
|
-
await session.enqueuePrompt(message.text);
|
|
560
|
-
}
|
|
561
|
-
async handleNewSession(channelId, agentName, workspacePath) {
|
|
562
|
-
const config = this.configManager.get();
|
|
563
|
-
const resolvedAgent = agentName || config.defaultAgent;
|
|
564
|
-
const resolvedWorkspace = this.configManager.resolveWorkspace(
|
|
565
|
-
workspacePath || config.agents[resolvedAgent]?.workingDirectory
|
|
566
|
-
);
|
|
567
|
-
const session = await this.sessionManager.createSession(
|
|
568
|
-
channelId,
|
|
569
|
-
resolvedAgent,
|
|
570
|
-
resolvedWorkspace,
|
|
571
|
-
this.agentManager
|
|
572
|
-
);
|
|
573
|
-
const adapter = this.adapters.get(channelId);
|
|
574
|
-
if (adapter) {
|
|
575
|
-
this.wireSessionEvents(session, adapter);
|
|
576
|
-
}
|
|
577
|
-
return session;
|
|
578
|
-
}
|
|
579
|
-
async handleNewChat(channelId, currentThreadId) {
|
|
580
|
-
const currentSession = this.sessionManager.getSessionByThread(channelId, currentThreadId);
|
|
581
|
-
if (!currentSession) return null;
|
|
582
|
-
return this.handleNewSession(
|
|
583
|
-
channelId,
|
|
584
|
-
currentSession.agentName,
|
|
585
|
-
currentSession.workingDirectory
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
// --- Event Wiring ---
|
|
589
|
-
toOutgoingMessage(event) {
|
|
590
|
-
switch (event.type) {
|
|
591
|
-
case "text":
|
|
592
|
-
return { type: "text", text: event.content };
|
|
593
|
-
case "thought":
|
|
594
|
-
return { type: "thought", text: event.content };
|
|
595
|
-
case "tool_call":
|
|
596
|
-
return { type: "tool_call", text: event.name, metadata: { id: event.id, kind: event.kind, status: event.status, content: event.content, locations: event.locations } };
|
|
597
|
-
case "tool_update":
|
|
598
|
-
return { type: "tool_update", text: "", metadata: { id: event.id, status: event.status, content: event.content } };
|
|
599
|
-
case "plan":
|
|
600
|
-
return { type: "plan", text: "", metadata: { entries: event.entries } };
|
|
601
|
-
case "usage":
|
|
602
|
-
return { type: "usage", text: "", metadata: { tokensUsed: event.tokensUsed, contextSize: event.contextSize, cost: event.cost } };
|
|
603
|
-
case "commands_update":
|
|
604
|
-
log.debug("Commands update:", event.commands);
|
|
605
|
-
return { type: "text", text: "" };
|
|
606
|
-
// no-op for now
|
|
607
|
-
default:
|
|
608
|
-
return { type: "text", text: "" };
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
// Public — adapters call this for assistant session wiring
|
|
612
|
-
wireSessionEvents(session, adapter) {
|
|
613
|
-
session.adapter = adapter;
|
|
614
|
-
session.agentInstance.onSessionUpdate = (event) => {
|
|
615
|
-
switch (event.type) {
|
|
616
|
-
case "text":
|
|
617
|
-
case "thought":
|
|
618
|
-
case "tool_call":
|
|
619
|
-
case "tool_update":
|
|
620
|
-
case "plan":
|
|
621
|
-
case "usage":
|
|
622
|
-
adapter.sendMessage(session.id, this.toOutgoingMessage(event));
|
|
623
|
-
break;
|
|
624
|
-
case "session_end":
|
|
625
|
-
session.status = "finished";
|
|
626
|
-
adapter.sendMessage(session.id, { type: "session_end", text: `Done (${event.reason})` });
|
|
627
|
-
this.notificationManager.notify(session.channelId, {
|
|
628
|
-
sessionId: session.id,
|
|
629
|
-
sessionName: session.name,
|
|
630
|
-
type: "completed",
|
|
631
|
-
summary: `Session "${session.name || session.id}" completed`
|
|
632
|
-
});
|
|
633
|
-
break;
|
|
634
|
-
case "error":
|
|
635
|
-
adapter.sendMessage(session.id, { type: "error", text: event.message });
|
|
636
|
-
this.notificationManager.notify(session.channelId, {
|
|
637
|
-
sessionId: session.id,
|
|
638
|
-
sessionName: session.name,
|
|
639
|
-
type: "error",
|
|
640
|
-
summary: event.message
|
|
641
|
-
});
|
|
642
|
-
break;
|
|
643
|
-
case "commands_update":
|
|
644
|
-
log.debug("Commands available:", event.commands);
|
|
645
|
-
break;
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
session.agentInstance.onPermissionRequest = async (request) => {
|
|
649
|
-
const promise = new Promise((resolve) => {
|
|
650
|
-
session.pendingPermission = { requestId: request.id, resolve };
|
|
651
|
-
});
|
|
652
|
-
await adapter.sendPermissionRequest(session.id, request);
|
|
653
|
-
return promise;
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
export {
|
|
659
|
-
nodeToWebWritable,
|
|
660
|
-
nodeToWebReadable,
|
|
661
|
-
StderrCapture,
|
|
662
|
-
AgentInstance,
|
|
663
|
-
AgentManager,
|
|
664
|
-
Session,
|
|
665
|
-
SessionManager,
|
|
666
|
-
NotificationManager,
|
|
667
|
-
OpenACPCore
|
|
668
|
-
};
|
|
669
|
-
//# sourceMappingURL=chunk-6YLIH7L5.js.map
|