@openacp/cli 2026.403.8 → 2026.404.1
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-CmTWKnpK.d.ts} +28 -1
- package/dist/cli.js +492 -100
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +58 -11
- package/dist/index.js +420 -51
- 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
|
}
|
|
@@ -8068,7 +8072,10 @@ var init_sessions = __esm({
|
|
|
8068
8072
|
});
|
|
8069
8073
|
PromptBodySchema = z.object({
|
|
8070
8074
|
// 100 KB limit — prevents memory exhaustion / DoS via enormous payloads
|
|
8071
|
-
prompt: z.string().min(1).max(1e5)
|
|
8075
|
+
prompt: z.string().min(1).max(1e5),
|
|
8076
|
+
// Multi-adapter routing fields
|
|
8077
|
+
sourceAdapterId: z.string().optional(),
|
|
8078
|
+
responseAdapterId: z.string().nullable().optional()
|
|
8072
8079
|
});
|
|
8073
8080
|
PermissionResponseBodySchema = z.object({
|
|
8074
8081
|
permissionId: z.string().min(1).max(200),
|
|
@@ -8105,22 +8112,23 @@ __export(sessions_exports, {
|
|
|
8105
8112
|
});
|
|
8106
8113
|
async function sessionRoutes(app, deps) {
|
|
8107
8114
|
app.get("/", { preHandler: requireScopes("sessions:read") }, async () => {
|
|
8108
|
-
const
|
|
8115
|
+
const summaries = deps.core.sessionManager.listAllSessions();
|
|
8109
8116
|
return {
|
|
8110
|
-
sessions:
|
|
8117
|
+
sessions: summaries.map((s) => ({
|
|
8111
8118
|
id: s.id,
|
|
8112
|
-
agent: s.
|
|
8119
|
+
agent: s.agent,
|
|
8113
8120
|
status: s.status,
|
|
8114
|
-
name: s.name
|
|
8115
|
-
workspace: s.
|
|
8116
|
-
|
|
8117
|
-
|
|
8121
|
+
name: s.name,
|
|
8122
|
+
workspace: s.workspace,
|
|
8123
|
+
channelId: s.channelId,
|
|
8124
|
+
createdAt: s.createdAt,
|
|
8125
|
+
lastActiveAt: s.lastActiveAt,
|
|
8126
|
+
dangerousMode: s.dangerousMode,
|
|
8118
8127
|
queueDepth: s.queueDepth,
|
|
8119
8128
|
promptRunning: s.promptRunning,
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
capabilities: s.agentCapabilities ?? null
|
|
8129
|
+
configOptions: s.configOptions,
|
|
8130
|
+
capabilities: s.capabilities,
|
|
8131
|
+
isLive: s.isLive
|
|
8124
8132
|
}))
|
|
8125
8133
|
};
|
|
8126
8134
|
});
|
|
@@ -8232,7 +8240,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8232
8240
|
async (request, reply) => {
|
|
8233
8241
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8234
8242
|
const sessionId = decodeURIComponent(rawId);
|
|
8235
|
-
const session = deps.core.
|
|
8243
|
+
const session = await deps.core.getOrResumeSessionById(sessionId);
|
|
8236
8244
|
if (!session) {
|
|
8237
8245
|
throw new NotFoundError(
|
|
8238
8246
|
"SESSION_NOT_FOUND",
|
|
@@ -8243,7 +8251,10 @@ async function sessionRoutes(app, deps) {
|
|
|
8243
8251
|
return reply.status(400).send({ error: `Session is ${session.status}` });
|
|
8244
8252
|
}
|
|
8245
8253
|
const body = PromptBodySchema.parse(request.body);
|
|
8246
|
-
await session.enqueuePrompt(body.prompt
|
|
8254
|
+
await session.enqueuePrompt(body.prompt, void 0, {
|
|
8255
|
+
sourceAdapterId: body.sourceAdapterId ?? "api",
|
|
8256
|
+
responseAdapterId: body.responseAdapterId
|
|
8257
|
+
});
|
|
8247
8258
|
return {
|
|
8248
8259
|
ok: true,
|
|
8249
8260
|
sessionId,
|
|
@@ -8290,6 +8301,9 @@ async function sessionRoutes(app, deps) {
|
|
|
8290
8301
|
const body = UpdateSessionBodySchema.parse(request.body);
|
|
8291
8302
|
const changes = {};
|
|
8292
8303
|
if (body.agentName !== void 0) {
|
|
8304
|
+
if (session.promptRunning) {
|
|
8305
|
+
await session.abortPrompt();
|
|
8306
|
+
}
|
|
8293
8307
|
const result = await deps.core.switchSessionAgent(sessionId, body.agentName);
|
|
8294
8308
|
changes.agentName = body.agentName;
|
|
8295
8309
|
changes.resumed = result.resumed;
|
|
@@ -8425,6 +8439,36 @@ async function sessionRoutes(app, deps) {
|
|
|
8425
8439
|
}
|
|
8426
8440
|
}
|
|
8427
8441
|
);
|
|
8442
|
+
app.post(
|
|
8443
|
+
"/:sessionId/attach",
|
|
8444
|
+
{ preHandler: requireScopes("sessions:write") },
|
|
8445
|
+
async (request, reply) => {
|
|
8446
|
+
const { sessionId } = request.params;
|
|
8447
|
+
const { adapterId } = request.body ?? {};
|
|
8448
|
+
if (!adapterId) return reply.code(400).send({ error: "adapterId is required" });
|
|
8449
|
+
try {
|
|
8450
|
+
const result = await deps.core.attachAdapter(sessionId, adapterId);
|
|
8451
|
+
return { ok: true, threadId: result.threadId };
|
|
8452
|
+
} catch (err) {
|
|
8453
|
+
return reply.code(400).send({ error: err.message });
|
|
8454
|
+
}
|
|
8455
|
+
}
|
|
8456
|
+
);
|
|
8457
|
+
app.post(
|
|
8458
|
+
"/:sessionId/detach",
|
|
8459
|
+
{ preHandler: requireScopes("sessions:write") },
|
|
8460
|
+
async (request, reply) => {
|
|
8461
|
+
const { sessionId } = request.params;
|
|
8462
|
+
const { adapterId } = request.body ?? {};
|
|
8463
|
+
if (!adapterId) return reply.code(400).send({ error: "adapterId is required" });
|
|
8464
|
+
try {
|
|
8465
|
+
await deps.core.detachAdapter(sessionId, adapterId);
|
|
8466
|
+
return { ok: true };
|
|
8467
|
+
} catch (err) {
|
|
8468
|
+
return reply.code(400).send({ error: err.message });
|
|
8469
|
+
}
|
|
8470
|
+
}
|
|
8471
|
+
);
|
|
8428
8472
|
app.get(
|
|
8429
8473
|
"/:sessionId/history",
|
|
8430
8474
|
{ preHandler: requireScopes("sessions:read") },
|
|
@@ -10603,7 +10647,7 @@ async function sseRoutes(app, deps) {
|
|
|
10603
10647
|
return reply.status(400).send({ error: `Session is ${session.status}` });
|
|
10604
10648
|
}
|
|
10605
10649
|
const body = PromptBodySchema.parse(request.body);
|
|
10606
|
-
await session.enqueuePrompt(body.prompt);
|
|
10650
|
+
await session.enqueuePrompt(body.prompt, void 0, { sourceAdapterId: "sse" });
|
|
10607
10651
|
return { ok: true, sessionId, queueDepth: session.queueDepth };
|
|
10608
10652
|
}
|
|
10609
10653
|
);
|
|
@@ -18855,6 +18899,7 @@ var init_agent_instance = __esm({
|
|
|
18855
18899
|
{ sessionId: this.sessionId, exitCode: code, signal },
|
|
18856
18900
|
"Agent process exited"
|
|
18857
18901
|
);
|
|
18902
|
+
if (signal === "SIGINT" || signal === "SIGTERM") return;
|
|
18858
18903
|
if (code !== 0 && code !== null || signal) {
|
|
18859
18904
|
const stderr = this.stderrCapture.getLastLines();
|
|
18860
18905
|
this.emit("agent_event", {
|
|
@@ -19351,21 +19396,21 @@ var init_prompt_queue = __esm({
|
|
|
19351
19396
|
queue = [];
|
|
19352
19397
|
processing = false;
|
|
19353
19398
|
abortController = null;
|
|
19354
|
-
async enqueue(text6, attachments) {
|
|
19399
|
+
async enqueue(text6, attachments, routing) {
|
|
19355
19400
|
if (this.processing) {
|
|
19356
19401
|
return new Promise((resolve8) => {
|
|
19357
|
-
this.queue.push({ text: text6, attachments, resolve: resolve8 });
|
|
19402
|
+
this.queue.push({ text: text6, attachments, routing, resolve: resolve8 });
|
|
19358
19403
|
});
|
|
19359
19404
|
}
|
|
19360
|
-
await this.process(text6, attachments);
|
|
19405
|
+
await this.process(text6, attachments, routing);
|
|
19361
19406
|
}
|
|
19362
|
-
async process(text6, attachments) {
|
|
19407
|
+
async process(text6, attachments, routing) {
|
|
19363
19408
|
this.processing = true;
|
|
19364
19409
|
this.abortController = new AbortController();
|
|
19365
19410
|
const { signal } = this.abortController;
|
|
19366
19411
|
try {
|
|
19367
19412
|
await Promise.race([
|
|
19368
|
-
this.processor(text6, attachments),
|
|
19413
|
+
this.processor(text6, attachments, routing),
|
|
19369
19414
|
new Promise((_, reject) => {
|
|
19370
19415
|
signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
|
|
19371
19416
|
})
|
|
@@ -19383,7 +19428,7 @@ var init_prompt_queue = __esm({
|
|
|
19383
19428
|
drainNext() {
|
|
19384
19429
|
const next = this.queue.shift();
|
|
19385
19430
|
if (next) {
|
|
19386
|
-
this.process(next.text, next.attachments).then(next.resolve);
|
|
19431
|
+
this.process(next.text, next.attachments, next.routing).then(next.resolve);
|
|
19387
19432
|
}
|
|
19388
19433
|
}
|
|
19389
19434
|
clear() {
|
|
@@ -19478,8 +19523,39 @@ var init_permission_gate = __esm({
|
|
|
19478
19523
|
}
|
|
19479
19524
|
});
|
|
19480
19525
|
|
|
19481
|
-
// src/core/sessions/
|
|
19526
|
+
// src/core/sessions/turn-context.ts
|
|
19482
19527
|
import { nanoid as nanoid3 } from "nanoid";
|
|
19528
|
+
function createTurnContext(sourceAdapterId, responseAdapterId) {
|
|
19529
|
+
return {
|
|
19530
|
+
turnId: nanoid3(8),
|
|
19531
|
+
sourceAdapterId,
|
|
19532
|
+
responseAdapterId
|
|
19533
|
+
};
|
|
19534
|
+
}
|
|
19535
|
+
function getEffectiveTarget(ctx) {
|
|
19536
|
+
if (ctx.responseAdapterId === null) return null;
|
|
19537
|
+
return ctx.responseAdapterId ?? ctx.sourceAdapterId;
|
|
19538
|
+
}
|
|
19539
|
+
function isSystemEvent(event) {
|
|
19540
|
+
return SYSTEM_EVENT_TYPES.has(event.type);
|
|
19541
|
+
}
|
|
19542
|
+
var SYSTEM_EVENT_TYPES;
|
|
19543
|
+
var init_turn_context = __esm({
|
|
19544
|
+
"src/core/sessions/turn-context.ts"() {
|
|
19545
|
+
"use strict";
|
|
19546
|
+
SYSTEM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
19547
|
+
"session_end",
|
|
19548
|
+
"system_message",
|
|
19549
|
+
"session_info_update",
|
|
19550
|
+
"config_option_update",
|
|
19551
|
+
"commands_update",
|
|
19552
|
+
"tts_strip"
|
|
19553
|
+
]);
|
|
19554
|
+
}
|
|
19555
|
+
});
|
|
19556
|
+
|
|
19557
|
+
// src/core/sessions/session.ts
|
|
19558
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
19483
19559
|
import * as fs41 from "fs";
|
|
19484
19560
|
var moduleLog, TTS_PROMPT_INSTRUCTION, TTS_BLOCK_REGEX, TTS_MAX_LENGTH, TTS_TIMEOUT_MS, VALID_TRANSITIONS, Session;
|
|
19485
19561
|
var init_session2 = __esm({
|
|
@@ -19489,6 +19565,7 @@ var init_session2 = __esm({
|
|
|
19489
19565
|
init_prompt_queue();
|
|
19490
19566
|
init_permission_gate();
|
|
19491
19567
|
init_log();
|
|
19568
|
+
init_turn_context();
|
|
19492
19569
|
moduleLog = createChildLogger({ module: "session" });
|
|
19493
19570
|
TTS_PROMPT_INSTRUCTION = `
|
|
19494
19571
|
|
|
@@ -19506,7 +19583,15 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19506
19583
|
Session = class extends TypedEmitter {
|
|
19507
19584
|
id;
|
|
19508
19585
|
channelId;
|
|
19509
|
-
threadId
|
|
19586
|
+
/** @deprecated Use threadIds map directly. Getter returns primary adapter's threadId. */
|
|
19587
|
+
get threadId() {
|
|
19588
|
+
return this.threadIds.get(this.channelId) ?? "";
|
|
19589
|
+
}
|
|
19590
|
+
set threadId(value) {
|
|
19591
|
+
if (value) {
|
|
19592
|
+
this.threadIds.set(this.channelId, value);
|
|
19593
|
+
}
|
|
19594
|
+
}
|
|
19510
19595
|
agentName;
|
|
19511
19596
|
workingDirectory;
|
|
19512
19597
|
agentInstance;
|
|
@@ -19527,14 +19612,21 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19527
19612
|
middlewareChain;
|
|
19528
19613
|
/** Latest commands emitted by the agent — buffered before bridge connects so they're not lost */
|
|
19529
19614
|
latestCommands = null;
|
|
19615
|
+
/** Adapters currently attached to this session (including primary) */
|
|
19616
|
+
attachedAdapters = [];
|
|
19617
|
+
/** Per-adapter thread IDs: adapterId → threadId */
|
|
19618
|
+
threadIds = /* @__PURE__ */ new Map();
|
|
19619
|
+
/** Active turn context — sealed on prompt dequeue, cleared on turn end */
|
|
19620
|
+
activeTurnContext = null;
|
|
19530
19621
|
permissionGate = new PermissionGate();
|
|
19531
19622
|
queue;
|
|
19532
19623
|
speechService;
|
|
19533
19624
|
pendingContext = null;
|
|
19534
19625
|
constructor(opts) {
|
|
19535
19626
|
super();
|
|
19536
|
-
this.id = opts.id ||
|
|
19627
|
+
this.id = opts.id || nanoid4(12);
|
|
19537
19628
|
this.channelId = opts.channelId;
|
|
19629
|
+
this.attachedAdapters = [opts.channelId];
|
|
19538
19630
|
this.agentName = opts.agentName;
|
|
19539
19631
|
this.firstAgent = opts.agentName;
|
|
19540
19632
|
this.workingDirectory = opts.workingDirectory;
|
|
@@ -19544,7 +19636,7 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19544
19636
|
this.log = createSessionLogger(this.id, moduleLog);
|
|
19545
19637
|
this.log.info({ agentName: this.agentName }, "Session created");
|
|
19546
19638
|
this.queue = new PromptQueue(
|
|
19547
|
-
(text6, attachments) => this.processPrompt(text6, attachments),
|
|
19639
|
+
(text6, attachments, routing) => this.processPrompt(text6, attachments, routing),
|
|
19548
19640
|
(err) => {
|
|
19549
19641
|
this.log.error({ err }, "Prompt execution failed");
|
|
19550
19642
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -19552,11 +19644,20 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19552
19644
|
this.emit("agent_event", { type: "error", message: `Prompt execution failed: ${message}` });
|
|
19553
19645
|
}
|
|
19554
19646
|
);
|
|
19555
|
-
this.
|
|
19647
|
+
this.wireCommandsBuffer();
|
|
19648
|
+
}
|
|
19649
|
+
/** Wire a listener on the current agentInstance to buffer commands_update events.
|
|
19650
|
+
* Must be called after every agentInstance replacement (constructor + switchAgent). */
|
|
19651
|
+
commandsBufferCleanup;
|
|
19652
|
+
wireCommandsBuffer() {
|
|
19653
|
+
this.commandsBufferCleanup?.();
|
|
19654
|
+
const handler = (event) => {
|
|
19556
19655
|
if (event.type === "commands_update") {
|
|
19557
19656
|
this.latestCommands = event.commands;
|
|
19558
19657
|
}
|
|
19559
|
-
}
|
|
19658
|
+
};
|
|
19659
|
+
this.agentInstance.on("agent_event", handler);
|
|
19660
|
+
this.commandsBufferCleanup = () => this.agentInstance.off("agent_event", handler);
|
|
19560
19661
|
}
|
|
19561
19662
|
// --- State Machine ---
|
|
19562
19663
|
get status() {
|
|
@@ -19610,7 +19711,7 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19610
19711
|
this.log.info({ voiceMode: mode }, "TTS mode changed");
|
|
19611
19712
|
}
|
|
19612
19713
|
// --- Public API ---
|
|
19613
|
-
async enqueuePrompt(text6, attachments) {
|
|
19714
|
+
async enqueuePrompt(text6, attachments, routing) {
|
|
19614
19715
|
if (this.middlewareChain) {
|
|
19615
19716
|
const payload = { text: text6, attachments, sessionId: this.id };
|
|
19616
19717
|
const result = await this.middlewareChain.execute("agent:beforePrompt", payload, async (p2) => p2);
|
|
@@ -19618,10 +19719,14 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
19618
19719
|
text6 = result.text;
|
|
19619
19720
|
attachments = result.attachments;
|
|
19620
19721
|
}
|
|
19621
|
-
await this.queue.enqueue(text6, attachments);
|
|
19722
|
+
await this.queue.enqueue(text6, attachments, routing);
|
|
19622
19723
|
}
|
|
19623
|
-
async processPrompt(text6, attachments) {
|
|
19724
|
+
async processPrompt(text6, attachments, routing) {
|
|
19624
19725
|
if (this._status === "finished") return;
|
|
19726
|
+
this.activeTurnContext = createTurnContext(
|
|
19727
|
+
routing?.sourceAdapterId ?? this.channelId,
|
|
19728
|
+
routing?.responseAdapterId
|
|
19729
|
+
);
|
|
19625
19730
|
this.promptCount++;
|
|
19626
19731
|
this.emit("prompt_count_changed", this.promptCount);
|
|
19627
19732
|
if (this._status === "initializing" || this._status === "cancelled" || this._status === "error") {
|
|
@@ -19688,6 +19793,7 @@ ${text6}`;
|
|
|
19688
19793
|
this.log.warn({ err }, "TTS post-processing failed");
|
|
19689
19794
|
});
|
|
19690
19795
|
}
|
|
19796
|
+
this.activeTurnContext = null;
|
|
19691
19797
|
if (!this.name) {
|
|
19692
19798
|
await this.autoName();
|
|
19693
19799
|
}
|
|
@@ -19930,6 +20036,7 @@ ${result.text}` : result.text;
|
|
|
19930
20036
|
this.configOptions = [];
|
|
19931
20037
|
this.latestCommands = null;
|
|
19932
20038
|
this.applySpawnResponse(newAgent.initialSessionResponse, newAgent.agentCapabilities);
|
|
20039
|
+
this.wireCommandsBuffer();
|
|
19933
20040
|
this.log.info({ from: this.agentSwitchHistory.at(-1).agentName, to: agentName }, "Agent switched");
|
|
19934
20041
|
}
|
|
19935
20042
|
async destroy() {
|
|
@@ -19994,6 +20101,8 @@ var init_session_manager = __esm({
|
|
|
19994
20101
|
}
|
|
19995
20102
|
getSessionByThread(channelId, threadId) {
|
|
19996
20103
|
for (const session of this.sessions.values()) {
|
|
20104
|
+
const adapterThread = session.threadIds.get(channelId);
|
|
20105
|
+
if (adapterThread === threadId) return session;
|
|
19997
20106
|
if (session.channelId === channelId && session.threadId === threadId) {
|
|
19998
20107
|
return session;
|
|
19999
20108
|
}
|
|
@@ -20061,6 +20170,67 @@ var init_session_manager = __esm({
|
|
|
20061
20170
|
if (channelId) return all.filter((s) => s.channelId === channelId);
|
|
20062
20171
|
return all;
|
|
20063
20172
|
}
|
|
20173
|
+
listAllSessions(channelId) {
|
|
20174
|
+
if (this.store) {
|
|
20175
|
+
let records = this.store.list();
|
|
20176
|
+
if (channelId) records = records.filter((r) => r.channelId === channelId);
|
|
20177
|
+
return records.map((record) => {
|
|
20178
|
+
const live2 = this.sessions.get(record.sessionId);
|
|
20179
|
+
if (live2) {
|
|
20180
|
+
return {
|
|
20181
|
+
id: live2.id,
|
|
20182
|
+
agent: live2.agentName,
|
|
20183
|
+
status: live2.status,
|
|
20184
|
+
name: live2.name ?? null,
|
|
20185
|
+
workspace: live2.workingDirectory,
|
|
20186
|
+
channelId: live2.channelId,
|
|
20187
|
+
createdAt: live2.createdAt.toISOString(),
|
|
20188
|
+
lastActiveAt: record.lastActiveAt ?? null,
|
|
20189
|
+
dangerousMode: live2.clientOverrides.bypassPermissions ?? false,
|
|
20190
|
+
queueDepth: live2.queueDepth,
|
|
20191
|
+
promptRunning: live2.promptRunning,
|
|
20192
|
+
configOptions: live2.configOptions?.length ? live2.configOptions : void 0,
|
|
20193
|
+
capabilities: live2.agentCapabilities ?? null,
|
|
20194
|
+
isLive: true
|
|
20195
|
+
};
|
|
20196
|
+
}
|
|
20197
|
+
return {
|
|
20198
|
+
id: record.sessionId,
|
|
20199
|
+
agent: record.agentName,
|
|
20200
|
+
status: record.status,
|
|
20201
|
+
name: record.name ?? null,
|
|
20202
|
+
workspace: record.workingDir,
|
|
20203
|
+
channelId: record.channelId,
|
|
20204
|
+
createdAt: record.createdAt,
|
|
20205
|
+
lastActiveAt: record.lastActiveAt ?? null,
|
|
20206
|
+
dangerousMode: record.clientOverrides?.bypassPermissions ?? false,
|
|
20207
|
+
queueDepth: 0,
|
|
20208
|
+
promptRunning: false,
|
|
20209
|
+
configOptions: record.acpState?.configOptions,
|
|
20210
|
+
capabilities: record.acpState?.agentCapabilities ?? null,
|
|
20211
|
+
isLive: false
|
|
20212
|
+
};
|
|
20213
|
+
});
|
|
20214
|
+
}
|
|
20215
|
+
let live = Array.from(this.sessions.values());
|
|
20216
|
+
if (channelId) live = live.filter((s) => s.channelId === channelId);
|
|
20217
|
+
return live.map((s) => ({
|
|
20218
|
+
id: s.id,
|
|
20219
|
+
agent: s.agentName,
|
|
20220
|
+
status: s.status,
|
|
20221
|
+
name: s.name ?? null,
|
|
20222
|
+
workspace: s.workingDirectory,
|
|
20223
|
+
channelId: s.channelId,
|
|
20224
|
+
createdAt: s.createdAt.toISOString(),
|
|
20225
|
+
lastActiveAt: null,
|
|
20226
|
+
dangerousMode: s.clientOverrides.bypassPermissions ?? false,
|
|
20227
|
+
queueDepth: s.queueDepth,
|
|
20228
|
+
promptRunning: s.promptRunning,
|
|
20229
|
+
configOptions: s.configOptions?.length ? s.configOptions : void 0,
|
|
20230
|
+
capabilities: s.agentCapabilities ?? null,
|
|
20231
|
+
isLive: true
|
|
20232
|
+
}));
|
|
20233
|
+
}
|
|
20064
20234
|
listRecords(filter) {
|
|
20065
20235
|
if (!this.store) return [];
|
|
20066
20236
|
let records = this.store.list();
|
|
@@ -20083,7 +20253,14 @@ var init_session_manager = __esm({
|
|
|
20083
20253
|
for (const session of this.sessions.values()) {
|
|
20084
20254
|
const record = this.store.get(session.id);
|
|
20085
20255
|
if (record) {
|
|
20086
|
-
await this.store.save({
|
|
20256
|
+
await this.store.save({
|
|
20257
|
+
...record,
|
|
20258
|
+
status: "finished",
|
|
20259
|
+
acpState: session.toAcpStateSnapshot(),
|
|
20260
|
+
clientOverrides: session.clientOverrides,
|
|
20261
|
+
currentPromptCount: session.promptCount,
|
|
20262
|
+
agentSwitchHistory: session.agentSwitchHistory
|
|
20263
|
+
});
|
|
20087
20264
|
}
|
|
20088
20265
|
}
|
|
20089
20266
|
this.store.flush();
|
|
@@ -20093,6 +20270,8 @@ var init_session_manager = __esm({
|
|
|
20093
20270
|
/**
|
|
20094
20271
|
* Forcefully destroy all sessions (kill agent subprocesses).
|
|
20095
20272
|
* Use only when sessions must be fully torn down (e.g. archive).
|
|
20273
|
+
* Unlike shutdownAll(), this does NOT snapshot live session state (acpState, etc.)
|
|
20274
|
+
* because destroyed sessions are terminal and will not be resumed.
|
|
20096
20275
|
*/
|
|
20097
20276
|
async destroyAll() {
|
|
20098
20277
|
if (this.store) {
|
|
@@ -20127,15 +20306,18 @@ var init_session_bridge = __esm({
|
|
|
20127
20306
|
"use strict";
|
|
20128
20307
|
init_log();
|
|
20129
20308
|
init_bypass_detection();
|
|
20309
|
+
init_turn_context();
|
|
20130
20310
|
log30 = createChildLogger({ module: "session-bridge" });
|
|
20131
20311
|
SessionBridge = class {
|
|
20132
|
-
constructor(session, adapter, deps) {
|
|
20312
|
+
constructor(session, adapter, deps, adapterId) {
|
|
20133
20313
|
this.session = session;
|
|
20134
20314
|
this.adapter = adapter;
|
|
20135
20315
|
this.deps = deps;
|
|
20316
|
+
this.adapterId = adapterId ?? adapter.name;
|
|
20136
20317
|
}
|
|
20137
20318
|
connected = false;
|
|
20138
20319
|
cleanupFns = [];
|
|
20320
|
+
adapterId;
|
|
20139
20321
|
get tracer() {
|
|
20140
20322
|
return this.session.agentInstance.debugTracer ?? null;
|
|
20141
20323
|
}
|
|
@@ -20166,6 +20348,15 @@ var init_session_bridge = __esm({
|
|
|
20166
20348
|
log30.error({ err, sessionId }, "Error in sendMessage middleware");
|
|
20167
20349
|
}
|
|
20168
20350
|
}
|
|
20351
|
+
/** Determine if this bridge should forward the given event based on turn routing. */
|
|
20352
|
+
shouldForward(event) {
|
|
20353
|
+
if (isSystemEvent(event)) return true;
|
|
20354
|
+
const ctx = this.session.activeTurnContext;
|
|
20355
|
+
if (!ctx) return true;
|
|
20356
|
+
const target = getEffectiveTarget(ctx);
|
|
20357
|
+
if (target === null) return false;
|
|
20358
|
+
return this.adapterId === target;
|
|
20359
|
+
}
|
|
20169
20360
|
connect() {
|
|
20170
20361
|
if (this.connected) return;
|
|
20171
20362
|
this.connected = true;
|
|
@@ -20173,11 +20364,29 @@ var init_session_bridge = __esm({
|
|
|
20173
20364
|
this.session.emit("agent_event", event);
|
|
20174
20365
|
});
|
|
20175
20366
|
this.listen(this.session, "agent_event", (event) => {
|
|
20176
|
-
this.
|
|
20367
|
+
if (this.shouldForward(event)) {
|
|
20368
|
+
this.dispatchAgentEvent(event);
|
|
20369
|
+
} else {
|
|
20370
|
+
this.deps.eventBus?.emit("agent:event", { sessionId: this.session.id, event });
|
|
20371
|
+
}
|
|
20372
|
+
});
|
|
20373
|
+
if (!this.session.agentInstance.onPermissionRequest || this.session.agentInstance.onPermissionRequest.__bridgeId === void 0) {
|
|
20374
|
+
const handler = async (request) => {
|
|
20375
|
+
return this.resolvePermission(request);
|
|
20376
|
+
};
|
|
20377
|
+
handler.__bridgeId = this.adapterId;
|
|
20378
|
+
this.session.agentInstance.onPermissionRequest = handler;
|
|
20379
|
+
}
|
|
20380
|
+
this.listen(this.session, "permission_request", async (request) => {
|
|
20381
|
+
const current = this.session.agentInstance.onPermissionRequest;
|
|
20382
|
+
if (current?.__bridgeId === this.adapterId) return;
|
|
20383
|
+
if (!this.session.permissionGate.isPending) return;
|
|
20384
|
+
try {
|
|
20385
|
+
await this.adapter.sendPermissionRequest(this.session.id, request);
|
|
20386
|
+
} catch (err) {
|
|
20387
|
+
log30.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
|
|
20388
|
+
}
|
|
20177
20389
|
});
|
|
20178
|
-
this.session.agentInstance.onPermissionRequest = async (request) => {
|
|
20179
|
-
return this.resolvePermission(request);
|
|
20180
|
-
};
|
|
20181
20390
|
this.listen(this.session, "status_change", (from, to) => {
|
|
20182
20391
|
this.deps.sessionManager.patchRecord(this.session.id, {
|
|
20183
20392
|
status: to,
|
|
@@ -20218,7 +20427,10 @@ var init_session_bridge = __esm({
|
|
|
20218
20427
|
this.connected = false;
|
|
20219
20428
|
this.cleanupFns.forEach((fn) => fn());
|
|
20220
20429
|
this.cleanupFns = [];
|
|
20221
|
-
this.session.agentInstance.onPermissionRequest
|
|
20430
|
+
const current = this.session.agentInstance.onPermissionRequest;
|
|
20431
|
+
if (current?.__bridgeId === this.adapterId) {
|
|
20432
|
+
this.session.agentInstance.onPermissionRequest = async () => "";
|
|
20433
|
+
}
|
|
20222
20434
|
}
|
|
20223
20435
|
/** Dispatch an agent event through middleware and to the adapter */
|
|
20224
20436
|
async dispatchAgentEvent(event) {
|
|
@@ -20349,8 +20561,10 @@ var init_session_bridge = __esm({
|
|
|
20349
20561
|
this.sendMessage(this.session.id, outgoing);
|
|
20350
20562
|
break;
|
|
20351
20563
|
case "config_option_update":
|
|
20352
|
-
this.session.updateConfigOptions(event.options)
|
|
20353
|
-
|
|
20564
|
+
this.session.updateConfigOptions(event.options).then(() => {
|
|
20565
|
+
this.persistAcpState();
|
|
20566
|
+
}).catch(() => {
|
|
20567
|
+
});
|
|
20354
20568
|
outgoing = this.deps.messageTransformer.transform(event);
|
|
20355
20569
|
this.sendMessage(this.session.id, outgoing);
|
|
20356
20570
|
break;
|
|
@@ -20394,19 +20608,27 @@ var init_session_bridge = __esm({
|
|
|
20394
20608
|
return result.autoResolve;
|
|
20395
20609
|
}
|
|
20396
20610
|
}
|
|
20397
|
-
this.session.emit("permission_request", permReq);
|
|
20398
20611
|
this.deps.eventBus?.emit("permission:request", {
|
|
20399
20612
|
sessionId: this.session.id,
|
|
20400
20613
|
permission: permReq
|
|
20401
20614
|
});
|
|
20402
20615
|
const autoDecision = this.checkAutoApprove(permReq);
|
|
20403
20616
|
if (autoDecision) {
|
|
20617
|
+
this.session.emit("permission_request", permReq);
|
|
20404
20618
|
this.emitAfterResolve(mw, permReq.id, autoDecision, "system", startTime);
|
|
20405
20619
|
return autoDecision;
|
|
20406
20620
|
}
|
|
20407
20621
|
const promise = this.session.permissionGate.setPending(permReq);
|
|
20622
|
+
this.session.emit("permission_request", permReq);
|
|
20408
20623
|
await this.adapter.sendPermissionRequest(this.session.id, permReq);
|
|
20409
20624
|
const optionId = await promise;
|
|
20625
|
+
this.deps.eventBus?.emit("permission:resolved", {
|
|
20626
|
+
sessionId: this.session.id,
|
|
20627
|
+
requestId: permReq.id,
|
|
20628
|
+
decision: optionId,
|
|
20629
|
+
optionId,
|
|
20630
|
+
resolvedBy: this.adapterId
|
|
20631
|
+
});
|
|
20410
20632
|
this.emitAfterResolve(mw, permReq.id, optionId, "user", startTime);
|
|
20411
20633
|
return optionId;
|
|
20412
20634
|
}
|
|
@@ -20906,6 +21128,9 @@ var init_session_store = __esm({
|
|
|
20906
21128
|
}
|
|
20907
21129
|
findByPlatform(channelId, predicate) {
|
|
20908
21130
|
for (const record of this.records.values()) {
|
|
21131
|
+
if (record.platforms?.[channelId]) {
|
|
21132
|
+
if (predicate(record.platforms[channelId])) return record;
|
|
21133
|
+
}
|
|
20909
21134
|
if (record.channelId === channelId && predicate(record.platform)) {
|
|
20910
21135
|
return record;
|
|
20911
21136
|
}
|
|
@@ -20972,7 +21197,7 @@ var init_session_store = __esm({
|
|
|
20972
21197
|
return;
|
|
20973
21198
|
}
|
|
20974
21199
|
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
20975
|
-
this.records.set(id, record);
|
|
21200
|
+
this.records.set(id, this.migrateRecord(record));
|
|
20976
21201
|
}
|
|
20977
21202
|
log32.debug({ count: this.records.size }, "Loaded session records");
|
|
20978
21203
|
} catch (err) {
|
|
@@ -20983,6 +21208,19 @@ var init_session_store = __esm({
|
|
|
20983
21208
|
}
|
|
20984
21209
|
}
|
|
20985
21210
|
}
|
|
21211
|
+
/** Migrate old SessionRecord format to new multi-adapter format. */
|
|
21212
|
+
migrateRecord(record) {
|
|
21213
|
+
if (!record.platforms && record.platform && typeof record.platform === "object") {
|
|
21214
|
+
const platformData = record.platform;
|
|
21215
|
+
if (Object.keys(platformData).length > 0) {
|
|
21216
|
+
record.platforms = { [record.channelId]: platformData };
|
|
21217
|
+
}
|
|
21218
|
+
}
|
|
21219
|
+
if (!record.attachedAdapters) {
|
|
21220
|
+
record.attachedAdapters = [record.channelId];
|
|
21221
|
+
}
|
|
21222
|
+
return record;
|
|
21223
|
+
}
|
|
20986
21224
|
cleanup() {
|
|
20987
21225
|
const cutoff = Date.now() - this.ttlDays * 24 * 60 * 60 * 1e3;
|
|
20988
21226
|
let removed = 0;
|
|
@@ -21171,6 +21409,65 @@ var init_session_factory = __esm({
|
|
|
21171
21409
|
if (session) return session;
|
|
21172
21410
|
return this.lazyResume(channelId, threadId);
|
|
21173
21411
|
}
|
|
21412
|
+
async getOrResumeById(sessionId) {
|
|
21413
|
+
const live = this.sessionManager.getSession(sessionId);
|
|
21414
|
+
if (live) return live;
|
|
21415
|
+
if (!this.sessionStore || !this.createFullSession) return null;
|
|
21416
|
+
const record = this.sessionStore.get(sessionId);
|
|
21417
|
+
if (!record) return null;
|
|
21418
|
+
if (record.status === "error" || record.status === "cancelled") return null;
|
|
21419
|
+
const existing = this.resumeLocks.get(sessionId);
|
|
21420
|
+
if (existing) return existing;
|
|
21421
|
+
const resumePromise = (async () => {
|
|
21422
|
+
try {
|
|
21423
|
+
const p2 = record.platform;
|
|
21424
|
+
const existingThreadId = p2?.topicId ? String(p2.topicId) : p2?.threadId;
|
|
21425
|
+
const session = await this.createFullSession({
|
|
21426
|
+
channelId: record.channelId,
|
|
21427
|
+
agentName: record.agentName,
|
|
21428
|
+
workingDirectory: record.workingDir,
|
|
21429
|
+
resumeAgentSessionId: record.agentSessionId,
|
|
21430
|
+
existingSessionId: record.sessionId,
|
|
21431
|
+
initialName: record.name,
|
|
21432
|
+
threadId: existingThreadId
|
|
21433
|
+
});
|
|
21434
|
+
session.activate();
|
|
21435
|
+
if (record.clientOverrides) {
|
|
21436
|
+
session.clientOverrides = record.clientOverrides;
|
|
21437
|
+
} else if (record.dangerousMode) {
|
|
21438
|
+
session.clientOverrides = { bypassPermissions: true };
|
|
21439
|
+
}
|
|
21440
|
+
if (record.firstAgent) session.firstAgent = record.firstAgent;
|
|
21441
|
+
if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
|
|
21442
|
+
if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
|
|
21443
|
+
if (record.attachedAdapters) session.attachedAdapters = record.attachedAdapters;
|
|
21444
|
+
if (record.platforms) {
|
|
21445
|
+
for (const [adapterId, platformData] of Object.entries(record.platforms)) {
|
|
21446
|
+
const data = platformData;
|
|
21447
|
+
const tid = adapterId === "telegram" ? String(data.topicId ?? "") : String(data.threadId ?? "");
|
|
21448
|
+
if (tid) session.threadIds.set(adapterId, tid);
|
|
21449
|
+
}
|
|
21450
|
+
}
|
|
21451
|
+
if (record.acpState) {
|
|
21452
|
+
if (record.acpState.configOptions && session.configOptions.length === 0) {
|
|
21453
|
+
session.setInitialConfigOptions(record.acpState.configOptions);
|
|
21454
|
+
}
|
|
21455
|
+
if (record.acpState.agentCapabilities && !session.agentCapabilities) {
|
|
21456
|
+
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
21457
|
+
}
|
|
21458
|
+
}
|
|
21459
|
+
log33.info({ sessionId }, "Lazy resume by ID successful");
|
|
21460
|
+
return session;
|
|
21461
|
+
} catch (err) {
|
|
21462
|
+
log33.error({ err, sessionId }, "Lazy resume by ID failed");
|
|
21463
|
+
return null;
|
|
21464
|
+
} finally {
|
|
21465
|
+
this.resumeLocks.delete(sessionId);
|
|
21466
|
+
}
|
|
21467
|
+
})();
|
|
21468
|
+
this.resumeLocks.set(sessionId, resumePromise);
|
|
21469
|
+
return resumePromise;
|
|
21470
|
+
}
|
|
21174
21471
|
async lazyResume(channelId, threadId) {
|
|
21175
21472
|
const store = this.sessionStore;
|
|
21176
21473
|
if (!store || !this.createFullSession) return null;
|
|
@@ -21179,7 +21476,7 @@ var init_session_factory = __esm({
|
|
|
21179
21476
|
if (existing) return existing;
|
|
21180
21477
|
const record = store.findByPlatform(
|
|
21181
21478
|
channelId,
|
|
21182
|
-
(p2) => String(p2.topicId) === threadId
|
|
21479
|
+
(p2) => String(p2.topicId) === threadId || String(p2.threadId ?? "") === threadId
|
|
21183
21480
|
);
|
|
21184
21481
|
if (!record) {
|
|
21185
21482
|
log33.debug({ threadId, channelId }, "No session record found for thread");
|
|
@@ -21214,11 +21511,21 @@ var init_session_factory = __esm({
|
|
|
21214
21511
|
if (record.firstAgent) session.firstAgent = record.firstAgent;
|
|
21215
21512
|
if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
|
|
21216
21513
|
if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
|
|
21514
|
+
if (record.attachedAdapters) {
|
|
21515
|
+
session.attachedAdapters = record.attachedAdapters;
|
|
21516
|
+
}
|
|
21517
|
+
if (record.platforms) {
|
|
21518
|
+
for (const [adapterId, platformData] of Object.entries(record.platforms)) {
|
|
21519
|
+
const data = platformData;
|
|
21520
|
+
const tid = adapterId === "telegram" ? String(data.topicId ?? "") : String(data.threadId ?? "");
|
|
21521
|
+
if (tid) session.threadIds.set(adapterId, tid);
|
|
21522
|
+
}
|
|
21523
|
+
}
|
|
21217
21524
|
if (record.acpState) {
|
|
21218
|
-
if (record.acpState.configOptions) {
|
|
21525
|
+
if (record.acpState.configOptions && session.configOptions.length === 0) {
|
|
21219
21526
|
session.setInitialConfigOptions(record.acpState.configOptions);
|
|
21220
21527
|
}
|
|
21221
|
-
if (record.acpState.agentCapabilities) {
|
|
21528
|
+
if (record.acpState.agentCapabilities && !session.agentCapabilities) {
|
|
21222
21529
|
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
21223
21530
|
}
|
|
21224
21531
|
}
|
|
@@ -21401,8 +21708,15 @@ var init_agent_switch_handler = __esm({
|
|
|
21401
21708
|
toAgent,
|
|
21402
21709
|
status: "starting"
|
|
21403
21710
|
});
|
|
21404
|
-
const
|
|
21405
|
-
|
|
21711
|
+
const sessionBridgeKeys = this.deps.getSessionBridgeKeys(sessionId);
|
|
21712
|
+
const hadBridges = sessionBridgeKeys.length > 0;
|
|
21713
|
+
for (const key of sessionBridgeKeys) {
|
|
21714
|
+
const bridge = bridges.get(key);
|
|
21715
|
+
if (bridge) {
|
|
21716
|
+
bridges.delete(key);
|
|
21717
|
+
bridge.disconnect();
|
|
21718
|
+
}
|
|
21719
|
+
}
|
|
21406
21720
|
const switchAdapter = adapters.get(session.channelId);
|
|
21407
21721
|
if (switchAdapter?.sendSkillCommands) {
|
|
21408
21722
|
await switchAdapter.sendSkillCommands(session.id, []);
|
|
@@ -21479,9 +21793,11 @@ var init_agent_switch_handler = __esm({
|
|
|
21479
21793
|
session.agentInstance = oldInstance;
|
|
21480
21794
|
session.agentName = fromAgent;
|
|
21481
21795
|
session.agentSessionId = oldInstance.sessionId;
|
|
21482
|
-
const
|
|
21483
|
-
|
|
21484
|
-
|
|
21796
|
+
for (const adapterId of session.attachedAdapters) {
|
|
21797
|
+
const adapter = adapters.get(adapterId);
|
|
21798
|
+
if (adapter) {
|
|
21799
|
+
createBridge(session, adapter, adapterId).connect();
|
|
21800
|
+
}
|
|
21485
21801
|
}
|
|
21486
21802
|
log34.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
|
|
21487
21803
|
} catch (rollbackErr) {
|
|
@@ -21490,10 +21806,14 @@ var init_agent_switch_handler = __esm({
|
|
|
21490
21806
|
}
|
|
21491
21807
|
throw err;
|
|
21492
21808
|
}
|
|
21493
|
-
if (
|
|
21494
|
-
const
|
|
21495
|
-
|
|
21496
|
-
|
|
21809
|
+
if (hadBridges) {
|
|
21810
|
+
for (const adapterId of session.attachedAdapters) {
|
|
21811
|
+
const adapter = adapters.get(adapterId);
|
|
21812
|
+
if (adapter) {
|
|
21813
|
+
createBridge(session, adapter, adapterId).connect();
|
|
21814
|
+
} else {
|
|
21815
|
+
log34.warn({ sessionId, adapterId }, "Adapter not available during switch reconnect, skipping bridge");
|
|
21816
|
+
}
|
|
21497
21817
|
}
|
|
21498
21818
|
}
|
|
21499
21819
|
await sessionManager.patchRecord(sessionId, {
|
|
@@ -23105,7 +23425,7 @@ var init_core = __esm({
|
|
|
23105
23425
|
sessionManager;
|
|
23106
23426
|
messageTransformer;
|
|
23107
23427
|
adapters = /* @__PURE__ */ new Map();
|
|
23108
|
-
/** sessionId → SessionBridge — tracks active bridges for disconnect/reconnect
|
|
23428
|
+
/** "adapterId:sessionId" → SessionBridge — tracks active bridges for disconnect/reconnect */
|
|
23109
23429
|
bridges = /* @__PURE__ */ new Map();
|
|
23110
23430
|
/** Set by main.ts — triggers graceful shutdown with restart exit code */
|
|
23111
23431
|
requestRestart = null;
|
|
@@ -23201,7 +23521,8 @@ var init_core = __esm({
|
|
|
23201
23521
|
eventBus: this.eventBus,
|
|
23202
23522
|
adapters: this.adapters,
|
|
23203
23523
|
bridges: this.bridges,
|
|
23204
|
-
createBridge: (session, adapter) => this.createBridge(session, adapter),
|
|
23524
|
+
createBridge: (session, adapter, adapterId) => this.createBridge(session, adapter, adapterId),
|
|
23525
|
+
getSessionBridgeKeys: (sessionId) => this.getSessionBridgeKeys(sessionId),
|
|
23205
23526
|
getMiddlewareChain: () => this.lifecycleManager?.middlewareChain,
|
|
23206
23527
|
getService: (name) => this.lifecycleManager.serviceRegistry.get(name)
|
|
23207
23528
|
});
|
|
@@ -23381,7 +23702,7 @@ User message:
|
|
|
23381
23702
|
${text6}`;
|
|
23382
23703
|
}
|
|
23383
23704
|
}
|
|
23384
|
-
await session.enqueuePrompt(text6, message.attachments);
|
|
23705
|
+
await session.enqueuePrompt(text6, message.attachments, message.routing);
|
|
23385
23706
|
}
|
|
23386
23707
|
// --- Unified Session Creation Pipeline ---
|
|
23387
23708
|
async createSession(params) {
|
|
@@ -23408,6 +23729,12 @@ ${text6}`;
|
|
|
23408
23729
|
platform2.threadId = session.threadId;
|
|
23409
23730
|
}
|
|
23410
23731
|
}
|
|
23732
|
+
const platforms = {
|
|
23733
|
+
...existingRecord?.platforms ?? {}
|
|
23734
|
+
};
|
|
23735
|
+
if (session.threadId) {
|
|
23736
|
+
platforms[params.channelId] = params.channelId === "telegram" ? { topicId: Number(session.threadId) || session.threadId } : { threadId: session.threadId };
|
|
23737
|
+
}
|
|
23411
23738
|
await this.sessionManager.patchRecord(session.id, {
|
|
23412
23739
|
sessionId: session.id,
|
|
23413
23740
|
agentSessionId: session.agentSessionId,
|
|
@@ -23419,6 +23746,7 @@ ${text6}`;
|
|
|
23419
23746
|
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23420
23747
|
name: session.name,
|
|
23421
23748
|
platform: platform2,
|
|
23749
|
+
platforms,
|
|
23422
23750
|
firstAgent: session.firstAgent,
|
|
23423
23751
|
currentPromptCount: session.promptCount,
|
|
23424
23752
|
agentSwitchHistory: session.agentSwitchHistory,
|
|
@@ -23426,7 +23754,7 @@ ${text6}`;
|
|
|
23426
23754
|
acpState: session.toAcpStateSnapshot()
|
|
23427
23755
|
}, { immediate: true });
|
|
23428
23756
|
if (adapter) {
|
|
23429
|
-
const bridge = this.createBridge(session, adapter);
|
|
23757
|
+
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
23430
23758
|
bridge.connect();
|
|
23431
23759
|
adapter.flushPendingSkillCommands?.(session.id).catch((err) => {
|
|
23432
23760
|
log40.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
|
|
@@ -23565,9 +23893,14 @@ ${text6}`;
|
|
|
23565
23893
|
} else {
|
|
23566
23894
|
adoptPlatform.threadId = session.threadId;
|
|
23567
23895
|
}
|
|
23896
|
+
const adoptPlatforms = {};
|
|
23897
|
+
if (session.threadId) {
|
|
23898
|
+
adoptPlatforms[adapterChannelId] = adapterChannelId === "telegram" ? { topicId: Number(session.threadId) || session.threadId } : { threadId: session.threadId };
|
|
23899
|
+
}
|
|
23568
23900
|
await this.sessionManager.patchRecord(session.id, {
|
|
23569
23901
|
originalAgentSessionId: agentSessionId,
|
|
23570
|
-
platform: adoptPlatform
|
|
23902
|
+
platform: adoptPlatform,
|
|
23903
|
+
platforms: adoptPlatforms
|
|
23571
23904
|
});
|
|
23572
23905
|
return {
|
|
23573
23906
|
ok: true,
|
|
@@ -23589,18 +23922,101 @@ ${text6}`;
|
|
|
23589
23922
|
async getOrResumeSession(channelId, threadId) {
|
|
23590
23923
|
return this.sessionFactory.getOrResume(channelId, threadId);
|
|
23591
23924
|
}
|
|
23925
|
+
async getOrResumeSessionById(sessionId) {
|
|
23926
|
+
return this.sessionFactory.getOrResumeById(sessionId);
|
|
23927
|
+
}
|
|
23928
|
+
async attachAdapter(sessionId, adapterId) {
|
|
23929
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
23930
|
+
if (!session) throw new Error(`Session ${sessionId} not found`);
|
|
23931
|
+
const adapter = this.adapters.get(adapterId);
|
|
23932
|
+
if (!adapter) throw new Error(`Adapter "${adapterId}" not found or not running`);
|
|
23933
|
+
if (session.attachedAdapters.includes(adapterId)) {
|
|
23934
|
+
const existingThread = session.threadIds.get(adapterId) ?? session.id;
|
|
23935
|
+
return { threadId: existingThread };
|
|
23936
|
+
}
|
|
23937
|
+
const threadId = await adapter.createSessionThread(
|
|
23938
|
+
session.id,
|
|
23939
|
+
session.name ?? `Session ${session.id.slice(0, 6)}`
|
|
23940
|
+
);
|
|
23941
|
+
session.threadIds.set(adapterId, threadId);
|
|
23942
|
+
session.attachedAdapters.push(adapterId);
|
|
23943
|
+
const bridge = this.createBridge(session, adapter, adapterId);
|
|
23944
|
+
bridge.connect();
|
|
23945
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
23946
|
+
attachedAdapters: session.attachedAdapters,
|
|
23947
|
+
platforms: this.buildPlatformsFromSession(session)
|
|
23948
|
+
});
|
|
23949
|
+
return { threadId };
|
|
23950
|
+
}
|
|
23951
|
+
async detachAdapter(sessionId, adapterId) {
|
|
23952
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
23953
|
+
if (!session) throw new Error(`Session ${sessionId} not found`);
|
|
23954
|
+
if (adapterId === session.channelId) {
|
|
23955
|
+
throw new Error("Cannot detach primary adapter (channelId)");
|
|
23956
|
+
}
|
|
23957
|
+
if (!session.attachedAdapters.includes(adapterId)) {
|
|
23958
|
+
return;
|
|
23959
|
+
}
|
|
23960
|
+
const adapter = this.adapters.get(adapterId);
|
|
23961
|
+
if (adapter) {
|
|
23962
|
+
try {
|
|
23963
|
+
await adapter.sendMessage(session.id, {
|
|
23964
|
+
type: "system_message",
|
|
23965
|
+
text: "Session detached from this adapter."
|
|
23966
|
+
});
|
|
23967
|
+
} catch {
|
|
23968
|
+
}
|
|
23969
|
+
}
|
|
23970
|
+
const key = this.bridgeKey(adapterId, session.id);
|
|
23971
|
+
const bridge = this.bridges.get(key);
|
|
23972
|
+
if (bridge) {
|
|
23973
|
+
bridge.disconnect();
|
|
23974
|
+
this.bridges.delete(key);
|
|
23975
|
+
}
|
|
23976
|
+
session.attachedAdapters = session.attachedAdapters.filter((a) => a !== adapterId);
|
|
23977
|
+
session.threadIds.delete(adapterId);
|
|
23978
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
23979
|
+
attachedAdapters: session.attachedAdapters,
|
|
23980
|
+
platforms: this.buildPlatformsFromSession(session)
|
|
23981
|
+
});
|
|
23982
|
+
}
|
|
23983
|
+
buildPlatformsFromSession(session) {
|
|
23984
|
+
const platforms = {};
|
|
23985
|
+
for (const [adapterId, threadId] of session.threadIds) {
|
|
23986
|
+
if (adapterId === "telegram") {
|
|
23987
|
+
platforms.telegram = { topicId: Number(threadId) || threadId };
|
|
23988
|
+
} else {
|
|
23989
|
+
platforms[adapterId] = { threadId };
|
|
23990
|
+
}
|
|
23991
|
+
}
|
|
23992
|
+
return platforms;
|
|
23993
|
+
}
|
|
23592
23994
|
// --- Event Wiring ---
|
|
23995
|
+
/** Composite bridge key: "adapterId:sessionId" */
|
|
23996
|
+
bridgeKey(adapterId, sessionId) {
|
|
23997
|
+
return `${adapterId}:${sessionId}`;
|
|
23998
|
+
}
|
|
23999
|
+
/** Get all bridge keys for a session (regardless of adapter) */
|
|
24000
|
+
getSessionBridgeKeys(sessionId) {
|
|
24001
|
+
const keys = [];
|
|
24002
|
+
for (const key of this.bridges.keys()) {
|
|
24003
|
+
if (key.endsWith(`:${sessionId}`)) keys.push(key);
|
|
24004
|
+
}
|
|
24005
|
+
return keys;
|
|
24006
|
+
}
|
|
23593
24007
|
/** Connect a session bridge for the given session (used by AssistantManager) */
|
|
23594
24008
|
connectSessionBridge(session) {
|
|
23595
24009
|
const adapter = this.adapters.get(session.channelId);
|
|
23596
24010
|
if (!adapter) return;
|
|
23597
|
-
const bridge = this.createBridge(session, adapter);
|
|
24011
|
+
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
23598
24012
|
bridge.connect();
|
|
23599
24013
|
}
|
|
23600
24014
|
/** 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
|
|
24015
|
+
* Disconnects any existing bridge for the same adapter+session first. */
|
|
24016
|
+
createBridge(session, adapter, adapterId) {
|
|
24017
|
+
const id = adapterId ?? adapter.name;
|
|
24018
|
+
const key = this.bridgeKey(id, session.id);
|
|
24019
|
+
const existing = this.bridges.get(key);
|
|
23604
24020
|
if (existing) {
|
|
23605
24021
|
existing.disconnect();
|
|
23606
24022
|
}
|
|
@@ -23611,8 +24027,8 @@ ${text6}`;
|
|
|
23611
24027
|
eventBus: this.eventBus,
|
|
23612
24028
|
fileService: this.fileService,
|
|
23613
24029
|
middlewareChain: this.lifecycleManager?.middlewareChain
|
|
23614
|
-
});
|
|
23615
|
-
this.bridges.set(
|
|
24030
|
+
}, id);
|
|
24031
|
+
this.bridges.set(key, bridge);
|
|
23616
24032
|
return bridge;
|
|
23617
24033
|
}
|
|
23618
24034
|
};
|
|
@@ -24257,13 +24673,15 @@ function registerSwitchCommands(registry, _core) {
|
|
|
24257
24673
|
return { type: "error", message: "No active session in this topic." };
|
|
24258
24674
|
}
|
|
24259
24675
|
if (raw) {
|
|
24676
|
+
const droppedCount = session.queueDepth;
|
|
24260
24677
|
if (session.promptRunning) {
|
|
24261
24678
|
await session.abortPrompt();
|
|
24262
24679
|
}
|
|
24263
24680
|
try {
|
|
24264
24681
|
const { resumed } = await core.switchSessionAgent(session.id, raw);
|
|
24265
24682
|
const status = resumed ? "resumed" : "new session";
|
|
24266
|
-
|
|
24683
|
+
const droppedNote = droppedCount > 0 ? ` (${droppedCount} queued prompt${droppedCount > 1 ? "s" : ""} cleared)` : "";
|
|
24684
|
+
return { type: "text", text: `\u2705 Switched to ${raw} (${status})${droppedNote}` };
|
|
24267
24685
|
} catch (err) {
|
|
24268
24686
|
return { type: "error", message: `Failed to switch agent: ${err.message || err}` };
|
|
24269
24687
|
}
|
|
@@ -24356,27 +24774,8 @@ function registerCategoryCommand(registry, core, category, commandName) {
|
|
|
24356
24774
|
if (configOption.currentValue === raw) {
|
|
24357
24775
|
return { type: "text", text: `Already using **${match.name}**.` };
|
|
24358
24776
|
}
|
|
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
24777
|
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
|
-
}
|
|
24778
|
+
await session.setConfigOption(configOption.id, { type: "select", value: raw });
|
|
24380
24779
|
core.eventBus.emit("session:configChanged", { sessionId: session.id });
|
|
24381
24780
|
return { type: "text", text: labels.successMsg(match.name, configOption.name) };
|
|
24382
24781
|
} catch (err) {
|
|
@@ -24438,17 +24837,7 @@ function registerDangerousCommand(registry, core) {
|
|
|
24438
24837
|
if (bypassValue && modeConfig) {
|
|
24439
24838
|
try {
|
|
24440
24839
|
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
|
-
}
|
|
24840
|
+
await session.setConfigOption(modeConfig.id, { type: "select", value: targetValue });
|
|
24452
24841
|
core.eventBus.emit("session:configChanged", { sessionId: session.id });
|
|
24453
24842
|
return {
|
|
24454
24843
|
type: "text",
|
|
@@ -26438,7 +26827,10 @@ async function startServer(opts) {
|
|
|
26438
26827
|
const tunnelErr = tunnelSvc.getStartError();
|
|
26439
26828
|
const url = tunnelSvc.getPublicUrl();
|
|
26440
26829
|
const isPublic = url && !url.startsWith("http://localhost") && !url.startsWith("http://127.0.0.1");
|
|
26441
|
-
if (tunnelErr) {
|
|
26830
|
+
if (tunnelErr && isPublic) {
|
|
26831
|
+
warn3(`Primary tunnel failed \u2014 using fallback: ${tunnelErr}`);
|
|
26832
|
+
tunnelUrl = url;
|
|
26833
|
+
} else if (tunnelErr) {
|
|
26442
26834
|
warn3(`Tunnel failed (${tunnelErr}) \u2014 retrying in background`);
|
|
26443
26835
|
} else if (isPublic) {
|
|
26444
26836
|
ok3("Tunnel ready");
|