@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/index.js
CHANGED
|
@@ -3474,7 +3474,9 @@ var init_sse_manager = __esm({
|
|
|
3474
3474
|
"session:updated",
|
|
3475
3475
|
"session:deleted",
|
|
3476
3476
|
"agent:event",
|
|
3477
|
-
"permission:request"
|
|
3477
|
+
"permission:request",
|
|
3478
|
+
"message:queued",
|
|
3479
|
+
"message:processing"
|
|
3478
3480
|
];
|
|
3479
3481
|
for (const eventName of events) {
|
|
3480
3482
|
const handler = (data) => {
|
|
@@ -3539,7 +3541,9 @@ data: ${JSON.stringify(data)}
|
|
|
3539
3541
|
const sessionEvents = [
|
|
3540
3542
|
"agent:event",
|
|
3541
3543
|
"permission:request",
|
|
3542
|
-
"session:updated"
|
|
3544
|
+
"session:updated",
|
|
3545
|
+
"message:queued",
|
|
3546
|
+
"message:processing"
|
|
3543
3547
|
];
|
|
3544
3548
|
for (const res of this.sseConnections) {
|
|
3545
3549
|
const filter = res.sessionFilter;
|
|
@@ -8362,7 +8366,7 @@ var init_commands = __esm({
|
|
|
8362
8366
|
|
|
8363
8367
|
// src/plugins/telegram/permissions.ts
|
|
8364
8368
|
import { InlineKeyboard as InlineKeyboard11 } from "grammy";
|
|
8365
|
-
import { nanoid as
|
|
8369
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
8366
8370
|
var log30, PermissionHandler;
|
|
8367
8371
|
var init_permissions = __esm({
|
|
8368
8372
|
"src/plugins/telegram/permissions.ts"() {
|
|
@@ -8381,7 +8385,7 @@ var init_permissions = __esm({
|
|
|
8381
8385
|
pending = /* @__PURE__ */ new Map();
|
|
8382
8386
|
async sendPermissionRequest(session, request) {
|
|
8383
8387
|
const threadId = Number(session.threadId);
|
|
8384
|
-
const callbackKey =
|
|
8388
|
+
const callbackKey = nanoid4(8);
|
|
8385
8389
|
this.pending.set(callbackKey, {
|
|
8386
8390
|
sessionId: session.id,
|
|
8387
8391
|
requestId: request.id,
|
|
@@ -11182,6 +11186,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
|
|
|
11182
11186
|
{ sessionId: this.sessionId, exitCode: code, signal },
|
|
11183
11187
|
"Agent process exited"
|
|
11184
11188
|
);
|
|
11189
|
+
if (signal === "SIGINT" || signal === "SIGTERM") return;
|
|
11185
11190
|
if (code !== 0 && code !== null || signal) {
|
|
11186
11191
|
const stderr = this.stderrCapture.getLastLines();
|
|
11187
11192
|
this.emit("agent_event", {
|
|
@@ -11657,7 +11662,7 @@ var AgentManager = class {
|
|
|
11657
11662
|
};
|
|
11658
11663
|
|
|
11659
11664
|
// src/core/sessions/session.ts
|
|
11660
|
-
import { nanoid } from "nanoid";
|
|
11665
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
11661
11666
|
|
|
11662
11667
|
// src/core/sessions/prompt-queue.ts
|
|
11663
11668
|
var PromptQueue = class {
|
|
@@ -11668,21 +11673,21 @@ var PromptQueue = class {
|
|
|
11668
11673
|
queue = [];
|
|
11669
11674
|
processing = false;
|
|
11670
11675
|
abortController = null;
|
|
11671
|
-
async enqueue(text3, attachments) {
|
|
11676
|
+
async enqueue(text3, attachments, routing, turnId) {
|
|
11672
11677
|
if (this.processing) {
|
|
11673
11678
|
return new Promise((resolve6) => {
|
|
11674
|
-
this.queue.push({ text: text3, attachments, resolve: resolve6 });
|
|
11679
|
+
this.queue.push({ text: text3, attachments, routing, turnId, resolve: resolve6 });
|
|
11675
11680
|
});
|
|
11676
11681
|
}
|
|
11677
|
-
await this.process(text3, attachments);
|
|
11682
|
+
await this.process(text3, attachments, routing, turnId);
|
|
11678
11683
|
}
|
|
11679
|
-
async process(text3, attachments) {
|
|
11684
|
+
async process(text3, attachments, routing, turnId) {
|
|
11680
11685
|
this.processing = true;
|
|
11681
11686
|
this.abortController = new AbortController();
|
|
11682
11687
|
const { signal } = this.abortController;
|
|
11683
11688
|
try {
|
|
11684
11689
|
await Promise.race([
|
|
11685
|
-
this.processor(text3, attachments),
|
|
11690
|
+
this.processor(text3, attachments, routing, turnId),
|
|
11686
11691
|
new Promise((_, reject) => {
|
|
11687
11692
|
signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
|
|
11688
11693
|
})
|
|
@@ -11700,7 +11705,7 @@ var PromptQueue = class {
|
|
|
11700
11705
|
drainNext() {
|
|
11701
11706
|
const next = this.queue.shift();
|
|
11702
11707
|
if (next) {
|
|
11703
|
-
this.process(next.text, next.attachments).then(next.resolve);
|
|
11708
|
+
this.process(next.text, next.attachments, next.routing, next.turnId).then(next.resolve);
|
|
11704
11709
|
}
|
|
11705
11710
|
}
|
|
11706
11711
|
clear() {
|
|
@@ -11790,6 +11795,33 @@ var PermissionGate = class {
|
|
|
11790
11795
|
// src/core/sessions/session.ts
|
|
11791
11796
|
init_log();
|
|
11792
11797
|
import * as fs10 from "fs";
|
|
11798
|
+
|
|
11799
|
+
// src/core/sessions/turn-context.ts
|
|
11800
|
+
import { nanoid } from "nanoid";
|
|
11801
|
+
function createTurnContext(sourceAdapterId, responseAdapterId, turnId) {
|
|
11802
|
+
return {
|
|
11803
|
+
turnId: turnId ?? nanoid(8),
|
|
11804
|
+
sourceAdapterId,
|
|
11805
|
+
responseAdapterId
|
|
11806
|
+
};
|
|
11807
|
+
}
|
|
11808
|
+
function getEffectiveTarget(ctx) {
|
|
11809
|
+
if (ctx.responseAdapterId === null) return null;
|
|
11810
|
+
return ctx.responseAdapterId ?? ctx.sourceAdapterId;
|
|
11811
|
+
}
|
|
11812
|
+
var SYSTEM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
11813
|
+
"session_end",
|
|
11814
|
+
"system_message",
|
|
11815
|
+
"session_info_update",
|
|
11816
|
+
"config_option_update",
|
|
11817
|
+
"commands_update",
|
|
11818
|
+
"tts_strip"
|
|
11819
|
+
]);
|
|
11820
|
+
function isSystemEvent(event) {
|
|
11821
|
+
return SYSTEM_EVENT_TYPES.has(event.type);
|
|
11822
|
+
}
|
|
11823
|
+
|
|
11824
|
+
// src/core/sessions/session.ts
|
|
11793
11825
|
var moduleLog = createChildLogger({ module: "session" });
|
|
11794
11826
|
var TTS_PROMPT_INSTRUCTION = `
|
|
11795
11827
|
|
|
@@ -11807,7 +11839,15 @@ var VALID_TRANSITIONS = {
|
|
|
11807
11839
|
var Session = class extends TypedEmitter {
|
|
11808
11840
|
id;
|
|
11809
11841
|
channelId;
|
|
11810
|
-
threadId
|
|
11842
|
+
/** @deprecated Use threadIds map directly. Getter returns primary adapter's threadId. */
|
|
11843
|
+
get threadId() {
|
|
11844
|
+
return this.threadIds.get(this.channelId) ?? "";
|
|
11845
|
+
}
|
|
11846
|
+
set threadId(value) {
|
|
11847
|
+
if (value) {
|
|
11848
|
+
this.threadIds.set(this.channelId, value);
|
|
11849
|
+
}
|
|
11850
|
+
}
|
|
11811
11851
|
agentName;
|
|
11812
11852
|
workingDirectory;
|
|
11813
11853
|
agentInstance;
|
|
@@ -11828,14 +11868,21 @@ var Session = class extends TypedEmitter {
|
|
|
11828
11868
|
middlewareChain;
|
|
11829
11869
|
/** Latest commands emitted by the agent — buffered before bridge connects so they're not lost */
|
|
11830
11870
|
latestCommands = null;
|
|
11871
|
+
/** Adapters currently attached to this session (including primary) */
|
|
11872
|
+
attachedAdapters = [];
|
|
11873
|
+
/** Per-adapter thread IDs: adapterId → threadId */
|
|
11874
|
+
threadIds = /* @__PURE__ */ new Map();
|
|
11875
|
+
/** Active turn context — sealed on prompt dequeue, cleared on turn end */
|
|
11876
|
+
activeTurnContext = null;
|
|
11831
11877
|
permissionGate = new PermissionGate();
|
|
11832
11878
|
queue;
|
|
11833
11879
|
speechService;
|
|
11834
11880
|
pendingContext = null;
|
|
11835
11881
|
constructor(opts) {
|
|
11836
11882
|
super();
|
|
11837
|
-
this.id = opts.id ||
|
|
11883
|
+
this.id = opts.id || nanoid2(12);
|
|
11838
11884
|
this.channelId = opts.channelId;
|
|
11885
|
+
this.attachedAdapters = [opts.channelId];
|
|
11839
11886
|
this.agentName = opts.agentName;
|
|
11840
11887
|
this.firstAgent = opts.agentName;
|
|
11841
11888
|
this.workingDirectory = opts.workingDirectory;
|
|
@@ -11845,7 +11892,7 @@ var Session = class extends TypedEmitter {
|
|
|
11845
11892
|
this.log = createSessionLogger(this.id, moduleLog);
|
|
11846
11893
|
this.log.info({ agentName: this.agentName }, "Session created");
|
|
11847
11894
|
this.queue = new PromptQueue(
|
|
11848
|
-
(text3, attachments) => this.processPrompt(text3, attachments),
|
|
11895
|
+
(text3, attachments, routing, turnId) => this.processPrompt(text3, attachments, routing, turnId),
|
|
11849
11896
|
(err) => {
|
|
11850
11897
|
this.log.error({ err }, "Prompt execution failed");
|
|
11851
11898
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -11853,11 +11900,20 @@ var Session = class extends TypedEmitter {
|
|
|
11853
11900
|
this.emit("agent_event", { type: "error", message: `Prompt execution failed: ${message}` });
|
|
11854
11901
|
}
|
|
11855
11902
|
);
|
|
11856
|
-
this.
|
|
11903
|
+
this.wireCommandsBuffer();
|
|
11904
|
+
}
|
|
11905
|
+
/** Wire a listener on the current agentInstance to buffer commands_update events.
|
|
11906
|
+
* Must be called after every agentInstance replacement (constructor + switchAgent). */
|
|
11907
|
+
commandsBufferCleanup;
|
|
11908
|
+
wireCommandsBuffer() {
|
|
11909
|
+
this.commandsBufferCleanup?.();
|
|
11910
|
+
const handler = (event) => {
|
|
11857
11911
|
if (event.type === "commands_update") {
|
|
11858
11912
|
this.latestCommands = event.commands;
|
|
11859
11913
|
}
|
|
11860
|
-
}
|
|
11914
|
+
};
|
|
11915
|
+
this.agentInstance.on("agent_event", handler);
|
|
11916
|
+
this.commandsBufferCleanup = () => this.agentInstance.off("agent_event", handler);
|
|
11861
11917
|
}
|
|
11862
11918
|
// --- State Machine ---
|
|
11863
11919
|
get status() {
|
|
@@ -11911,18 +11967,26 @@ var Session = class extends TypedEmitter {
|
|
|
11911
11967
|
this.log.info({ voiceMode: mode }, "TTS mode changed");
|
|
11912
11968
|
}
|
|
11913
11969
|
// --- Public API ---
|
|
11914
|
-
async enqueuePrompt(text3, attachments) {
|
|
11970
|
+
async enqueuePrompt(text3, attachments, routing, externalTurnId) {
|
|
11971
|
+
const turnId = externalTurnId ?? nanoid2(8);
|
|
11915
11972
|
if (this.middlewareChain) {
|
|
11916
11973
|
const payload = { text: text3, attachments, sessionId: this.id };
|
|
11917
11974
|
const result = await this.middlewareChain.execute("agent:beforePrompt", payload, async (p) => p);
|
|
11918
|
-
if (!result) return;
|
|
11975
|
+
if (!result) return turnId;
|
|
11919
11976
|
text3 = result.text;
|
|
11920
11977
|
attachments = result.attachments;
|
|
11921
11978
|
}
|
|
11922
|
-
await this.queue.enqueue(text3, attachments);
|
|
11979
|
+
await this.queue.enqueue(text3, attachments, routing, turnId);
|
|
11980
|
+
return turnId;
|
|
11923
11981
|
}
|
|
11924
|
-
async processPrompt(text3, attachments) {
|
|
11982
|
+
async processPrompt(text3, attachments, routing, turnId) {
|
|
11925
11983
|
if (this._status === "finished") return;
|
|
11984
|
+
this.activeTurnContext = createTurnContext(
|
|
11985
|
+
routing?.sourceAdapterId ?? this.channelId,
|
|
11986
|
+
routing?.responseAdapterId,
|
|
11987
|
+
turnId
|
|
11988
|
+
);
|
|
11989
|
+
this.emit("turn_started", this.activeTurnContext);
|
|
11926
11990
|
this.promptCount++;
|
|
11927
11991
|
this.emit("prompt_count_changed", this.promptCount);
|
|
11928
11992
|
if (this._status === "initializing" || this._status === "cancelled" || this._status === "error") {
|
|
@@ -11989,6 +12053,7 @@ ${text3}`;
|
|
|
11989
12053
|
this.log.warn({ err }, "TTS post-processing failed");
|
|
11990
12054
|
});
|
|
11991
12055
|
}
|
|
12056
|
+
this.activeTurnContext = null;
|
|
11992
12057
|
if (!this.name) {
|
|
11993
12058
|
await this.autoName();
|
|
11994
12059
|
}
|
|
@@ -12231,6 +12296,7 @@ ${result.text}` : result.text;
|
|
|
12231
12296
|
this.configOptions = [];
|
|
12232
12297
|
this.latestCommands = null;
|
|
12233
12298
|
this.applySpawnResponse(newAgent.initialSessionResponse, newAgent.agentCapabilities);
|
|
12299
|
+
this.wireCommandsBuffer();
|
|
12234
12300
|
this.log.info({ from: this.agentSwitchHistory.at(-1).agentName, to: agentName }, "Agent switched");
|
|
12235
12301
|
}
|
|
12236
12302
|
async destroy() {
|
|
@@ -12698,6 +12764,8 @@ var SessionManager = class {
|
|
|
12698
12764
|
}
|
|
12699
12765
|
getSessionByThread(channelId, threadId) {
|
|
12700
12766
|
for (const session of this.sessions.values()) {
|
|
12767
|
+
const adapterThread = session.threadIds.get(channelId);
|
|
12768
|
+
if (adapterThread === threadId) return session;
|
|
12701
12769
|
if (session.channelId === channelId && session.threadId === threadId) {
|
|
12702
12770
|
return session;
|
|
12703
12771
|
}
|
|
@@ -12765,6 +12833,67 @@ var SessionManager = class {
|
|
|
12765
12833
|
if (channelId) return all.filter((s) => s.channelId === channelId);
|
|
12766
12834
|
return all;
|
|
12767
12835
|
}
|
|
12836
|
+
listAllSessions(channelId) {
|
|
12837
|
+
if (this.store) {
|
|
12838
|
+
let records = this.store.list();
|
|
12839
|
+
if (channelId) records = records.filter((r) => r.channelId === channelId);
|
|
12840
|
+
return records.map((record) => {
|
|
12841
|
+
const live2 = this.sessions.get(record.sessionId);
|
|
12842
|
+
if (live2) {
|
|
12843
|
+
return {
|
|
12844
|
+
id: live2.id,
|
|
12845
|
+
agent: live2.agentName,
|
|
12846
|
+
status: live2.status,
|
|
12847
|
+
name: live2.name ?? null,
|
|
12848
|
+
workspace: live2.workingDirectory,
|
|
12849
|
+
channelId: live2.channelId,
|
|
12850
|
+
createdAt: live2.createdAt.toISOString(),
|
|
12851
|
+
lastActiveAt: record.lastActiveAt ?? null,
|
|
12852
|
+
dangerousMode: live2.clientOverrides.bypassPermissions ?? false,
|
|
12853
|
+
queueDepth: live2.queueDepth,
|
|
12854
|
+
promptRunning: live2.promptRunning,
|
|
12855
|
+
configOptions: live2.configOptions?.length ? live2.configOptions : void 0,
|
|
12856
|
+
capabilities: live2.agentCapabilities ?? null,
|
|
12857
|
+
isLive: true
|
|
12858
|
+
};
|
|
12859
|
+
}
|
|
12860
|
+
return {
|
|
12861
|
+
id: record.sessionId,
|
|
12862
|
+
agent: record.agentName,
|
|
12863
|
+
status: record.status,
|
|
12864
|
+
name: record.name ?? null,
|
|
12865
|
+
workspace: record.workingDir,
|
|
12866
|
+
channelId: record.channelId,
|
|
12867
|
+
createdAt: record.createdAt,
|
|
12868
|
+
lastActiveAt: record.lastActiveAt ?? null,
|
|
12869
|
+
dangerousMode: record.clientOverrides?.bypassPermissions ?? false,
|
|
12870
|
+
queueDepth: 0,
|
|
12871
|
+
promptRunning: false,
|
|
12872
|
+
configOptions: record.acpState?.configOptions,
|
|
12873
|
+
capabilities: record.acpState?.agentCapabilities ?? null,
|
|
12874
|
+
isLive: false
|
|
12875
|
+
};
|
|
12876
|
+
});
|
|
12877
|
+
}
|
|
12878
|
+
let live = Array.from(this.sessions.values());
|
|
12879
|
+
if (channelId) live = live.filter((s) => s.channelId === channelId);
|
|
12880
|
+
return live.map((s) => ({
|
|
12881
|
+
id: s.id,
|
|
12882
|
+
agent: s.agentName,
|
|
12883
|
+
status: s.status,
|
|
12884
|
+
name: s.name ?? null,
|
|
12885
|
+
workspace: s.workingDirectory,
|
|
12886
|
+
channelId: s.channelId,
|
|
12887
|
+
createdAt: s.createdAt.toISOString(),
|
|
12888
|
+
lastActiveAt: null,
|
|
12889
|
+
dangerousMode: s.clientOverrides.bypassPermissions ?? false,
|
|
12890
|
+
queueDepth: s.queueDepth,
|
|
12891
|
+
promptRunning: s.promptRunning,
|
|
12892
|
+
configOptions: s.configOptions?.length ? s.configOptions : void 0,
|
|
12893
|
+
capabilities: s.agentCapabilities ?? null,
|
|
12894
|
+
isLive: true
|
|
12895
|
+
}));
|
|
12896
|
+
}
|
|
12768
12897
|
listRecords(filter) {
|
|
12769
12898
|
if (!this.store) return [];
|
|
12770
12899
|
let records = this.store.list();
|
|
@@ -12787,7 +12916,14 @@ var SessionManager = class {
|
|
|
12787
12916
|
for (const session of this.sessions.values()) {
|
|
12788
12917
|
const record = this.store.get(session.id);
|
|
12789
12918
|
if (record) {
|
|
12790
|
-
await this.store.save({
|
|
12919
|
+
await this.store.save({
|
|
12920
|
+
...record,
|
|
12921
|
+
status: "finished",
|
|
12922
|
+
acpState: session.toAcpStateSnapshot(),
|
|
12923
|
+
clientOverrides: session.clientOverrides,
|
|
12924
|
+
currentPromptCount: session.promptCount,
|
|
12925
|
+
agentSwitchHistory: session.agentSwitchHistory
|
|
12926
|
+
});
|
|
12791
12927
|
}
|
|
12792
12928
|
}
|
|
12793
12929
|
this.store.flush();
|
|
@@ -12797,6 +12933,8 @@ var SessionManager = class {
|
|
|
12797
12933
|
/**
|
|
12798
12934
|
* Forcefully destroy all sessions (kill agent subprocesses).
|
|
12799
12935
|
* Use only when sessions must be fully torn down (e.g. archive).
|
|
12936
|
+
* Unlike shutdownAll(), this does NOT snapshot live session state (acpState, etc.)
|
|
12937
|
+
* because destroyed sessions are terminal and will not be resumed.
|
|
12800
12938
|
*/
|
|
12801
12939
|
async destroyAll() {
|
|
12802
12940
|
if (this.store) {
|
|
@@ -12827,13 +12965,15 @@ init_log();
|
|
|
12827
12965
|
init_bypass_detection();
|
|
12828
12966
|
var log6 = createChildLogger({ module: "session-bridge" });
|
|
12829
12967
|
var SessionBridge = class {
|
|
12830
|
-
constructor(session, adapter, deps) {
|
|
12968
|
+
constructor(session, adapter, deps, adapterId) {
|
|
12831
12969
|
this.session = session;
|
|
12832
12970
|
this.adapter = adapter;
|
|
12833
12971
|
this.deps = deps;
|
|
12972
|
+
this.adapterId = adapterId ?? adapter.name;
|
|
12834
12973
|
}
|
|
12835
12974
|
connected = false;
|
|
12836
12975
|
cleanupFns = [];
|
|
12976
|
+
adapterId;
|
|
12837
12977
|
get tracer() {
|
|
12838
12978
|
return this.session.agentInstance.debugTracer ?? null;
|
|
12839
12979
|
}
|
|
@@ -12864,6 +13004,15 @@ var SessionBridge = class {
|
|
|
12864
13004
|
log6.error({ err, sessionId }, "Error in sendMessage middleware");
|
|
12865
13005
|
}
|
|
12866
13006
|
}
|
|
13007
|
+
/** Determine if this bridge should forward the given event based on turn routing. */
|
|
13008
|
+
shouldForward(event) {
|
|
13009
|
+
if (isSystemEvent(event)) return true;
|
|
13010
|
+
const ctx = this.session.activeTurnContext;
|
|
13011
|
+
if (!ctx) return true;
|
|
13012
|
+
const target = getEffectiveTarget(ctx);
|
|
13013
|
+
if (target === null) return false;
|
|
13014
|
+
return this.adapterId === target;
|
|
13015
|
+
}
|
|
12867
13016
|
connect() {
|
|
12868
13017
|
if (this.connected) return;
|
|
12869
13018
|
this.connected = true;
|
|
@@ -12871,11 +13020,29 @@ var SessionBridge = class {
|
|
|
12871
13020
|
this.session.emit("agent_event", event);
|
|
12872
13021
|
});
|
|
12873
13022
|
this.listen(this.session, "agent_event", (event) => {
|
|
12874
|
-
this.
|
|
13023
|
+
if (this.shouldForward(event)) {
|
|
13024
|
+
this.dispatchAgentEvent(event);
|
|
13025
|
+
} else {
|
|
13026
|
+
this.deps.eventBus?.emit("agent:event", { sessionId: this.session.id, event });
|
|
13027
|
+
}
|
|
13028
|
+
});
|
|
13029
|
+
if (!this.session.agentInstance.onPermissionRequest || this.session.agentInstance.onPermissionRequest.__bridgeId === void 0) {
|
|
13030
|
+
const handler = async (request) => {
|
|
13031
|
+
return this.resolvePermission(request);
|
|
13032
|
+
};
|
|
13033
|
+
handler.__bridgeId = this.adapterId;
|
|
13034
|
+
this.session.agentInstance.onPermissionRequest = handler;
|
|
13035
|
+
}
|
|
13036
|
+
this.listen(this.session, "permission_request", async (request) => {
|
|
13037
|
+
const current = this.session.agentInstance.onPermissionRequest;
|
|
13038
|
+
if (current?.__bridgeId === this.adapterId) return;
|
|
13039
|
+
if (!this.session.permissionGate.isPending) return;
|
|
13040
|
+
try {
|
|
13041
|
+
await this.adapter.sendPermissionRequest(this.session.id, request);
|
|
13042
|
+
} catch (err) {
|
|
13043
|
+
log6.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
|
|
13044
|
+
}
|
|
12875
13045
|
});
|
|
12876
|
-
this.session.agentInstance.onPermissionRequest = async (request) => {
|
|
12877
|
-
return this.resolvePermission(request);
|
|
12878
|
-
};
|
|
12879
13046
|
this.listen(this.session, "status_change", (from, to) => {
|
|
12880
13047
|
this.deps.sessionManager.patchRecord(this.session.id, {
|
|
12881
13048
|
status: to,
|
|
@@ -12904,6 +13071,16 @@ var SessionBridge = class {
|
|
|
12904
13071
|
this.listen(this.session, "prompt_count_changed", (count) => {
|
|
12905
13072
|
this.deps.sessionManager.patchRecord(this.session.id, { currentPromptCount: count });
|
|
12906
13073
|
});
|
|
13074
|
+
this.listen(this.session, "turn_started", (ctx) => {
|
|
13075
|
+
if (ctx.sourceAdapterId !== "sse" && ctx.sourceAdapterId !== "api") {
|
|
13076
|
+
this.deps.eventBus?.emit("message:processing", {
|
|
13077
|
+
sessionId: this.session.id,
|
|
13078
|
+
turnId: ctx.turnId,
|
|
13079
|
+
sourceAdapterId: ctx.sourceAdapterId,
|
|
13080
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
13081
|
+
});
|
|
13082
|
+
}
|
|
13083
|
+
});
|
|
12907
13084
|
if (this.session.latestCommands !== null) {
|
|
12908
13085
|
this.session.emit("agent_event", { type: "commands_update", commands: this.session.latestCommands });
|
|
12909
13086
|
}
|
|
@@ -12916,7 +13093,10 @@ var SessionBridge = class {
|
|
|
12916
13093
|
this.connected = false;
|
|
12917
13094
|
this.cleanupFns.forEach((fn) => fn());
|
|
12918
13095
|
this.cleanupFns = [];
|
|
12919
|
-
this.session.agentInstance.onPermissionRequest
|
|
13096
|
+
const current = this.session.agentInstance.onPermissionRequest;
|
|
13097
|
+
if (current?.__bridgeId === this.adapterId) {
|
|
13098
|
+
this.session.agentInstance.onPermissionRequest = async () => "";
|
|
13099
|
+
}
|
|
12920
13100
|
}
|
|
12921
13101
|
/** Dispatch an agent event through middleware and to the adapter */
|
|
12922
13102
|
async dispatchAgentEvent(event) {
|
|
@@ -13047,8 +13227,10 @@ var SessionBridge = class {
|
|
|
13047
13227
|
this.sendMessage(this.session.id, outgoing);
|
|
13048
13228
|
break;
|
|
13049
13229
|
case "config_option_update":
|
|
13050
|
-
this.session.updateConfigOptions(event.options)
|
|
13051
|
-
|
|
13230
|
+
this.session.updateConfigOptions(event.options).then(() => {
|
|
13231
|
+
this.persistAcpState();
|
|
13232
|
+
}).catch(() => {
|
|
13233
|
+
});
|
|
13052
13234
|
outgoing = this.deps.messageTransformer.transform(event);
|
|
13053
13235
|
this.sendMessage(this.session.id, outgoing);
|
|
13054
13236
|
break;
|
|
@@ -13092,19 +13274,27 @@ var SessionBridge = class {
|
|
|
13092
13274
|
return result.autoResolve;
|
|
13093
13275
|
}
|
|
13094
13276
|
}
|
|
13095
|
-
this.session.emit("permission_request", permReq);
|
|
13096
13277
|
this.deps.eventBus?.emit("permission:request", {
|
|
13097
13278
|
sessionId: this.session.id,
|
|
13098
13279
|
permission: permReq
|
|
13099
13280
|
});
|
|
13100
13281
|
const autoDecision = this.checkAutoApprove(permReq);
|
|
13101
13282
|
if (autoDecision) {
|
|
13283
|
+
this.session.emit("permission_request", permReq);
|
|
13102
13284
|
this.emitAfterResolve(mw, permReq.id, autoDecision, "system", startTime);
|
|
13103
13285
|
return autoDecision;
|
|
13104
13286
|
}
|
|
13105
13287
|
const promise = this.session.permissionGate.setPending(permReq);
|
|
13288
|
+
this.session.emit("permission_request", permReq);
|
|
13106
13289
|
await this.adapter.sendPermissionRequest(this.session.id, permReq);
|
|
13107
13290
|
const optionId = await promise;
|
|
13291
|
+
this.deps.eventBus?.emit("permission:resolved", {
|
|
13292
|
+
sessionId: this.session.id,
|
|
13293
|
+
requestId: permReq.id,
|
|
13294
|
+
decision: optionId,
|
|
13295
|
+
optionId,
|
|
13296
|
+
resolvedBy: this.adapterId
|
|
13297
|
+
});
|
|
13108
13298
|
this.emitAfterResolve(mw, permReq.id, optionId, "user", startTime);
|
|
13109
13299
|
return optionId;
|
|
13110
13300
|
}
|
|
@@ -13298,6 +13488,65 @@ var SessionFactory = class {
|
|
|
13298
13488
|
if (session) return session;
|
|
13299
13489
|
return this.lazyResume(channelId, threadId);
|
|
13300
13490
|
}
|
|
13491
|
+
async getOrResumeById(sessionId) {
|
|
13492
|
+
const live = this.sessionManager.getSession(sessionId);
|
|
13493
|
+
if (live) return live;
|
|
13494
|
+
if (!this.sessionStore || !this.createFullSession) return null;
|
|
13495
|
+
const record = this.sessionStore.get(sessionId);
|
|
13496
|
+
if (!record) return null;
|
|
13497
|
+
if (record.status === "error" || record.status === "cancelled") return null;
|
|
13498
|
+
const existing = this.resumeLocks.get(sessionId);
|
|
13499
|
+
if (existing) return existing;
|
|
13500
|
+
const resumePromise = (async () => {
|
|
13501
|
+
try {
|
|
13502
|
+
const p = record.platform;
|
|
13503
|
+
const existingThreadId = p?.topicId ? String(p.topicId) : p?.threadId;
|
|
13504
|
+
const session = await this.createFullSession({
|
|
13505
|
+
channelId: record.channelId,
|
|
13506
|
+
agentName: record.agentName,
|
|
13507
|
+
workingDirectory: record.workingDir,
|
|
13508
|
+
resumeAgentSessionId: record.agentSessionId,
|
|
13509
|
+
existingSessionId: record.sessionId,
|
|
13510
|
+
initialName: record.name,
|
|
13511
|
+
threadId: existingThreadId
|
|
13512
|
+
});
|
|
13513
|
+
session.activate();
|
|
13514
|
+
if (record.clientOverrides) {
|
|
13515
|
+
session.clientOverrides = record.clientOverrides;
|
|
13516
|
+
} else if (record.dangerousMode) {
|
|
13517
|
+
session.clientOverrides = { bypassPermissions: true };
|
|
13518
|
+
}
|
|
13519
|
+
if (record.firstAgent) session.firstAgent = record.firstAgent;
|
|
13520
|
+
if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
|
|
13521
|
+
if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
|
|
13522
|
+
if (record.attachedAdapters) session.attachedAdapters = record.attachedAdapters;
|
|
13523
|
+
if (record.platforms) {
|
|
13524
|
+
for (const [adapterId, platformData] of Object.entries(record.platforms)) {
|
|
13525
|
+
const data = platformData;
|
|
13526
|
+
const tid = adapterId === "telegram" ? String(data.topicId ?? "") : String(data.threadId ?? "");
|
|
13527
|
+
if (tid) session.threadIds.set(adapterId, tid);
|
|
13528
|
+
}
|
|
13529
|
+
}
|
|
13530
|
+
if (record.acpState) {
|
|
13531
|
+
if (record.acpState.configOptions && session.configOptions.length === 0) {
|
|
13532
|
+
session.setInitialConfigOptions(record.acpState.configOptions);
|
|
13533
|
+
}
|
|
13534
|
+
if (record.acpState.agentCapabilities && !session.agentCapabilities) {
|
|
13535
|
+
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13538
|
+
log7.info({ sessionId }, "Lazy resume by ID successful");
|
|
13539
|
+
return session;
|
|
13540
|
+
} catch (err) {
|
|
13541
|
+
log7.error({ err, sessionId }, "Lazy resume by ID failed");
|
|
13542
|
+
return null;
|
|
13543
|
+
} finally {
|
|
13544
|
+
this.resumeLocks.delete(sessionId);
|
|
13545
|
+
}
|
|
13546
|
+
})();
|
|
13547
|
+
this.resumeLocks.set(sessionId, resumePromise);
|
|
13548
|
+
return resumePromise;
|
|
13549
|
+
}
|
|
13301
13550
|
async lazyResume(channelId, threadId) {
|
|
13302
13551
|
const store = this.sessionStore;
|
|
13303
13552
|
if (!store || !this.createFullSession) return null;
|
|
@@ -13306,7 +13555,7 @@ var SessionFactory = class {
|
|
|
13306
13555
|
if (existing) return existing;
|
|
13307
13556
|
const record = store.findByPlatform(
|
|
13308
13557
|
channelId,
|
|
13309
|
-
(p) => String(p.topicId) === threadId
|
|
13558
|
+
(p) => String(p.topicId) === threadId || String(p.threadId ?? "") === threadId
|
|
13310
13559
|
);
|
|
13311
13560
|
if (!record) {
|
|
13312
13561
|
log7.debug({ threadId, channelId }, "No session record found for thread");
|
|
@@ -13341,11 +13590,21 @@ var SessionFactory = class {
|
|
|
13341
13590
|
if (record.firstAgent) session.firstAgent = record.firstAgent;
|
|
13342
13591
|
if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
|
|
13343
13592
|
if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
|
|
13593
|
+
if (record.attachedAdapters) {
|
|
13594
|
+
session.attachedAdapters = record.attachedAdapters;
|
|
13595
|
+
}
|
|
13596
|
+
if (record.platforms) {
|
|
13597
|
+
for (const [adapterId, platformData] of Object.entries(record.platforms)) {
|
|
13598
|
+
const data = platformData;
|
|
13599
|
+
const tid = adapterId === "telegram" ? String(data.topicId ?? "") : String(data.threadId ?? "");
|
|
13600
|
+
if (tid) session.threadIds.set(adapterId, tid);
|
|
13601
|
+
}
|
|
13602
|
+
}
|
|
13344
13603
|
if (record.acpState) {
|
|
13345
|
-
if (record.acpState.configOptions) {
|
|
13604
|
+
if (record.acpState.configOptions && session.configOptions.length === 0) {
|
|
13346
13605
|
session.setInitialConfigOptions(record.acpState.configOptions);
|
|
13347
13606
|
}
|
|
13348
|
-
if (record.acpState.agentCapabilities) {
|
|
13607
|
+
if (record.acpState.agentCapabilities && !session.agentCapabilities) {
|
|
13349
13608
|
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
13350
13609
|
}
|
|
13351
13610
|
}
|
|
@@ -13475,6 +13734,7 @@ var SessionFactory = class {
|
|
|
13475
13734
|
// src/core/core.ts
|
|
13476
13735
|
import path17 from "path";
|
|
13477
13736
|
import os8 from "os";
|
|
13737
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
13478
13738
|
|
|
13479
13739
|
// src/core/sessions/session-store.ts
|
|
13480
13740
|
init_log();
|
|
@@ -13512,6 +13772,9 @@ var JsonFileSessionStore = class {
|
|
|
13512
13772
|
}
|
|
13513
13773
|
findByPlatform(channelId, predicate) {
|
|
13514
13774
|
for (const record of this.records.values()) {
|
|
13775
|
+
if (record.platforms?.[channelId]) {
|
|
13776
|
+
if (predicate(record.platforms[channelId])) return record;
|
|
13777
|
+
}
|
|
13515
13778
|
if (record.channelId === channelId && predicate(record.platform)) {
|
|
13516
13779
|
return record;
|
|
13517
13780
|
}
|
|
@@ -13578,7 +13841,7 @@ var JsonFileSessionStore = class {
|
|
|
13578
13841
|
return;
|
|
13579
13842
|
}
|
|
13580
13843
|
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
13581
|
-
this.records.set(id, record);
|
|
13844
|
+
this.records.set(id, this.migrateRecord(record));
|
|
13582
13845
|
}
|
|
13583
13846
|
log8.debug({ count: this.records.size }, "Loaded session records");
|
|
13584
13847
|
} catch (err) {
|
|
@@ -13589,6 +13852,19 @@ var JsonFileSessionStore = class {
|
|
|
13589
13852
|
}
|
|
13590
13853
|
}
|
|
13591
13854
|
}
|
|
13855
|
+
/** Migrate old SessionRecord format to new multi-adapter format. */
|
|
13856
|
+
migrateRecord(record) {
|
|
13857
|
+
if (!record.platforms && record.platform && typeof record.platform === "object") {
|
|
13858
|
+
const platformData = record.platform;
|
|
13859
|
+
if (Object.keys(platformData).length > 0) {
|
|
13860
|
+
record.platforms = { [record.channelId]: platformData };
|
|
13861
|
+
}
|
|
13862
|
+
}
|
|
13863
|
+
if (!record.attachedAdapters) {
|
|
13864
|
+
record.attachedAdapters = [record.channelId];
|
|
13865
|
+
}
|
|
13866
|
+
return record;
|
|
13867
|
+
}
|
|
13592
13868
|
cleanup() {
|
|
13593
13869
|
const cutoff = Date.now() - this.ttlDays * 24 * 60 * 60 * 1e3;
|
|
13594
13870
|
let removed = 0;
|
|
@@ -13667,8 +13943,15 @@ var AgentSwitchHandler = class {
|
|
|
13667
13943
|
toAgent,
|
|
13668
13944
|
status: "starting"
|
|
13669
13945
|
});
|
|
13670
|
-
const
|
|
13671
|
-
|
|
13946
|
+
const sessionBridgeKeys = this.deps.getSessionBridgeKeys(sessionId);
|
|
13947
|
+
const hadBridges = sessionBridgeKeys.length > 0;
|
|
13948
|
+
for (const key of sessionBridgeKeys) {
|
|
13949
|
+
const bridge = bridges.get(key);
|
|
13950
|
+
if (bridge) {
|
|
13951
|
+
bridges.delete(key);
|
|
13952
|
+
bridge.disconnect();
|
|
13953
|
+
}
|
|
13954
|
+
}
|
|
13672
13955
|
const switchAdapter = adapters.get(session.channelId);
|
|
13673
13956
|
if (switchAdapter?.sendSkillCommands) {
|
|
13674
13957
|
await switchAdapter.sendSkillCommands(session.id, []);
|
|
@@ -13745,9 +14028,11 @@ var AgentSwitchHandler = class {
|
|
|
13745
14028
|
session.agentInstance = oldInstance;
|
|
13746
14029
|
session.agentName = fromAgent;
|
|
13747
14030
|
session.agentSessionId = oldInstance.sessionId;
|
|
13748
|
-
const
|
|
13749
|
-
|
|
13750
|
-
|
|
14031
|
+
for (const adapterId of session.attachedAdapters) {
|
|
14032
|
+
const adapter = adapters.get(adapterId);
|
|
14033
|
+
if (adapter) {
|
|
14034
|
+
createBridge(session, adapter, adapterId).connect();
|
|
14035
|
+
}
|
|
13751
14036
|
}
|
|
13752
14037
|
log9.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
|
|
13753
14038
|
} catch (rollbackErr) {
|
|
@@ -13756,10 +14041,14 @@ var AgentSwitchHandler = class {
|
|
|
13756
14041
|
}
|
|
13757
14042
|
throw err;
|
|
13758
14043
|
}
|
|
13759
|
-
if (
|
|
13760
|
-
const
|
|
13761
|
-
|
|
13762
|
-
|
|
14044
|
+
if (hadBridges) {
|
|
14045
|
+
for (const adapterId of session.attachedAdapters) {
|
|
14046
|
+
const adapter = adapters.get(adapterId);
|
|
14047
|
+
if (adapter) {
|
|
14048
|
+
createBridge(session, adapter, adapterId).connect();
|
|
14049
|
+
} else {
|
|
14050
|
+
log9.warn({ sessionId, adapterId }, "Adapter not available during switch reconnect, skipping bridge");
|
|
14051
|
+
}
|
|
13763
14052
|
}
|
|
13764
14053
|
}
|
|
13765
14054
|
await sessionManager.patchRecord(sessionId, {
|
|
@@ -15212,7 +15501,7 @@ var OpenACPCore = class {
|
|
|
15212
15501
|
sessionManager;
|
|
15213
15502
|
messageTransformer;
|
|
15214
15503
|
adapters = /* @__PURE__ */ new Map();
|
|
15215
|
-
/** sessionId → SessionBridge — tracks active bridges for disconnect/reconnect
|
|
15504
|
+
/** "adapterId:sessionId" → SessionBridge — tracks active bridges for disconnect/reconnect */
|
|
15216
15505
|
bridges = /* @__PURE__ */ new Map();
|
|
15217
15506
|
/** Set by main.ts — triggers graceful shutdown with restart exit code */
|
|
15218
15507
|
requestRestart = null;
|
|
@@ -15308,7 +15597,8 @@ var OpenACPCore = class {
|
|
|
15308
15597
|
eventBus: this.eventBus,
|
|
15309
15598
|
adapters: this.adapters,
|
|
15310
15599
|
bridges: this.bridges,
|
|
15311
|
-
createBridge: (session, adapter) => this.createBridge(session, adapter),
|
|
15600
|
+
createBridge: (session, adapter, adapterId) => this.createBridge(session, adapter, adapterId),
|
|
15601
|
+
getSessionBridgeKeys: (sessionId) => this.getSessionBridgeKeys(sessionId),
|
|
15312
15602
|
getMiddlewareChain: () => this.lifecycleManager?.middlewareChain,
|
|
15313
15603
|
getService: (name) => this.lifecycleManager.serviceRegistry.get(name)
|
|
15314
15604
|
});
|
|
@@ -15488,7 +15778,22 @@ User message:
|
|
|
15488
15778
|
${text3}`;
|
|
15489
15779
|
}
|
|
15490
15780
|
}
|
|
15491
|
-
|
|
15781
|
+
const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
|
|
15782
|
+
if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
|
|
15783
|
+
const turnId = nanoid3(8);
|
|
15784
|
+
this.eventBus.emit("message:queued", {
|
|
15785
|
+
sessionId: session.id,
|
|
15786
|
+
turnId,
|
|
15787
|
+
text: text3,
|
|
15788
|
+
sourceAdapterId,
|
|
15789
|
+
attachments: message.attachments,
|
|
15790
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15791
|
+
queueDepth: session.queueDepth
|
|
15792
|
+
});
|
|
15793
|
+
await session.enqueuePrompt(text3, message.attachments, message.routing, turnId);
|
|
15794
|
+
} else {
|
|
15795
|
+
await session.enqueuePrompt(text3, message.attachments, message.routing);
|
|
15796
|
+
}
|
|
15492
15797
|
}
|
|
15493
15798
|
// --- Unified Session Creation Pipeline ---
|
|
15494
15799
|
async createSession(params) {
|
|
@@ -15515,6 +15820,12 @@ ${text3}`;
|
|
|
15515
15820
|
platform2.threadId = session.threadId;
|
|
15516
15821
|
}
|
|
15517
15822
|
}
|
|
15823
|
+
const platforms = {
|
|
15824
|
+
...existingRecord?.platforms ?? {}
|
|
15825
|
+
};
|
|
15826
|
+
if (session.threadId) {
|
|
15827
|
+
platforms[params.channelId] = params.channelId === "telegram" ? { topicId: Number(session.threadId) || session.threadId } : { threadId: session.threadId };
|
|
15828
|
+
}
|
|
15518
15829
|
await this.sessionManager.patchRecord(session.id, {
|
|
15519
15830
|
sessionId: session.id,
|
|
15520
15831
|
agentSessionId: session.agentSessionId,
|
|
@@ -15526,6 +15837,7 @@ ${text3}`;
|
|
|
15526
15837
|
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15527
15838
|
name: session.name,
|
|
15528
15839
|
platform: platform2,
|
|
15840
|
+
platforms,
|
|
15529
15841
|
firstAgent: session.firstAgent,
|
|
15530
15842
|
currentPromptCount: session.promptCount,
|
|
15531
15843
|
agentSwitchHistory: session.agentSwitchHistory,
|
|
@@ -15533,7 +15845,7 @@ ${text3}`;
|
|
|
15533
15845
|
acpState: session.toAcpStateSnapshot()
|
|
15534
15846
|
}, { immediate: true });
|
|
15535
15847
|
if (adapter) {
|
|
15536
|
-
const bridge = this.createBridge(session, adapter);
|
|
15848
|
+
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
15537
15849
|
bridge.connect();
|
|
15538
15850
|
adapter.flushPendingSkillCommands?.(session.id).catch((err) => {
|
|
15539
15851
|
log16.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
|
|
@@ -15672,9 +15984,14 @@ ${text3}`;
|
|
|
15672
15984
|
} else {
|
|
15673
15985
|
adoptPlatform.threadId = session.threadId;
|
|
15674
15986
|
}
|
|
15987
|
+
const adoptPlatforms = {};
|
|
15988
|
+
if (session.threadId) {
|
|
15989
|
+
adoptPlatforms[adapterChannelId] = adapterChannelId === "telegram" ? { topicId: Number(session.threadId) || session.threadId } : { threadId: session.threadId };
|
|
15990
|
+
}
|
|
15675
15991
|
await this.sessionManager.patchRecord(session.id, {
|
|
15676
15992
|
originalAgentSessionId: agentSessionId,
|
|
15677
|
-
platform: adoptPlatform
|
|
15993
|
+
platform: adoptPlatform,
|
|
15994
|
+
platforms: adoptPlatforms
|
|
15678
15995
|
});
|
|
15679
15996
|
return {
|
|
15680
15997
|
ok: true,
|
|
@@ -15696,18 +16013,101 @@ ${text3}`;
|
|
|
15696
16013
|
async getOrResumeSession(channelId, threadId) {
|
|
15697
16014
|
return this.sessionFactory.getOrResume(channelId, threadId);
|
|
15698
16015
|
}
|
|
16016
|
+
async getOrResumeSessionById(sessionId) {
|
|
16017
|
+
return this.sessionFactory.getOrResumeById(sessionId);
|
|
16018
|
+
}
|
|
16019
|
+
async attachAdapter(sessionId, adapterId) {
|
|
16020
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
16021
|
+
if (!session) throw new Error(`Session ${sessionId} not found`);
|
|
16022
|
+
const adapter = this.adapters.get(adapterId);
|
|
16023
|
+
if (!adapter) throw new Error(`Adapter "${adapterId}" not found or not running`);
|
|
16024
|
+
if (session.attachedAdapters.includes(adapterId)) {
|
|
16025
|
+
const existingThread = session.threadIds.get(adapterId) ?? session.id;
|
|
16026
|
+
return { threadId: existingThread };
|
|
16027
|
+
}
|
|
16028
|
+
const threadId = await adapter.createSessionThread(
|
|
16029
|
+
session.id,
|
|
16030
|
+
session.name ?? `Session ${session.id.slice(0, 6)}`
|
|
16031
|
+
);
|
|
16032
|
+
session.threadIds.set(adapterId, threadId);
|
|
16033
|
+
session.attachedAdapters.push(adapterId);
|
|
16034
|
+
const bridge = this.createBridge(session, adapter, adapterId);
|
|
16035
|
+
bridge.connect();
|
|
16036
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
16037
|
+
attachedAdapters: session.attachedAdapters,
|
|
16038
|
+
platforms: this.buildPlatformsFromSession(session)
|
|
16039
|
+
});
|
|
16040
|
+
return { threadId };
|
|
16041
|
+
}
|
|
16042
|
+
async detachAdapter(sessionId, adapterId) {
|
|
16043
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
16044
|
+
if (!session) throw new Error(`Session ${sessionId} not found`);
|
|
16045
|
+
if (adapterId === session.channelId) {
|
|
16046
|
+
throw new Error("Cannot detach primary adapter (channelId)");
|
|
16047
|
+
}
|
|
16048
|
+
if (!session.attachedAdapters.includes(adapterId)) {
|
|
16049
|
+
return;
|
|
16050
|
+
}
|
|
16051
|
+
const adapter = this.adapters.get(adapterId);
|
|
16052
|
+
if (adapter) {
|
|
16053
|
+
try {
|
|
16054
|
+
await adapter.sendMessage(session.id, {
|
|
16055
|
+
type: "system_message",
|
|
16056
|
+
text: "Session detached from this adapter."
|
|
16057
|
+
});
|
|
16058
|
+
} catch {
|
|
16059
|
+
}
|
|
16060
|
+
}
|
|
16061
|
+
const key = this.bridgeKey(adapterId, session.id);
|
|
16062
|
+
const bridge = this.bridges.get(key);
|
|
16063
|
+
if (bridge) {
|
|
16064
|
+
bridge.disconnect();
|
|
16065
|
+
this.bridges.delete(key);
|
|
16066
|
+
}
|
|
16067
|
+
session.attachedAdapters = session.attachedAdapters.filter((a) => a !== adapterId);
|
|
16068
|
+
session.threadIds.delete(adapterId);
|
|
16069
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
16070
|
+
attachedAdapters: session.attachedAdapters,
|
|
16071
|
+
platforms: this.buildPlatformsFromSession(session)
|
|
16072
|
+
});
|
|
16073
|
+
}
|
|
16074
|
+
buildPlatformsFromSession(session) {
|
|
16075
|
+
const platforms = {};
|
|
16076
|
+
for (const [adapterId, threadId] of session.threadIds) {
|
|
16077
|
+
if (adapterId === "telegram") {
|
|
16078
|
+
platforms.telegram = { topicId: Number(threadId) || threadId };
|
|
16079
|
+
} else {
|
|
16080
|
+
platforms[adapterId] = { threadId };
|
|
16081
|
+
}
|
|
16082
|
+
}
|
|
16083
|
+
return platforms;
|
|
16084
|
+
}
|
|
15699
16085
|
// --- Event Wiring ---
|
|
16086
|
+
/** Composite bridge key: "adapterId:sessionId" */
|
|
16087
|
+
bridgeKey(adapterId, sessionId) {
|
|
16088
|
+
return `${adapterId}:${sessionId}`;
|
|
16089
|
+
}
|
|
16090
|
+
/** Get all bridge keys for a session (regardless of adapter) */
|
|
16091
|
+
getSessionBridgeKeys(sessionId) {
|
|
16092
|
+
const keys = [];
|
|
16093
|
+
for (const key of this.bridges.keys()) {
|
|
16094
|
+
if (key.endsWith(`:${sessionId}`)) keys.push(key);
|
|
16095
|
+
}
|
|
16096
|
+
return keys;
|
|
16097
|
+
}
|
|
15700
16098
|
/** Connect a session bridge for the given session (used by AssistantManager) */
|
|
15701
16099
|
connectSessionBridge(session) {
|
|
15702
16100
|
const adapter = this.adapters.get(session.channelId);
|
|
15703
16101
|
if (!adapter) return;
|
|
15704
|
-
const bridge = this.createBridge(session, adapter);
|
|
16102
|
+
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
15705
16103
|
bridge.connect();
|
|
15706
16104
|
}
|
|
15707
16105
|
/** Create a SessionBridge for the given session and adapter.
|
|
15708
|
-
* Disconnects any existing bridge for the same session first. */
|
|
15709
|
-
createBridge(session, adapter) {
|
|
15710
|
-
const
|
|
16106
|
+
* Disconnects any existing bridge for the same adapter+session first. */
|
|
16107
|
+
createBridge(session, adapter, adapterId) {
|
|
16108
|
+
const id = adapterId ?? adapter.name;
|
|
16109
|
+
const key = this.bridgeKey(id, session.id);
|
|
16110
|
+
const existing = this.bridges.get(key);
|
|
15711
16111
|
if (existing) {
|
|
15712
16112
|
existing.disconnect();
|
|
15713
16113
|
}
|
|
@@ -15718,8 +16118,8 @@ ${text3}`;
|
|
|
15718
16118
|
eventBus: this.eventBus,
|
|
15719
16119
|
fileService: this.fileService,
|
|
15720
16120
|
middlewareChain: this.lifecycleManager?.middlewareChain
|
|
15721
|
-
});
|
|
15722
|
-
this.bridges.set(
|
|
16121
|
+
}, id);
|
|
16122
|
+
this.bridges.set(key, bridge);
|
|
15723
16123
|
return bridge;
|
|
15724
16124
|
}
|
|
15725
16125
|
};
|
|
@@ -17754,12 +18154,14 @@ export {
|
|
|
17754
18154
|
createApiServerService,
|
|
17755
18155
|
createChildLogger,
|
|
17756
18156
|
createSessionLogger,
|
|
18157
|
+
createTurnContext,
|
|
17757
18158
|
expandHome3 as expandHome,
|
|
17758
18159
|
extractContentText,
|
|
17759
18160
|
formatTokens,
|
|
17760
18161
|
formatToolSummary,
|
|
17761
18162
|
formatToolTitle,
|
|
17762
18163
|
getConfigValue,
|
|
18164
|
+
getEffectiveTarget,
|
|
17763
18165
|
getFieldDef,
|
|
17764
18166
|
getPidPath,
|
|
17765
18167
|
getSafeFields,
|
|
@@ -17769,6 +18171,7 @@ export {
|
|
|
17769
18171
|
isAutoStartInstalled,
|
|
17770
18172
|
isAutoStartSupported,
|
|
17771
18173
|
isHotReloadable,
|
|
18174
|
+
isSystemEvent,
|
|
17772
18175
|
log,
|
|
17773
18176
|
nodeToWebReadable,
|
|
17774
18177
|
nodeToWebWritable,
|