@openacp/cli 2026.403.8 → 2026.404.2
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/dist/{channel-BL33p1ZP.d.ts → channel-DQWwxUKX.d.ts} +29 -1
- package/dist/cli.js +529 -103
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +74 -11
- package/dist/index.js +457 -54
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6611,16 +6611,20 @@ var init_tunnel_service = __esm({
|
|
|
6611
6611
|
return entry.publicUrl || `http://localhost:${apiPort}`;
|
|
6612
6612
|
} catch (err) {
|
|
6613
6613
|
if (this.config.provider === "openacp") {
|
|
6614
|
-
|
|
6614
|
+
const reason = err.message ?? String(err);
|
|
6615
|
+
log11.error({ err: reason }, "[tunnel] OpenACP tunnel failed \u2014 falling back to Cloudflare quick tunnel");
|
|
6615
6616
|
try {
|
|
6616
6617
|
const fallbackEntry = await this.registry.add(apiPort, {
|
|
6617
6618
|
type: "system",
|
|
6618
6619
|
provider: "cloudflare",
|
|
6619
6620
|
label: "system"
|
|
6620
6621
|
});
|
|
6621
|
-
|
|
6622
|
-
|
|
6622
|
+
const fallbackUrl = fallbackEntry.publicUrl || `http://localhost:${apiPort}`;
|
|
6623
|
+
log11.warn({ url: fallbackUrl, reason }, "[tunnel] Cloudflare fallback tunnel active");
|
|
6624
|
+
this.startError = `OpenACP tunnel unavailable (${reason}) \u2014 using Cloudflare quick tunnel`;
|
|
6625
|
+
return fallbackUrl;
|
|
6623
6626
|
} catch (fallbackErr) {
|
|
6627
|
+
log11.error({ err: fallbackErr.message }, "[tunnel] Cloudflare fallback also failed \u2014 no public URL");
|
|
6624
6628
|
this.startError = fallbackErr.message;
|
|
6625
6629
|
return `http://localhost:${apiPort}`;
|
|
6626
6630
|
}
|
|
@@ -7814,7 +7818,9 @@ var init_sse_manager = __esm({
|
|
|
7814
7818
|
"session:updated",
|
|
7815
7819
|
"session:deleted",
|
|
7816
7820
|
"agent:event",
|
|
7817
|
-
"permission:request"
|
|
7821
|
+
"permission:request",
|
|
7822
|
+
"message:queued",
|
|
7823
|
+
"message:processing"
|
|
7818
7824
|
];
|
|
7819
7825
|
for (const eventName of events) {
|
|
7820
7826
|
const handler = (data) => {
|
|
@@ -7879,7 +7885,9 @@ data: ${JSON.stringify(data)}
|
|
|
7879
7885
|
const sessionEvents = [
|
|
7880
7886
|
"agent:event",
|
|
7881
7887
|
"permission:request",
|
|
7882
|
-
"session:updated"
|
|
7888
|
+
"session:updated",
|
|
7889
|
+
"message:queued",
|
|
7890
|
+
"message:processing"
|
|
7883
7891
|
];
|
|
7884
7892
|
for (const res of this.sseConnections) {
|
|
7885
7893
|
const filter = res.sessionFilter;
|
|
@@ -8068,7 +8076,10 @@ var init_sessions = __esm({
|
|
|
8068
8076
|
});
|
|
8069
8077
|
PromptBodySchema = z.object({
|
|
8070
8078
|
// 100 KB limit — prevents memory exhaustion / DoS via enormous payloads
|
|
8071
|
-
prompt: z.string().min(1).max(1e5)
|
|
8079
|
+
prompt: z.string().min(1).max(1e5),
|
|
8080
|
+
// Multi-adapter routing fields
|
|
8081
|
+
sourceAdapterId: z.string().optional(),
|
|
8082
|
+
responseAdapterId: z.string().nullable().optional()
|
|
8072
8083
|
});
|
|
8073
8084
|
PermissionResponseBodySchema = z.object({
|
|
8074
8085
|
permissionId: z.string().min(1).max(200),
|
|
@@ -8105,22 +8116,23 @@ __export(sessions_exports, {
|
|
|
8105
8116
|
});
|
|
8106
8117
|
async function sessionRoutes(app, deps) {
|
|
8107
8118
|
app.get("/", { preHandler: requireScopes("sessions:read") }, async () => {
|
|
8108
|
-
const
|
|
8119
|
+
const summaries = deps.core.sessionManager.listAllSessions();
|
|
8109
8120
|
return {
|
|
8110
|
-
sessions:
|
|
8121
|
+
sessions: summaries.map((s) => ({
|
|
8111
8122
|
id: s.id,
|
|
8112
|
-
agent: s.
|
|
8123
|
+
agent: s.agent,
|
|
8113
8124
|
status: s.status,
|
|
8114
|
-
name: s.name
|
|
8115
|
-
workspace: s.
|
|
8116
|
-
|
|
8117
|
-
|
|
8125
|
+
name: s.name,
|
|
8126
|
+
workspace: s.workspace,
|
|
8127
|
+
channelId: s.channelId,
|
|
8128
|
+
createdAt: s.createdAt,
|
|
8129
|
+
lastActiveAt: s.lastActiveAt,
|
|
8130
|
+
dangerousMode: s.dangerousMode,
|
|
8118
8131
|
queueDepth: s.queueDepth,
|
|
8119
8132
|
promptRunning: s.promptRunning,
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
capabilities: s.agentCapabilities ?? null
|
|
8133
|
+
configOptions: s.configOptions,
|
|
8134
|
+
capabilities: s.capabilities,
|
|
8135
|
+
isLive: s.isLive
|
|
8124
8136
|
}))
|
|
8125
8137
|
};
|
|
8126
8138
|
});
|
|
@@ -8232,7 +8244,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8232
8244
|
async (request, reply) => {
|
|
8233
8245
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8234
8246
|
const sessionId = decodeURIComponent(rawId);
|
|
8235
|
-
const session = deps.core.
|
|
8247
|
+
const session = await deps.core.getOrResumeSessionById(sessionId);
|
|
8236
8248
|
if (!session) {
|
|
8237
8249
|
throw new NotFoundError(
|
|
8238
8250
|
"SESSION_NOT_FOUND",
|
|
@@ -8243,7 +8255,10 @@ async function sessionRoutes(app, deps) {
|
|
|
8243
8255
|
return reply.status(400).send({ error: `Session is ${session.status}` });
|
|
8244
8256
|
}
|
|
8245
8257
|
const body = PromptBodySchema.parse(request.body);
|
|
8246
|
-
await session.enqueuePrompt(body.prompt
|
|
8258
|
+
await session.enqueuePrompt(body.prompt, void 0, {
|
|
8259
|
+
sourceAdapterId: body.sourceAdapterId ?? "api",
|
|
8260
|
+
responseAdapterId: body.responseAdapterId
|
|
8261
|
+
});
|
|
8247
8262
|
return {
|
|
8248
8263
|
ok: true,
|
|
8249
8264
|
sessionId,
|
|
@@ -8290,6 +8305,9 @@ async function sessionRoutes(app, deps) {
|
|
|
8290
8305
|
const body = UpdateSessionBodySchema.parse(request.body);
|
|
8291
8306
|
const changes = {};
|
|
8292
8307
|
if (body.agentName !== void 0) {
|
|
8308
|
+
if (session.promptRunning) {
|
|
8309
|
+
await session.abortPrompt();
|
|
8310
|
+
}
|
|
8293
8311
|
const result = await deps.core.switchSessionAgent(sessionId, body.agentName);
|
|
8294
8312
|
changes.agentName = body.agentName;
|
|
8295
8313
|
changes.resumed = result.resumed;
|
|
@@ -8425,6 +8443,36 @@ async function sessionRoutes(app, deps) {
|
|
|
8425
8443
|
}
|
|
8426
8444
|
}
|
|
8427
8445
|
);
|
|
8446
|
+
app.post(
|
|
8447
|
+
"/:sessionId/attach",
|
|
8448
|
+
{ preHandler: requireScopes("sessions:write") },
|
|
8449
|
+
async (request, reply) => {
|
|
8450
|
+
const { sessionId } = request.params;
|
|
8451
|
+
const { adapterId } = request.body ?? {};
|
|
8452
|
+
if (!adapterId) return reply.code(400).send({ error: "adapterId is required" });
|
|
8453
|
+
try {
|
|
8454
|
+
const result = await deps.core.attachAdapter(sessionId, adapterId);
|
|
8455
|
+
return { ok: true, threadId: result.threadId };
|
|
8456
|
+
} catch (err) {
|
|
8457
|
+
return reply.code(400).send({ error: err.message });
|
|
8458
|
+
}
|
|
8459
|
+
}
|
|
8460
|
+
);
|
|
8461
|
+
app.post(
|
|
8462
|
+
"/:sessionId/detach",
|
|
8463
|
+
{ preHandler: requireScopes("sessions:write") },
|
|
8464
|
+
async (request, reply) => {
|
|
8465
|
+
const { sessionId } = request.params;
|
|
8466
|
+
const { adapterId } = request.body ?? {};
|
|
8467
|
+
if (!adapterId) return reply.code(400).send({ error: "adapterId is required" });
|
|
8468
|
+
try {
|
|
8469
|
+
await deps.core.detachAdapter(sessionId, adapterId);
|
|
8470
|
+
return { ok: true };
|
|
8471
|
+
} catch (err) {
|
|
8472
|
+
return reply.code(400).send({ error: err.message });
|
|
8473
|
+
}
|
|
8474
|
+
}
|
|
8475
|
+
);
|
|
8428
8476
|
app.get(
|
|
8429
8477
|
"/:sessionId/history",
|
|
8430
8478
|
{ preHandler: requireScopes("sessions:read") },
|
|
@@ -10603,7 +10651,7 @@ async function sseRoutes(app, deps) {
|
|
|
10603
10651
|
return reply.status(400).send({ error: `Session is ${session.status}` });
|
|
10604
10652
|
}
|
|
10605
10653
|
const body = PromptBodySchema.parse(request.body);
|
|
10606
|
-
await session.enqueuePrompt(body.prompt);
|
|
10654
|
+
await session.enqueuePrompt(body.prompt, void 0, { sourceAdapterId: "sse" });
|
|
10607
10655
|
return { ok: true, sessionId, queueDepth: session.queueDepth };
|
|
10608
10656
|
}
|
|
10609
10657
|
);
|
|
@@ -18855,6 +18903,7 @@ var init_agent_instance = __esm({
|
|
|
18855
18903
|
{ sessionId: this.sessionId, exitCode: code, signal },
|
|
18856
18904
|
"Agent process exited"
|
|
18857
18905
|
);
|
|
18906
|
+
if (signal === "SIGINT" || signal === "SIGTERM") return;
|
|
18858
18907
|
if (code !== 0 && code !== null || signal) {
|
|
18859
18908
|
const stderr = this.stderrCapture.getLastLines();
|
|
18860
18909
|
this.emit("agent_event", {
|
|
@@ -19351,21 +19400,21 @@ var init_prompt_queue = __esm({
|
|
|
19351
19400
|
queue = [];
|
|
19352
19401
|
processing = false;
|
|
19353
19402
|
abortController = null;
|
|
19354
|
-
async enqueue(text6, attachments) {
|
|
19403
|
+
async enqueue(text6, attachments, routing, turnId) {
|
|
19355
19404
|
if (this.processing) {
|
|
19356
19405
|
return new Promise((resolve8) => {
|
|
19357
|
-
this.queue.push({ text: text6, attachments, resolve: resolve8 });
|
|
19406
|
+
this.queue.push({ text: text6, attachments, routing, turnId, resolve: resolve8 });
|
|
19358
19407
|
});
|
|
19359
19408
|
}
|
|
19360
|
-
await this.process(text6, attachments);
|
|
19409
|
+
await this.process(text6, attachments, routing, turnId);
|
|
19361
19410
|
}
|
|
19362
|
-
async process(text6, attachments) {
|
|
19411
|
+
async process(text6, attachments, routing, turnId) {
|
|
19363
19412
|
this.processing = true;
|
|
19364
19413
|
this.abortController = new AbortController();
|
|
19365
19414
|
const { signal } = this.abortController;
|
|
19366
19415
|
try {
|
|
19367
19416
|
await Promise.race([
|
|
19368
|
-
this.processor(text6, attachments),
|
|
19417
|
+
this.processor(text6, attachments, routing, turnId),
|
|
19369
19418
|
new Promise((_, reject) => {
|
|
19370
19419
|
signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
|
|
19371
19420
|
})
|
|
@@ -19383,7 +19432,7 @@ var init_prompt_queue = __esm({
|
|
|
19383
19432
|
drainNext() {
|
|
19384
19433
|
const next = this.queue.shift();
|
|
19385
19434
|
if (next) {
|
|
19386
|
-
this.process(next.text, next.attachments).then(next.resolve);
|
|
19435
|
+
this.process(next.text, next.attachments, next.routing, next.turnId).then(next.resolve);
|
|
19387
19436
|
}
|
|
19388
19437
|
}
|
|
19389
19438
|
clear() {
|
|
@@ -19478,8 +19527,39 @@ var init_permission_gate = __esm({
|
|
|
19478
19527
|
}
|
|
19479
19528
|
});
|
|
19480
19529
|
|
|
19481
|
-
// src/core/sessions/
|
|
19530
|
+
// src/core/sessions/turn-context.ts
|
|
19482
19531
|
import { nanoid as nanoid3 } from "nanoid";
|
|
19532
|
+
function createTurnContext(sourceAdapterId, responseAdapterId, turnId) {
|
|
19533
|
+
return {
|
|
19534
|
+
turnId: turnId ?? nanoid3(8),
|
|
19535
|
+
sourceAdapterId,
|
|
19536
|
+
responseAdapterId
|
|
19537
|
+
};
|
|
19538
|
+
}
|
|
19539
|
+
function getEffectiveTarget(ctx) {
|
|
19540
|
+
if (ctx.responseAdapterId === null) return null;
|
|
19541
|
+
return ctx.responseAdapterId ?? ctx.sourceAdapterId;
|
|
19542
|
+
}
|
|
19543
|
+
function isSystemEvent(event) {
|
|
19544
|
+
return SYSTEM_EVENT_TYPES.has(event.type);
|
|
19545
|
+
}
|
|
19546
|
+
var SYSTEM_EVENT_TYPES;
|
|
19547
|
+
var init_turn_context = __esm({
|
|
19548
|
+
"src/core/sessions/turn-context.ts"() {
|
|
19549
|
+
"use strict";
|
|
19550
|
+
SYSTEM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
19551
|
+
"session_end",
|
|
19552
|
+
"system_message",
|
|
19553
|
+
"session_info_update",
|
|
19554
|
+
"config_option_update",
|
|
19555
|
+
"commands_update",
|
|
19556
|
+
"tts_strip"
|
|
19557
|
+
]);
|
|
19558
|
+
}
|
|
19559
|
+
});
|
|
19560
|
+
|
|
19561
|
+
// src/core/sessions/session.ts
|
|
19562
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
19483
19563
|
import * as fs41 from "fs";
|
|
19484
19564
|
var moduleLog, TTS_PROMPT_INSTRUCTION, TTS_BLOCK_REGEX, TTS_MAX_LENGTH, TTS_TIMEOUT_MS, VALID_TRANSITIONS, Session;
|
|
19485
19565
|
var init_session2 = __esm({
|
|
@@ -19489,6 +19569,7 @@ var init_session2 = __esm({
|
|
|
19489
19569
|
init_prompt_queue();
|
|
19490
19570
|
init_permission_gate();
|
|
19491
19571
|
init_log();
|
|
19572
|
+
init_turn_context();
|
|
19492
19573
|
moduleLog = createChildLogger({ module: "session" });
|
|
19493
19574
|
TTS_PROMPT_INSTRUCTION = `
|
|
19494
19575
|
|
|
@@ -19506,7 +19587,15 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19506
19587
|
Session = class extends TypedEmitter {
|
|
19507
19588
|
id;
|
|
19508
19589
|
channelId;
|
|
19509
|
-
threadId
|
|
19590
|
+
/** @deprecated Use threadIds map directly. Getter returns primary adapter's threadId. */
|
|
19591
|
+
get threadId() {
|
|
19592
|
+
return this.threadIds.get(this.channelId) ?? "";
|
|
19593
|
+
}
|
|
19594
|
+
set threadId(value) {
|
|
19595
|
+
if (value) {
|
|
19596
|
+
this.threadIds.set(this.channelId, value);
|
|
19597
|
+
}
|
|
19598
|
+
}
|
|
19510
19599
|
agentName;
|
|
19511
19600
|
workingDirectory;
|
|
19512
19601
|
agentInstance;
|
|
@@ -19527,14 +19616,21 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19527
19616
|
middlewareChain;
|
|
19528
19617
|
/** Latest commands emitted by the agent — buffered before bridge connects so they're not lost */
|
|
19529
19618
|
latestCommands = null;
|
|
19619
|
+
/** Adapters currently attached to this session (including primary) */
|
|
19620
|
+
attachedAdapters = [];
|
|
19621
|
+
/** Per-adapter thread IDs: adapterId → threadId */
|
|
19622
|
+
threadIds = /* @__PURE__ */ new Map();
|
|
19623
|
+
/** Active turn context — sealed on prompt dequeue, cleared on turn end */
|
|
19624
|
+
activeTurnContext = null;
|
|
19530
19625
|
permissionGate = new PermissionGate();
|
|
19531
19626
|
queue;
|
|
19532
19627
|
speechService;
|
|
19533
19628
|
pendingContext = null;
|
|
19534
19629
|
constructor(opts) {
|
|
19535
19630
|
super();
|
|
19536
|
-
this.id = opts.id ||
|
|
19631
|
+
this.id = opts.id || nanoid4(12);
|
|
19537
19632
|
this.channelId = opts.channelId;
|
|
19633
|
+
this.attachedAdapters = [opts.channelId];
|
|
19538
19634
|
this.agentName = opts.agentName;
|
|
19539
19635
|
this.firstAgent = opts.agentName;
|
|
19540
19636
|
this.workingDirectory = opts.workingDirectory;
|
|
@@ -19544,7 +19640,7 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19544
19640
|
this.log = createSessionLogger(this.id, moduleLog);
|
|
19545
19641
|
this.log.info({ agentName: this.agentName }, "Session created");
|
|
19546
19642
|
this.queue = new PromptQueue(
|
|
19547
|
-
(text6, attachments) => this.processPrompt(text6, attachments),
|
|
19643
|
+
(text6, attachments, routing, turnId) => this.processPrompt(text6, attachments, routing, turnId),
|
|
19548
19644
|
(err) => {
|
|
19549
19645
|
this.log.error({ err }, "Prompt execution failed");
|
|
19550
19646
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -19552,11 +19648,20 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19552
19648
|
this.emit("agent_event", { type: "error", message: `Prompt execution failed: ${message}` });
|
|
19553
19649
|
}
|
|
19554
19650
|
);
|
|
19555
|
-
this.
|
|
19651
|
+
this.wireCommandsBuffer();
|
|
19652
|
+
}
|
|
19653
|
+
/** Wire a listener on the current agentInstance to buffer commands_update events.
|
|
19654
|
+
* Must be called after every agentInstance replacement (constructor + switchAgent). */
|
|
19655
|
+
commandsBufferCleanup;
|
|
19656
|
+
wireCommandsBuffer() {
|
|
19657
|
+
this.commandsBufferCleanup?.();
|
|
19658
|
+
const handler = (event) => {
|
|
19556
19659
|
if (event.type === "commands_update") {
|
|
19557
19660
|
this.latestCommands = event.commands;
|
|
19558
19661
|
}
|
|
19559
|
-
}
|
|
19662
|
+
};
|
|
19663
|
+
this.agentInstance.on("agent_event", handler);
|
|
19664
|
+
this.commandsBufferCleanup = () => this.agentInstance.off("agent_event", handler);
|
|
19560
19665
|
}
|
|
19561
19666
|
// --- State Machine ---
|
|
19562
19667
|
get status() {
|
|
@@ -19610,18 +19715,26 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19610
19715
|
this.log.info({ voiceMode: mode }, "TTS mode changed");
|
|
19611
19716
|
}
|
|
19612
19717
|
// --- Public API ---
|
|
19613
|
-
async enqueuePrompt(text6, attachments) {
|
|
19718
|
+
async enqueuePrompt(text6, attachments, routing, externalTurnId) {
|
|
19719
|
+
const turnId = externalTurnId ?? nanoid4(8);
|
|
19614
19720
|
if (this.middlewareChain) {
|
|
19615
19721
|
const payload = { text: text6, attachments, sessionId: this.id };
|
|
19616
19722
|
const result = await this.middlewareChain.execute("agent:beforePrompt", payload, async (p2) => p2);
|
|
19617
|
-
if (!result) return;
|
|
19723
|
+
if (!result) return turnId;
|
|
19618
19724
|
text6 = result.text;
|
|
19619
19725
|
attachments = result.attachments;
|
|
19620
19726
|
}
|
|
19621
|
-
await this.queue.enqueue(text6, attachments);
|
|
19727
|
+
await this.queue.enqueue(text6, attachments, routing, turnId);
|
|
19728
|
+
return turnId;
|
|
19622
19729
|
}
|
|
19623
|
-
async processPrompt(text6, attachments) {
|
|
19730
|
+
async processPrompt(text6, attachments, routing, turnId) {
|
|
19624
19731
|
if (this._status === "finished") return;
|
|
19732
|
+
this.activeTurnContext = createTurnContext(
|
|
19733
|
+
routing?.sourceAdapterId ?? this.channelId,
|
|
19734
|
+
routing?.responseAdapterId,
|
|
19735
|
+
turnId
|
|
19736
|
+
);
|
|
19737
|
+
this.emit("turn_started", this.activeTurnContext);
|
|
19625
19738
|
this.promptCount++;
|
|
19626
19739
|
this.emit("prompt_count_changed", this.promptCount);
|
|
19627
19740
|
if (this._status === "initializing" || this._status === "cancelled" || this._status === "error") {
|
|
@@ -19688,6 +19801,7 @@ ${text6}`;
|
|
|
19688
19801
|
this.log.warn({ err }, "TTS post-processing failed");
|
|
19689
19802
|
});
|
|
19690
19803
|
}
|
|
19804
|
+
this.activeTurnContext = null;
|
|
19691
19805
|
if (!this.name) {
|
|
19692
19806
|
await this.autoName();
|
|
19693
19807
|
}
|
|
@@ -19930,6 +20044,7 @@ ${result.text}` : result.text;
|
|
|
19930
20044
|
this.configOptions = [];
|
|
19931
20045
|
this.latestCommands = null;
|
|
19932
20046
|
this.applySpawnResponse(newAgent.initialSessionResponse, newAgent.agentCapabilities);
|
|
20047
|
+
this.wireCommandsBuffer();
|
|
19933
20048
|
this.log.info({ from: this.agentSwitchHistory.at(-1).agentName, to: agentName }, "Agent switched");
|
|
19934
20049
|
}
|
|
19935
20050
|
async destroy() {
|
|
@@ -19994,6 +20109,8 @@ var init_session_manager = __esm({
|
|
|
19994
20109
|
}
|
|
19995
20110
|
getSessionByThread(channelId, threadId) {
|
|
19996
20111
|
for (const session of this.sessions.values()) {
|
|
20112
|
+
const adapterThread = session.threadIds.get(channelId);
|
|
20113
|
+
if (adapterThread === threadId) return session;
|
|
19997
20114
|
if (session.channelId === channelId && session.threadId === threadId) {
|
|
19998
20115
|
return session;
|
|
19999
20116
|
}
|
|
@@ -20061,6 +20178,67 @@ var init_session_manager = __esm({
|
|
|
20061
20178
|
if (channelId) return all.filter((s) => s.channelId === channelId);
|
|
20062
20179
|
return all;
|
|
20063
20180
|
}
|
|
20181
|
+
listAllSessions(channelId) {
|
|
20182
|
+
if (this.store) {
|
|
20183
|
+
let records = this.store.list();
|
|
20184
|
+
if (channelId) records = records.filter((r) => r.channelId === channelId);
|
|
20185
|
+
return records.map((record) => {
|
|
20186
|
+
const live2 = this.sessions.get(record.sessionId);
|
|
20187
|
+
if (live2) {
|
|
20188
|
+
return {
|
|
20189
|
+
id: live2.id,
|
|
20190
|
+
agent: live2.agentName,
|
|
20191
|
+
status: live2.status,
|
|
20192
|
+
name: live2.name ?? null,
|
|
20193
|
+
workspace: live2.workingDirectory,
|
|
20194
|
+
channelId: live2.channelId,
|
|
20195
|
+
createdAt: live2.createdAt.toISOString(),
|
|
20196
|
+
lastActiveAt: record.lastActiveAt ?? null,
|
|
20197
|
+
dangerousMode: live2.clientOverrides.bypassPermissions ?? false,
|
|
20198
|
+
queueDepth: live2.queueDepth,
|
|
20199
|
+
promptRunning: live2.promptRunning,
|
|
20200
|
+
configOptions: live2.configOptions?.length ? live2.configOptions : void 0,
|
|
20201
|
+
capabilities: live2.agentCapabilities ?? null,
|
|
20202
|
+
isLive: true
|
|
20203
|
+
};
|
|
20204
|
+
}
|
|
20205
|
+
return {
|
|
20206
|
+
id: record.sessionId,
|
|
20207
|
+
agent: record.agentName,
|
|
20208
|
+
status: record.status,
|
|
20209
|
+
name: record.name ?? null,
|
|
20210
|
+
workspace: record.workingDir,
|
|
20211
|
+
channelId: record.channelId,
|
|
20212
|
+
createdAt: record.createdAt,
|
|
20213
|
+
lastActiveAt: record.lastActiveAt ?? null,
|
|
20214
|
+
dangerousMode: record.clientOverrides?.bypassPermissions ?? false,
|
|
20215
|
+
queueDepth: 0,
|
|
20216
|
+
promptRunning: false,
|
|
20217
|
+
configOptions: record.acpState?.configOptions,
|
|
20218
|
+
capabilities: record.acpState?.agentCapabilities ?? null,
|
|
20219
|
+
isLive: false
|
|
20220
|
+
};
|
|
20221
|
+
});
|
|
20222
|
+
}
|
|
20223
|
+
let live = Array.from(this.sessions.values());
|
|
20224
|
+
if (channelId) live = live.filter((s) => s.channelId === channelId);
|
|
20225
|
+
return live.map((s) => ({
|
|
20226
|
+
id: s.id,
|
|
20227
|
+
agent: s.agentName,
|
|
20228
|
+
status: s.status,
|
|
20229
|
+
name: s.name ?? null,
|
|
20230
|
+
workspace: s.workingDirectory,
|
|
20231
|
+
channelId: s.channelId,
|
|
20232
|
+
createdAt: s.createdAt.toISOString(),
|
|
20233
|
+
lastActiveAt: null,
|
|
20234
|
+
dangerousMode: s.clientOverrides.bypassPermissions ?? false,
|
|
20235
|
+
queueDepth: s.queueDepth,
|
|
20236
|
+
promptRunning: s.promptRunning,
|
|
20237
|
+
configOptions: s.configOptions?.length ? s.configOptions : void 0,
|
|
20238
|
+
capabilities: s.agentCapabilities ?? null,
|
|
20239
|
+
isLive: true
|
|
20240
|
+
}));
|
|
20241
|
+
}
|
|
20064
20242
|
listRecords(filter) {
|
|
20065
20243
|
if (!this.store) return [];
|
|
20066
20244
|
let records = this.store.list();
|
|
@@ -20083,7 +20261,14 @@ var init_session_manager = __esm({
|
|
|
20083
20261
|
for (const session of this.sessions.values()) {
|
|
20084
20262
|
const record = this.store.get(session.id);
|
|
20085
20263
|
if (record) {
|
|
20086
|
-
await this.store.save({
|
|
20264
|
+
await this.store.save({
|
|
20265
|
+
...record,
|
|
20266
|
+
status: "finished",
|
|
20267
|
+
acpState: session.toAcpStateSnapshot(),
|
|
20268
|
+
clientOverrides: session.clientOverrides,
|
|
20269
|
+
currentPromptCount: session.promptCount,
|
|
20270
|
+
agentSwitchHistory: session.agentSwitchHistory
|
|
20271
|
+
});
|
|
20087
20272
|
}
|
|
20088
20273
|
}
|
|
20089
20274
|
this.store.flush();
|
|
@@ -20093,6 +20278,8 @@ var init_session_manager = __esm({
|
|
|
20093
20278
|
/**
|
|
20094
20279
|
* Forcefully destroy all sessions (kill agent subprocesses).
|
|
20095
20280
|
* Use only when sessions must be fully torn down (e.g. archive).
|
|
20281
|
+
* Unlike shutdownAll(), this does NOT snapshot live session state (acpState, etc.)
|
|
20282
|
+
* because destroyed sessions are terminal and will not be resumed.
|
|
20096
20283
|
*/
|
|
20097
20284
|
async destroyAll() {
|
|
20098
20285
|
if (this.store) {
|
|
@@ -20127,15 +20314,18 @@ var init_session_bridge = __esm({
|
|
|
20127
20314
|
"use strict";
|
|
20128
20315
|
init_log();
|
|
20129
20316
|
init_bypass_detection();
|
|
20317
|
+
init_turn_context();
|
|
20130
20318
|
log30 = createChildLogger({ module: "session-bridge" });
|
|
20131
20319
|
SessionBridge = class {
|
|
20132
|
-
constructor(session, adapter, deps) {
|
|
20320
|
+
constructor(session, adapter, deps, adapterId) {
|
|
20133
20321
|
this.session = session;
|
|
20134
20322
|
this.adapter = adapter;
|
|
20135
20323
|
this.deps = deps;
|
|
20324
|
+
this.adapterId = adapterId ?? adapter.name;
|
|
20136
20325
|
}
|
|
20137
20326
|
connected = false;
|
|
20138
20327
|
cleanupFns = [];
|
|
20328
|
+
adapterId;
|
|
20139
20329
|
get tracer() {
|
|
20140
20330
|
return this.session.agentInstance.debugTracer ?? null;
|
|
20141
20331
|
}
|
|
@@ -20166,6 +20356,15 @@ var init_session_bridge = __esm({
|
|
|
20166
20356
|
log30.error({ err, sessionId }, "Error in sendMessage middleware");
|
|
20167
20357
|
}
|
|
20168
20358
|
}
|
|
20359
|
+
/** Determine if this bridge should forward the given event based on turn routing. */
|
|
20360
|
+
shouldForward(event) {
|
|
20361
|
+
if (isSystemEvent(event)) return true;
|
|
20362
|
+
const ctx = this.session.activeTurnContext;
|
|
20363
|
+
if (!ctx) return true;
|
|
20364
|
+
const target = getEffectiveTarget(ctx);
|
|
20365
|
+
if (target === null) return false;
|
|
20366
|
+
return this.adapterId === target;
|
|
20367
|
+
}
|
|
20169
20368
|
connect() {
|
|
20170
20369
|
if (this.connected) return;
|
|
20171
20370
|
this.connected = true;
|
|
@@ -20173,11 +20372,29 @@ var init_session_bridge = __esm({
|
|
|
20173
20372
|
this.session.emit("agent_event", event);
|
|
20174
20373
|
});
|
|
20175
20374
|
this.listen(this.session, "agent_event", (event) => {
|
|
20176
|
-
this.
|
|
20375
|
+
if (this.shouldForward(event)) {
|
|
20376
|
+
this.dispatchAgentEvent(event);
|
|
20377
|
+
} else {
|
|
20378
|
+
this.deps.eventBus?.emit("agent:event", { sessionId: this.session.id, event });
|
|
20379
|
+
}
|
|
20380
|
+
});
|
|
20381
|
+
if (!this.session.agentInstance.onPermissionRequest || this.session.agentInstance.onPermissionRequest.__bridgeId === void 0) {
|
|
20382
|
+
const handler = async (request) => {
|
|
20383
|
+
return this.resolvePermission(request);
|
|
20384
|
+
};
|
|
20385
|
+
handler.__bridgeId = this.adapterId;
|
|
20386
|
+
this.session.agentInstance.onPermissionRequest = handler;
|
|
20387
|
+
}
|
|
20388
|
+
this.listen(this.session, "permission_request", async (request) => {
|
|
20389
|
+
const current = this.session.agentInstance.onPermissionRequest;
|
|
20390
|
+
if (current?.__bridgeId === this.adapterId) return;
|
|
20391
|
+
if (!this.session.permissionGate.isPending) return;
|
|
20392
|
+
try {
|
|
20393
|
+
await this.adapter.sendPermissionRequest(this.session.id, request);
|
|
20394
|
+
} catch (err) {
|
|
20395
|
+
log30.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
|
|
20396
|
+
}
|
|
20177
20397
|
});
|
|
20178
|
-
this.session.agentInstance.onPermissionRequest = async (request) => {
|
|
20179
|
-
return this.resolvePermission(request);
|
|
20180
|
-
};
|
|
20181
20398
|
this.listen(this.session, "status_change", (from, to) => {
|
|
20182
20399
|
this.deps.sessionManager.patchRecord(this.session.id, {
|
|
20183
20400
|
status: to,
|
|
@@ -20206,6 +20423,16 @@ var init_session_bridge = __esm({
|
|
|
20206
20423
|
this.listen(this.session, "prompt_count_changed", (count) => {
|
|
20207
20424
|
this.deps.sessionManager.patchRecord(this.session.id, { currentPromptCount: count });
|
|
20208
20425
|
});
|
|
20426
|
+
this.listen(this.session, "turn_started", (ctx) => {
|
|
20427
|
+
if (ctx.sourceAdapterId !== "sse" && ctx.sourceAdapterId !== "api") {
|
|
20428
|
+
this.deps.eventBus?.emit("message:processing", {
|
|
20429
|
+
sessionId: this.session.id,
|
|
20430
|
+
turnId: ctx.turnId,
|
|
20431
|
+
sourceAdapterId: ctx.sourceAdapterId,
|
|
20432
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
20433
|
+
});
|
|
20434
|
+
}
|
|
20435
|
+
});
|
|
20209
20436
|
if (this.session.latestCommands !== null) {
|
|
20210
20437
|
this.session.emit("agent_event", { type: "commands_update", commands: this.session.latestCommands });
|
|
20211
20438
|
}
|
|
@@ -20218,7 +20445,10 @@ var init_session_bridge = __esm({
|
|
|
20218
20445
|
this.connected = false;
|
|
20219
20446
|
this.cleanupFns.forEach((fn) => fn());
|
|
20220
20447
|
this.cleanupFns = [];
|
|
20221
|
-
this.session.agentInstance.onPermissionRequest
|
|
20448
|
+
const current = this.session.agentInstance.onPermissionRequest;
|
|
20449
|
+
if (current?.__bridgeId === this.adapterId) {
|
|
20450
|
+
this.session.agentInstance.onPermissionRequest = async () => "";
|
|
20451
|
+
}
|
|
20222
20452
|
}
|
|
20223
20453
|
/** Dispatch an agent event through middleware and to the adapter */
|
|
20224
20454
|
async dispatchAgentEvent(event) {
|
|
@@ -20349,8 +20579,10 @@ var init_session_bridge = __esm({
|
|
|
20349
20579
|
this.sendMessage(this.session.id, outgoing);
|
|
20350
20580
|
break;
|
|
20351
20581
|
case "config_option_update":
|
|
20352
|
-
this.session.updateConfigOptions(event.options)
|
|
20353
|
-
|
|
20582
|
+
this.session.updateConfigOptions(event.options).then(() => {
|
|
20583
|
+
this.persistAcpState();
|
|
20584
|
+
}).catch(() => {
|
|
20585
|
+
});
|
|
20354
20586
|
outgoing = this.deps.messageTransformer.transform(event);
|
|
20355
20587
|
this.sendMessage(this.session.id, outgoing);
|
|
20356
20588
|
break;
|
|
@@ -20394,19 +20626,27 @@ var init_session_bridge = __esm({
|
|
|
20394
20626
|
return result.autoResolve;
|
|
20395
20627
|
}
|
|
20396
20628
|
}
|
|
20397
|
-
this.session.emit("permission_request", permReq);
|
|
20398
20629
|
this.deps.eventBus?.emit("permission:request", {
|
|
20399
20630
|
sessionId: this.session.id,
|
|
20400
20631
|
permission: permReq
|
|
20401
20632
|
});
|
|
20402
20633
|
const autoDecision = this.checkAutoApprove(permReq);
|
|
20403
20634
|
if (autoDecision) {
|
|
20635
|
+
this.session.emit("permission_request", permReq);
|
|
20404
20636
|
this.emitAfterResolve(mw, permReq.id, autoDecision, "system", startTime);
|
|
20405
20637
|
return autoDecision;
|
|
20406
20638
|
}
|
|
20407
20639
|
const promise = this.session.permissionGate.setPending(permReq);
|
|
20640
|
+
this.session.emit("permission_request", permReq);
|
|
20408
20641
|
await this.adapter.sendPermissionRequest(this.session.id, permReq);
|
|
20409
20642
|
const optionId = await promise;
|
|
20643
|
+
this.deps.eventBus?.emit("permission:resolved", {
|
|
20644
|
+
sessionId: this.session.id,
|
|
20645
|
+
requestId: permReq.id,
|
|
20646
|
+
decision: optionId,
|
|
20647
|
+
optionId,
|
|
20648
|
+
resolvedBy: this.adapterId
|
|
20649
|
+
});
|
|
20410
20650
|
this.emitAfterResolve(mw, permReq.id, optionId, "user", startTime);
|
|
20411
20651
|
return optionId;
|
|
20412
20652
|
}
|
|
@@ -20906,6 +21146,9 @@ var init_session_store = __esm({
|
|
|
20906
21146
|
}
|
|
20907
21147
|
findByPlatform(channelId, predicate) {
|
|
20908
21148
|
for (const record of this.records.values()) {
|
|
21149
|
+
if (record.platforms?.[channelId]) {
|
|
21150
|
+
if (predicate(record.platforms[channelId])) return record;
|
|
21151
|
+
}
|
|
20909
21152
|
if (record.channelId === channelId && predicate(record.platform)) {
|
|
20910
21153
|
return record;
|
|
20911
21154
|
}
|
|
@@ -20972,7 +21215,7 @@ var init_session_store = __esm({
|
|
|
20972
21215
|
return;
|
|
20973
21216
|
}
|
|
20974
21217
|
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
20975
|
-
this.records.set(id, record);
|
|
21218
|
+
this.records.set(id, this.migrateRecord(record));
|
|
20976
21219
|
}
|
|
20977
21220
|
log32.debug({ count: this.records.size }, "Loaded session records");
|
|
20978
21221
|
} catch (err) {
|
|
@@ -20983,6 +21226,19 @@ var init_session_store = __esm({
|
|
|
20983
21226
|
}
|
|
20984
21227
|
}
|
|
20985
21228
|
}
|
|
21229
|
+
/** Migrate old SessionRecord format to new multi-adapter format. */
|
|
21230
|
+
migrateRecord(record) {
|
|
21231
|
+
if (!record.platforms && record.platform && typeof record.platform === "object") {
|
|
21232
|
+
const platformData = record.platform;
|
|
21233
|
+
if (Object.keys(platformData).length > 0) {
|
|
21234
|
+
record.platforms = { [record.channelId]: platformData };
|
|
21235
|
+
}
|
|
21236
|
+
}
|
|
21237
|
+
if (!record.attachedAdapters) {
|
|
21238
|
+
record.attachedAdapters = [record.channelId];
|
|
21239
|
+
}
|
|
21240
|
+
return record;
|
|
21241
|
+
}
|
|
20986
21242
|
cleanup() {
|
|
20987
21243
|
const cutoff = Date.now() - this.ttlDays * 24 * 60 * 60 * 1e3;
|
|
20988
21244
|
let removed = 0;
|
|
@@ -21171,6 +21427,65 @@ var init_session_factory = __esm({
|
|
|
21171
21427
|
if (session) return session;
|
|
21172
21428
|
return this.lazyResume(channelId, threadId);
|
|
21173
21429
|
}
|
|
21430
|
+
async getOrResumeById(sessionId) {
|
|
21431
|
+
const live = this.sessionManager.getSession(sessionId);
|
|
21432
|
+
if (live) return live;
|
|
21433
|
+
if (!this.sessionStore || !this.createFullSession) return null;
|
|
21434
|
+
const record = this.sessionStore.get(sessionId);
|
|
21435
|
+
if (!record) return null;
|
|
21436
|
+
if (record.status === "error" || record.status === "cancelled") return null;
|
|
21437
|
+
const existing = this.resumeLocks.get(sessionId);
|
|
21438
|
+
if (existing) return existing;
|
|
21439
|
+
const resumePromise = (async () => {
|
|
21440
|
+
try {
|
|
21441
|
+
const p2 = record.platform;
|
|
21442
|
+
const existingThreadId = p2?.topicId ? String(p2.topicId) : p2?.threadId;
|
|
21443
|
+
const session = await this.createFullSession({
|
|
21444
|
+
channelId: record.channelId,
|
|
21445
|
+
agentName: record.agentName,
|
|
21446
|
+
workingDirectory: record.workingDir,
|
|
21447
|
+
resumeAgentSessionId: record.agentSessionId,
|
|
21448
|
+
existingSessionId: record.sessionId,
|
|
21449
|
+
initialName: record.name,
|
|
21450
|
+
threadId: existingThreadId
|
|
21451
|
+
});
|
|
21452
|
+
session.activate();
|
|
21453
|
+
if (record.clientOverrides) {
|
|
21454
|
+
session.clientOverrides = record.clientOverrides;
|
|
21455
|
+
} else if (record.dangerousMode) {
|
|
21456
|
+
session.clientOverrides = { bypassPermissions: true };
|
|
21457
|
+
}
|
|
21458
|
+
if (record.firstAgent) session.firstAgent = record.firstAgent;
|
|
21459
|
+
if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
|
|
21460
|
+
if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
|
|
21461
|
+
if (record.attachedAdapters) session.attachedAdapters = record.attachedAdapters;
|
|
21462
|
+
if (record.platforms) {
|
|
21463
|
+
for (const [adapterId, platformData] of Object.entries(record.platforms)) {
|
|
21464
|
+
const data = platformData;
|
|
21465
|
+
const tid = adapterId === "telegram" ? String(data.topicId ?? "") : String(data.threadId ?? "");
|
|
21466
|
+
if (tid) session.threadIds.set(adapterId, tid);
|
|
21467
|
+
}
|
|
21468
|
+
}
|
|
21469
|
+
if (record.acpState) {
|
|
21470
|
+
if (record.acpState.configOptions && session.configOptions.length === 0) {
|
|
21471
|
+
session.setInitialConfigOptions(record.acpState.configOptions);
|
|
21472
|
+
}
|
|
21473
|
+
if (record.acpState.agentCapabilities && !session.agentCapabilities) {
|
|
21474
|
+
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
21475
|
+
}
|
|
21476
|
+
}
|
|
21477
|
+
log33.info({ sessionId }, "Lazy resume by ID successful");
|
|
21478
|
+
return session;
|
|
21479
|
+
} catch (err) {
|
|
21480
|
+
log33.error({ err, sessionId }, "Lazy resume by ID failed");
|
|
21481
|
+
return null;
|
|
21482
|
+
} finally {
|
|
21483
|
+
this.resumeLocks.delete(sessionId);
|
|
21484
|
+
}
|
|
21485
|
+
})();
|
|
21486
|
+
this.resumeLocks.set(sessionId, resumePromise);
|
|
21487
|
+
return resumePromise;
|
|
21488
|
+
}
|
|
21174
21489
|
async lazyResume(channelId, threadId) {
|
|
21175
21490
|
const store = this.sessionStore;
|
|
21176
21491
|
if (!store || !this.createFullSession) return null;
|
|
@@ -21179,7 +21494,7 @@ var init_session_factory = __esm({
|
|
|
21179
21494
|
if (existing) return existing;
|
|
21180
21495
|
const record = store.findByPlatform(
|
|
21181
21496
|
channelId,
|
|
21182
|
-
(p2) => String(p2.topicId) === threadId
|
|
21497
|
+
(p2) => String(p2.topicId) === threadId || String(p2.threadId ?? "") === threadId
|
|
21183
21498
|
);
|
|
21184
21499
|
if (!record) {
|
|
21185
21500
|
log33.debug({ threadId, channelId }, "No session record found for thread");
|
|
@@ -21214,11 +21529,21 @@ var init_session_factory = __esm({
|
|
|
21214
21529
|
if (record.firstAgent) session.firstAgent = record.firstAgent;
|
|
21215
21530
|
if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
|
|
21216
21531
|
if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
|
|
21532
|
+
if (record.attachedAdapters) {
|
|
21533
|
+
session.attachedAdapters = record.attachedAdapters;
|
|
21534
|
+
}
|
|
21535
|
+
if (record.platforms) {
|
|
21536
|
+
for (const [adapterId, platformData] of Object.entries(record.platforms)) {
|
|
21537
|
+
const data = platformData;
|
|
21538
|
+
const tid = adapterId === "telegram" ? String(data.topicId ?? "") : String(data.threadId ?? "");
|
|
21539
|
+
if (tid) session.threadIds.set(adapterId, tid);
|
|
21540
|
+
}
|
|
21541
|
+
}
|
|
21217
21542
|
if (record.acpState) {
|
|
21218
|
-
if (record.acpState.configOptions) {
|
|
21543
|
+
if (record.acpState.configOptions && session.configOptions.length === 0) {
|
|
21219
21544
|
session.setInitialConfigOptions(record.acpState.configOptions);
|
|
21220
21545
|
}
|
|
21221
|
-
if (record.acpState.agentCapabilities) {
|
|
21546
|
+
if (record.acpState.agentCapabilities && !session.agentCapabilities) {
|
|
21222
21547
|
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
21223
21548
|
}
|
|
21224
21549
|
}
|
|
@@ -21401,8 +21726,15 @@ var init_agent_switch_handler = __esm({
|
|
|
21401
21726
|
toAgent,
|
|
21402
21727
|
status: "starting"
|
|
21403
21728
|
});
|
|
21404
|
-
const
|
|
21405
|
-
|
|
21729
|
+
const sessionBridgeKeys = this.deps.getSessionBridgeKeys(sessionId);
|
|
21730
|
+
const hadBridges = sessionBridgeKeys.length > 0;
|
|
21731
|
+
for (const key of sessionBridgeKeys) {
|
|
21732
|
+
const bridge = bridges.get(key);
|
|
21733
|
+
if (bridge) {
|
|
21734
|
+
bridges.delete(key);
|
|
21735
|
+
bridge.disconnect();
|
|
21736
|
+
}
|
|
21737
|
+
}
|
|
21406
21738
|
const switchAdapter = adapters.get(session.channelId);
|
|
21407
21739
|
if (switchAdapter?.sendSkillCommands) {
|
|
21408
21740
|
await switchAdapter.sendSkillCommands(session.id, []);
|
|
@@ -21479,9 +21811,11 @@ var init_agent_switch_handler = __esm({
|
|
|
21479
21811
|
session.agentInstance = oldInstance;
|
|
21480
21812
|
session.agentName = fromAgent;
|
|
21481
21813
|
session.agentSessionId = oldInstance.sessionId;
|
|
21482
|
-
const
|
|
21483
|
-
|
|
21484
|
-
|
|
21814
|
+
for (const adapterId of session.attachedAdapters) {
|
|
21815
|
+
const adapter = adapters.get(adapterId);
|
|
21816
|
+
if (adapter) {
|
|
21817
|
+
createBridge(session, adapter, adapterId).connect();
|
|
21818
|
+
}
|
|
21485
21819
|
}
|
|
21486
21820
|
log34.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
|
|
21487
21821
|
} catch (rollbackErr) {
|
|
@@ -21490,10 +21824,14 @@ var init_agent_switch_handler = __esm({
|
|
|
21490
21824
|
}
|
|
21491
21825
|
throw err;
|
|
21492
21826
|
}
|
|
21493
|
-
if (
|
|
21494
|
-
const
|
|
21495
|
-
|
|
21496
|
-
|
|
21827
|
+
if (hadBridges) {
|
|
21828
|
+
for (const adapterId of session.attachedAdapters) {
|
|
21829
|
+
const adapter = adapters.get(adapterId);
|
|
21830
|
+
if (adapter) {
|
|
21831
|
+
createBridge(session, adapter, adapterId).connect();
|
|
21832
|
+
} else {
|
|
21833
|
+
log34.warn({ sessionId, adapterId }, "Adapter not available during switch reconnect, skipping bridge");
|
|
21834
|
+
}
|
|
21497
21835
|
}
|
|
21498
21836
|
}
|
|
21499
21837
|
await sessionManager.patchRecord(sessionId, {
|
|
@@ -23073,6 +23411,7 @@ var init_core_items = __esm({
|
|
|
23073
23411
|
// src/core/core.ts
|
|
23074
23412
|
import path52 from "path";
|
|
23075
23413
|
import os23 from "os";
|
|
23414
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
23076
23415
|
var log40, OpenACPCore;
|
|
23077
23416
|
var init_core = __esm({
|
|
23078
23417
|
"src/core/core.ts"() {
|
|
@@ -23105,7 +23444,7 @@ var init_core = __esm({
|
|
|
23105
23444
|
sessionManager;
|
|
23106
23445
|
messageTransformer;
|
|
23107
23446
|
adapters = /* @__PURE__ */ new Map();
|
|
23108
|
-
/** sessionId → SessionBridge — tracks active bridges for disconnect/reconnect
|
|
23447
|
+
/** "adapterId:sessionId" → SessionBridge — tracks active bridges for disconnect/reconnect */
|
|
23109
23448
|
bridges = /* @__PURE__ */ new Map();
|
|
23110
23449
|
/** Set by main.ts — triggers graceful shutdown with restart exit code */
|
|
23111
23450
|
requestRestart = null;
|
|
@@ -23201,7 +23540,8 @@ var init_core = __esm({
|
|
|
23201
23540
|
eventBus: this.eventBus,
|
|
23202
23541
|
adapters: this.adapters,
|
|
23203
23542
|
bridges: this.bridges,
|
|
23204
|
-
createBridge: (session, adapter) => this.createBridge(session, adapter),
|
|
23543
|
+
createBridge: (session, adapter, adapterId) => this.createBridge(session, adapter, adapterId),
|
|
23544
|
+
getSessionBridgeKeys: (sessionId) => this.getSessionBridgeKeys(sessionId),
|
|
23205
23545
|
getMiddlewareChain: () => this.lifecycleManager?.middlewareChain,
|
|
23206
23546
|
getService: (name) => this.lifecycleManager.serviceRegistry.get(name)
|
|
23207
23547
|
});
|
|
@@ -23381,7 +23721,22 @@ User message:
|
|
|
23381
23721
|
${text6}`;
|
|
23382
23722
|
}
|
|
23383
23723
|
}
|
|
23384
|
-
|
|
23724
|
+
const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
|
|
23725
|
+
if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
|
|
23726
|
+
const turnId = nanoid5(8);
|
|
23727
|
+
this.eventBus.emit("message:queued", {
|
|
23728
|
+
sessionId: session.id,
|
|
23729
|
+
turnId,
|
|
23730
|
+
text: text6,
|
|
23731
|
+
sourceAdapterId,
|
|
23732
|
+
attachments: message.attachments,
|
|
23733
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23734
|
+
queueDepth: session.queueDepth
|
|
23735
|
+
});
|
|
23736
|
+
await session.enqueuePrompt(text6, message.attachments, message.routing, turnId);
|
|
23737
|
+
} else {
|
|
23738
|
+
await session.enqueuePrompt(text6, message.attachments, message.routing);
|
|
23739
|
+
}
|
|
23385
23740
|
}
|
|
23386
23741
|
// --- Unified Session Creation Pipeline ---
|
|
23387
23742
|
async createSession(params) {
|
|
@@ -23408,6 +23763,12 @@ ${text6}`;
|
|
|
23408
23763
|
platform2.threadId = session.threadId;
|
|
23409
23764
|
}
|
|
23410
23765
|
}
|
|
23766
|
+
const platforms = {
|
|
23767
|
+
...existingRecord?.platforms ?? {}
|
|
23768
|
+
};
|
|
23769
|
+
if (session.threadId) {
|
|
23770
|
+
platforms[params.channelId] = params.channelId === "telegram" ? { topicId: Number(session.threadId) || session.threadId } : { threadId: session.threadId };
|
|
23771
|
+
}
|
|
23411
23772
|
await this.sessionManager.patchRecord(session.id, {
|
|
23412
23773
|
sessionId: session.id,
|
|
23413
23774
|
agentSessionId: session.agentSessionId,
|
|
@@ -23419,6 +23780,7 @@ ${text6}`;
|
|
|
23419
23780
|
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23420
23781
|
name: session.name,
|
|
23421
23782
|
platform: platform2,
|
|
23783
|
+
platforms,
|
|
23422
23784
|
firstAgent: session.firstAgent,
|
|
23423
23785
|
currentPromptCount: session.promptCount,
|
|
23424
23786
|
agentSwitchHistory: session.agentSwitchHistory,
|
|
@@ -23426,7 +23788,7 @@ ${text6}`;
|
|
|
23426
23788
|
acpState: session.toAcpStateSnapshot()
|
|
23427
23789
|
}, { immediate: true });
|
|
23428
23790
|
if (adapter) {
|
|
23429
|
-
const bridge = this.createBridge(session, adapter);
|
|
23791
|
+
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
23430
23792
|
bridge.connect();
|
|
23431
23793
|
adapter.flushPendingSkillCommands?.(session.id).catch((err) => {
|
|
23432
23794
|
log40.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
|
|
@@ -23565,9 +23927,14 @@ ${text6}`;
|
|
|
23565
23927
|
} else {
|
|
23566
23928
|
adoptPlatform.threadId = session.threadId;
|
|
23567
23929
|
}
|
|
23930
|
+
const adoptPlatforms = {};
|
|
23931
|
+
if (session.threadId) {
|
|
23932
|
+
adoptPlatforms[adapterChannelId] = adapterChannelId === "telegram" ? { topicId: Number(session.threadId) || session.threadId } : { threadId: session.threadId };
|
|
23933
|
+
}
|
|
23568
23934
|
await this.sessionManager.patchRecord(session.id, {
|
|
23569
23935
|
originalAgentSessionId: agentSessionId,
|
|
23570
|
-
platform: adoptPlatform
|
|
23936
|
+
platform: adoptPlatform,
|
|
23937
|
+
platforms: adoptPlatforms
|
|
23571
23938
|
});
|
|
23572
23939
|
return {
|
|
23573
23940
|
ok: true,
|
|
@@ -23589,18 +23956,101 @@ ${text6}`;
|
|
|
23589
23956
|
async getOrResumeSession(channelId, threadId) {
|
|
23590
23957
|
return this.sessionFactory.getOrResume(channelId, threadId);
|
|
23591
23958
|
}
|
|
23959
|
+
async getOrResumeSessionById(sessionId) {
|
|
23960
|
+
return this.sessionFactory.getOrResumeById(sessionId);
|
|
23961
|
+
}
|
|
23962
|
+
async attachAdapter(sessionId, adapterId) {
|
|
23963
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
23964
|
+
if (!session) throw new Error(`Session ${sessionId} not found`);
|
|
23965
|
+
const adapter = this.adapters.get(adapterId);
|
|
23966
|
+
if (!adapter) throw new Error(`Adapter "${adapterId}" not found or not running`);
|
|
23967
|
+
if (session.attachedAdapters.includes(adapterId)) {
|
|
23968
|
+
const existingThread = session.threadIds.get(adapterId) ?? session.id;
|
|
23969
|
+
return { threadId: existingThread };
|
|
23970
|
+
}
|
|
23971
|
+
const threadId = await adapter.createSessionThread(
|
|
23972
|
+
session.id,
|
|
23973
|
+
session.name ?? `Session ${session.id.slice(0, 6)}`
|
|
23974
|
+
);
|
|
23975
|
+
session.threadIds.set(adapterId, threadId);
|
|
23976
|
+
session.attachedAdapters.push(adapterId);
|
|
23977
|
+
const bridge = this.createBridge(session, adapter, adapterId);
|
|
23978
|
+
bridge.connect();
|
|
23979
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
23980
|
+
attachedAdapters: session.attachedAdapters,
|
|
23981
|
+
platforms: this.buildPlatformsFromSession(session)
|
|
23982
|
+
});
|
|
23983
|
+
return { threadId };
|
|
23984
|
+
}
|
|
23985
|
+
async detachAdapter(sessionId, adapterId) {
|
|
23986
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
23987
|
+
if (!session) throw new Error(`Session ${sessionId} not found`);
|
|
23988
|
+
if (adapterId === session.channelId) {
|
|
23989
|
+
throw new Error("Cannot detach primary adapter (channelId)");
|
|
23990
|
+
}
|
|
23991
|
+
if (!session.attachedAdapters.includes(adapterId)) {
|
|
23992
|
+
return;
|
|
23993
|
+
}
|
|
23994
|
+
const adapter = this.adapters.get(adapterId);
|
|
23995
|
+
if (adapter) {
|
|
23996
|
+
try {
|
|
23997
|
+
await adapter.sendMessage(session.id, {
|
|
23998
|
+
type: "system_message",
|
|
23999
|
+
text: "Session detached from this adapter."
|
|
24000
|
+
});
|
|
24001
|
+
} catch {
|
|
24002
|
+
}
|
|
24003
|
+
}
|
|
24004
|
+
const key = this.bridgeKey(adapterId, session.id);
|
|
24005
|
+
const bridge = this.bridges.get(key);
|
|
24006
|
+
if (bridge) {
|
|
24007
|
+
bridge.disconnect();
|
|
24008
|
+
this.bridges.delete(key);
|
|
24009
|
+
}
|
|
24010
|
+
session.attachedAdapters = session.attachedAdapters.filter((a) => a !== adapterId);
|
|
24011
|
+
session.threadIds.delete(adapterId);
|
|
24012
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
24013
|
+
attachedAdapters: session.attachedAdapters,
|
|
24014
|
+
platforms: this.buildPlatformsFromSession(session)
|
|
24015
|
+
});
|
|
24016
|
+
}
|
|
24017
|
+
buildPlatformsFromSession(session) {
|
|
24018
|
+
const platforms = {};
|
|
24019
|
+
for (const [adapterId, threadId] of session.threadIds) {
|
|
24020
|
+
if (adapterId === "telegram") {
|
|
24021
|
+
platforms.telegram = { topicId: Number(threadId) || threadId };
|
|
24022
|
+
} else {
|
|
24023
|
+
platforms[adapterId] = { threadId };
|
|
24024
|
+
}
|
|
24025
|
+
}
|
|
24026
|
+
return platforms;
|
|
24027
|
+
}
|
|
23592
24028
|
// --- Event Wiring ---
|
|
24029
|
+
/** Composite bridge key: "adapterId:sessionId" */
|
|
24030
|
+
bridgeKey(adapterId, sessionId) {
|
|
24031
|
+
return `${adapterId}:${sessionId}`;
|
|
24032
|
+
}
|
|
24033
|
+
/** Get all bridge keys for a session (regardless of adapter) */
|
|
24034
|
+
getSessionBridgeKeys(sessionId) {
|
|
24035
|
+
const keys = [];
|
|
24036
|
+
for (const key of this.bridges.keys()) {
|
|
24037
|
+
if (key.endsWith(`:${sessionId}`)) keys.push(key);
|
|
24038
|
+
}
|
|
24039
|
+
return keys;
|
|
24040
|
+
}
|
|
23593
24041
|
/** Connect a session bridge for the given session (used by AssistantManager) */
|
|
23594
24042
|
connectSessionBridge(session) {
|
|
23595
24043
|
const adapter = this.adapters.get(session.channelId);
|
|
23596
24044
|
if (!adapter) return;
|
|
23597
|
-
const bridge = this.createBridge(session, adapter);
|
|
24045
|
+
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
23598
24046
|
bridge.connect();
|
|
23599
24047
|
}
|
|
23600
24048
|
/** Create a SessionBridge for the given session and adapter.
|
|
23601
|
-
* Disconnects any existing bridge for the same session first. */
|
|
23602
|
-
createBridge(session, adapter) {
|
|
23603
|
-
const
|
|
24049
|
+
* Disconnects any existing bridge for the same adapter+session first. */
|
|
24050
|
+
createBridge(session, adapter, adapterId) {
|
|
24051
|
+
const id = adapterId ?? adapter.name;
|
|
24052
|
+
const key = this.bridgeKey(id, session.id);
|
|
24053
|
+
const existing = this.bridges.get(key);
|
|
23604
24054
|
if (existing) {
|
|
23605
24055
|
existing.disconnect();
|
|
23606
24056
|
}
|
|
@@ -23611,8 +24061,8 @@ ${text6}`;
|
|
|
23611
24061
|
eventBus: this.eventBus,
|
|
23612
24062
|
fileService: this.fileService,
|
|
23613
24063
|
middlewareChain: this.lifecycleManager?.middlewareChain
|
|
23614
|
-
});
|
|
23615
|
-
this.bridges.set(
|
|
24064
|
+
}, id);
|
|
24065
|
+
this.bridges.set(key, bridge);
|
|
23616
24066
|
return bridge;
|
|
23617
24067
|
}
|
|
23618
24068
|
};
|
|
@@ -24257,13 +24707,15 @@ function registerSwitchCommands(registry, _core) {
|
|
|
24257
24707
|
return { type: "error", message: "No active session in this topic." };
|
|
24258
24708
|
}
|
|
24259
24709
|
if (raw) {
|
|
24710
|
+
const droppedCount = session.queueDepth;
|
|
24260
24711
|
if (session.promptRunning) {
|
|
24261
24712
|
await session.abortPrompt();
|
|
24262
24713
|
}
|
|
24263
24714
|
try {
|
|
24264
24715
|
const { resumed } = await core.switchSessionAgent(session.id, raw);
|
|
24265
24716
|
const status = resumed ? "resumed" : "new session";
|
|
24266
|
-
|
|
24717
|
+
const droppedNote = droppedCount > 0 ? ` (${droppedCount} queued prompt${droppedCount > 1 ? "s" : ""} cleared)` : "";
|
|
24718
|
+
return { type: "text", text: `\u2705 Switched to ${raw} (${status})${droppedNote}` };
|
|
24267
24719
|
} catch (err) {
|
|
24268
24720
|
return { type: "error", message: `Failed to switch agent: ${err.message || err}` };
|
|
24269
24721
|
}
|
|
@@ -24356,27 +24808,8 @@ function registerCategoryCommand(registry, core, category, commandName) {
|
|
|
24356
24808
|
if (configOption.currentValue === raw) {
|
|
24357
24809
|
return { type: "text", text: `Already using **${match.name}**.` };
|
|
24358
24810
|
}
|
|
24359
|
-
if (session.middlewareChain) {
|
|
24360
|
-
const result = await session.middlewareChain.execute("config:beforeChange", {
|
|
24361
|
-
sessionId: session.id,
|
|
24362
|
-
configId: configOption.id,
|
|
24363
|
-
oldValue: configOption.currentValue,
|
|
24364
|
-
newValue: raw
|
|
24365
|
-
}, async (p2) => p2);
|
|
24366
|
-
if (!result) return { type: "error", message: `This change was blocked by a plugin.` };
|
|
24367
|
-
}
|
|
24368
24811
|
try {
|
|
24369
|
-
|
|
24370
|
-
configOption.id,
|
|
24371
|
-
{ type: "select", value: raw }
|
|
24372
|
-
);
|
|
24373
|
-
if (response.configOptions && response.configOptions.length > 0) {
|
|
24374
|
-
session.configOptions = response.configOptions;
|
|
24375
|
-
} else {
|
|
24376
|
-
session.configOptions = session.configOptions.map(
|
|
24377
|
-
(o) => o.id === configOption.id && o.type === "select" ? { ...o, currentValue: raw } : o
|
|
24378
|
-
);
|
|
24379
|
-
}
|
|
24812
|
+
await session.setConfigOption(configOption.id, { type: "select", value: raw });
|
|
24380
24813
|
core.eventBus.emit("session:configChanged", { sessionId: session.id });
|
|
24381
24814
|
return { type: "text", text: labels.successMsg(match.name, configOption.name) };
|
|
24382
24815
|
} catch (err) {
|
|
@@ -24438,17 +24871,7 @@ function registerDangerousCommand(registry, core) {
|
|
|
24438
24871
|
if (bypassValue && modeConfig) {
|
|
24439
24872
|
try {
|
|
24440
24873
|
const targetValue = wantOn ? bypassValue : nonBypassDefault;
|
|
24441
|
-
|
|
24442
|
-
modeConfig.id,
|
|
24443
|
-
{ type: "select", value: targetValue }
|
|
24444
|
-
);
|
|
24445
|
-
if (response.configOptions && response.configOptions.length > 0) {
|
|
24446
|
-
session.configOptions = response.configOptions;
|
|
24447
|
-
} else {
|
|
24448
|
-
session.configOptions = session.configOptions.map(
|
|
24449
|
-
(o) => o.id === modeConfig.id && o.type === "select" ? { ...o, currentValue: targetValue } : o
|
|
24450
|
-
);
|
|
24451
|
-
}
|
|
24874
|
+
await session.setConfigOption(modeConfig.id, { type: "select", value: targetValue });
|
|
24452
24875
|
core.eventBus.emit("session:configChanged", { sessionId: session.id });
|
|
24453
24876
|
return {
|
|
24454
24877
|
type: "text",
|
|
@@ -26438,7 +26861,10 @@ async function startServer(opts) {
|
|
|
26438
26861
|
const tunnelErr = tunnelSvc.getStartError();
|
|
26439
26862
|
const url = tunnelSvc.getPublicUrl();
|
|
26440
26863
|
const isPublic = url && !url.startsWith("http://localhost") && !url.startsWith("http://127.0.0.1");
|
|
26441
|
-
if (tunnelErr) {
|
|
26864
|
+
if (tunnelErr && isPublic) {
|
|
26865
|
+
warn3(`Primary tunnel failed \u2014 using fallback: ${tunnelErr}`);
|
|
26866
|
+
tunnelUrl = url;
|
|
26867
|
+
} else if (tunnelErr) {
|
|
26442
26868
|
warn3(`Tunnel failed (${tunnelErr}) \u2014 retrying in background`);
|
|
26443
26869
|
} else if (isPublic) {
|
|
26444
26870
|
ok3("Tunnel ready");
|