@openacp/cli 0.4.9 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/dist/api-client-UN7BXQOQ.js +11 -0
- package/dist/{autostart-DZ3MHHMM.js → autostart-K73RQZVV.js} +3 -3
- package/dist/chunk-3DIPXFZJ.js +650 -0
- package/dist/chunk-3DIPXFZJ.js.map +1 -0
- package/dist/{chunk-45DFYWJT.js → chunk-66RVSUAR.js} +1782 -1415
- package/dist/chunk-66RVSUAR.js.map +1 -0
- package/dist/chunk-BGKQHQB4.js +276 -0
- package/dist/chunk-BGKQHQB4.js.map +1 -0
- package/dist/chunk-C33LTDZV.js +97 -0
- package/dist/chunk-C33LTDZV.js.map +1 -0
- package/dist/{chunk-LYKCQTH5.js → chunk-ESOPMQAY.js} +5 -1
- package/dist/chunk-ESOPMQAY.js.map +1 -0
- package/dist/{chunk-6MJLVZXV.js → chunk-FKOARMAE.js} +58 -21
- package/dist/{chunk-6MJLVZXV.js.map → chunk-FKOARMAE.js.map} +1 -1
- package/dist/chunk-OORPX73T.js +30 -0
- package/dist/chunk-OORPX73T.js.map +1 -0
- package/dist/{chunk-V3BA2MJ6.js → chunk-RF3DUYFO.js} +2 -2
- package/dist/{chunk-UAUTLC4E.js → chunk-W7QQA6CW.js} +4 -4
- package/dist/{chunk-ZRFBLD3W.js → chunk-WYZFGHHI.js} +14 -4
- package/dist/chunk-WYZFGHHI.js.map +1 -0
- package/dist/{chunk-MRKYJ422.js → chunk-X6LLG7XN.js} +2 -2
- package/dist/{chunk-C6YIUTGR.js → chunk-YRJEZD7R.js} +2 -2
- package/dist/{chunk-HZD3CGPK.js → chunk-ZW444AQY.js} +3 -3
- package/dist/cli.js +141 -52
- package/dist/cli.js.map +1 -1
- package/dist/{config-H2DSEHNW.js → config-XURP6B3S.js} +3 -3
- package/dist/config-editor-AALY3URF.js +11 -0
- package/dist/config-registry-OGX4YM2U.js +17 -0
- package/dist/{daemon-VF6HJQXD.js → daemon-GWJM2S4A.js} +4 -4
- package/dist/doctor-X477CVZN.js +9 -0
- package/dist/doctor-X477CVZN.js.map +1 -0
- package/dist/index.d.ts +76 -27
- package/dist/index.js +33 -13
- package/dist/install-cloudflared-BTGUD7SW.js +8 -0
- package/dist/install-cloudflared-BTGUD7SW.js.map +1 -0
- package/dist/log-SPS2S6FO.js +19 -0
- package/dist/log-SPS2S6FO.js.map +1 -0
- package/dist/{main-7T5YHFHO.js → main-2QKD2EI2.js} +17 -14
- package/dist/{main-7T5YHFHO.js.map → main-2QKD2EI2.js.map} +1 -1
- package/dist/menu-CARRTW2F.js +17 -0
- package/dist/menu-CARRTW2F.js.map +1 -0
- package/dist/{setup-FCVL75K6.js → setup-TTOL7XAN.js} +4 -4
- package/dist/setup-TTOL7XAN.js.map +1 -0
- package/dist/{tunnel-service-DASSH7OA.js → tunnel-service-LEVPLXAZ.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-45DFYWJT.js.map +0 -1
- package/dist/chunk-LYKCQTH5.js.map +0 -1
- package/dist/chunk-ZRFBLD3W.js.map +0 -1
- package/dist/config-editor-SKS4LJLT.js +0 -11
- package/dist/install-cloudflared-ILUXKLAC.js +0 -8
- /package/dist/{autostart-DZ3MHHMM.js.map → api-client-UN7BXQOQ.js.map} +0 -0
- /package/dist/{config-H2DSEHNW.js.map → autostart-K73RQZVV.js.map} +0 -0
- /package/dist/{chunk-V3BA2MJ6.js.map → chunk-RF3DUYFO.js.map} +0 -0
- /package/dist/{chunk-UAUTLC4E.js.map → chunk-W7QQA6CW.js.map} +0 -0
- /package/dist/{chunk-MRKYJ422.js.map → chunk-X6LLG7XN.js.map} +0 -0
- /package/dist/{chunk-C6YIUTGR.js.map → chunk-YRJEZD7R.js.map} +0 -0
- /package/dist/{chunk-HZD3CGPK.js.map → chunk-ZW444AQY.js.map} +0 -0
- /package/dist/{config-editor-SKS4LJLT.js.map → config-XURP6B3S.js.map} +0 -0
- /package/dist/{daemon-VF6HJQXD.js.map → config-editor-AALY3URF.js.map} +0 -0
- /package/dist/{install-cloudflared-ILUXKLAC.js.map → config-registry-OGX4YM2U.js.map} +0 -0
- /package/dist/{setup-FCVL75K6.js.map → daemon-GWJM2S4A.js.map} +0 -0
- /package/dist/{tunnel-service-DASSH7OA.js.map → tunnel-service-LEVPLXAZ.js.map} +0 -0
|
@@ -1,10 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DoctorEngine
|
|
3
|
+
} from "./chunk-3DIPXFZJ.js";
|
|
1
4
|
import {
|
|
2
5
|
getAgentCapabilities
|
|
3
6
|
} from "./chunk-VA2M52CM.js";
|
|
7
|
+
import {
|
|
8
|
+
buildMenuKeyboard,
|
|
9
|
+
buildSkillMessages,
|
|
10
|
+
escapeHtml,
|
|
11
|
+
formatToolCall,
|
|
12
|
+
formatToolUpdate,
|
|
13
|
+
formatUsage,
|
|
14
|
+
handleAgents,
|
|
15
|
+
handleClear,
|
|
16
|
+
handleHelp,
|
|
17
|
+
handleMenu,
|
|
18
|
+
markdownToTelegramHtml,
|
|
19
|
+
splitMessage
|
|
20
|
+
} from "./chunk-BGKQHQB4.js";
|
|
21
|
+
import {
|
|
22
|
+
getConfigValue,
|
|
23
|
+
getSafeFields,
|
|
24
|
+
isHotReloadable,
|
|
25
|
+
resolveOptions
|
|
26
|
+
} from "./chunk-C33LTDZV.js";
|
|
4
27
|
import {
|
|
5
28
|
createChildLogger,
|
|
6
29
|
createSessionLogger
|
|
7
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-ESOPMQAY.js";
|
|
8
31
|
|
|
9
32
|
// src/core/streams.ts
|
|
10
33
|
function nodeToWebWritable(nodeStream) {
|
|
@@ -674,6 +697,13 @@ var PermissionGate = class {
|
|
|
674
697
|
// src/core/session.ts
|
|
675
698
|
import { nanoid } from "nanoid";
|
|
676
699
|
var moduleLog = createChildLogger({ module: "session" });
|
|
700
|
+
var VALID_TRANSITIONS = {
|
|
701
|
+
initializing: /* @__PURE__ */ new Set(["active", "error"]),
|
|
702
|
+
active: /* @__PURE__ */ new Set(["error", "finished", "cancelled"]),
|
|
703
|
+
error: /* @__PURE__ */ new Set(["active"]),
|
|
704
|
+
cancelled: /* @__PURE__ */ new Set(["active"]),
|
|
705
|
+
finished: /* @__PURE__ */ new Set()
|
|
706
|
+
};
|
|
677
707
|
var Session = class extends TypedEmitter {
|
|
678
708
|
id;
|
|
679
709
|
channelId;
|
|
@@ -682,11 +712,9 @@ var Session = class extends TypedEmitter {
|
|
|
682
712
|
workingDirectory;
|
|
683
713
|
agentInstance;
|
|
684
714
|
agentSessionId = "";
|
|
685
|
-
|
|
715
|
+
_status = "initializing";
|
|
686
716
|
name;
|
|
687
717
|
createdAt = /* @__PURE__ */ new Date();
|
|
688
|
-
adapter;
|
|
689
|
-
// Set by wireSessionEvents for renaming
|
|
690
718
|
dangerousMode = false;
|
|
691
719
|
log;
|
|
692
720
|
permissionGate = new PermissionGate();
|
|
@@ -703,21 +731,44 @@ var Session = class extends TypedEmitter {
|
|
|
703
731
|
this.queue = new PromptQueue(
|
|
704
732
|
(text) => this.processPrompt(text),
|
|
705
733
|
(err) => {
|
|
706
|
-
this.
|
|
734
|
+
this.fail("Prompt execution failed");
|
|
707
735
|
this.log.error({ err }, "Prompt execution failed");
|
|
708
736
|
}
|
|
709
737
|
);
|
|
710
738
|
}
|
|
711
|
-
// ---
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
739
|
+
// --- State Machine ---
|
|
740
|
+
get status() {
|
|
741
|
+
return this._status;
|
|
742
|
+
}
|
|
743
|
+
/** Transition to active — from initializing, error, or cancelled */
|
|
744
|
+
activate() {
|
|
745
|
+
this.transition("active");
|
|
746
|
+
}
|
|
747
|
+
/** Transition to error — from initializing or active */
|
|
748
|
+
fail(reason) {
|
|
749
|
+
this.transition("error");
|
|
750
|
+
this.emit("error", new Error(reason));
|
|
751
|
+
}
|
|
752
|
+
/** Transition to finished — from active only. Emits session_end for backward compat. */
|
|
753
|
+
finish(reason) {
|
|
754
|
+
this.transition("finished");
|
|
755
|
+
this.emit("session_end", reason ?? "completed");
|
|
756
|
+
}
|
|
757
|
+
/** Transition to cancelled — from active only (terminal session cancel) */
|
|
758
|
+
markCancelled() {
|
|
759
|
+
this.transition("cancelled");
|
|
760
|
+
}
|
|
761
|
+
transition(to) {
|
|
762
|
+
const from = this._status;
|
|
763
|
+
const allowed = VALID_TRANSITIONS[from];
|
|
764
|
+
if (!allowed?.has(to)) {
|
|
765
|
+
throw new Error(
|
|
766
|
+
`Invalid session transition: ${from} \u2192 ${to}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
this._status = to;
|
|
770
|
+
this.log.debug({ from, to }, "Session status transition");
|
|
771
|
+
this.emit("status_change", from, to);
|
|
721
772
|
}
|
|
722
773
|
/** Number of prompts waiting in queue */
|
|
723
774
|
get queueDepth() {
|
|
@@ -735,7 +786,9 @@ var Session = class extends TypedEmitter {
|
|
|
735
786
|
await this.runWarmup();
|
|
736
787
|
return;
|
|
737
788
|
}
|
|
738
|
-
this.
|
|
789
|
+
if (this._status === "initializing") {
|
|
790
|
+
this.activate();
|
|
791
|
+
}
|
|
739
792
|
const promptStart = Date.now();
|
|
740
793
|
this.log.debug("Prompt execution started");
|
|
741
794
|
await this.agentInstance.prompt(text);
|
|
@@ -760,9 +813,7 @@ var Session = class extends TypedEmitter {
|
|
|
760
813
|
);
|
|
761
814
|
this.name = title.trim().slice(0, 50) || `Session ${this.id.slice(0, 6)}`;
|
|
762
815
|
this.log.info({ name: this.name }, "Session auto-named");
|
|
763
|
-
|
|
764
|
-
await this.adapter.renameSessionThread(this.id, this.name);
|
|
765
|
-
}
|
|
816
|
+
this.emit("named", this.name);
|
|
766
817
|
} catch {
|
|
767
818
|
this.name = `Session ${this.id.slice(0, 6)}`;
|
|
768
819
|
} finally {
|
|
@@ -781,7 +832,7 @@ var Session = class extends TypedEmitter {
|
|
|
781
832
|
try {
|
|
782
833
|
const start = Date.now();
|
|
783
834
|
await this.agentInstance.prompt('Reply with only "ready".');
|
|
784
|
-
this.
|
|
835
|
+
this.activate();
|
|
785
836
|
this.log.info({ durationMs: Date.now() - start }, "Warm-up complete");
|
|
786
837
|
} catch (err) {
|
|
787
838
|
this.log.error({ err }, "Warm-up failed");
|
|
@@ -790,11 +841,11 @@ var Session = class extends TypedEmitter {
|
|
|
790
841
|
this.resume();
|
|
791
842
|
}
|
|
792
843
|
}
|
|
793
|
-
|
|
844
|
+
/** Cancel the current prompt and clear the queue. Stays in active state. */
|
|
845
|
+
async abortPrompt() {
|
|
794
846
|
this.queue.clear();
|
|
795
|
-
this.log.info("
|
|
847
|
+
this.log.info("Prompt aborted");
|
|
796
848
|
await this.agentInstance.cancel();
|
|
797
|
-
this.status = "active";
|
|
798
849
|
}
|
|
799
850
|
async destroy() {
|
|
800
851
|
this.log.info("Session destroyed");
|
|
@@ -867,42 +918,13 @@ var SessionManager = class {
|
|
|
867
918
|
registerSession(session) {
|
|
868
919
|
this.sessions.set(session.id, session);
|
|
869
920
|
}
|
|
870
|
-
async
|
|
871
|
-
if (!this.store) return;
|
|
872
|
-
const record = this.store.get(sessionId);
|
|
873
|
-
if (record) {
|
|
874
|
-
await this.store.save({ ...record, platform });
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
async updateSessionActivity(sessionId) {
|
|
878
|
-
if (!this.store) return;
|
|
879
|
-
const record = this.store.get(sessionId);
|
|
880
|
-
if (record) {
|
|
881
|
-
await this.store.save({
|
|
882
|
-
...record,
|
|
883
|
-
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
async updateSessionStatus(sessionId, status) {
|
|
888
|
-
if (!this.store) return;
|
|
889
|
-
const record = this.store.get(sessionId);
|
|
890
|
-
if (record) {
|
|
891
|
-
await this.store.save({ ...record, status });
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
async updateSessionDangerousMode(sessionId, dangerousMode) {
|
|
895
|
-
if (!this.store) return;
|
|
896
|
-
const record = this.store.get(sessionId);
|
|
897
|
-
if (record) {
|
|
898
|
-
await this.store.save({ ...record, dangerousMode });
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
async updateSessionName(sessionId, name) {
|
|
921
|
+
async patchRecord(sessionId, patch) {
|
|
902
922
|
if (!this.store) return;
|
|
903
923
|
const record = this.store.get(sessionId);
|
|
904
924
|
if (record) {
|
|
905
|
-
await this.store.save({ ...record,
|
|
925
|
+
await this.store.save({ ...record, ...patch });
|
|
926
|
+
} else if (patch.sessionId) {
|
|
927
|
+
await this.store.save(patch);
|
|
906
928
|
}
|
|
907
929
|
}
|
|
908
930
|
getSessionRecord(sessionId) {
|
|
@@ -911,7 +933,8 @@ var SessionManager = class {
|
|
|
911
933
|
async cancelSession(sessionId) {
|
|
912
934
|
const session = this.sessions.get(sessionId);
|
|
913
935
|
if (session) {
|
|
914
|
-
await session.
|
|
936
|
+
await session.abortPrompt();
|
|
937
|
+
session.markCancelled();
|
|
915
938
|
}
|
|
916
939
|
if (this.store) {
|
|
917
940
|
const record = this.store.get(sessionId);
|
|
@@ -953,6 +976,133 @@ var SessionManager = class {
|
|
|
953
976
|
}
|
|
954
977
|
};
|
|
955
978
|
|
|
979
|
+
// src/core/session-bridge.ts
|
|
980
|
+
var log2 = createChildLogger({ module: "session-bridge" });
|
|
981
|
+
var SessionBridge = class {
|
|
982
|
+
constructor(session, adapter, deps) {
|
|
983
|
+
this.session = session;
|
|
984
|
+
this.adapter = adapter;
|
|
985
|
+
this.deps = deps;
|
|
986
|
+
}
|
|
987
|
+
connected = false;
|
|
988
|
+
agentEventHandler;
|
|
989
|
+
statusChangeHandler;
|
|
990
|
+
namedHandler;
|
|
991
|
+
connect() {
|
|
992
|
+
if (this.connected) return;
|
|
993
|
+
this.connected = true;
|
|
994
|
+
this.wireAgentToSession();
|
|
995
|
+
this.wireSessionToAdapter();
|
|
996
|
+
this.wirePermissions();
|
|
997
|
+
this.wireLifecycle();
|
|
998
|
+
}
|
|
999
|
+
disconnect() {
|
|
1000
|
+
if (!this.connected) return;
|
|
1001
|
+
this.connected = false;
|
|
1002
|
+
if (this.agentEventHandler) {
|
|
1003
|
+
this.session.off("agent_event", this.agentEventHandler);
|
|
1004
|
+
}
|
|
1005
|
+
if (this.statusChangeHandler) {
|
|
1006
|
+
this.session.off("status_change", this.statusChangeHandler);
|
|
1007
|
+
}
|
|
1008
|
+
if (this.namedHandler) {
|
|
1009
|
+
this.session.off("named", this.namedHandler);
|
|
1010
|
+
}
|
|
1011
|
+
this.session.agentInstance.onSessionUpdate = () => {
|
|
1012
|
+
};
|
|
1013
|
+
this.session.agentInstance.onPermissionRequest = async () => "";
|
|
1014
|
+
}
|
|
1015
|
+
wireAgentToSession() {
|
|
1016
|
+
this.session.agentInstance.onSessionUpdate = (event) => {
|
|
1017
|
+
this.session.emit("agent_event", event);
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
wireSessionToAdapter() {
|
|
1021
|
+
const session = this.session;
|
|
1022
|
+
const ctx = {
|
|
1023
|
+
get id() {
|
|
1024
|
+
return session.id;
|
|
1025
|
+
},
|
|
1026
|
+
get workingDirectory() {
|
|
1027
|
+
return session.workingDirectory;
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
this.agentEventHandler = (event) => {
|
|
1031
|
+
switch (event.type) {
|
|
1032
|
+
case "text":
|
|
1033
|
+
case "thought":
|
|
1034
|
+
case "tool_call":
|
|
1035
|
+
case "tool_update":
|
|
1036
|
+
case "plan":
|
|
1037
|
+
case "usage":
|
|
1038
|
+
this.adapter.sendMessage(
|
|
1039
|
+
this.session.id,
|
|
1040
|
+
this.deps.messageTransformer.transform(event, ctx)
|
|
1041
|
+
);
|
|
1042
|
+
break;
|
|
1043
|
+
case "session_end":
|
|
1044
|
+
this.session.finish(event.reason);
|
|
1045
|
+
this.adapter.cleanupSkillCommands(this.session.id);
|
|
1046
|
+
this.adapter.sendMessage(
|
|
1047
|
+
this.session.id,
|
|
1048
|
+
this.deps.messageTransformer.transform(event)
|
|
1049
|
+
);
|
|
1050
|
+
this.deps.notificationManager.notify(this.session.channelId, {
|
|
1051
|
+
sessionId: this.session.id,
|
|
1052
|
+
sessionName: this.session.name,
|
|
1053
|
+
type: "completed",
|
|
1054
|
+
summary: `Session "${this.session.name || this.session.id}" completed`
|
|
1055
|
+
});
|
|
1056
|
+
break;
|
|
1057
|
+
case "error":
|
|
1058
|
+
this.session.fail(event.message);
|
|
1059
|
+
this.adapter.cleanupSkillCommands(this.session.id);
|
|
1060
|
+
this.adapter.sendMessage(
|
|
1061
|
+
this.session.id,
|
|
1062
|
+
this.deps.messageTransformer.transform(event)
|
|
1063
|
+
);
|
|
1064
|
+
this.deps.notificationManager.notify(this.session.channelId, {
|
|
1065
|
+
sessionId: this.session.id,
|
|
1066
|
+
sessionName: this.session.name,
|
|
1067
|
+
type: "error",
|
|
1068
|
+
summary: event.message
|
|
1069
|
+
});
|
|
1070
|
+
break;
|
|
1071
|
+
case "commands_update":
|
|
1072
|
+
log2.debug({ commands: event.commands }, "Commands available");
|
|
1073
|
+
this.adapter.sendSkillCommands(this.session.id, event.commands);
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
this.session.on("agent_event", this.agentEventHandler);
|
|
1078
|
+
}
|
|
1079
|
+
wirePermissions() {
|
|
1080
|
+
this.session.agentInstance.onPermissionRequest = async (request) => {
|
|
1081
|
+
this.session.emit("permission_request", request);
|
|
1082
|
+
const promise = this.session.permissionGate.setPending(request);
|
|
1083
|
+
await this.adapter.sendPermissionRequest(this.session.id, request);
|
|
1084
|
+
return promise;
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
wireLifecycle() {
|
|
1088
|
+
this.statusChangeHandler = (from, to) => {
|
|
1089
|
+
this.deps.sessionManager.patchRecord(this.session.id, {
|
|
1090
|
+
status: to,
|
|
1091
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1092
|
+
});
|
|
1093
|
+
if (to === "finished" || to === "cancelled") {
|
|
1094
|
+
queueMicrotask(() => this.disconnect());
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
this.session.on("status_change", this.statusChangeHandler);
|
|
1098
|
+
this.namedHandler = (name) => {
|
|
1099
|
+
this.deps.sessionManager.patchRecord(this.session.id, { name });
|
|
1100
|
+
this.adapter.renameSessionThread(this.session.id, name);
|
|
1101
|
+
};
|
|
1102
|
+
this.session.on("named", this.namedHandler);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
|
|
956
1106
|
// src/core/notification.ts
|
|
957
1107
|
var NotificationManager = class {
|
|
958
1108
|
constructor(adapters) {
|
|
@@ -1062,7 +1212,7 @@ function parseContent(content) {
|
|
|
1062
1212
|
}
|
|
1063
1213
|
|
|
1064
1214
|
// src/core/message-transformer.ts
|
|
1065
|
-
var
|
|
1215
|
+
var log3 = createChildLogger({ module: "message-transformer" });
|
|
1066
1216
|
var MessageTransformer = class {
|
|
1067
1217
|
constructor(tunnelService) {
|
|
1068
1218
|
this.tunnelService = tunnelService;
|
|
@@ -1124,7 +1274,7 @@ var MessageTransformer = class {
|
|
|
1124
1274
|
if (!this.tunnelService || !sessionContext) return;
|
|
1125
1275
|
const name = "name" in event ? event.name || "" : "";
|
|
1126
1276
|
const kind = "kind" in event ? event.kind : void 0;
|
|
1127
|
-
|
|
1277
|
+
log3.debug(
|
|
1128
1278
|
{ name, kind, status: event.status, hasContent: !!event.content },
|
|
1129
1279
|
"enrichWithViewerLinks: inspecting event"
|
|
1130
1280
|
);
|
|
@@ -1136,7 +1286,7 @@ var MessageTransformer = class {
|
|
|
1136
1286
|
event.meta
|
|
1137
1287
|
);
|
|
1138
1288
|
if (!fileInfo) return;
|
|
1139
|
-
|
|
1289
|
+
log3.info(
|
|
1140
1290
|
{
|
|
1141
1291
|
name,
|
|
1142
1292
|
kind,
|
|
@@ -1178,7 +1328,7 @@ import os from "os";
|
|
|
1178
1328
|
// src/core/session-store.ts
|
|
1179
1329
|
import fs2 from "fs";
|
|
1180
1330
|
import path2 from "path";
|
|
1181
|
-
var
|
|
1331
|
+
var log4 = createChildLogger({ module: "session-store" });
|
|
1182
1332
|
var DEBOUNCE_MS = 2e3;
|
|
1183
1333
|
var JsonFileSessionStore = class {
|
|
1184
1334
|
records = /* @__PURE__ */ new Map();
|
|
@@ -1260,7 +1410,7 @@ var JsonFileSessionStore = class {
|
|
|
1260
1410
|
fs2.readFileSync(this.filePath, "utf-8")
|
|
1261
1411
|
);
|
|
1262
1412
|
if (raw.version !== 1) {
|
|
1263
|
-
|
|
1413
|
+
log4.warn(
|
|
1264
1414
|
{ version: raw.version },
|
|
1265
1415
|
"Unknown session store version, skipping load"
|
|
1266
1416
|
);
|
|
@@ -1269,9 +1419,9 @@ var JsonFileSessionStore = class {
|
|
|
1269
1419
|
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
1270
1420
|
this.records.set(id, record);
|
|
1271
1421
|
}
|
|
1272
|
-
|
|
1422
|
+
log4.info({ count: this.records.size }, "Loaded session records");
|
|
1273
1423
|
} catch (err) {
|
|
1274
|
-
|
|
1424
|
+
log4.error({ err }, "Failed to load session store");
|
|
1275
1425
|
}
|
|
1276
1426
|
}
|
|
1277
1427
|
cleanup() {
|
|
@@ -1287,7 +1437,7 @@ var JsonFileSessionStore = class {
|
|
|
1287
1437
|
}
|
|
1288
1438
|
}
|
|
1289
1439
|
if (removed > 0) {
|
|
1290
|
-
|
|
1440
|
+
log4.info({ removed }, "Cleaned up expired session records");
|
|
1291
1441
|
this.scheduleDiskWrite();
|
|
1292
1442
|
}
|
|
1293
1443
|
}
|
|
@@ -1300,7 +1450,7 @@ var JsonFileSessionStore = class {
|
|
|
1300
1450
|
};
|
|
1301
1451
|
|
|
1302
1452
|
// src/core/core.ts
|
|
1303
|
-
var
|
|
1453
|
+
var log5 = createChildLogger({ module: "core" });
|
|
1304
1454
|
var OpenACPCore = class {
|
|
1305
1455
|
configManager;
|
|
1306
1456
|
agentManager;
|
|
@@ -1325,6 +1475,13 @@ var OpenACPCore = class {
|
|
|
1325
1475
|
this.sessionManager = new SessionManager(this.sessionStore);
|
|
1326
1476
|
this.notificationManager = new NotificationManager(this.adapters);
|
|
1327
1477
|
this.messageTransformer = new MessageTransformer();
|
|
1478
|
+
this.configManager.on("config:changed", async ({ path: configPath, value }) => {
|
|
1479
|
+
if (configPath === "logging.level" && typeof value === "string") {
|
|
1480
|
+
const { setLogLevel: setLogLevel2 } = await import("./log-SPS2S6FO.js");
|
|
1481
|
+
setLogLevel2(value);
|
|
1482
|
+
log5.info({ level: value }, "Log level changed at runtime");
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1328
1485
|
}
|
|
1329
1486
|
get tunnelService() {
|
|
1330
1487
|
return this._tunnelService;
|
|
@@ -1358,7 +1515,7 @@ var OpenACPCore = class {
|
|
|
1358
1515
|
// --- Message Routing ---
|
|
1359
1516
|
async handleMessage(message) {
|
|
1360
1517
|
const config = this.configManager.get();
|
|
1361
|
-
|
|
1518
|
+
log5.debug(
|
|
1362
1519
|
{
|
|
1363
1520
|
channelId: message.channelId,
|
|
1364
1521
|
threadId: message.threadId,
|
|
@@ -1368,7 +1525,7 @@ var OpenACPCore = class {
|
|
|
1368
1525
|
);
|
|
1369
1526
|
if (config.security.allowedUserIds.length > 0) {
|
|
1370
1527
|
if (!config.security.allowedUserIds.includes(message.userId)) {
|
|
1371
|
-
|
|
1528
|
+
log5.warn(
|
|
1372
1529
|
{ userId: message.userId },
|
|
1373
1530
|
"Rejected message from unauthorized user"
|
|
1374
1531
|
);
|
|
@@ -1377,7 +1534,7 @@ var OpenACPCore = class {
|
|
|
1377
1534
|
}
|
|
1378
1535
|
const activeSessions = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
|
|
1379
1536
|
if (activeSessions.length >= config.security.maxConcurrentSessions) {
|
|
1380
|
-
|
|
1537
|
+
log5.warn(
|
|
1381
1538
|
{
|
|
1382
1539
|
userId: message.userId,
|
|
1383
1540
|
currentCount: activeSessions.length,
|
|
@@ -1401,28 +1558,87 @@ var OpenACPCore = class {
|
|
|
1401
1558
|
if (!session) {
|
|
1402
1559
|
session = await this.lazyResume(message) ?? void 0;
|
|
1403
1560
|
}
|
|
1404
|
-
if (!session)
|
|
1405
|
-
|
|
1561
|
+
if (!session) {
|
|
1562
|
+
log5.warn(
|
|
1563
|
+
{ channelId: message.channelId, threadId: message.threadId },
|
|
1564
|
+
"No session found for thread (in-memory miss + lazy resume returned null)"
|
|
1565
|
+
);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
this.sessionManager.patchRecord(session.id, { lastActiveAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1406
1569
|
await session.enqueuePrompt(message.text);
|
|
1407
1570
|
}
|
|
1571
|
+
// --- Unified Session Creation Pipeline ---
|
|
1572
|
+
async createSession(params) {
|
|
1573
|
+
const agentInstance = params.resumeAgentSessionId ? await this.agentManager.resume(
|
|
1574
|
+
params.agentName,
|
|
1575
|
+
params.workingDirectory,
|
|
1576
|
+
params.resumeAgentSessionId
|
|
1577
|
+
) : await this.agentManager.spawn(
|
|
1578
|
+
params.agentName,
|
|
1579
|
+
params.workingDirectory
|
|
1580
|
+
);
|
|
1581
|
+
const session = new Session({
|
|
1582
|
+
id: params.existingSessionId,
|
|
1583
|
+
channelId: params.channelId,
|
|
1584
|
+
agentName: params.agentName,
|
|
1585
|
+
workingDirectory: params.workingDirectory,
|
|
1586
|
+
agentInstance
|
|
1587
|
+
});
|
|
1588
|
+
session.agentSessionId = agentInstance.sessionId;
|
|
1589
|
+
if (params.initialName) {
|
|
1590
|
+
session.name = params.initialName;
|
|
1591
|
+
}
|
|
1592
|
+
this.sessionManager.registerSession(session);
|
|
1593
|
+
const adapter = this.adapters.get(params.channelId);
|
|
1594
|
+
if (params.createThread && adapter) {
|
|
1595
|
+
const threadId = await adapter.createSessionThread(
|
|
1596
|
+
session.id,
|
|
1597
|
+
params.initialName ?? `\u{1F504} ${params.agentName} \u2014 New Session`
|
|
1598
|
+
);
|
|
1599
|
+
session.threadId = threadId;
|
|
1600
|
+
}
|
|
1601
|
+
if (adapter) {
|
|
1602
|
+
const bridge = this.createBridge(session, adapter);
|
|
1603
|
+
bridge.connect();
|
|
1604
|
+
}
|
|
1605
|
+
const existingRecord = this.sessionStore?.get(session.id);
|
|
1606
|
+
const platform = {
|
|
1607
|
+
...existingRecord?.platform ?? {}
|
|
1608
|
+
};
|
|
1609
|
+
if (session.threadId) {
|
|
1610
|
+
platform.topicId = Number(session.threadId);
|
|
1611
|
+
}
|
|
1612
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
1613
|
+
sessionId: session.id,
|
|
1614
|
+
agentSessionId: agentInstance.sessionId,
|
|
1615
|
+
agentName: params.agentName,
|
|
1616
|
+
workingDir: params.workingDirectory,
|
|
1617
|
+
channelId: params.channelId,
|
|
1618
|
+
status: session.status,
|
|
1619
|
+
createdAt: session.createdAt.toISOString(),
|
|
1620
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1621
|
+
name: session.name,
|
|
1622
|
+
platform
|
|
1623
|
+
});
|
|
1624
|
+
log5.info(
|
|
1625
|
+
{ sessionId: session.id, agentName: params.agentName },
|
|
1626
|
+
"Session created via pipeline"
|
|
1627
|
+
);
|
|
1628
|
+
return session;
|
|
1629
|
+
}
|
|
1408
1630
|
async handleNewSession(channelId, agentName, workspacePath) {
|
|
1409
1631
|
const config = this.configManager.get();
|
|
1410
1632
|
const resolvedAgent = agentName || config.defaultAgent;
|
|
1411
|
-
|
|
1633
|
+
log5.info({ channelId, agentName: resolvedAgent }, "New session request");
|
|
1412
1634
|
const resolvedWorkspace = this.configManager.resolveWorkspace(
|
|
1413
1635
|
workspacePath || config.agents[resolvedAgent]?.workingDirectory
|
|
1414
1636
|
);
|
|
1415
|
-
|
|
1637
|
+
return this.createSession({
|
|
1416
1638
|
channelId,
|
|
1417
|
-
resolvedAgent,
|
|
1418
|
-
resolvedWorkspace
|
|
1419
|
-
|
|
1420
|
-
);
|
|
1421
|
-
const adapter = this.adapters.get(channelId);
|
|
1422
|
-
if (adapter) {
|
|
1423
|
-
this.wireSessionEvents(session, adapter);
|
|
1424
|
-
}
|
|
1425
|
-
return session;
|
|
1639
|
+
agentName: resolvedAgent,
|
|
1640
|
+
workingDirectory: resolvedWorkspace
|
|
1641
|
+
});
|
|
1426
1642
|
}
|
|
1427
1643
|
async adoptSession(agentName, agentSessionId, cwd) {
|
|
1428
1644
|
const caps = getAgentCapabilities(agentName);
|
|
@@ -1445,10 +1661,10 @@ var OpenACPCore = class {
|
|
|
1445
1661
|
if (existingRecord) {
|
|
1446
1662
|
const platform = existingRecord.platform;
|
|
1447
1663
|
if (platform?.topicId) {
|
|
1448
|
-
const
|
|
1449
|
-
if (
|
|
1664
|
+
const adapter = this.adapters.values().next().value;
|
|
1665
|
+
if (adapter) {
|
|
1450
1666
|
try {
|
|
1451
|
-
await
|
|
1667
|
+
await adapter.sendMessage(existingRecord.sessionId, {
|
|
1452
1668
|
type: "text",
|
|
1453
1669
|
text: "Session resumed from CLI."
|
|
1454
1670
|
});
|
|
@@ -1463,9 +1679,21 @@ var OpenACPCore = class {
|
|
|
1463
1679
|
};
|
|
1464
1680
|
}
|
|
1465
1681
|
}
|
|
1466
|
-
|
|
1682
|
+
const firstEntry = this.adapters.entries().next().value;
|
|
1683
|
+
if (!firstEntry) {
|
|
1684
|
+
return { ok: false, error: "no_adapter", message: "No channel adapter registered" };
|
|
1685
|
+
}
|
|
1686
|
+
const [adapterChannelId] = firstEntry;
|
|
1687
|
+
let session;
|
|
1467
1688
|
try {
|
|
1468
|
-
|
|
1689
|
+
session = await this.createSession({
|
|
1690
|
+
channelId: adapterChannelId,
|
|
1691
|
+
agentName,
|
|
1692
|
+
workingDirectory: cwd,
|
|
1693
|
+
resumeAgentSessionId: agentSessionId,
|
|
1694
|
+
createThread: true,
|
|
1695
|
+
initialName: "Adopted session"
|
|
1696
|
+
});
|
|
1469
1697
|
} catch (err) {
|
|
1470
1698
|
return {
|
|
1471
1699
|
ok: false,
|
|
@@ -1473,43 +1701,14 @@ var OpenACPCore = class {
|
|
|
1473
1701
|
message: `Failed to resume session: ${err instanceof Error ? err.message : String(err)}`
|
|
1474
1702
|
};
|
|
1475
1703
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
workingDirectory: cwd,
|
|
1480
|
-
agentInstance
|
|
1704
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
1705
|
+
originalAgentSessionId: agentSessionId,
|
|
1706
|
+
platform: { topicId: Number(session.threadId) }
|
|
1481
1707
|
});
|
|
1482
|
-
session.agentSessionId = agentInstance.sessionId;
|
|
1483
|
-
this.sessionManager.registerSession(session);
|
|
1484
|
-
const firstEntry = this.adapters.entries().next().value;
|
|
1485
|
-
if (!firstEntry) {
|
|
1486
|
-
await session.destroy();
|
|
1487
|
-
return { ok: false, error: "no_adapter", message: "No channel adapter registered" };
|
|
1488
|
-
}
|
|
1489
|
-
const [adapterChannelId, adapter] = firstEntry;
|
|
1490
|
-
const threadId = await adapter.createSessionThread(session.id, session.name ?? "Adopted session");
|
|
1491
|
-
session.channelId = adapterChannelId;
|
|
1492
|
-
session.threadId = threadId;
|
|
1493
|
-
this.wireSessionEvents(session, adapter);
|
|
1494
|
-
if (this.sessionStore) {
|
|
1495
|
-
await this.sessionStore.save({
|
|
1496
|
-
sessionId: session.id,
|
|
1497
|
-
agentSessionId: agentInstance.sessionId,
|
|
1498
|
-
originalAgentSessionId: agentSessionId,
|
|
1499
|
-
agentName,
|
|
1500
|
-
workingDir: cwd,
|
|
1501
|
-
channelId: adapterChannelId,
|
|
1502
|
-
status: "active",
|
|
1503
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1504
|
-
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1505
|
-
name: session.name,
|
|
1506
|
-
platform: { topicId: Number(threadId) }
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
1708
|
return {
|
|
1510
1709
|
ok: true,
|
|
1511
1710
|
sessionId: session.id,
|
|
1512
|
-
threadId,
|
|
1711
|
+
threadId: session.threadId,
|
|
1513
1712
|
status: "adopted"
|
|
1514
1713
|
};
|
|
1515
1714
|
}
|
|
@@ -1544,45 +1743,54 @@ var OpenACPCore = class {
|
|
|
1544
1743
|
message.channelId,
|
|
1545
1744
|
(p) => String(p.topicId) === message.threadId
|
|
1546
1745
|
);
|
|
1547
|
-
if (!record)
|
|
1548
|
-
|
|
1746
|
+
if (!record) {
|
|
1747
|
+
log5.debug(
|
|
1748
|
+
{ threadId: message.threadId, channelId: message.channelId },
|
|
1749
|
+
"No session record found for thread"
|
|
1750
|
+
);
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
if (record.status === "cancelled" || record.status === "error") {
|
|
1754
|
+
log5.debug(
|
|
1755
|
+
{ threadId: message.threadId, sessionId: record.sessionId, status: record.status },
|
|
1756
|
+
"Skipping resume of cancelled/error session"
|
|
1757
|
+
);
|
|
1758
|
+
return null;
|
|
1759
|
+
}
|
|
1760
|
+
log5.info(
|
|
1761
|
+
{ threadId: message.threadId, sessionId: record.sessionId, status: record.status },
|
|
1762
|
+
"Lazy resume: found record, attempting resume"
|
|
1763
|
+
);
|
|
1549
1764
|
const resumePromise = (async () => {
|
|
1550
1765
|
try {
|
|
1551
|
-
const
|
|
1552
|
-
record.agentName,
|
|
1553
|
-
record.workingDir,
|
|
1554
|
-
record.agentSessionId
|
|
1555
|
-
);
|
|
1556
|
-
const session = new Session({
|
|
1557
|
-
id: record.sessionId,
|
|
1766
|
+
const session = await this.createSession({
|
|
1558
1767
|
channelId: record.channelId,
|
|
1559
1768
|
agentName: record.agentName,
|
|
1560
1769
|
workingDirectory: record.workingDir,
|
|
1561
|
-
|
|
1770
|
+
resumeAgentSessionId: record.agentSessionId,
|
|
1771
|
+
existingSessionId: record.sessionId,
|
|
1772
|
+
initialName: record.name
|
|
1562
1773
|
});
|
|
1563
1774
|
session.threadId = message.threadId;
|
|
1564
|
-
session.
|
|
1565
|
-
session.status = "active";
|
|
1566
|
-
session.name = record.name;
|
|
1775
|
+
session.activate();
|
|
1567
1776
|
session.dangerousMode = record.dangerousMode ?? false;
|
|
1568
|
-
|
|
1569
|
-
const adapter = this.adapters.get(message.channelId);
|
|
1570
|
-
if (adapter) {
|
|
1571
|
-
this.wireSessionEvents(session, adapter);
|
|
1572
|
-
}
|
|
1573
|
-
await store.save({
|
|
1574
|
-
...record,
|
|
1575
|
-
agentSessionId: agentInstance.sessionId,
|
|
1576
|
-
status: "active",
|
|
1577
|
-
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1578
|
-
});
|
|
1579
|
-
log4.info(
|
|
1777
|
+
log5.info(
|
|
1580
1778
|
{ sessionId: session.id, threadId: message.threadId },
|
|
1581
1779
|
"Lazy resume successful"
|
|
1582
1780
|
);
|
|
1583
1781
|
return session;
|
|
1584
1782
|
} catch (err) {
|
|
1585
|
-
|
|
1783
|
+
log5.error({ err, record }, "Lazy resume failed");
|
|
1784
|
+
const adapter = this.adapters.get(message.channelId);
|
|
1785
|
+
if (adapter) {
|
|
1786
|
+
try {
|
|
1787
|
+
await adapter.sendMessage(message.threadId, {
|
|
1788
|
+
type: "error",
|
|
1789
|
+
text: `\u26A0\uFE0F Failed to resume session: ${err instanceof Error ? err.message : String(err)}`
|
|
1790
|
+
});
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1586
1794
|
return null;
|
|
1587
1795
|
} finally {
|
|
1588
1796
|
this.resumeLocks.delete(lockKey);
|
|
@@ -1592,73 +1800,12 @@ var OpenACPCore = class {
|
|
|
1592
1800
|
return resumePromise;
|
|
1593
1801
|
}
|
|
1594
1802
|
// --- Event Wiring ---
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
session
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
session.agentInstance.onPermissionRequest = async (request) => {
|
|
1602
|
-
session.emit("permission_request", request);
|
|
1603
|
-
const promise = session.permissionGate.setPending(request);
|
|
1604
|
-
await adapter.sendPermissionRequest(session.id, request);
|
|
1605
|
-
return promise;
|
|
1606
|
-
};
|
|
1607
|
-
const sessionContext = {
|
|
1608
|
-
get id() {
|
|
1609
|
-
return session.id;
|
|
1610
|
-
},
|
|
1611
|
-
get workingDirectory() {
|
|
1612
|
-
return session.workingDirectory;
|
|
1613
|
-
}
|
|
1614
|
-
};
|
|
1615
|
-
session.on("agent_event", (event) => {
|
|
1616
|
-
switch (event.type) {
|
|
1617
|
-
case "text":
|
|
1618
|
-
case "thought":
|
|
1619
|
-
case "tool_call":
|
|
1620
|
-
case "tool_update":
|
|
1621
|
-
case "plan":
|
|
1622
|
-
case "usage":
|
|
1623
|
-
adapter.sendMessage(
|
|
1624
|
-
session.id,
|
|
1625
|
-
this.messageTransformer.transform(event, sessionContext)
|
|
1626
|
-
);
|
|
1627
|
-
break;
|
|
1628
|
-
case "session_end":
|
|
1629
|
-
session.status = "finished";
|
|
1630
|
-
this.sessionManager.updateSessionStatus(session.id, "finished");
|
|
1631
|
-
adapter.cleanupSkillCommands(session.id);
|
|
1632
|
-
adapter.sendMessage(
|
|
1633
|
-
session.id,
|
|
1634
|
-
this.messageTransformer.transform(event)
|
|
1635
|
-
);
|
|
1636
|
-
this.notificationManager.notify(session.channelId, {
|
|
1637
|
-
sessionId: session.id,
|
|
1638
|
-
sessionName: session.name,
|
|
1639
|
-
type: "completed",
|
|
1640
|
-
summary: `Session "${session.name || session.id}" completed`
|
|
1641
|
-
});
|
|
1642
|
-
break;
|
|
1643
|
-
case "error":
|
|
1644
|
-
this.sessionManager.updateSessionStatus(session.id, "error");
|
|
1645
|
-
adapter.cleanupSkillCommands(session.id);
|
|
1646
|
-
adapter.sendMessage(
|
|
1647
|
-
session.id,
|
|
1648
|
-
this.messageTransformer.transform(event)
|
|
1649
|
-
);
|
|
1650
|
-
this.notificationManager.notify(session.channelId, {
|
|
1651
|
-
sessionId: session.id,
|
|
1652
|
-
sessionName: session.name,
|
|
1653
|
-
type: "error",
|
|
1654
|
-
summary: event.message
|
|
1655
|
-
});
|
|
1656
|
-
break;
|
|
1657
|
-
case "commands_update":
|
|
1658
|
-
log4.debug({ commands: event.commands }, "Commands available");
|
|
1659
|
-
adapter.sendSkillCommands(session.id, event.commands);
|
|
1660
|
-
break;
|
|
1661
|
-
}
|
|
1803
|
+
/** Create a SessionBridge for the given session and adapter */
|
|
1804
|
+
createBridge(session, adapter) {
|
|
1805
|
+
return new SessionBridge(session, adapter, {
|
|
1806
|
+
messageTransformer: this.messageTransformer,
|
|
1807
|
+
notificationManager: this.notificationManager,
|
|
1808
|
+
sessionManager: this.sessionManager
|
|
1662
1809
|
});
|
|
1663
1810
|
}
|
|
1664
1811
|
};
|
|
@@ -1684,7 +1831,7 @@ import * as fs3 from "fs";
|
|
|
1684
1831
|
import * as path4 from "path";
|
|
1685
1832
|
import * as os2 from "os";
|
|
1686
1833
|
import { fileURLToPath } from "url";
|
|
1687
|
-
var
|
|
1834
|
+
var log6 = createChildLogger({ module: "api-server" });
|
|
1688
1835
|
var DEFAULT_PORT_FILE = path4.join(os2.homedir(), ".openacp", "api.port");
|
|
1689
1836
|
var cachedVersion;
|
|
1690
1837
|
function getVersion() {
|
|
@@ -1730,7 +1877,7 @@ var ApiServer = class {
|
|
|
1730
1877
|
await new Promise((resolve2, reject) => {
|
|
1731
1878
|
this.server.on("error", (err) => {
|
|
1732
1879
|
if (err.code === "EADDRINUSE") {
|
|
1733
|
-
|
|
1880
|
+
log6.warn({ port: this.config.port }, "API port in use, continuing without API server");
|
|
1734
1881
|
this.server = null;
|
|
1735
1882
|
resolve2();
|
|
1736
1883
|
} else {
|
|
@@ -1743,7 +1890,7 @@ var ApiServer = class {
|
|
|
1743
1890
|
this.actualPort = addr.port;
|
|
1744
1891
|
}
|
|
1745
1892
|
this.writePortFile();
|
|
1746
|
-
|
|
1893
|
+
log6.info({ host: this.config.host, port: this.actualPort }, "API server listening");
|
|
1747
1894
|
resolve2();
|
|
1748
1895
|
});
|
|
1749
1896
|
});
|
|
@@ -1799,6 +1946,8 @@ var ApiServer = class {
|
|
|
1799
1946
|
await this.handleHealth(res);
|
|
1800
1947
|
} else if (method === "GET" && url === "/api/version") {
|
|
1801
1948
|
await this.handleVersion(res);
|
|
1949
|
+
} else if (method === "GET" && url === "/api/config/editable") {
|
|
1950
|
+
await this.handleGetEditableConfig(res);
|
|
1802
1951
|
} else if (method === "GET" && url === "/api/config") {
|
|
1803
1952
|
await this.handleGetConfig(res);
|
|
1804
1953
|
} else if (method === "PATCH" && url === "/api/config") {
|
|
@@ -1822,7 +1971,7 @@ var ApiServer = class {
|
|
|
1822
1971
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1823
1972
|
}
|
|
1824
1973
|
} catch (err) {
|
|
1825
|
-
|
|
1974
|
+
log6.error({ err }, "API request error");
|
|
1826
1975
|
this.sendJson(res, 500, { error: "Internal server error" });
|
|
1827
1976
|
}
|
|
1828
1977
|
}
|
|
@@ -1850,24 +1999,25 @@ var ApiServer = class {
|
|
|
1850
1999
|
}
|
|
1851
2000
|
const [adapterId, adapter] = this.core.adapters.entries().next().value ?? [null, null];
|
|
1852
2001
|
const channelId = adapterId ?? "api";
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2002
|
+
const resolvedAgent = agent || config.defaultAgent;
|
|
2003
|
+
const resolvedWorkspace = this.core.configManager.resolveWorkspace(
|
|
2004
|
+
workspace || config.agents[resolvedAgent]?.workingDirectory
|
|
2005
|
+
);
|
|
2006
|
+
const session = await this.core.createSession({
|
|
2007
|
+
channelId,
|
|
2008
|
+
agentName: resolvedAgent,
|
|
2009
|
+
workingDirectory: resolvedWorkspace,
|
|
2010
|
+
createThread: !!adapter,
|
|
2011
|
+
initialName: `\u{1F504} ${resolvedAgent} \u2014 New Session`
|
|
2012
|
+
});
|
|
1863
2013
|
if (!adapter) {
|
|
1864
2014
|
session.agentInstance.onPermissionRequest = async (request) => {
|
|
1865
2015
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
1866
|
-
|
|
2016
|
+
log6.debug({ sessionId: session.id, permissionId: request.id, option: allowOption?.id }, "Auto-approving permission for API session");
|
|
1867
2017
|
return allowOption?.id ?? request.options[0]?.id ?? "";
|
|
1868
2018
|
};
|
|
1869
2019
|
}
|
|
1870
|
-
session.warmup().catch((err) =>
|
|
2020
|
+
session.warmup().catch((err) => log6.warn({ err, sessionId: session.id }, "API session warmup failed"));
|
|
1871
2021
|
this.sendJson(res, 200, {
|
|
1872
2022
|
sessionId: session.id,
|
|
1873
2023
|
agent: session.agentName,
|
|
@@ -1949,7 +2099,7 @@ var ApiServer = class {
|
|
|
1949
2099
|
return;
|
|
1950
2100
|
}
|
|
1951
2101
|
session.dangerousMode = enabled;
|
|
1952
|
-
await this.core.sessionManager.
|
|
2102
|
+
await this.core.sessionManager.patchRecord(sessionId, { dangerousMode: enabled });
|
|
1953
2103
|
this.sendJson(res, 200, { ok: true, dangerousMode: enabled });
|
|
1954
2104
|
}
|
|
1955
2105
|
async handleHealth(res) {
|
|
@@ -1977,6 +2127,21 @@ var ApiServer = class {
|
|
|
1977
2127
|
async handleVersion(res) {
|
|
1978
2128
|
this.sendJson(res, 200, { version: getVersion() });
|
|
1979
2129
|
}
|
|
2130
|
+
async handleGetEditableConfig(res) {
|
|
2131
|
+
const { getSafeFields: getSafeFields2, resolveOptions: resolveOptions2, getConfigValue: getConfigValue2 } = await import("./config-registry-OGX4YM2U.js");
|
|
2132
|
+
const config = this.core.configManager.get();
|
|
2133
|
+
const safeFields = getSafeFields2();
|
|
2134
|
+
const fields = safeFields.map((def) => ({
|
|
2135
|
+
path: def.path,
|
|
2136
|
+
displayName: def.displayName,
|
|
2137
|
+
group: def.group,
|
|
2138
|
+
type: def.type,
|
|
2139
|
+
options: resolveOptions2(def, config),
|
|
2140
|
+
value: getConfigValue2(config, def.path),
|
|
2141
|
+
hotReload: def.hotReload
|
|
2142
|
+
}));
|
|
2143
|
+
this.sendJson(res, 200, { fields });
|
|
2144
|
+
}
|
|
1980
2145
|
async handleGetConfig(res) {
|
|
1981
2146
|
const config = this.core.configManager.get();
|
|
1982
2147
|
this.sendJson(res, 200, { config: redactConfig(config) });
|
|
@@ -2017,7 +2182,7 @@ var ApiServer = class {
|
|
|
2017
2182
|
return;
|
|
2018
2183
|
}
|
|
2019
2184
|
target[lastKey] = value;
|
|
2020
|
-
const { ConfigSchema } = await import("./config-
|
|
2185
|
+
const { ConfigSchema } = await import("./config-XURP6B3S.js");
|
|
2021
2186
|
const result = ConfigSchema.safeParse(cloned);
|
|
2022
2187
|
if (!result.success) {
|
|
2023
2188
|
this.sendJson(res, 400, {
|
|
@@ -2033,12 +2198,9 @@ var ApiServer = class {
|
|
|
2033
2198
|
updateTarget = updateTarget[parts[i]];
|
|
2034
2199
|
}
|
|
2035
2200
|
updateTarget[lastKey] = value;
|
|
2036
|
-
await this.core.configManager.save(updates);
|
|
2037
|
-
const
|
|
2038
|
-
const needsRestart =
|
|
2039
|
-
(prefix) => configPath.startsWith(prefix) || configPath === prefix.replace(/\.$/, "")
|
|
2040
|
-
// exact match for non-wildcard
|
|
2041
|
-
);
|
|
2201
|
+
await this.core.configManager.save(updates, configPath);
|
|
2202
|
+
const { isHotReloadable: isHotReloadable2 } = await import("./config-registry-OGX4YM2U.js");
|
|
2203
|
+
const needsRestart = !isHotReloadable2(configPath);
|
|
2042
2204
|
this.sendJson(res, 200, {
|
|
2043
2205
|
ok: true,
|
|
2044
2206
|
needsRestart,
|
|
@@ -2097,7 +2259,7 @@ var ApiServer = class {
|
|
|
2097
2259
|
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
2098
2260
|
return;
|
|
2099
2261
|
}
|
|
2100
|
-
await session.
|
|
2262
|
+
await session.abortPrompt();
|
|
2101
2263
|
this.sendJson(res, 200, { ok: true });
|
|
2102
2264
|
}
|
|
2103
2265
|
async handleListSessions(res) {
|
|
@@ -2206,7 +2368,7 @@ var ApiServer = class {
|
|
|
2206
2368
|
};
|
|
2207
2369
|
|
|
2208
2370
|
// src/core/topic-manager.ts
|
|
2209
|
-
var
|
|
2371
|
+
var log7 = createChildLogger({ module: "topic-manager" });
|
|
2210
2372
|
var TopicManager = class {
|
|
2211
2373
|
constructor(sessionManager, adapter, systemTopicIds) {
|
|
2212
2374
|
this.sessionManager = sessionManager;
|
|
@@ -2245,7 +2407,7 @@ var TopicManager = class {
|
|
|
2245
2407
|
try {
|
|
2246
2408
|
await this.adapter.deleteSessionThread(sessionId);
|
|
2247
2409
|
} catch (err) {
|
|
2248
|
-
|
|
2410
|
+
log7.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
|
|
2249
2411
|
}
|
|
2250
2412
|
}
|
|
2251
2413
|
await this.sessionManager.removeRecord(sessionId);
|
|
@@ -2268,7 +2430,7 @@ var TopicManager = class {
|
|
|
2268
2430
|
try {
|
|
2269
2431
|
await this.adapter.deleteSessionThread(record.sessionId);
|
|
2270
2432
|
} catch (err) {
|
|
2271
|
-
|
|
2433
|
+
log7.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
|
|
2272
2434
|
}
|
|
2273
2435
|
}
|
|
2274
2436
|
await this.sessionManager.removeRecord(record.sessionId);
|
|
@@ -2289,497 +2451,223 @@ var TopicManager = class {
|
|
|
2289
2451
|
// src/adapters/telegram/adapter.ts
|
|
2290
2452
|
import { Bot } from "grammy";
|
|
2291
2453
|
|
|
2292
|
-
// src/adapters/telegram/
|
|
2293
|
-
function
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
let text = md.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
|
|
2301
|
-
const index = codeBlocks.length;
|
|
2302
|
-
const escapedCode = escapeHtml(code);
|
|
2303
|
-
const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : "";
|
|
2304
|
-
codeBlocks.push(`<pre><code${langAttr}>${escapedCode}</code></pre>`);
|
|
2305
|
-
return `\0CODE_BLOCK_${index}\0`;
|
|
2306
|
-
});
|
|
2307
|
-
text = text.replace(/`([^`]+)`/g, (_match, code) => {
|
|
2308
|
-
const index = inlineCodes.length;
|
|
2309
|
-
inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
|
|
2310
|
-
return `\0INLINE_CODE_${index}\0`;
|
|
2311
|
-
});
|
|
2312
|
-
text = escapeHtml(text);
|
|
2313
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
2314
|
-
text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<i>$1</i>");
|
|
2315
|
-
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
2316
|
-
text = text.replace(/\x00CODE_BLOCK_(\d+)\x00/g, (_match, idx) => {
|
|
2317
|
-
return codeBlocks[parseInt(idx, 10)];
|
|
2318
|
-
});
|
|
2319
|
-
text = text.replace(/\x00INLINE_CODE_(\d+)\x00/g, (_match, idx) => {
|
|
2320
|
-
return inlineCodes[parseInt(idx, 10)];
|
|
2321
|
-
});
|
|
2322
|
-
return text;
|
|
2323
|
-
}
|
|
2324
|
-
var STATUS_ICON = {
|
|
2325
|
-
pending: "\u23F3",
|
|
2326
|
-
in_progress: "\u{1F504}",
|
|
2327
|
-
completed: "\u2705",
|
|
2328
|
-
failed: "\u274C"
|
|
2329
|
-
};
|
|
2330
|
-
var KIND_ICON = {
|
|
2331
|
-
read: "\u{1F4D6}",
|
|
2332
|
-
edit: "\u270F\uFE0F",
|
|
2333
|
-
delete: "\u{1F5D1}\uFE0F",
|
|
2334
|
-
execute: "\u25B6\uFE0F",
|
|
2335
|
-
search: "\u{1F50D}",
|
|
2336
|
-
fetch: "\u{1F310}",
|
|
2337
|
-
think: "\u{1F9E0}",
|
|
2338
|
-
move: "\u{1F4E6}",
|
|
2339
|
-
other: "\u{1F6E0}\uFE0F"
|
|
2340
|
-
};
|
|
2341
|
-
function extractContentText(content, depth = 0) {
|
|
2342
|
-
if (!content || depth > 5) return "";
|
|
2343
|
-
if (typeof content === "string") return content;
|
|
2344
|
-
if (Array.isArray(content)) {
|
|
2345
|
-
return content.map((c) => extractContentText(c, depth + 1)).filter(Boolean).join("\n");
|
|
2346
|
-
}
|
|
2347
|
-
if (typeof content === "object" && content !== null) {
|
|
2348
|
-
const c = content;
|
|
2349
|
-
if (c.type === "text" && typeof c.text === "string") return c.text;
|
|
2350
|
-
if (typeof c.text === "string") return c.text;
|
|
2351
|
-
if (typeof c.content === "string") return c.content;
|
|
2352
|
-
if (c.content && typeof c.content === "object") return extractContentText(c.content, depth + 1);
|
|
2353
|
-
if (c.input) return extractContentText(c.input, depth + 1);
|
|
2354
|
-
if (c.output) return extractContentText(c.output, depth + 1);
|
|
2355
|
-
const keys = Object.keys(c).filter((k) => k !== "type");
|
|
2356
|
-
if (keys.length === 0) return "";
|
|
2357
|
-
return JSON.stringify(c, null, 2);
|
|
2358
|
-
}
|
|
2359
|
-
return String(content);
|
|
2360
|
-
}
|
|
2361
|
-
function truncateContent(text, maxLen = 3800) {
|
|
2362
|
-
if (text.length <= maxLen) return text;
|
|
2363
|
-
return text.slice(0, maxLen) + "\n\u2026 (truncated)";
|
|
2364
|
-
}
|
|
2365
|
-
function formatToolCall(tool) {
|
|
2366
|
-
const si = STATUS_ICON[tool.status || ""] || "\u{1F527}";
|
|
2367
|
-
const ki = KIND_ICON[tool.kind || ""] || "\u{1F6E0}\uFE0F";
|
|
2368
|
-
let text = `${si} ${ki} <b>${escapeHtml(tool.name || "Tool")}</b>`;
|
|
2369
|
-
text += formatViewerLinks(tool.viewerLinks, tool.viewerFilePath);
|
|
2370
|
-
if (!tool.viewerLinks) {
|
|
2371
|
-
const details = extractContentText(tool.content);
|
|
2372
|
-
if (details) {
|
|
2373
|
-
text += `
|
|
2374
|
-
<pre>${escapeHtml(truncateContent(details))}</pre>`;
|
|
2375
|
-
}
|
|
2454
|
+
// src/adapters/telegram/topics.ts
|
|
2455
|
+
async function ensureTopics(bot, chatId, config, saveConfig) {
|
|
2456
|
+
let notificationTopicId = config.notificationTopicId;
|
|
2457
|
+
let assistantTopicId = config.assistantTopicId;
|
|
2458
|
+
if (notificationTopicId === null) {
|
|
2459
|
+
const topic = await bot.api.createForumTopic(chatId, "\u{1F4CB} Notifications");
|
|
2460
|
+
notificationTopicId = topic.message_thread_id;
|
|
2461
|
+
await saveConfig({ notificationTopicId });
|
|
2376
2462
|
}
|
|
2377
|
-
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
const ki = KIND_ICON[update.kind || ""] || "\u{1F6E0}\uFE0F";
|
|
2382
|
-
const name = update.name || "Tool";
|
|
2383
|
-
let text = `${si} ${ki} <b>${escapeHtml(name)}</b>`;
|
|
2384
|
-
text += formatViewerLinks(update.viewerLinks, update.viewerFilePath);
|
|
2385
|
-
if (!update.viewerLinks) {
|
|
2386
|
-
const details = extractContentText(update.content);
|
|
2387
|
-
if (details) {
|
|
2388
|
-
text += `
|
|
2389
|
-
<pre>${escapeHtml(truncateContent(details))}</pre>`;
|
|
2390
|
-
}
|
|
2463
|
+
if (assistantTopicId === null) {
|
|
2464
|
+
const topic = await bot.api.createForumTopic(chatId, "\u{1F916} Assistant");
|
|
2465
|
+
assistantTopicId = topic.message_thread_id;
|
|
2466
|
+
await saveConfig({ assistantTopicId });
|
|
2391
2467
|
}
|
|
2392
|
-
return
|
|
2393
|
-
}
|
|
2394
|
-
function formatViewerLinks(links, filePath) {
|
|
2395
|
-
if (!links) return "";
|
|
2396
|
-
const fileName = filePath ? filePath.split("/").pop() || filePath : "";
|
|
2397
|
-
let text = "\n";
|
|
2398
|
-
if (links.file) text += `
|
|
2399
|
-
\u{1F4C4} <a href="${escapeHtml(links.file)}">View ${escapeHtml(fileName || "file")}</a>`;
|
|
2400
|
-
if (links.diff) text += `
|
|
2401
|
-
\u{1F4DD} <a href="${escapeHtml(links.diff)}">View diff${fileName ? ` \u2014 ${escapeHtml(fileName)}` : ""}</a>`;
|
|
2402
|
-
return text;
|
|
2403
|
-
}
|
|
2404
|
-
function formatTokens(n) {
|
|
2405
|
-
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
2406
|
-
}
|
|
2407
|
-
function progressBar(ratio) {
|
|
2408
|
-
const filled = Math.round(Math.min(ratio, 1) * 10);
|
|
2409
|
-
return "\u2593".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
2468
|
+
return { notificationTopicId, assistantTopicId };
|
|
2410
2469
|
}
|
|
2411
|
-
function
|
|
2412
|
-
const
|
|
2413
|
-
|
|
2414
|
-
if (contextSize == null) return `\u{1F4CA} ${formatTokens(tokensUsed)} tokens`;
|
|
2415
|
-
const ratio = tokensUsed / contextSize;
|
|
2416
|
-
const pct = Math.round(ratio * 100);
|
|
2417
|
-
const bar = progressBar(ratio);
|
|
2418
|
-
const emoji = pct >= 85 ? "\u26A0\uFE0F" : "\u{1F4CA}";
|
|
2419
|
-
return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens
|
|
2420
|
-
${bar} ${pct}%`;
|
|
2470
|
+
async function createSessionTopic(bot, chatId, name) {
|
|
2471
|
+
const topic = await bot.api.createForumTopic(chatId, name);
|
|
2472
|
+
return topic.message_thread_id;
|
|
2421
2473
|
}
|
|
2422
|
-
function
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
while (remaining.length > 0) {
|
|
2427
|
-
if (remaining.length <= maxLength) {
|
|
2428
|
-
chunks.push(remaining);
|
|
2429
|
-
break;
|
|
2430
|
-
}
|
|
2431
|
-
let splitAt = remaining.lastIndexOf("\n\n", maxLength);
|
|
2432
|
-
if (splitAt === -1 || splitAt < maxLength * 0.2) {
|
|
2433
|
-
splitAt = remaining.lastIndexOf("\n", maxLength);
|
|
2434
|
-
}
|
|
2435
|
-
if (splitAt === -1 || splitAt < maxLength * 0.2) {
|
|
2436
|
-
splitAt = maxLength;
|
|
2437
|
-
}
|
|
2438
|
-
const candidate = remaining.slice(0, splitAt);
|
|
2439
|
-
const fences = candidate.match(/```/g);
|
|
2440
|
-
if (fences && fences.length % 2 !== 0) {
|
|
2441
|
-
const closingFence = remaining.indexOf("```", splitAt);
|
|
2442
|
-
if (closingFence !== -1) {
|
|
2443
|
-
const afterFence = remaining.indexOf("\n", closingFence + 3);
|
|
2444
|
-
splitAt = afterFence !== -1 ? afterFence + 1 : closingFence + 3;
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
chunks.push(remaining.slice(0, splitAt));
|
|
2448
|
-
remaining = remaining.slice(splitAt).replace(/^\n+/, "");
|
|
2474
|
+
async function renameSessionTopic(bot, chatId, threadId, name) {
|
|
2475
|
+
try {
|
|
2476
|
+
await bot.api.editForumTopic(chatId, threadId, { name });
|
|
2477
|
+
} catch {
|
|
2449
2478
|
}
|
|
2450
|
-
|
|
2479
|
+
}
|
|
2480
|
+
function buildDeepLink(chatId, messageId) {
|
|
2481
|
+
const cleanId = String(chatId).replace("-100", "");
|
|
2482
|
+
return `https://t.me/c/${cleanId}/${messageId}`;
|
|
2451
2483
|
}
|
|
2452
2484
|
|
|
2453
|
-
// src/adapters/telegram/
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
}
|
|
2474
|
-
scheduleFlush() {
|
|
2475
|
-
if (this.flushTimer) return;
|
|
2476
|
-
this.flushTimer = setTimeout(() => {
|
|
2477
|
-
this.flushTimer = void 0;
|
|
2478
|
-
this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
|
|
2485
|
+
// src/adapters/telegram/commands/new-session.ts
|
|
2486
|
+
import { InlineKeyboard as InlineKeyboard2 } from "grammy";
|
|
2487
|
+
|
|
2488
|
+
// src/adapters/telegram/commands/admin.ts
|
|
2489
|
+
import { InlineKeyboard } from "grammy";
|
|
2490
|
+
var log9 = createChildLogger({ module: "telegram-cmd-admin" });
|
|
2491
|
+
function buildDangerousModeKeyboard(sessionId, enabled) {
|
|
2492
|
+
return new InlineKeyboard().text(
|
|
2493
|
+
enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
|
|
2494
|
+
`d:${sessionId}`
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
function setupDangerousModeCallbacks(bot, core) {
|
|
2498
|
+
bot.callbackQuery(/^d:/, async (ctx) => {
|
|
2499
|
+
const sessionId = ctx.callbackQuery.data.slice(2);
|
|
2500
|
+
const session = core.sessionManager.getSession(sessionId);
|
|
2501
|
+
if (session) {
|
|
2502
|
+
session.dangerousMode = !session.dangerousMode;
|
|
2503
|
+
log9.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
|
|
2504
|
+
core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {
|
|
2479
2505
|
});
|
|
2480
|
-
|
|
2481
|
-
}
|
|
2482
|
-
async flush() {
|
|
2483
|
-
if (!this.buffer) return;
|
|
2484
|
-
if (this.firstFlushPending) return;
|
|
2485
|
-
let displayBuffer = this.buffer;
|
|
2486
|
-
if (displayBuffer.length > 3800) {
|
|
2487
|
-
let cutAt = displayBuffer.lastIndexOf("\n", 3800);
|
|
2488
|
-
if (cutAt < 800) cutAt = 3800;
|
|
2489
|
-
displayBuffer = displayBuffer.slice(0, cutAt) + "\n\u2026";
|
|
2490
|
-
}
|
|
2491
|
-
let html = markdownToTelegramHtml(displayBuffer);
|
|
2492
|
-
if (!html) return;
|
|
2493
|
-
if (html.length > 4096) {
|
|
2494
|
-
html = html.slice(0, 4090) + "\n\u2026";
|
|
2495
|
-
}
|
|
2496
|
-
if (!this.messageId) {
|
|
2497
|
-
this.firstFlushPending = true;
|
|
2506
|
+
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2498
2507
|
try {
|
|
2499
|
-
|
|
2500
|
-
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
2501
|
-
message_thread_id: this.threadId,
|
|
2502
|
-
parse_mode: "HTML",
|
|
2503
|
-
disable_notification: true
|
|
2504
|
-
}),
|
|
2505
|
-
{ type: "other" }
|
|
2506
|
-
);
|
|
2507
|
-
if (result) {
|
|
2508
|
-
this.messageId = result.message_id;
|
|
2509
|
-
this.lastSentBuffer = this.buffer;
|
|
2510
|
-
}
|
|
2508
|
+
await ctx.answerCallbackQuery({ text: toastText2 });
|
|
2511
2509
|
} catch {
|
|
2512
|
-
} finally {
|
|
2513
|
-
this.firstFlushPending = false;
|
|
2514
2510
|
}
|
|
2515
|
-
} else {
|
|
2516
2511
|
try {
|
|
2517
|
-
await
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
}),
|
|
2521
|
-
{ type: "text", key: this.sessionId }
|
|
2522
|
-
);
|
|
2523
|
-
this.lastSentBuffer = this.buffer;
|
|
2512
|
+
await ctx.editMessageReplyMarkup({
|
|
2513
|
+
reply_markup: buildDangerousModeKeyboard(sessionId, session.dangerousMode)
|
|
2514
|
+
});
|
|
2524
2515
|
} catch {
|
|
2525
2516
|
}
|
|
2517
|
+
return;
|
|
2526
2518
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
if (this.flushTimer) {
|
|
2530
|
-
clearTimeout(this.flushTimer);
|
|
2531
|
-
this.flushTimer = void 0;
|
|
2532
|
-
}
|
|
2533
|
-
await this.flushPromise;
|
|
2534
|
-
if (!this.buffer) return this.messageId;
|
|
2535
|
-
if (this.messageId && this.buffer === this.lastSentBuffer) {
|
|
2536
|
-
return this.messageId;
|
|
2537
|
-
}
|
|
2538
|
-
const mdChunks = splitMessage(this.buffer);
|
|
2539
|
-
for (let i = 0; i < mdChunks.length; i++) {
|
|
2540
|
-
const html = markdownToTelegramHtml(mdChunks[i]);
|
|
2519
|
+
const record = core.sessionManager.getSessionRecord(sessionId);
|
|
2520
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
2541
2521
|
try {
|
|
2542
|
-
|
|
2543
|
-
await this.sendQueue.enqueue(
|
|
2544
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
2545
|
-
parse_mode: "HTML"
|
|
2546
|
-
}),
|
|
2547
|
-
{ type: "other" }
|
|
2548
|
-
);
|
|
2549
|
-
} else {
|
|
2550
|
-
const msg = await this.sendQueue.enqueue(
|
|
2551
|
-
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
2552
|
-
message_thread_id: this.threadId,
|
|
2553
|
-
parse_mode: "HTML",
|
|
2554
|
-
disable_notification: true
|
|
2555
|
-
}),
|
|
2556
|
-
{ type: "other" }
|
|
2557
|
-
);
|
|
2558
|
-
if (msg) {
|
|
2559
|
-
this.messageId = msg.message_id;
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2522
|
+
await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
|
|
2562
2523
|
} catch {
|
|
2563
|
-
try {
|
|
2564
|
-
if (i === 0 && this.messageId) {
|
|
2565
|
-
await this.sendQueue.enqueue(
|
|
2566
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId, mdChunks[i].slice(0, 4096)),
|
|
2567
|
-
{ type: "other" }
|
|
2568
|
-
);
|
|
2569
|
-
} else {
|
|
2570
|
-
const msg = await this.sendQueue.enqueue(
|
|
2571
|
-
() => this.bot.api.sendMessage(this.chatId, mdChunks[i].slice(0, 4096), {
|
|
2572
|
-
message_thread_id: this.threadId,
|
|
2573
|
-
disable_notification: true
|
|
2574
|
-
}),
|
|
2575
|
-
{ type: "other" }
|
|
2576
|
-
);
|
|
2577
|
-
if (msg) {
|
|
2578
|
-
this.messageId = msg.message_id;
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
} catch {
|
|
2582
|
-
}
|
|
2583
2524
|
}
|
|
2525
|
+
return;
|
|
2584
2526
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
};
|
|
2591
|
-
|
|
2592
|
-
// src/adapters/telegram/topics.ts
|
|
2593
|
-
async function ensureTopics(bot, chatId, config, saveConfig) {
|
|
2594
|
-
let notificationTopicId = config.notificationTopicId;
|
|
2595
|
-
let assistantTopicId = config.assistantTopicId;
|
|
2596
|
-
if (notificationTopicId === null) {
|
|
2597
|
-
const topic = await bot.api.createForumTopic(chatId, "\u{1F4CB} Notifications");
|
|
2598
|
-
notificationTopicId = topic.message_thread_id;
|
|
2599
|
-
await saveConfig({ notificationTopicId });
|
|
2600
|
-
}
|
|
2601
|
-
if (assistantTopicId === null) {
|
|
2602
|
-
const topic = await bot.api.createForumTopic(chatId, "\u{1F916} Assistant");
|
|
2603
|
-
assistantTopicId = topic.message_thread_id;
|
|
2604
|
-
await saveConfig({ assistantTopicId });
|
|
2605
|
-
}
|
|
2606
|
-
return { notificationTopicId, assistantTopicId };
|
|
2607
|
-
}
|
|
2608
|
-
async function createSessionTopic(bot, chatId, name) {
|
|
2609
|
-
const topic = await bot.api.createForumTopic(chatId, name);
|
|
2610
|
-
return topic.message_thread_id;
|
|
2611
|
-
}
|
|
2612
|
-
async function renameSessionTopic(bot, chatId, threadId, name) {
|
|
2613
|
-
try {
|
|
2614
|
-
await bot.api.editForumTopic(chatId, threadId, { name });
|
|
2615
|
-
} catch {
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
function buildDeepLink(chatId, messageId) {
|
|
2619
|
-
const cleanId = String(chatId).replace("-100", "");
|
|
2620
|
-
return `https://t.me/c/${cleanId}/${messageId}`;
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
// src/adapters/telegram/commands.ts
|
|
2624
|
-
import { InlineKeyboard } from "grammy";
|
|
2625
|
-
var log8 = createChildLogger({ module: "telegram-commands" });
|
|
2626
|
-
var pendingNewSessions = /* @__PURE__ */ new Map();
|
|
2627
|
-
var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2628
|
-
function cleanupPending(userId) {
|
|
2629
|
-
const pending = pendingNewSessions.get(userId);
|
|
2630
|
-
if (pending) {
|
|
2631
|
-
clearTimeout(pending.timer);
|
|
2632
|
-
pendingNewSessions.delete(userId);
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
function setupCommands(bot, core, chatId, assistant) {
|
|
2636
|
-
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
2637
|
-
bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
|
|
2638
|
-
bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
|
|
2639
|
-
bot.command("status", (ctx) => handleStatus(ctx, core));
|
|
2640
|
-
bot.command("sessions", (ctx) => handleTopics(ctx, core));
|
|
2641
|
-
bot.command("agents", (ctx) => handleAgents(ctx, core));
|
|
2642
|
-
bot.command("help", (ctx) => handleHelp(ctx));
|
|
2643
|
-
bot.command("menu", (ctx) => handleMenu(ctx));
|
|
2644
|
-
bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
|
|
2645
|
-
bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
|
|
2646
|
-
bot.command("restart", (ctx) => handleRestart(ctx, core));
|
|
2647
|
-
bot.command("update", (ctx) => handleUpdate(ctx, core));
|
|
2648
|
-
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
2649
|
-
bot.command("clear", (ctx) => handleClear(ctx, assistant));
|
|
2650
|
-
}
|
|
2651
|
-
function buildMenuKeyboard() {
|
|
2652
|
-
return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4CB} Sessions", "m:topics").row().text("\u{1F4CA} Status", "m:status").text("\u{1F916} Agents", "m:agents").row().text("\u{1F517} Integrate", "m:integrate").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
|
|
2653
|
-
}
|
|
2654
|
-
function setupMenuCallbacks(bot, core, chatId, systemTopicIds) {
|
|
2655
|
-
bot.callbackQuery(/^m:/, async (ctx) => {
|
|
2656
|
-
const data = ctx.callbackQuery.data;
|
|
2527
|
+
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
2528
|
+
core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {
|
|
2529
|
+
});
|
|
2530
|
+
log9.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
|
|
2531
|
+
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2657
2532
|
try {
|
|
2658
|
-
await ctx.answerCallbackQuery();
|
|
2533
|
+
await ctx.answerCallbackQuery({ text: toastText });
|
|
2659
2534
|
} catch {
|
|
2660
2535
|
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2536
|
+
try {
|
|
2537
|
+
await ctx.editMessageReplyMarkup({
|
|
2538
|
+
reply_markup: buildDangerousModeKeyboard(sessionId, newDangerousMode)
|
|
2539
|
+
});
|
|
2540
|
+
} catch {
|
|
2541
|
+
}
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
async function handleEnableDangerous(ctx, core) {
|
|
2545
|
+
const threadId = ctx.message?.message_thread_id;
|
|
2546
|
+
if (!threadId) {
|
|
2547
|
+
await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
2551
|
+
if (session) {
|
|
2552
|
+
if (session.dangerousMode) {
|
|
2553
|
+
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
2665
2554
|
return;
|
|
2666
2555
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
await
|
|
2556
|
+
session.dangerousMode = true;
|
|
2557
|
+
core.sessionManager.patchRecord(session.id, { dangerousMode: true }).catch(() => {
|
|
2558
|
+
});
|
|
2559
|
+
} else {
|
|
2560
|
+
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
2561
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
2562
|
+
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
2674
2563
|
return;
|
|
2675
2564
|
}
|
|
2676
|
-
if (
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
`\u270F\uFE0F <b>Enter your project path:</b>
|
|
2565
|
+
if (record.dangerousMode) {
|
|
2566
|
+
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
core.sessionManager.patchRecord(record.sessionId, { dangerousMode: true }).catch(() => {
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
await ctx.reply(
|
|
2573
|
+
`\u26A0\uFE0F <b>Dangerous mode enabled</b>
|
|
2686
2574
|
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2575
|
+
All permission requests will be auto-approved. Claude can run arbitrary commands without asking.
|
|
2576
|
+
|
|
2577
|
+
Use /disable_dangerous to restore normal behaviour.`,
|
|
2578
|
+
{ parse_mode: "HTML" }
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
async function handleDisableDangerous(ctx, core) {
|
|
2582
|
+
const threadId = ctx.message?.message_thread_id;
|
|
2583
|
+
if (!threadId) {
|
|
2584
|
+
await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
2588
|
+
if (session) {
|
|
2589
|
+
if (!session.dangerousMode) {
|
|
2590
|
+
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
2700
2591
|
return;
|
|
2701
2592
|
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
try {
|
|
2710
|
-
await ctx.api.editMessageText(chatId, confirmMsgId, `\u23F3 Creating session...`, { parse_mode: "HTML" });
|
|
2711
|
-
} catch {
|
|
2712
|
-
}
|
|
2713
|
-
const resultThreadId = await createSessionDirect(ctx, core, chatId, pending.agentName, pending.workspace);
|
|
2714
|
-
try {
|
|
2715
|
-
if (resultThreadId) {
|
|
2716
|
-
const link = buildDeepLink(chatId, resultThreadId);
|
|
2717
|
-
await ctx.api.editMessageText(chatId, confirmMsgId, `\u2705 Session created \u2192 <a href="${link}">Open topic</a>`, { parse_mode: "HTML" });
|
|
2718
|
-
} else {
|
|
2719
|
-
await ctx.api.editMessageText(chatId, confirmMsgId, `\u274C Session creation failed.`, { parse_mode: "HTML" });
|
|
2720
|
-
}
|
|
2721
|
-
} catch {
|
|
2722
|
-
}
|
|
2593
|
+
session.dangerousMode = false;
|
|
2594
|
+
core.sessionManager.patchRecord(session.id, { dangerousMode: false }).catch(() => {
|
|
2595
|
+
});
|
|
2596
|
+
} else {
|
|
2597
|
+
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
2598
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
2599
|
+
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
2723
2600
|
return;
|
|
2724
2601
|
}
|
|
2725
|
-
if (
|
|
2726
|
-
|
|
2727
|
-
if (userId) cleanupPending(userId);
|
|
2728
|
-
try {
|
|
2729
|
-
await ctx.editMessageText("\u274C Session creation cancelled.", { parse_mode: "HTML" });
|
|
2730
|
-
} catch {
|
|
2731
|
-
}
|
|
2602
|
+
if (!record.dangerousMode) {
|
|
2603
|
+
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
2732
2604
|
return;
|
|
2733
2605
|
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
case "m:status":
|
|
2739
|
-
await handleStatus(ctx, core);
|
|
2740
|
-
break;
|
|
2741
|
-
case "m:agents":
|
|
2742
|
-
await handleAgents(ctx, core);
|
|
2743
|
-
break;
|
|
2744
|
-
case "m:help":
|
|
2745
|
-
await handleHelp(ctx);
|
|
2746
|
-
break;
|
|
2747
|
-
case "m:restart":
|
|
2748
|
-
await handleRestart(ctx, core);
|
|
2749
|
-
break;
|
|
2750
|
-
case "m:update":
|
|
2751
|
-
await handleUpdate(ctx, core);
|
|
2752
|
-
break;
|
|
2753
|
-
case "m:integrate":
|
|
2754
|
-
await handleIntegrate(ctx, core);
|
|
2755
|
-
break;
|
|
2756
|
-
case "m:topics":
|
|
2757
|
-
await handleTopics(ctx, core);
|
|
2758
|
-
break;
|
|
2759
|
-
case "m:cleanup:finished":
|
|
2760
|
-
await handleCleanup(ctx, core, chatId, ["finished"]);
|
|
2761
|
-
break;
|
|
2762
|
-
case "m:cleanup:errors":
|
|
2763
|
-
await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
|
|
2764
|
-
break;
|
|
2765
|
-
case "m:cleanup:all":
|
|
2766
|
-
await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
|
|
2767
|
-
break;
|
|
2768
|
-
case "m:cleanup:everything":
|
|
2769
|
-
await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
|
|
2770
|
-
break;
|
|
2771
|
-
case "m:cleanup:everything:confirm":
|
|
2772
|
-
await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
|
|
2773
|
-
break;
|
|
2774
|
-
}
|
|
2775
|
-
});
|
|
2606
|
+
core.sessionManager.patchRecord(record.sessionId, { dangerousMode: false }).catch(() => {
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
|
|
2776
2610
|
}
|
|
2777
|
-
async function
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
});
|
|
2611
|
+
async function handleUpdate(ctx, core) {
|
|
2612
|
+
if (!core.requestRestart) {
|
|
2613
|
+
await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
|
|
2617
|
+
const current = getCurrentVersion();
|
|
2618
|
+
const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
|
|
2619
|
+
const latest = await getLatestVersion();
|
|
2620
|
+
if (!latest) {
|
|
2621
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
if (compareVersions(current, latest) >= 0) {
|
|
2625
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
await ctx.api.editMessageText(
|
|
2629
|
+
ctx.chat.id,
|
|
2630
|
+
statusMsg.message_id,
|
|
2631
|
+
`\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
|
|
2632
|
+
{ parse_mode: "HTML" }
|
|
2633
|
+
);
|
|
2634
|
+
const ok = await runUpdate();
|
|
2635
|
+
if (!ok) {
|
|
2636
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
await ctx.api.editMessageText(
|
|
2640
|
+
ctx.chat.id,
|
|
2641
|
+
statusMsg.message_id,
|
|
2642
|
+
`\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
|
|
2643
|
+
{ parse_mode: "HTML" }
|
|
2644
|
+
);
|
|
2645
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2646
|
+
await core.requestRestart();
|
|
2647
|
+
}
|
|
2648
|
+
async function handleRestart(ctx, core) {
|
|
2649
|
+
if (!core.requestRestart) {
|
|
2650
|
+
await ctx.reply("\u26A0\uFE0F Restart is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
await ctx.reply("\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.", { parse_mode: "HTML" });
|
|
2654
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2655
|
+
await core.requestRestart();
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// src/adapters/telegram/commands/new-session.ts
|
|
2659
|
+
var log10 = createChildLogger({ module: "telegram-cmd-new-session" });
|
|
2660
|
+
var pendingNewSessions = /* @__PURE__ */ new Map();
|
|
2661
|
+
var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2662
|
+
function cleanupPending(userId) {
|
|
2663
|
+
const pending = pendingNewSessions.get(userId);
|
|
2664
|
+
if (pending) {
|
|
2665
|
+
clearTimeout(pending.timer);
|
|
2666
|
+
pendingNewSessions.delete(userId);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
function botFromCtx(ctx) {
|
|
2670
|
+
return { api: ctx.api };
|
|
2783
2671
|
}
|
|
2784
2672
|
async function handleNew(ctx, core, chatId, assistant) {
|
|
2785
2673
|
const rawMatch = ctx.match;
|
|
@@ -2809,11 +2697,12 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2809
2697
|
await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
|
|
2810
2698
|
return;
|
|
2811
2699
|
}
|
|
2812
|
-
const keyboard = new
|
|
2700
|
+
const keyboard = new InlineKeyboard2();
|
|
2813
2701
|
for (const agent of agents) {
|
|
2814
2702
|
const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
|
|
2815
2703
|
keyboard.text(label, `m:new:agent:${agent.name}`).row();
|
|
2816
2704
|
}
|
|
2705
|
+
keyboard.text("\u274C Cancel", "m:new:cancel");
|
|
2817
2706
|
const msg = await ctx.reply(
|
|
2818
2707
|
`\u{1F916} <b>Choose an agent:</b>`,
|
|
2819
2708
|
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
@@ -2829,7 +2718,7 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2829
2718
|
async function startWorkspaceStep(ctx, core, chatId, userId, agentName) {
|
|
2830
2719
|
const config = core.configManager.get();
|
|
2831
2720
|
const baseDir = config.workspace.baseDir;
|
|
2832
|
-
const keyboard = new
|
|
2721
|
+
const keyboard = new InlineKeyboard2().text(`\u{1F4C1} Use ${baseDir}`, "m:new:ws:default").row().text("\u270F\uFE0F Enter project path", "m:new:ws:custom").row().text("\u274C Cancel", "m:new:cancel");
|
|
2833
2722
|
const text = `\u{1F4C1} <b>Where should ${escapeHtml(agentName)} work?</b>
|
|
2834
2723
|
|
|
2835
2724
|
Enter the path to your project folder \u2014 the agent will read, write, and run code there.
|
|
@@ -2860,7 +2749,7 @@ Or use the default directory below:`;
|
|
|
2860
2749
|
});
|
|
2861
2750
|
}
|
|
2862
2751
|
async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
|
|
2863
|
-
const keyboard = new
|
|
2752
|
+
const keyboard = new InlineKeyboard2().text("\u2705 Create", "m:new:confirm").text("\u274C Cancel", "m:new:cancel");
|
|
2864
2753
|
const text = `\u2705 <b>Ready to create session?</b>
|
|
2865
2754
|
|
|
2866
2755
|
<b>Agent:</b> ${escapeHtml(agentName)}
|
|
@@ -2891,7 +2780,7 @@ async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
|
|
|
2891
2780
|
});
|
|
2892
2781
|
}
|
|
2893
2782
|
async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
|
|
2894
|
-
|
|
2783
|
+
log10.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
|
|
2895
2784
|
let threadId;
|
|
2896
2785
|
try {
|
|
2897
2786
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -2902,7 +2791,7 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
|
|
|
2902
2791
|
});
|
|
2903
2792
|
const session = await core.handleNewSession("telegram", agentName, workspace);
|
|
2904
2793
|
session.threadId = String(threadId);
|
|
2905
|
-
await core.sessionManager.
|
|
2794
|
+
await core.sessionManager.patchRecord(session.id, { platform: { topicId: threadId } });
|
|
2906
2795
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
2907
2796
|
try {
|
|
2908
2797
|
await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
|
|
@@ -2921,10 +2810,10 @@ This is your coding session \u2014 chat here to work with the agent.`,
|
|
|
2921
2810
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2922
2811
|
}
|
|
2923
2812
|
);
|
|
2924
|
-
session.warmup().catch((err) =>
|
|
2813
|
+
session.warmup().catch((err) => log10.error({ err }, "Warm-up error"));
|
|
2925
2814
|
return threadId ?? null;
|
|
2926
2815
|
} catch (err) {
|
|
2927
|
-
|
|
2816
|
+
log10.error({ err }, "Session creation failed");
|
|
2928
2817
|
if (threadId) {
|
|
2929
2818
|
try {
|
|
2930
2819
|
await ctx.api.deleteForumTopic(chatId, threadId);
|
|
@@ -2988,9 +2877,9 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
2988
2877
|
workspace
|
|
2989
2878
|
);
|
|
2990
2879
|
session.threadId = String(newThreadId);
|
|
2991
|
-
await core.sessionManager.
|
|
2880
|
+
await core.sessionManager.patchRecord(session.id, { platform: {
|
|
2992
2881
|
topicId: newThreadId
|
|
2993
|
-
});
|
|
2882
|
+
} });
|
|
2994
2883
|
await ctx.api.sendMessage(
|
|
2995
2884
|
chatId,
|
|
2996
2885
|
`\u2705 New chat (same agent & workspace)
|
|
@@ -3001,19 +2890,182 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
3001
2890
|
parse_mode: "HTML",
|
|
3002
2891
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
3003
2892
|
}
|
|
3004
|
-
);
|
|
3005
|
-
session.warmup().catch((err) =>
|
|
3006
|
-
} catch (err) {
|
|
3007
|
-
if (newThreadId) {
|
|
2893
|
+
);
|
|
2894
|
+
session.warmup().catch((err) => log10.error({ err }, "Warm-up error"));
|
|
2895
|
+
} catch (err) {
|
|
2896
|
+
if (newThreadId) {
|
|
2897
|
+
try {
|
|
2898
|
+
await ctx.api.deleteForumTopic(chatId, newThreadId);
|
|
2899
|
+
} catch {
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2903
|
+
await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
async function executeNewSession(bot, core, chatId, agentName, workspace) {
|
|
2907
|
+
const threadId = await createSessionTopic(bot, chatId, "\u{1F504} New Session");
|
|
2908
|
+
const setupMsg = await bot.api.sendMessage(chatId, "\u23F3 Setting up session, please wait...", {
|
|
2909
|
+
message_thread_id: threadId,
|
|
2910
|
+
parse_mode: "HTML"
|
|
2911
|
+
});
|
|
2912
|
+
const firstMsgId = setupMsg.message_id;
|
|
2913
|
+
try {
|
|
2914
|
+
const session = await core.handleNewSession(
|
|
2915
|
+
"telegram",
|
|
2916
|
+
agentName,
|
|
2917
|
+
workspace
|
|
2918
|
+
);
|
|
2919
|
+
session.threadId = String(threadId);
|
|
2920
|
+
await core.sessionManager.patchRecord(session.id, { platform: {
|
|
2921
|
+
topicId: threadId
|
|
2922
|
+
} });
|
|
2923
|
+
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
2924
|
+
await renameSessionTopic(bot, chatId, threadId, finalName);
|
|
2925
|
+
session.warmup().catch((err) => log10.error({ err }, "Warm-up error"));
|
|
2926
|
+
return { session, threadId, firstMsgId };
|
|
2927
|
+
} catch (err) {
|
|
2928
|
+
try {
|
|
2929
|
+
await bot.api.deleteForumTopic(chatId, threadId);
|
|
2930
|
+
} catch {
|
|
2931
|
+
}
|
|
2932
|
+
throw err;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
async function handlePendingWorkspaceInput(ctx, core, chatId, assistantTopicId) {
|
|
2936
|
+
const userId = ctx.from?.id;
|
|
2937
|
+
if (!userId) return false;
|
|
2938
|
+
const pending = pendingNewSessions.get(userId);
|
|
2939
|
+
if (!pending || !ctx.message?.text) return false;
|
|
2940
|
+
if (pending.step !== "workspace_input" && pending.step !== "workspace") return false;
|
|
2941
|
+
const threadId = ctx.message.message_thread_id;
|
|
2942
|
+
if (threadId && threadId !== assistantTopicId) return false;
|
|
2943
|
+
let workspace = ctx.message.text.trim();
|
|
2944
|
+
if (!workspace || !pending.agentName) {
|
|
2945
|
+
await ctx.reply("\u26A0\uFE0F Please enter a valid directory path.", { parse_mode: "HTML" });
|
|
2946
|
+
return true;
|
|
2947
|
+
}
|
|
2948
|
+
if (!workspace.startsWith("/") && !workspace.startsWith("~")) {
|
|
2949
|
+
const baseDir = core.configManager.get().workspace.baseDir;
|
|
2950
|
+
workspace = `${baseDir.replace(/\/$/, "")}/${workspace}`;
|
|
2951
|
+
}
|
|
2952
|
+
await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
|
|
2953
|
+
return true;
|
|
2954
|
+
}
|
|
2955
|
+
async function startInteractiveNewSession(ctx, core, chatId, agentName) {
|
|
2956
|
+
const userId = ctx.from?.id;
|
|
2957
|
+
if (!userId) return;
|
|
2958
|
+
const agents = core.agentManager.getAvailableAgents();
|
|
2959
|
+
const config = core.configManager.get();
|
|
2960
|
+
if (agentName || agents.length === 1) {
|
|
2961
|
+
const selectedAgent = agentName || config.defaultAgent;
|
|
2962
|
+
await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
const keyboard = new InlineKeyboard2();
|
|
2966
|
+
for (const agent of agents) {
|
|
2967
|
+
const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
|
|
2968
|
+
keyboard.text(label, `m:new:agent:${agent.name}`).row();
|
|
2969
|
+
}
|
|
2970
|
+
keyboard.text("\u274C Cancel", "m:new:cancel");
|
|
2971
|
+
const msg = await ctx.reply(
|
|
2972
|
+
`\u{1F916} <b>Choose an agent:</b>`,
|
|
2973
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
2974
|
+
);
|
|
2975
|
+
cleanupPending(userId);
|
|
2976
|
+
pendingNewSessions.set(userId, {
|
|
2977
|
+
step: "agent",
|
|
2978
|
+
messageId: msg.message_id,
|
|
2979
|
+
threadId: ctx.callbackQuery?.message?.message_thread_id,
|
|
2980
|
+
timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
function setupNewSessionCallbacks(bot, core, chatId) {
|
|
2984
|
+
bot.callbackQuery(/^m:new:/, async (ctx) => {
|
|
2985
|
+
const data = ctx.callbackQuery.data;
|
|
2986
|
+
try {
|
|
2987
|
+
await ctx.answerCallbackQuery();
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
if (data.startsWith("m:new:agent:")) {
|
|
2991
|
+
const agentName = data.replace("m:new:agent:", "");
|
|
2992
|
+
const userId = ctx.from?.id;
|
|
2993
|
+
if (userId) await startWorkspaceStep(ctx, core, chatId, userId, agentName);
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
if (data === "m:new:ws:default") {
|
|
2997
|
+
const userId = ctx.from?.id;
|
|
2998
|
+
if (!userId) return;
|
|
2999
|
+
const pending = pendingNewSessions.get(userId);
|
|
3000
|
+
if (!pending?.agentName) return;
|
|
3001
|
+
const workspace = core.configManager.get().workspace.baseDir;
|
|
3002
|
+
await startConfirmStep(ctx, chatId, userId, pending.agentName, workspace);
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
if (data === "m:new:ws:custom") {
|
|
3006
|
+
const userId = ctx.from?.id;
|
|
3007
|
+
if (!userId) return;
|
|
3008
|
+
const pending = pendingNewSessions.get(userId);
|
|
3009
|
+
if (!pending?.agentName) return;
|
|
3010
|
+
try {
|
|
3011
|
+
await ctx.api.editMessageText(
|
|
3012
|
+
chatId,
|
|
3013
|
+
pending.messageId,
|
|
3014
|
+
`\u270F\uFE0F <b>Enter your project path:</b>
|
|
3015
|
+
|
|
3016
|
+
Full path like <code>~/code/my-project</code>
|
|
3017
|
+
Or just the folder name like <code>my-project</code> (will use ${core.configManager.get().workspace.baseDir}/)`,
|
|
3018
|
+
{ parse_mode: "HTML" }
|
|
3019
|
+
);
|
|
3020
|
+
} catch {
|
|
3021
|
+
await ctx.reply(
|
|
3022
|
+
`\u270F\uFE0F <b>Enter your project path:</b>`,
|
|
3023
|
+
{ parse_mode: "HTML" }
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
clearTimeout(pending.timer);
|
|
3027
|
+
pending.step = "workspace_input";
|
|
3028
|
+
pending.timer = setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS);
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
if (data === "m:new:confirm") {
|
|
3032
|
+
const userId = ctx.from?.id;
|
|
3033
|
+
if (!userId) return;
|
|
3034
|
+
const pending = pendingNewSessions.get(userId);
|
|
3035
|
+
if (!pending?.agentName || !pending?.workspace) return;
|
|
3036
|
+
cleanupPending(userId);
|
|
3037
|
+
const confirmMsgId = pending.messageId;
|
|
3038
|
+
try {
|
|
3039
|
+
await ctx.api.editMessageText(chatId, confirmMsgId, `\u23F3 Creating session...`, { parse_mode: "HTML" });
|
|
3040
|
+
} catch {
|
|
3041
|
+
}
|
|
3042
|
+
const resultThreadId = await createSessionDirect(ctx, core, chatId, pending.agentName, pending.workspace);
|
|
3043
|
+
try {
|
|
3044
|
+
if (resultThreadId) {
|
|
3045
|
+
const link = buildDeepLink(chatId, resultThreadId);
|
|
3046
|
+
await ctx.api.editMessageText(chatId, confirmMsgId, `\u2705 Session created \u2192 <a href="${link}">Open topic</a>`, { parse_mode: "HTML" });
|
|
3047
|
+
} else {
|
|
3048
|
+
await ctx.api.editMessageText(chatId, confirmMsgId, `\u274C Session creation failed.`, { parse_mode: "HTML" });
|
|
3049
|
+
}
|
|
3050
|
+
} catch {
|
|
3051
|
+
}
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
if (data === "m:new:cancel") {
|
|
3055
|
+
const userId = ctx.from?.id;
|
|
3056
|
+
if (userId) cleanupPending(userId);
|
|
3008
3057
|
try {
|
|
3009
|
-
await ctx.
|
|
3058
|
+
await ctx.editMessageText("\u274C Session creation cancelled.", { parse_mode: "HTML" });
|
|
3010
3059
|
} catch {
|
|
3011
3060
|
}
|
|
3061
|
+
return;
|
|
3012
3062
|
}
|
|
3013
|
-
|
|
3014
|
-
await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
|
|
3015
|
-
}
|
|
3063
|
+
});
|
|
3016
3064
|
}
|
|
3065
|
+
|
|
3066
|
+
// src/adapters/telegram/commands/session.ts
|
|
3067
|
+
import { InlineKeyboard as InlineKeyboard3 } from "grammy";
|
|
3068
|
+
var log11 = createChildLogger({ module: "telegram-cmd-session" });
|
|
3017
3069
|
async function handleCancel(ctx, core, assistant) {
|
|
3018
3070
|
const threadId = ctx.message?.message_thread_id;
|
|
3019
3071
|
if (!threadId) return;
|
|
@@ -3031,14 +3083,14 @@ async function handleCancel(ctx, core, assistant) {
|
|
|
3031
3083
|
String(threadId)
|
|
3032
3084
|
);
|
|
3033
3085
|
if (session) {
|
|
3034
|
-
|
|
3035
|
-
await session.
|
|
3086
|
+
log11.info({ sessionId: session.id }, "Cancel session command");
|
|
3087
|
+
await session.abortPrompt();
|
|
3036
3088
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
3037
3089
|
return;
|
|
3038
3090
|
}
|
|
3039
3091
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
3040
3092
|
if (record && record.status !== "cancelled" && record.status !== "error") {
|
|
3041
|
-
|
|
3093
|
+
log11.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
|
|
3042
3094
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
3043
3095
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
3044
3096
|
}
|
|
@@ -3122,10 +3174,9 @@ async function handleTopics(ctx, core) {
|
|
|
3122
3174
|
const truncated = records.length > MAX_DISPLAY ? `
|
|
3123
3175
|
|
|
3124
3176
|
<i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
|
|
3125
|
-
const finishedCount =
|
|
3126
|
-
const errorCount =
|
|
3127
|
-
const
|
|
3128
|
-
const keyboard = new InlineKeyboard();
|
|
3177
|
+
const finishedCount = allRecords.filter((r) => r.status === "finished").length;
|
|
3178
|
+
const errorCount = allRecords.filter((r) => r.status === "error" || r.status === "cancelled").length;
|
|
3179
|
+
const keyboard = new InlineKeyboard3();
|
|
3129
3180
|
if (finishedCount > 0) {
|
|
3130
3181
|
keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
|
|
3131
3182
|
}
|
|
@@ -3135,7 +3186,7 @@ async function handleTopics(ctx, core) {
|
|
|
3135
3186
|
if (finishedCount + errorCount > 0) {
|
|
3136
3187
|
keyboard.text(`Cleanup all non-active (${finishedCount + errorCount})`, "m:cleanup:all").row();
|
|
3137
3188
|
}
|
|
3138
|
-
keyboard.text(`\u26A0\uFE0F Cleanup ALL (${
|
|
3189
|
+
keyboard.text(`\u26A0\uFE0F Cleanup ALL (${allRecords.length})`, "m:cleanup:everything").row();
|
|
3139
3190
|
keyboard.text("Refresh", "m:topics");
|
|
3140
3191
|
await ctx.reply(
|
|
3141
3192
|
`${header}
|
|
@@ -3144,17 +3195,14 @@ ${lines.join("\n")}${truncated}`,
|
|
|
3144
3195
|
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3145
3196
|
);
|
|
3146
3197
|
} catch (err) {
|
|
3147
|
-
|
|
3198
|
+
log11.error({ err }, "handleTopics error");
|
|
3148
3199
|
await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
|
|
3149
3200
|
});
|
|
3150
3201
|
}
|
|
3151
3202
|
}
|
|
3152
3203
|
async function handleCleanup(ctx, core, chatId, statuses) {
|
|
3153
3204
|
const allRecords = core.sessionManager.listRecords();
|
|
3154
|
-
const cleanable = allRecords.filter((r) =>
|
|
3155
|
-
const platform = r.platform;
|
|
3156
|
-
return !!platform?.topicId && statuses.includes(r.status);
|
|
3157
|
-
});
|
|
3205
|
+
const cleanable = allRecords.filter((r) => statuses.includes(r.status));
|
|
3158
3206
|
if (cleanable.length === 0) {
|
|
3159
3207
|
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3160
3208
|
return;
|
|
@@ -3168,13 +3216,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
|
|
|
3168
3216
|
try {
|
|
3169
3217
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3170
3218
|
} catch (err) {
|
|
3171
|
-
|
|
3219
|
+
log11.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3172
3220
|
}
|
|
3173
3221
|
}
|
|
3174
3222
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
3175
3223
|
deleted++;
|
|
3176
3224
|
} catch (err) {
|
|
3177
|
-
|
|
3225
|
+
log11.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3178
3226
|
failed++;
|
|
3179
3227
|
}
|
|
3180
3228
|
}
|
|
@@ -3187,8 +3235,7 @@ async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
|
|
|
3187
3235
|
const allRecords = core.sessionManager.listRecords();
|
|
3188
3236
|
const cleanable = allRecords.filter((r) => {
|
|
3189
3237
|
const platform = r.platform;
|
|
3190
|
-
if (
|
|
3191
|
-
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3238
|
+
if (systemTopicIds && platform?.topicId && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3192
3239
|
return true;
|
|
3193
3240
|
});
|
|
3194
3241
|
if (cleanable.length === 0) {
|
|
@@ -3211,357 +3258,104 @@ async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
|
|
|
3211
3258
|
const activeWarning = activeCount > 0 ? `
|
|
3212
3259
|
|
|
3213
3260
|
\u26A0\uFE0F <b>${activeCount} active session(s) will be cancelled and their agents stopped!</b>` : "";
|
|
3214
|
-
const keyboard = new
|
|
3261
|
+
const keyboard = new InlineKeyboard3().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
|
|
3215
3262
|
await ctx.reply(
|
|
3216
3263
|
`<b>Delete ${cleanable.length} topics?</b>
|
|
3217
|
-
|
|
3218
|
-
This will:
|
|
3219
|
-
\u2022 Delete all session topics from this group
|
|
3220
|
-
\u2022 Cancel any running agent sessions
|
|
3221
|
-
\u2022 Remove all session records
|
|
3222
|
-
|
|
3223
|
-
<b>Breakdown:</b>
|
|
3224
|
-
${breakdown}${activeWarning}
|
|
3225
|
-
|
|
3226
|
-
<i>Notifications and Assistant topics will NOT be deleted.</i>`,
|
|
3227
|
-
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3228
|
-
);
|
|
3229
|
-
}
|
|
3230
|
-
async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds) {
|
|
3231
|
-
const allRecords = core.sessionManager.listRecords();
|
|
3232
|
-
const cleanable = allRecords.filter((r) => {
|
|
3233
|
-
const platform = r.platform;
|
|
3234
|
-
if (!platform?.topicId) return false;
|
|
3235
|
-
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3236
|
-
return true;
|
|
3237
|
-
});
|
|
3238
|
-
if (cleanable.length === 0) {
|
|
3239
|
-
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3240
|
-
return;
|
|
3241
|
-
}
|
|
3242
|
-
let deleted = 0;
|
|
3243
|
-
let failed = 0;
|
|
3244
|
-
for (const record of cleanable) {
|
|
3245
|
-
try {
|
|
3246
|
-
if (record.status === "active" || record.status === "initializing") {
|
|
3247
|
-
try {
|
|
3248
|
-
await core.sessionManager.cancelSession(record.sessionId);
|
|
3249
|
-
} catch (err) {
|
|
3250
|
-
log8.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
const topicId = record.platform?.topicId;
|
|
3254
|
-
if (topicId) {
|
|
3255
|
-
try {
|
|
3256
|
-
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3257
|
-
} catch (err) {
|
|
3258
|
-
log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
await core.sessionManager.removeRecord(record.sessionId);
|
|
3262
|
-
deleted++;
|
|
3263
|
-
} catch (err) {
|
|
3264
|
-
log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3265
|
-
failed++;
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
|
-
await ctx.reply(
|
|
3269
|
-
`\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
|
|
3270
|
-
{ parse_mode: "HTML" }
|
|
3271
|
-
);
|
|
3272
|
-
}
|
|
3273
|
-
async function handleAgents(ctx, core) {
|
|
3274
|
-
const agents = core.agentManager.getAvailableAgents();
|
|
3275
|
-
const defaultAgent = core.configManager.get().defaultAgent;
|
|
3276
|
-
const lines = agents.map(
|
|
3277
|
-
(a) => `\u2022 <b>${escapeHtml(a.name)}</b>${a.name === defaultAgent ? " (default)" : ""}
|
|
3278
|
-
<code>${escapeHtml(a.command)} ${a.args.map((arg) => escapeHtml(arg)).join(" ")}</code>`
|
|
3279
|
-
);
|
|
3280
|
-
const text = lines.length > 0 ? `<b>Available Agents:</b>
|
|
3281
|
-
|
|
3282
|
-
${lines.join("\n")}` : `<b>Available Agents:</b>
|
|
3283
|
-
|
|
3284
|
-
No agents configured.`;
|
|
3285
|
-
await ctx.reply(text, { parse_mode: "HTML" });
|
|
3286
|
-
}
|
|
3287
|
-
async function handleHelp(ctx) {
|
|
3288
|
-
await ctx.reply(
|
|
3289
|
-
`\u{1F4D6} <b>OpenACP Help</b>
|
|
3290
|
-
|
|
3291
|
-
\u{1F680} <b>Getting Started</b>
|
|
3292
|
-
Tap \u{1F195} New Session to start coding with AI.
|
|
3293
|
-
Each session gets its own topic \u2014 chat there to work with the agent.
|
|
3294
|
-
|
|
3295
|
-
\u{1F4A1} <b>Common Tasks</b>
|
|
3296
|
-
/new [agent] [workspace] \u2014 Create new session
|
|
3297
|
-
/cancel \u2014 Cancel session (in session topic)
|
|
3298
|
-
/status \u2014 Show session or system status
|
|
3299
|
-
/sessions \u2014 List all sessions
|
|
3300
|
-
/agents \u2014 List available agents
|
|
3301
|
-
|
|
3302
|
-
\u2699\uFE0F <b>System</b>
|
|
3303
|
-
/restart \u2014 Restart OpenACP
|
|
3304
|
-
/update \u2014 Update to latest version
|
|
3305
|
-
/integrate \u2014 Manage agent integrations
|
|
3306
|
-
/menu \u2014 Show action menu
|
|
3307
|
-
|
|
3308
|
-
\u{1F512} <b>Session Options</b>
|
|
3309
|
-
/enable_dangerous \u2014 Auto-approve permissions
|
|
3310
|
-
/disable_dangerous \u2014 Restore permission prompts
|
|
3311
|
-
/handoff \u2014 Continue session in terminal
|
|
3312
|
-
/clear \u2014 Clear assistant history
|
|
3313
|
-
|
|
3314
|
-
\u{1F4AC} Need help? Just ask me in this topic!`,
|
|
3315
|
-
{ parse_mode: "HTML" }
|
|
3316
|
-
);
|
|
3317
|
-
}
|
|
3318
|
-
async function handleClear(ctx, assistant) {
|
|
3319
|
-
if (!assistant) {
|
|
3320
|
-
await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
|
|
3321
|
-
return;
|
|
3322
|
-
}
|
|
3323
|
-
const threadId = ctx.message?.message_thread_id;
|
|
3324
|
-
if (threadId !== assistant.topicId) {
|
|
3325
|
-
await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
|
|
3326
|
-
return;
|
|
3327
|
-
}
|
|
3328
|
-
await ctx.reply("\u{1F504} Clearing assistant history...", { parse_mode: "HTML" });
|
|
3329
|
-
try {
|
|
3330
|
-
await assistant.respawn();
|
|
3331
|
-
await ctx.reply("\u2705 Assistant history cleared.", { parse_mode: "HTML" });
|
|
3332
|
-
} catch (err) {
|
|
3333
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3334
|
-
await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
function buildDangerousModeKeyboard(sessionId, enabled) {
|
|
3338
|
-
return new InlineKeyboard().text(
|
|
3339
|
-
enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
|
|
3340
|
-
`d:${sessionId}`
|
|
3341
|
-
);
|
|
3342
|
-
}
|
|
3343
|
-
function setupDangerousModeCallbacks(bot, core) {
|
|
3344
|
-
bot.callbackQuery(/^d:/, async (ctx) => {
|
|
3345
|
-
const sessionId = ctx.callbackQuery.data.slice(2);
|
|
3346
|
-
const session = core.sessionManager.getSession(sessionId);
|
|
3347
|
-
if (session) {
|
|
3348
|
-
session.dangerousMode = !session.dangerousMode;
|
|
3349
|
-
log8.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
|
|
3350
|
-
core.sessionManager.updateSessionDangerousMode(sessionId, session.dangerousMode).catch(() => {
|
|
3351
|
-
});
|
|
3352
|
-
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
3353
|
-
try {
|
|
3354
|
-
await ctx.answerCallbackQuery({ text: toastText2 });
|
|
3355
|
-
} catch {
|
|
3356
|
-
}
|
|
3357
|
-
try {
|
|
3358
|
-
await ctx.editMessageReplyMarkup({
|
|
3359
|
-
reply_markup: buildDangerousModeKeyboard(sessionId, session.dangerousMode)
|
|
3360
|
-
});
|
|
3361
|
-
} catch {
|
|
3362
|
-
}
|
|
3363
|
-
return;
|
|
3364
|
-
}
|
|
3365
|
-
const record = core.sessionManager.getSessionRecord(sessionId);
|
|
3366
|
-
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
3367
|
-
try {
|
|
3368
|
-
await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
|
|
3369
|
-
} catch {
|
|
3370
|
-
}
|
|
3371
|
-
return;
|
|
3372
|
-
}
|
|
3373
|
-
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
3374
|
-
core.sessionManager.updateSessionDangerousMode(sessionId, newDangerousMode).catch(() => {
|
|
3375
|
-
});
|
|
3376
|
-
log8.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
|
|
3377
|
-
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
3378
|
-
try {
|
|
3379
|
-
await ctx.answerCallbackQuery({ text: toastText });
|
|
3380
|
-
} catch {
|
|
3381
|
-
}
|
|
3382
|
-
try {
|
|
3383
|
-
await ctx.editMessageReplyMarkup({
|
|
3384
|
-
reply_markup: buildDangerousModeKeyboard(sessionId, newDangerousMode)
|
|
3385
|
-
});
|
|
3386
|
-
} catch {
|
|
3387
|
-
}
|
|
3388
|
-
});
|
|
3389
|
-
}
|
|
3390
|
-
async function handleEnableDangerous(ctx, core) {
|
|
3391
|
-
const threadId = ctx.message?.message_thread_id;
|
|
3392
|
-
if (!threadId) {
|
|
3393
|
-
await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
|
|
3394
|
-
return;
|
|
3395
|
-
}
|
|
3396
|
-
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
3397
|
-
if (session) {
|
|
3398
|
-
if (session.dangerousMode) {
|
|
3399
|
-
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
3400
|
-
return;
|
|
3401
|
-
}
|
|
3402
|
-
session.dangerousMode = true;
|
|
3403
|
-
core.sessionManager.updateSessionDangerousMode(session.id, true).catch(() => {
|
|
3404
|
-
});
|
|
3405
|
-
} else {
|
|
3406
|
-
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
3407
|
-
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
3408
|
-
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
3409
|
-
return;
|
|
3410
|
-
}
|
|
3411
|
-
if (record.dangerousMode) {
|
|
3412
|
-
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
3413
|
-
return;
|
|
3414
|
-
}
|
|
3415
|
-
core.sessionManager.updateSessionDangerousMode(record.sessionId, true).catch(() => {
|
|
3416
|
-
});
|
|
3417
|
-
}
|
|
3418
|
-
await ctx.reply(
|
|
3419
|
-
`\u26A0\uFE0F <b>Dangerous mode enabled</b>
|
|
3420
|
-
|
|
3421
|
-
All permission requests will be auto-approved. Claude can run arbitrary commands without asking.
|
|
3422
|
-
|
|
3423
|
-
Use /disable_dangerous to restore normal behaviour.`,
|
|
3424
|
-
{ parse_mode: "HTML" }
|
|
3425
|
-
);
|
|
3426
|
-
}
|
|
3427
|
-
async function handleUpdate(ctx, core) {
|
|
3428
|
-
if (!core.requestRestart) {
|
|
3429
|
-
await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
3430
|
-
return;
|
|
3431
|
-
}
|
|
3432
|
-
const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
|
|
3433
|
-
const current = getCurrentVersion();
|
|
3434
|
-
const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
|
|
3435
|
-
const latest = await getLatestVersion();
|
|
3436
|
-
if (!latest) {
|
|
3437
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
|
|
3438
|
-
return;
|
|
3439
|
-
}
|
|
3440
|
-
if (compareVersions(current, latest) >= 0) {
|
|
3441
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
|
|
3442
|
-
return;
|
|
3443
|
-
}
|
|
3444
|
-
await ctx.api.editMessageText(
|
|
3445
|
-
ctx.chat.id,
|
|
3446
|
-
statusMsg.message_id,
|
|
3447
|
-
`\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
|
|
3448
|
-
{ parse_mode: "HTML" }
|
|
3449
|
-
);
|
|
3450
|
-
const ok = await runUpdate();
|
|
3451
|
-
if (!ok) {
|
|
3452
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
|
|
3453
|
-
return;
|
|
3454
|
-
}
|
|
3455
|
-
await ctx.api.editMessageText(
|
|
3456
|
-
ctx.chat.id,
|
|
3457
|
-
statusMsg.message_id,
|
|
3458
|
-
`\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
|
|
3459
|
-
{ parse_mode: "HTML" }
|
|
3460
|
-
);
|
|
3461
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
3462
|
-
await core.requestRestart();
|
|
3463
|
-
}
|
|
3464
|
-
async function handleRestart(ctx, core) {
|
|
3465
|
-
if (!core.requestRestart) {
|
|
3466
|
-
await ctx.reply("\u26A0\uFE0F Restart is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
3467
|
-
return;
|
|
3468
|
-
}
|
|
3469
|
-
await ctx.reply("\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.", { parse_mode: "HTML" });
|
|
3470
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
3471
|
-
await core.requestRestart();
|
|
3472
|
-
}
|
|
3473
|
-
async function handleDisableDangerous(ctx, core) {
|
|
3474
|
-
const threadId = ctx.message?.message_thread_id;
|
|
3475
|
-
if (!threadId) {
|
|
3476
|
-
await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
|
|
3477
|
-
return;
|
|
3478
|
-
}
|
|
3479
|
-
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
3480
|
-
if (session) {
|
|
3481
|
-
if (!session.dangerousMode) {
|
|
3482
|
-
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
3483
|
-
return;
|
|
3484
|
-
}
|
|
3485
|
-
session.dangerousMode = false;
|
|
3486
|
-
core.sessionManager.updateSessionDangerousMode(session.id, false).catch(() => {
|
|
3487
|
-
});
|
|
3488
|
-
} else {
|
|
3489
|
-
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
3490
|
-
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
3491
|
-
await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
|
|
3492
|
-
return;
|
|
3493
|
-
}
|
|
3494
|
-
if (!record.dangerousMode) {
|
|
3495
|
-
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
3496
|
-
return;
|
|
3497
|
-
}
|
|
3498
|
-
core.sessionManager.updateSessionDangerousMode(record.sessionId, false).catch(() => {
|
|
3499
|
-
});
|
|
3500
|
-
}
|
|
3501
|
-
await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
|
|
3502
|
-
}
|
|
3503
|
-
function botFromCtx(ctx) {
|
|
3504
|
-
return { api: ctx.api };
|
|
3505
|
-
}
|
|
3506
|
-
var TELEGRAM_MSG_LIMIT = 4096;
|
|
3507
|
-
function buildSkillMessages(commands) {
|
|
3508
|
-
const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
3509
|
-
const header = "\u{1F6E0} <b>Available Skills</b>\n";
|
|
3510
|
-
const lines = sorted.map((c) => `<code>/${c.name}</code>`);
|
|
3511
|
-
const messages = [];
|
|
3512
|
-
let current = header;
|
|
3513
|
-
for (const line of lines) {
|
|
3514
|
-
const candidate = current + "\n" + line;
|
|
3515
|
-
if (candidate.length > TELEGRAM_MSG_LIMIT) {
|
|
3516
|
-
messages.push(current);
|
|
3517
|
-
current = line;
|
|
3518
|
-
} else {
|
|
3519
|
-
current = candidate;
|
|
3520
|
-
}
|
|
3521
|
-
}
|
|
3522
|
-
if (current) messages.push(current);
|
|
3523
|
-
return messages;
|
|
3264
|
+
|
|
3265
|
+
This will:
|
|
3266
|
+
\u2022 Delete all session topics from this group
|
|
3267
|
+
\u2022 Cancel any running agent sessions
|
|
3268
|
+
\u2022 Remove all session records
|
|
3269
|
+
|
|
3270
|
+
<b>Breakdown:</b>
|
|
3271
|
+
${breakdown}${activeWarning}
|
|
3272
|
+
|
|
3273
|
+
<i>Notifications and Assistant topics will NOT be deleted.</i>`,
|
|
3274
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3275
|
+
);
|
|
3524
3276
|
}
|
|
3525
|
-
async function
|
|
3526
|
-
const
|
|
3527
|
-
const
|
|
3528
|
-
|
|
3529
|
-
|
|
3277
|
+
async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds) {
|
|
3278
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3279
|
+
const cleanable = allRecords.filter((r) => {
|
|
3280
|
+
const platform = r.platform;
|
|
3281
|
+
if (systemTopicIds && platform?.topicId && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3282
|
+
return true;
|
|
3530
3283
|
});
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
session.threadId = String(threadId);
|
|
3539
|
-
await core.sessionManager.updateSessionPlatform(session.id, {
|
|
3540
|
-
topicId: threadId
|
|
3541
|
-
});
|
|
3542
|
-
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
3543
|
-
await renameSessionTopic(bot, chatId, threadId, finalName);
|
|
3544
|
-
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
3545
|
-
return { session, threadId, firstMsgId };
|
|
3546
|
-
} catch (err) {
|
|
3284
|
+
if (cleanable.length === 0) {
|
|
3285
|
+
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
let deleted = 0;
|
|
3289
|
+
let failed = 0;
|
|
3290
|
+
for (const record of cleanable) {
|
|
3547
3291
|
try {
|
|
3548
|
-
|
|
3549
|
-
|
|
3292
|
+
if (record.status === "active" || record.status === "initializing") {
|
|
3293
|
+
try {
|
|
3294
|
+
await core.sessionManager.cancelSession(record.sessionId);
|
|
3295
|
+
} catch (err) {
|
|
3296
|
+
log11.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
const topicId = record.platform?.topicId;
|
|
3300
|
+
if (topicId) {
|
|
3301
|
+
try {
|
|
3302
|
+
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3303
|
+
} catch (err) {
|
|
3304
|
+
log11.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
await core.sessionManager.removeRecord(record.sessionId);
|
|
3308
|
+
deleted++;
|
|
3309
|
+
} catch (err) {
|
|
3310
|
+
log11.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3311
|
+
failed++;
|
|
3550
3312
|
}
|
|
3551
|
-
throw err;
|
|
3552
3313
|
}
|
|
3314
|
+
await ctx.reply(
|
|
3315
|
+
`\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
|
|
3316
|
+
{ parse_mode: "HTML" }
|
|
3317
|
+
);
|
|
3553
3318
|
}
|
|
3554
3319
|
async function executeCancelSession(core, excludeSessionId) {
|
|
3555
3320
|
const sessions = core.sessionManager.listSessions("telegram").filter((s) => s.status === "active" && s.id !== excludeSessionId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
3556
3321
|
const session = sessions[0];
|
|
3557
3322
|
if (!session) return null;
|
|
3558
|
-
await session.
|
|
3323
|
+
await session.abortPrompt();
|
|
3559
3324
|
return session;
|
|
3560
3325
|
}
|
|
3326
|
+
function setupSessionCallbacks(bot, core, chatId, systemTopicIds) {
|
|
3327
|
+
bot.callbackQuery(/^m:cleanup/, async (ctx) => {
|
|
3328
|
+
const data = ctx.callbackQuery.data;
|
|
3329
|
+
try {
|
|
3330
|
+
await ctx.answerCallbackQuery();
|
|
3331
|
+
} catch {
|
|
3332
|
+
}
|
|
3333
|
+
switch (data) {
|
|
3334
|
+
case "m:cleanup:finished":
|
|
3335
|
+
await handleCleanup(ctx, core, chatId, ["finished"]);
|
|
3336
|
+
break;
|
|
3337
|
+
case "m:cleanup:errors":
|
|
3338
|
+
await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
|
|
3339
|
+
break;
|
|
3340
|
+
case "m:cleanup:all":
|
|
3341
|
+
await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
|
|
3342
|
+
break;
|
|
3343
|
+
case "m:cleanup:everything":
|
|
3344
|
+
await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
|
|
3345
|
+
break;
|
|
3346
|
+
case "m:cleanup:everything:confirm":
|
|
3347
|
+
await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
|
|
3348
|
+
break;
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
// src/adapters/telegram/commands/integrate.ts
|
|
3354
|
+
import { InlineKeyboard as InlineKeyboard4 } from "grammy";
|
|
3561
3355
|
async function handleIntegrate(ctx, _core) {
|
|
3562
3356
|
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3563
3357
|
const agents = listIntegrations();
|
|
3564
|
-
const keyboard = new
|
|
3358
|
+
const keyboard = new InlineKeyboard4();
|
|
3565
3359
|
for (const agent of agents) {
|
|
3566
3360
|
keyboard.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3567
3361
|
}
|
|
@@ -3573,7 +3367,7 @@ Select an agent to manage its integrations.`,
|
|
|
3573
3367
|
);
|
|
3574
3368
|
}
|
|
3575
3369
|
function buildAgentItemsKeyboard(agentName, items) {
|
|
3576
|
-
const keyboard = new
|
|
3370
|
+
const keyboard = new InlineKeyboard4();
|
|
3577
3371
|
for (const item of items) {
|
|
3578
3372
|
const installed = item.isInstalled();
|
|
3579
3373
|
keyboard.text(
|
|
@@ -3594,7 +3388,7 @@ function setupIntegrateCallbacks(bot, core) {
|
|
|
3594
3388
|
if (data === "i:back") {
|
|
3595
3389
|
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3596
3390
|
const agents = listIntegrations();
|
|
3597
|
-
const keyboard2 = new
|
|
3391
|
+
const keyboard2 = new InlineKeyboard4();
|
|
3598
3392
|
for (const agent of agents) {
|
|
3599
3393
|
keyboard2.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3600
3394
|
}
|
|
@@ -3673,51 +3467,357 @@ ${resultText}`,
|
|
|
3673
3467
|
}
|
|
3674
3468
|
});
|
|
3675
3469
|
}
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
const
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3470
|
+
|
|
3471
|
+
// src/adapters/telegram/commands/settings.ts
|
|
3472
|
+
import { InlineKeyboard as InlineKeyboard5 } from "grammy";
|
|
3473
|
+
var log12 = createChildLogger({ module: "telegram-settings" });
|
|
3474
|
+
function buildSettingsKeyboard(core) {
|
|
3475
|
+
const config = core.configManager.get();
|
|
3476
|
+
const fields = getSafeFields();
|
|
3477
|
+
const kb = new InlineKeyboard5();
|
|
3478
|
+
for (const field of fields) {
|
|
3479
|
+
const value = getConfigValue(config, field.path);
|
|
3480
|
+
const label = formatFieldLabel(field, value);
|
|
3481
|
+
if (field.type === "toggle") {
|
|
3482
|
+
kb.text(`${label}`, `s:toggle:${field.path}`).row();
|
|
3483
|
+
} else if (field.type === "select") {
|
|
3484
|
+
kb.text(`${label}`, `s:select:${field.path}`).row();
|
|
3485
|
+
} else {
|
|
3486
|
+
kb.text(`${label}`, `s:input:${field.path}`).row();
|
|
3487
|
+
}
|
|
3692
3488
|
}
|
|
3693
|
-
|
|
3694
|
-
return
|
|
3489
|
+
kb.text("\u25C0\uFE0F Back to Menu", "s:back");
|
|
3490
|
+
return kb;
|
|
3695
3491
|
}
|
|
3696
|
-
|
|
3697
|
-
const
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3492
|
+
function formatFieldLabel(field, value) {
|
|
3493
|
+
const icons = {
|
|
3494
|
+
agent: "\u{1F916}",
|
|
3495
|
+
logging: "\u{1F4DD}",
|
|
3496
|
+
tunnel: "\u{1F517}",
|
|
3497
|
+
security: "\u{1F512}",
|
|
3498
|
+
workspace: "\u{1F4C1}",
|
|
3499
|
+
storage: "\u{1F4BE}"
|
|
3500
|
+
};
|
|
3501
|
+
const icon = icons[field.group] ?? "\u2699\uFE0F";
|
|
3502
|
+
if (field.type === "toggle") {
|
|
3503
|
+
return `${icon} ${field.displayName}: ${value ? "ON" : "OFF"}`;
|
|
3705
3504
|
}
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3505
|
+
return `${icon} ${field.displayName}: ${String(value)}`;
|
|
3506
|
+
}
|
|
3507
|
+
async function handleSettings(ctx, core) {
|
|
3508
|
+
const kb = buildSettingsKeyboard(core);
|
|
3509
|
+
await ctx.reply(`<b>\u2699\uFE0F Settings</b>
|
|
3510
|
+
Tap to change:`, {
|
|
3511
|
+
parse_mode: "HTML",
|
|
3512
|
+
reply_markup: kb
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
3516
|
+
bot.callbackQuery(/^s:toggle:/, async (ctx) => {
|
|
3517
|
+
const fieldPath = ctx.callbackQuery.data.replace("s:toggle:", "");
|
|
3518
|
+
const config = core.configManager.get();
|
|
3519
|
+
const currentValue = getConfigValue(config, fieldPath);
|
|
3520
|
+
const newValue = !currentValue;
|
|
3521
|
+
try {
|
|
3522
|
+
const updates = buildNestedUpdate(fieldPath, newValue);
|
|
3523
|
+
await core.configManager.save(updates, fieldPath);
|
|
3524
|
+
const toast = isHotReloadable(fieldPath) ? `\u2705 ${fieldPath} = ${newValue}` : `\u2705 ${fieldPath} = ${newValue} (restart needed)`;
|
|
3525
|
+
try {
|
|
3526
|
+
await ctx.answerCallbackQuery({ text: toast });
|
|
3527
|
+
} catch {
|
|
3528
|
+
}
|
|
3529
|
+
try {
|
|
3530
|
+
await ctx.editMessageReplyMarkup({ reply_markup: buildSettingsKeyboard(core) });
|
|
3531
|
+
} catch {
|
|
3532
|
+
}
|
|
3533
|
+
} catch (err) {
|
|
3534
|
+
log12.error({ err, fieldPath }, "Failed to toggle config");
|
|
3535
|
+
try {
|
|
3536
|
+
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
3537
|
+
} catch {
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
});
|
|
3541
|
+
bot.callbackQuery(/^s:select:/, async (ctx) => {
|
|
3542
|
+
const fieldPath = ctx.callbackQuery.data.replace("s:select:", "");
|
|
3543
|
+
const config = core.configManager.get();
|
|
3544
|
+
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
3545
|
+
if (!fieldDef) return;
|
|
3546
|
+
const options = resolveOptions(fieldDef, config) ?? [];
|
|
3547
|
+
const currentValue = getConfigValue(config, fieldPath);
|
|
3548
|
+
const kb = new InlineKeyboard5();
|
|
3549
|
+
for (const opt of options) {
|
|
3550
|
+
const marker = opt === String(currentValue) ? " \u2713" : "";
|
|
3551
|
+
kb.text(`${opt}${marker}`, `s:pick:${fieldPath}:${opt}`).row();
|
|
3552
|
+
}
|
|
3553
|
+
kb.text("\u25C0\uFE0F Back", "s:back:refresh");
|
|
3554
|
+
try {
|
|
3555
|
+
await ctx.answerCallbackQuery();
|
|
3556
|
+
} catch {
|
|
3557
|
+
}
|
|
3558
|
+
try {
|
|
3559
|
+
await ctx.editMessageText(`<b>\u2699\uFE0F ${fieldDef.displayName}</b>
|
|
3560
|
+
Select a value:`, {
|
|
3561
|
+
parse_mode: "HTML",
|
|
3562
|
+
reply_markup: kb
|
|
3563
|
+
});
|
|
3564
|
+
} catch {
|
|
3565
|
+
}
|
|
3566
|
+
});
|
|
3567
|
+
bot.callbackQuery(/^s:pick:/, async (ctx) => {
|
|
3568
|
+
const parts = ctx.callbackQuery.data.replace("s:pick:", "").split(":");
|
|
3569
|
+
const fieldPath = parts.slice(0, -1).join(":");
|
|
3570
|
+
const newValue = parts[parts.length - 1];
|
|
3571
|
+
try {
|
|
3572
|
+
const updates = buildNestedUpdate(fieldPath, newValue);
|
|
3573
|
+
await core.configManager.save(updates, fieldPath);
|
|
3574
|
+
try {
|
|
3575
|
+
await ctx.answerCallbackQuery({ text: `\u2705 ${fieldPath} = ${newValue}` });
|
|
3576
|
+
} catch {
|
|
3577
|
+
}
|
|
3578
|
+
try {
|
|
3579
|
+
await ctx.editMessageText(`<b>\u2699\uFE0F Settings</b>
|
|
3580
|
+
Tap to change:`, {
|
|
3581
|
+
parse_mode: "HTML",
|
|
3582
|
+
reply_markup: buildSettingsKeyboard(core)
|
|
3583
|
+
});
|
|
3584
|
+
} catch {
|
|
3585
|
+
}
|
|
3586
|
+
} catch (err) {
|
|
3587
|
+
log12.error({ err, fieldPath }, "Failed to set config");
|
|
3588
|
+
try {
|
|
3589
|
+
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
3590
|
+
} catch {
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
});
|
|
3594
|
+
bot.callbackQuery(/^s:input:/, async (ctx) => {
|
|
3595
|
+
const fieldPath = ctx.callbackQuery.data.replace("s:input:", "");
|
|
3596
|
+
const config = core.configManager.get();
|
|
3597
|
+
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
3598
|
+
if (!fieldDef) return;
|
|
3599
|
+
const currentValue = getConfigValue(config, fieldPath);
|
|
3600
|
+
const assistant = getAssistantSession();
|
|
3601
|
+
if (!assistant) {
|
|
3602
|
+
try {
|
|
3603
|
+
await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Start the assistant first (/assistant)" });
|
|
3604
|
+
} catch {
|
|
3605
|
+
}
|
|
3606
|
+
return;
|
|
3607
|
+
}
|
|
3608
|
+
try {
|
|
3609
|
+
await ctx.answerCallbackQuery({ text: `Delegating to assistant...` });
|
|
3610
|
+
} catch {
|
|
3611
|
+
}
|
|
3612
|
+
const prompt = `User wants to change ${fieldDef.displayName} (config path: ${fieldPath}). Current value: ${JSON.stringify(currentValue)}. Ask them for the new value and apply it using: openacp config set ${fieldPath} <value>`;
|
|
3613
|
+
await assistant.enqueuePrompt(prompt);
|
|
3614
|
+
});
|
|
3615
|
+
bot.callbackQuery("s:back", async (ctx) => {
|
|
3616
|
+
try {
|
|
3617
|
+
await ctx.answerCallbackQuery();
|
|
3618
|
+
} catch {
|
|
3619
|
+
}
|
|
3620
|
+
const { buildMenuKeyboard: buildMenuKeyboard3 } = await import("./menu-CARRTW2F.js");
|
|
3621
|
+
try {
|
|
3622
|
+
await ctx.editMessageText(`<b>OpenACP Menu</b>
|
|
3623
|
+
Choose an action:`, {
|
|
3624
|
+
parse_mode: "HTML",
|
|
3625
|
+
reply_markup: buildMenuKeyboard3()
|
|
3626
|
+
});
|
|
3627
|
+
} catch {
|
|
3628
|
+
}
|
|
3629
|
+
});
|
|
3630
|
+
bot.callbackQuery("s:back:refresh", async (ctx) => {
|
|
3631
|
+
try {
|
|
3632
|
+
await ctx.answerCallbackQuery();
|
|
3633
|
+
} catch {
|
|
3634
|
+
}
|
|
3635
|
+
try {
|
|
3636
|
+
await ctx.editMessageText(`<b>\u2699\uFE0F Settings</b>
|
|
3637
|
+
Tap to change:`, {
|
|
3638
|
+
parse_mode: "HTML",
|
|
3639
|
+
reply_markup: buildSettingsKeyboard(core)
|
|
3640
|
+
});
|
|
3641
|
+
} catch {
|
|
3642
|
+
}
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
function buildNestedUpdate(dotPath, value) {
|
|
3646
|
+
const parts = dotPath.split(".");
|
|
3647
|
+
const result = {};
|
|
3648
|
+
let target = result;
|
|
3649
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3650
|
+
target[parts[i]] = {};
|
|
3651
|
+
target = target[parts[i]];
|
|
3652
|
+
}
|
|
3653
|
+
target[parts[parts.length - 1]] = value;
|
|
3654
|
+
return result;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
// src/adapters/telegram/commands/doctor.ts
|
|
3658
|
+
import { InlineKeyboard as InlineKeyboard6 } from "grammy";
|
|
3659
|
+
var log13 = createChildLogger({ module: "telegram-cmd-doctor" });
|
|
3660
|
+
var pendingFixesStore = /* @__PURE__ */ new Map();
|
|
3661
|
+
function renderReport(report) {
|
|
3662
|
+
const icons = { pass: "\u2705", warn: "\u26A0\uFE0F", fail: "\u274C" };
|
|
3663
|
+
const lines = ["\u{1FA7A} <b>OpenACP Doctor</b>\n"];
|
|
3664
|
+
for (const category of report.categories) {
|
|
3665
|
+
lines.push(`<b>${category.name}</b>`);
|
|
3666
|
+
for (const result of category.results) {
|
|
3667
|
+
lines.push(` ${icons[result.status]} ${escapeHtml2(result.message)}`);
|
|
3668
|
+
}
|
|
3669
|
+
lines.push("");
|
|
3670
|
+
}
|
|
3671
|
+
const { passed, warnings, failed, fixed } = report.summary;
|
|
3672
|
+
const fixedStr = fixed > 0 ? `, ${fixed} fixed` : "";
|
|
3673
|
+
lines.push(`<b>Result:</b> ${passed} passed, ${warnings} warnings, ${failed} failed${fixedStr}`);
|
|
3674
|
+
let keyboard;
|
|
3675
|
+
if (report.pendingFixes.length > 0) {
|
|
3676
|
+
keyboard = new InlineKeyboard6();
|
|
3677
|
+
for (let i = 0; i < report.pendingFixes.length; i++) {
|
|
3678
|
+
const label = `\u{1F527} Fix: ${report.pendingFixes[i].message.slice(0, 30)}`;
|
|
3679
|
+
keyboard.text(label, `m:doctor:fix:${i}`).row();
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
return { text: lines.join("\n"), keyboard };
|
|
3683
|
+
}
|
|
3684
|
+
function escapeHtml2(text) {
|
|
3685
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3686
|
+
}
|
|
3687
|
+
async function handleDoctor(ctx) {
|
|
3688
|
+
const statusMsg = await ctx.reply("\u{1FA7A} Running diagnostics...", { parse_mode: "HTML" });
|
|
3689
|
+
try {
|
|
3690
|
+
const engine = new DoctorEngine();
|
|
3691
|
+
const report = await engine.runAll();
|
|
3692
|
+
const { text, keyboard } = renderReport(report);
|
|
3693
|
+
const storeKey = `${ctx.chat.id}:${statusMsg.message_id}`;
|
|
3694
|
+
if (report.pendingFixes.length > 0) {
|
|
3695
|
+
pendingFixesStore.set(storeKey, report.pendingFixes);
|
|
3696
|
+
}
|
|
3697
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, text, {
|
|
3698
|
+
parse_mode: "HTML",
|
|
3699
|
+
reply_markup: keyboard
|
|
3700
|
+
});
|
|
3701
|
+
} catch (err) {
|
|
3702
|
+
log13.error({ err }, "Doctor command failed");
|
|
3703
|
+
await ctx.api.editMessageText(
|
|
3704
|
+
ctx.chat.id,
|
|
3705
|
+
statusMsg.message_id,
|
|
3706
|
+
`\u274C Doctor failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
3707
|
+
{ parse_mode: "HTML" }
|
|
3708
|
+
);
|
|
3710
3709
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3710
|
+
}
|
|
3711
|
+
function setupDoctorCallbacks(bot) {
|
|
3712
|
+
bot.callbackQuery(/^m:doctor:fix:/, async (ctx) => {
|
|
3713
|
+
const data = ctx.callbackQuery.data;
|
|
3714
|
+
const index = parseInt(data.replace("m:doctor:fix:", ""), 10);
|
|
3715
|
+
const chatId = ctx.callbackQuery.message?.chat.id;
|
|
3716
|
+
const messageId = ctx.callbackQuery.message?.message_id;
|
|
3717
|
+
try {
|
|
3718
|
+
await ctx.answerCallbackQuery({ text: "Applying fix..." });
|
|
3719
|
+
} catch {
|
|
3720
|
+
}
|
|
3721
|
+
if (chatId === void 0 || messageId === void 0) return;
|
|
3722
|
+
const storeKey = `${chatId}:${messageId}`;
|
|
3723
|
+
const fixes = pendingFixesStore.get(storeKey);
|
|
3724
|
+
if (!fixes || index < 0 || index >= fixes.length) {
|
|
3725
|
+
try {
|
|
3726
|
+
await ctx.answerCallbackQuery({ text: "Fix no longer available" });
|
|
3727
|
+
} catch {
|
|
3728
|
+
}
|
|
3729
|
+
return;
|
|
3730
|
+
}
|
|
3731
|
+
const pending = fixes[index];
|
|
3732
|
+
try {
|
|
3733
|
+
const result = await pending.fix();
|
|
3734
|
+
if (result.success) {
|
|
3735
|
+
const engine = new DoctorEngine();
|
|
3736
|
+
const report = await engine.runAll();
|
|
3737
|
+
const { text, keyboard } = renderReport(report);
|
|
3738
|
+
if (report.pendingFixes.length > 0) {
|
|
3739
|
+
pendingFixesStore.set(storeKey, report.pendingFixes);
|
|
3740
|
+
} else {
|
|
3741
|
+
pendingFixesStore.delete(storeKey);
|
|
3742
|
+
}
|
|
3743
|
+
await ctx.editMessageText(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
3744
|
+
} else {
|
|
3745
|
+
try {
|
|
3746
|
+
await ctx.answerCallbackQuery({ text: `Fix failed: ${result.message}` });
|
|
3747
|
+
} catch {
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
} catch (err) {
|
|
3751
|
+
log13.error({ err, index }, "Doctor fix callback failed");
|
|
3752
|
+
}
|
|
3753
|
+
});
|
|
3754
|
+
bot.callbackQuery("m:doctor", async (ctx) => {
|
|
3755
|
+
try {
|
|
3756
|
+
await ctx.answerCallbackQuery();
|
|
3757
|
+
} catch {
|
|
3758
|
+
}
|
|
3759
|
+
await handleDoctor(ctx);
|
|
3760
|
+
});
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
// src/adapters/telegram/commands/index.ts
|
|
3764
|
+
function setupCommands(bot, core, chatId, assistant) {
|
|
3765
|
+
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
3766
|
+
bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
|
|
3767
|
+
bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
|
|
3768
|
+
bot.command("status", (ctx) => handleStatus(ctx, core));
|
|
3769
|
+
bot.command("sessions", (ctx) => handleTopics(ctx, core));
|
|
3770
|
+
bot.command("agents", (ctx) => handleAgents(ctx, core));
|
|
3771
|
+
bot.command("help", (ctx) => handleHelp(ctx));
|
|
3772
|
+
bot.command("menu", (ctx) => handleMenu(ctx));
|
|
3773
|
+
bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
|
|
3774
|
+
bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
|
|
3775
|
+
bot.command("restart", (ctx) => handleRestart(ctx, core));
|
|
3776
|
+
bot.command("update", (ctx) => handleUpdate(ctx, core));
|
|
3777
|
+
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
3778
|
+
bot.command("clear", (ctx) => handleClear(ctx, assistant));
|
|
3779
|
+
bot.command("doctor", (ctx) => handleDoctor(ctx));
|
|
3780
|
+
}
|
|
3781
|
+
function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession) {
|
|
3782
|
+
setupNewSessionCallbacks(bot, core, chatId);
|
|
3783
|
+
setupSessionCallbacks(bot, core, chatId, systemTopicIds);
|
|
3784
|
+
setupSettingsCallbacks(bot, core, getAssistantSession ?? (() => void 0));
|
|
3785
|
+
setupDoctorCallbacks(bot);
|
|
3786
|
+
bot.callbackQuery(/^m:/, async (ctx) => {
|
|
3787
|
+
const data = ctx.callbackQuery.data;
|
|
3788
|
+
try {
|
|
3789
|
+
await ctx.answerCallbackQuery();
|
|
3790
|
+
} catch {
|
|
3791
|
+
}
|
|
3792
|
+
switch (data) {
|
|
3793
|
+
case "m:new":
|
|
3794
|
+
await handleNew(ctx, core, chatId);
|
|
3795
|
+
break;
|
|
3796
|
+
case "m:status":
|
|
3797
|
+
await handleStatus(ctx, core);
|
|
3798
|
+
break;
|
|
3799
|
+
case "m:agents":
|
|
3800
|
+
await handleAgents(ctx, core);
|
|
3801
|
+
break;
|
|
3802
|
+
case "m:help":
|
|
3803
|
+
await handleHelp(ctx);
|
|
3804
|
+
break;
|
|
3805
|
+
case "m:restart":
|
|
3806
|
+
await handleRestart(ctx, core);
|
|
3807
|
+
break;
|
|
3808
|
+
case "m:update":
|
|
3809
|
+
await handleUpdate(ctx, core);
|
|
3810
|
+
break;
|
|
3811
|
+
case "m:integrate":
|
|
3812
|
+
await handleIntegrate(ctx, core);
|
|
3813
|
+
break;
|
|
3814
|
+
case "m:topics":
|
|
3815
|
+
await handleTopics(ctx, core);
|
|
3816
|
+
break;
|
|
3817
|
+
case "m:settings":
|
|
3818
|
+
await handleSettings(ctx, core);
|
|
3819
|
+
break;
|
|
3820
|
+
}
|
|
3721
3821
|
});
|
|
3722
3822
|
}
|
|
3723
3823
|
var STATIC_COMMANDS = [
|
|
@@ -3735,13 +3835,14 @@ var STATIC_COMMANDS = [
|
|
|
3735
3835
|
{ command: "handoff", description: "Continue this session in your terminal" },
|
|
3736
3836
|
{ command: "clear", description: "Clear assistant history" },
|
|
3737
3837
|
{ command: "restart", description: "Restart OpenACP" },
|
|
3738
|
-
{ command: "update", description: "Update to latest version and restart" }
|
|
3838
|
+
{ command: "update", description: "Update to latest version and restart" },
|
|
3839
|
+
{ command: "doctor", description: "Run system diagnostics" }
|
|
3739
3840
|
];
|
|
3740
3841
|
|
|
3741
3842
|
// src/adapters/telegram/permissions.ts
|
|
3742
|
-
import { InlineKeyboard as
|
|
3843
|
+
import { InlineKeyboard as InlineKeyboard7 } from "grammy";
|
|
3743
3844
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3744
|
-
var
|
|
3845
|
+
var log14 = createChildLogger({ module: "telegram-permissions" });
|
|
3745
3846
|
var PermissionHandler = class {
|
|
3746
3847
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
3747
3848
|
this.bot = bot;
|
|
@@ -3758,7 +3859,7 @@ var PermissionHandler = class {
|
|
|
3758
3859
|
requestId: request.id,
|
|
3759
3860
|
options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
|
|
3760
3861
|
});
|
|
3761
|
-
const keyboard = new
|
|
3862
|
+
const keyboard = new InlineKeyboard7();
|
|
3762
3863
|
for (const option of request.options) {
|
|
3763
3864
|
const emoji = option.isAllow ? "\u2705" : "\u274C";
|
|
3764
3865
|
keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
|
|
@@ -3801,7 +3902,7 @@ ${escapeHtml(request.description)}`,
|
|
|
3801
3902
|
}
|
|
3802
3903
|
const session = this.getSession(pending.sessionId);
|
|
3803
3904
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
3804
|
-
|
|
3905
|
+
log14.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
3805
3906
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
3806
3907
|
session.permissionGate.resolve(optionId);
|
|
3807
3908
|
}
|
|
@@ -4183,20 +4284,19 @@ Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetenti
|
|
|
4183
4284
|
`;
|
|
4184
4285
|
|
|
4185
4286
|
// src/adapters/telegram/assistant.ts
|
|
4186
|
-
var
|
|
4287
|
+
var log15 = createChildLogger({ module: "telegram-assistant" });
|
|
4187
4288
|
async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
4188
4289
|
const config = core.configManager.get();
|
|
4189
|
-
|
|
4190
|
-
const session = await core.
|
|
4191
|
-
"telegram",
|
|
4192
|
-
config.defaultAgent,
|
|
4193
|
-
core.configManager.resolveWorkspace(),
|
|
4194
|
-
|
|
4195
|
-
|
|
4290
|
+
log15.info({ agent: config.defaultAgent }, "Creating assistant session...");
|
|
4291
|
+
const session = await core.createSession({
|
|
4292
|
+
channelId: "telegram",
|
|
4293
|
+
agentName: config.defaultAgent,
|
|
4294
|
+
workingDirectory: core.configManager.resolveWorkspace(),
|
|
4295
|
+
initialName: "Assistant"
|
|
4296
|
+
// Prevent auto-naming from triggering after system prompt
|
|
4297
|
+
});
|
|
4196
4298
|
session.threadId = String(assistantTopicId);
|
|
4197
|
-
session.
|
|
4198
|
-
log10.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
4199
|
-
core.wireSessionEvents(session, adapter);
|
|
4299
|
+
log15.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
4200
4300
|
const allRecords = core.sessionManager.listRecords();
|
|
4201
4301
|
const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
4202
4302
|
const statusCounts = /* @__PURE__ */ new Map();
|
|
@@ -4212,9 +4312,9 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
4212
4312
|
};
|
|
4213
4313
|
const systemPrompt = buildAssistantSystemPrompt(ctx);
|
|
4214
4314
|
const ready = session.enqueuePrompt(systemPrompt).then(() => {
|
|
4215
|
-
|
|
4315
|
+
log15.info({ sessionId: session.id }, "Assistant system prompt completed");
|
|
4216
4316
|
}).catch((err) => {
|
|
4217
|
-
|
|
4317
|
+
log15.warn({ err }, "Assistant system prompt failed");
|
|
4218
4318
|
});
|
|
4219
4319
|
return { session, ready };
|
|
4220
4320
|
}
|
|
@@ -4282,8 +4382,10 @@ function buildAssistantSystemPrompt(ctx) {
|
|
|
4282
4382
|
- Execute: \`openacp api cleanup --status <statuses>\`
|
|
4283
4383
|
|
|
4284
4384
|
### Configuration
|
|
4285
|
-
- View: \`openacp api config\`
|
|
4286
|
-
- Update: \`openacp
|
|
4385
|
+
- View: \`openacp config\` (or \`openacp api config\` \u2014 deprecated)
|
|
4386
|
+
- Update: \`openacp config set <key> <value>\`
|
|
4387
|
+
- When user asks about "settings" or "config", use \`openacp config set\` directly
|
|
4388
|
+
- When receiving a delegated request from the Settings menu, ask user for the new value, then apply with \`openacp config set <path> <value>\`
|
|
4287
4389
|
|
|
4288
4390
|
### Restart / Update
|
|
4289
4391
|
- Always ask for confirmation \u2014 these are disruptive actions
|
|
@@ -4313,8 +4415,10 @@ openacp api cleanup --status finished,error
|
|
|
4313
4415
|
|
|
4314
4416
|
# System
|
|
4315
4417
|
openacp api health # System health
|
|
4316
|
-
openacp
|
|
4317
|
-
openacp
|
|
4418
|
+
openacp config # Edit config (interactive)
|
|
4419
|
+
openacp config set <key> <value> # Update config value
|
|
4420
|
+
openacp api config # Show config (deprecated)
|
|
4421
|
+
openacp api config set <key> <value> # Update config (deprecated)
|
|
4318
4422
|
openacp api adapters # List adapters
|
|
4319
4423
|
openacp api tunnel # Tunnel status
|
|
4320
4424
|
openacp api notify "message" # Send notification
|
|
@@ -4347,7 +4451,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
|
|
|
4347
4451
|
}
|
|
4348
4452
|
|
|
4349
4453
|
// src/adapters/telegram/activity.ts
|
|
4350
|
-
var
|
|
4454
|
+
var log16 = createChildLogger({ module: "telegram:activity" });
|
|
4351
4455
|
var THINKING_REFRESH_MS = 15e3;
|
|
4352
4456
|
var THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
4353
4457
|
var ThinkingIndicator = class {
|
|
@@ -4379,7 +4483,7 @@ var ThinkingIndicator = class {
|
|
|
4379
4483
|
this.startRefreshTimer();
|
|
4380
4484
|
}
|
|
4381
4485
|
} catch (err) {
|
|
4382
|
-
|
|
4486
|
+
log16.warn({ err }, "ThinkingIndicator.show() failed");
|
|
4383
4487
|
} finally {
|
|
4384
4488
|
this.sending = false;
|
|
4385
4489
|
}
|
|
@@ -4452,7 +4556,7 @@ var UsageMessage = class {
|
|
|
4452
4556
|
if (result) this.msgId = result.message_id;
|
|
4453
4557
|
}
|
|
4454
4558
|
} catch (err) {
|
|
4455
|
-
|
|
4559
|
+
log16.warn({ err }, "UsageMessage.send() failed");
|
|
4456
4560
|
}
|
|
4457
4561
|
}
|
|
4458
4562
|
getMsgId() {
|
|
@@ -4465,7 +4569,7 @@ var UsageMessage = class {
|
|
|
4465
4569
|
try {
|
|
4466
4570
|
await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
|
|
4467
4571
|
} catch (err) {
|
|
4468
|
-
|
|
4572
|
+
log16.warn({ err }, "UsageMessage.delete() failed");
|
|
4469
4573
|
}
|
|
4470
4574
|
}
|
|
4471
4575
|
};
|
|
@@ -4551,7 +4655,7 @@ var PlanCard = class {
|
|
|
4551
4655
|
if (result) this.msgId = result.message_id;
|
|
4552
4656
|
}
|
|
4553
4657
|
} catch (err) {
|
|
4554
|
-
|
|
4658
|
+
log16.warn({ err }, "PlanCard flush failed");
|
|
4555
4659
|
}
|
|
4556
4660
|
}
|
|
4557
4661
|
};
|
|
@@ -4614,7 +4718,7 @@ var ActivityTracker = class {
|
|
|
4614
4718
|
})
|
|
4615
4719
|
);
|
|
4616
4720
|
} catch (err) {
|
|
4617
|
-
|
|
4721
|
+
log16.warn({ err }, "ActivityTracker.onComplete() Done send failed");
|
|
4618
4722
|
}
|
|
4619
4723
|
}
|
|
4620
4724
|
}
|
|
@@ -4697,7 +4801,7 @@ var TelegramSendQueue = class {
|
|
|
4697
4801
|
|
|
4698
4802
|
// src/adapters/telegram/action-detect.ts
|
|
4699
4803
|
import { nanoid as nanoid3 } from "nanoid";
|
|
4700
|
-
import { InlineKeyboard as
|
|
4804
|
+
import { InlineKeyboard as InlineKeyboard8 } from "grammy";
|
|
4701
4805
|
var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
|
|
4702
4806
|
var CMD_CANCEL_RE = /\/cancel\b/;
|
|
4703
4807
|
var KW_NEW_RE = /(?:create|new)\s+session/i;
|
|
@@ -4744,7 +4848,7 @@ function removeAction(id) {
|
|
|
4744
4848
|
actionMap.delete(id);
|
|
4745
4849
|
}
|
|
4746
4850
|
function buildActionKeyboard(actionId, action) {
|
|
4747
|
-
const keyboard = new
|
|
4851
|
+
const keyboard = new InlineKeyboard8();
|
|
4748
4852
|
if (action.action === "new_session") {
|
|
4749
4853
|
keyboard.text("\u2705 Create session", `a:${actionId}`);
|
|
4750
4854
|
keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
|
|
@@ -4807,52 +4911,492 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
4807
4911
|
});
|
|
4808
4912
|
} catch {
|
|
4809
4913
|
}
|
|
4810
|
-
await startInteractiveNewSession(ctx, core, chatId, action.agent);
|
|
4914
|
+
await startInteractiveNewSession(ctx, core, chatId, action.agent);
|
|
4915
|
+
}
|
|
4916
|
+
} else if (action.action === "cancel_session") {
|
|
4917
|
+
const assistantId = getAssistantSessionId();
|
|
4918
|
+
const cancelled = await executeCancelSession(core, assistantId);
|
|
4919
|
+
if (cancelled) {
|
|
4920
|
+
await ctx.answerCallbackQuery({ text: "\u26D4 Session cancelled" });
|
|
4921
|
+
const originalText = ctx.callbackQuery.message?.text ?? "";
|
|
4922
|
+
try {
|
|
4923
|
+
await ctx.editMessageText(
|
|
4924
|
+
originalText + `
|
|
4925
|
+
|
|
4926
|
+
\u26D4 Session "${cancelled.name ?? cancelled.id}" cancelled`,
|
|
4927
|
+
{ parse_mode: "HTML" }
|
|
4928
|
+
);
|
|
4929
|
+
} catch {
|
|
4930
|
+
await ctx.editMessageReplyMarkup({
|
|
4931
|
+
reply_markup: { inline_keyboard: [] }
|
|
4932
|
+
});
|
|
4933
|
+
}
|
|
4934
|
+
} else {
|
|
4935
|
+
await ctx.answerCallbackQuery({
|
|
4936
|
+
text: "No active session"
|
|
4937
|
+
});
|
|
4938
|
+
try {
|
|
4939
|
+
await ctx.editMessageReplyMarkup({
|
|
4940
|
+
reply_markup: { inline_keyboard: [] }
|
|
4941
|
+
});
|
|
4942
|
+
} catch {
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
} catch {
|
|
4947
|
+
await ctx.answerCallbackQuery({ text: "\u274C Error, try again later" });
|
|
4948
|
+
try {
|
|
4949
|
+
await ctx.editMessageReplyMarkup({
|
|
4950
|
+
reply_markup: { inline_keyboard: [] }
|
|
4951
|
+
});
|
|
4952
|
+
} catch {
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
});
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
// src/adapters/telegram/tool-call-tracker.ts
|
|
4959
|
+
var log17 = createChildLogger({ module: "tool-call-tracker" });
|
|
4960
|
+
var ToolCallTracker = class {
|
|
4961
|
+
constructor(bot, chatId, sendQueue) {
|
|
4962
|
+
this.bot = bot;
|
|
4963
|
+
this.chatId = chatId;
|
|
4964
|
+
this.sendQueue = sendQueue;
|
|
4965
|
+
}
|
|
4966
|
+
sessions = /* @__PURE__ */ new Map();
|
|
4967
|
+
async trackNewCall(sessionId, threadId, meta) {
|
|
4968
|
+
if (!this.sessions.has(sessionId)) {
|
|
4969
|
+
this.sessions.set(sessionId, /* @__PURE__ */ new Map());
|
|
4970
|
+
}
|
|
4971
|
+
let resolveReady;
|
|
4972
|
+
const ready = new Promise((r) => {
|
|
4973
|
+
resolveReady = r;
|
|
4974
|
+
});
|
|
4975
|
+
this.sessions.get(sessionId).set(meta.id, {
|
|
4976
|
+
msgId: 0,
|
|
4977
|
+
name: meta.name,
|
|
4978
|
+
kind: meta.kind,
|
|
4979
|
+
viewerLinks: meta.viewerLinks,
|
|
4980
|
+
viewerFilePath: meta.viewerFilePath,
|
|
4981
|
+
ready
|
|
4982
|
+
});
|
|
4983
|
+
const msg = await this.sendQueue.enqueue(
|
|
4984
|
+
() => this.bot.api.sendMessage(
|
|
4985
|
+
this.chatId,
|
|
4986
|
+
formatToolCall(meta),
|
|
4987
|
+
{
|
|
4988
|
+
message_thread_id: threadId,
|
|
4989
|
+
parse_mode: "HTML",
|
|
4990
|
+
disable_notification: true
|
|
4991
|
+
}
|
|
4992
|
+
)
|
|
4993
|
+
);
|
|
4994
|
+
const toolEntry = this.sessions.get(sessionId).get(meta.id);
|
|
4995
|
+
toolEntry.msgId = msg.message_id;
|
|
4996
|
+
resolveReady();
|
|
4997
|
+
}
|
|
4998
|
+
async updateCall(sessionId, meta) {
|
|
4999
|
+
const toolState = this.sessions.get(sessionId)?.get(meta.id);
|
|
5000
|
+
if (!toolState) return;
|
|
5001
|
+
if (meta.viewerLinks) {
|
|
5002
|
+
toolState.viewerLinks = meta.viewerLinks;
|
|
5003
|
+
log17.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
5004
|
+
}
|
|
5005
|
+
if (meta.viewerFilePath) toolState.viewerFilePath = meta.viewerFilePath;
|
|
5006
|
+
if (meta.name) toolState.name = meta.name;
|
|
5007
|
+
if (meta.kind) toolState.kind = meta.kind;
|
|
5008
|
+
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
5009
|
+
if (!isTerminal) return;
|
|
5010
|
+
await toolState.ready;
|
|
5011
|
+
log17.debug(
|
|
5012
|
+
{
|
|
5013
|
+
toolId: meta.id,
|
|
5014
|
+
status: meta.status,
|
|
5015
|
+
hasViewerLinks: !!toolState.viewerLinks,
|
|
5016
|
+
viewerLinks: toolState.viewerLinks,
|
|
5017
|
+
name: toolState.name,
|
|
5018
|
+
msgId: toolState.msgId
|
|
5019
|
+
},
|
|
5020
|
+
"Tool completed, preparing edit"
|
|
5021
|
+
);
|
|
5022
|
+
const merged = {
|
|
5023
|
+
...meta,
|
|
5024
|
+
name: toolState.name,
|
|
5025
|
+
kind: toolState.kind,
|
|
5026
|
+
viewerLinks: toolState.viewerLinks,
|
|
5027
|
+
viewerFilePath: toolState.viewerFilePath
|
|
5028
|
+
};
|
|
5029
|
+
const formattedText = formatToolUpdate(merged);
|
|
5030
|
+
try {
|
|
5031
|
+
await this.sendQueue.enqueue(
|
|
5032
|
+
() => this.bot.api.editMessageText(
|
|
5033
|
+
this.chatId,
|
|
5034
|
+
toolState.msgId,
|
|
5035
|
+
formattedText,
|
|
5036
|
+
{ parse_mode: "HTML" }
|
|
5037
|
+
)
|
|
5038
|
+
);
|
|
5039
|
+
} catch (err) {
|
|
5040
|
+
log17.warn(
|
|
5041
|
+
{
|
|
5042
|
+
err,
|
|
5043
|
+
msgId: toolState.msgId,
|
|
5044
|
+
textLen: formattedText.length,
|
|
5045
|
+
hasViewerLinks: !!merged.viewerLinks
|
|
5046
|
+
},
|
|
5047
|
+
"Tool update edit failed"
|
|
5048
|
+
);
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
cleanup(sessionId) {
|
|
5052
|
+
this.sessions.delete(sessionId);
|
|
5053
|
+
}
|
|
5054
|
+
};
|
|
5055
|
+
|
|
5056
|
+
// src/adapters/telegram/streaming.ts
|
|
5057
|
+
var FLUSH_INTERVAL = 5e3;
|
|
5058
|
+
var MessageDraft = class {
|
|
5059
|
+
constructor(bot, chatId, threadId, sendQueue, sessionId) {
|
|
5060
|
+
this.bot = bot;
|
|
5061
|
+
this.chatId = chatId;
|
|
5062
|
+
this.threadId = threadId;
|
|
5063
|
+
this.sendQueue = sendQueue;
|
|
5064
|
+
this.sessionId = sessionId;
|
|
5065
|
+
}
|
|
5066
|
+
buffer = "";
|
|
5067
|
+
messageId;
|
|
5068
|
+
firstFlushPending = false;
|
|
5069
|
+
flushTimer;
|
|
5070
|
+
flushPromise = Promise.resolve();
|
|
5071
|
+
lastSentBuffer = "";
|
|
5072
|
+
displayTruncated = false;
|
|
5073
|
+
append(text) {
|
|
5074
|
+
if (!text) return;
|
|
5075
|
+
this.buffer += text;
|
|
5076
|
+
this.scheduleFlush();
|
|
5077
|
+
}
|
|
5078
|
+
scheduleFlush() {
|
|
5079
|
+
if (this.flushTimer) return;
|
|
5080
|
+
this.flushTimer = setTimeout(() => {
|
|
5081
|
+
this.flushTimer = void 0;
|
|
5082
|
+
this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
|
|
5083
|
+
});
|
|
5084
|
+
}, FLUSH_INTERVAL);
|
|
5085
|
+
}
|
|
5086
|
+
async flush() {
|
|
5087
|
+
if (!this.buffer) return;
|
|
5088
|
+
if (this.firstFlushPending) return;
|
|
5089
|
+
const snapshot = this.buffer;
|
|
5090
|
+
let html = markdownToTelegramHtml(snapshot);
|
|
5091
|
+
if (!html) return;
|
|
5092
|
+
let truncated = false;
|
|
5093
|
+
if (html.length > 4096) {
|
|
5094
|
+
const ratio = 4e3 / html.length;
|
|
5095
|
+
const targetLen = Math.floor(snapshot.length * ratio);
|
|
5096
|
+
let cutAt = snapshot.lastIndexOf("\n", targetLen);
|
|
5097
|
+
if (cutAt < targetLen * 0.5) cutAt = targetLen;
|
|
5098
|
+
html = markdownToTelegramHtml(snapshot.slice(0, cutAt) + "\n\u2026");
|
|
5099
|
+
truncated = true;
|
|
5100
|
+
if (html.length > 4096) {
|
|
5101
|
+
html = html.slice(0, 4090) + "\n\u2026";
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
if (!this.messageId) {
|
|
5105
|
+
this.firstFlushPending = true;
|
|
5106
|
+
try {
|
|
5107
|
+
const result = await this.sendQueue.enqueue(
|
|
5108
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
5109
|
+
message_thread_id: this.threadId,
|
|
5110
|
+
parse_mode: "HTML",
|
|
5111
|
+
disable_notification: true
|
|
5112
|
+
}),
|
|
5113
|
+
{ type: "other" }
|
|
5114
|
+
);
|
|
5115
|
+
if (result) {
|
|
5116
|
+
this.messageId = result.message_id;
|
|
5117
|
+
if (!truncated) {
|
|
5118
|
+
this.lastSentBuffer = snapshot;
|
|
5119
|
+
this.displayTruncated = false;
|
|
5120
|
+
} else {
|
|
5121
|
+
this.displayTruncated = true;
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
} catch {
|
|
5125
|
+
} finally {
|
|
5126
|
+
this.firstFlushPending = false;
|
|
5127
|
+
}
|
|
5128
|
+
} else {
|
|
5129
|
+
try {
|
|
5130
|
+
const result = await this.sendQueue.enqueue(
|
|
5131
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
5132
|
+
parse_mode: "HTML"
|
|
5133
|
+
}),
|
|
5134
|
+
{ type: "text", key: this.sessionId }
|
|
5135
|
+
);
|
|
5136
|
+
if (result !== void 0) {
|
|
5137
|
+
if (!truncated) {
|
|
5138
|
+
this.lastSentBuffer = snapshot;
|
|
5139
|
+
this.displayTruncated = false;
|
|
5140
|
+
} else {
|
|
5141
|
+
this.displayTruncated = true;
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
} catch {
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
async finalize() {
|
|
5149
|
+
if (this.flushTimer) {
|
|
5150
|
+
clearTimeout(this.flushTimer);
|
|
5151
|
+
this.flushTimer = void 0;
|
|
5152
|
+
}
|
|
5153
|
+
await this.flushPromise;
|
|
5154
|
+
if (!this.buffer) return this.messageId;
|
|
5155
|
+
if (this.messageId && this.buffer === this.lastSentBuffer && !this.displayTruncated) {
|
|
5156
|
+
return this.messageId;
|
|
5157
|
+
}
|
|
5158
|
+
const fullHtml = markdownToTelegramHtml(this.buffer);
|
|
5159
|
+
if (fullHtml.length <= 4096) {
|
|
5160
|
+
try {
|
|
5161
|
+
if (this.messageId) {
|
|
5162
|
+
await this.sendQueue.enqueue(
|
|
5163
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, fullHtml, {
|
|
5164
|
+
parse_mode: "HTML"
|
|
5165
|
+
}),
|
|
5166
|
+
{ type: "other" }
|
|
5167
|
+
);
|
|
5168
|
+
} else {
|
|
5169
|
+
const msg = await this.sendQueue.enqueue(
|
|
5170
|
+
() => this.bot.api.sendMessage(this.chatId, fullHtml, {
|
|
5171
|
+
message_thread_id: this.threadId,
|
|
5172
|
+
parse_mode: "HTML",
|
|
5173
|
+
disable_notification: true
|
|
5174
|
+
}),
|
|
5175
|
+
{ type: "other" }
|
|
5176
|
+
);
|
|
5177
|
+
if (msg) this.messageId = msg.message_id;
|
|
5178
|
+
}
|
|
5179
|
+
return this.messageId;
|
|
5180
|
+
} catch {
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
const mdChunks = splitMessage(this.buffer);
|
|
5184
|
+
for (let i = 0; i < mdChunks.length; i++) {
|
|
5185
|
+
const html = markdownToTelegramHtml(mdChunks[i]);
|
|
5186
|
+
try {
|
|
5187
|
+
if (i === 0 && this.messageId) {
|
|
5188
|
+
await this.sendQueue.enqueue(
|
|
5189
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
5190
|
+
parse_mode: "HTML"
|
|
5191
|
+
}),
|
|
5192
|
+
{ type: "other" }
|
|
5193
|
+
);
|
|
5194
|
+
} else {
|
|
5195
|
+
const msg = await this.sendQueue.enqueue(
|
|
5196
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
5197
|
+
message_thread_id: this.threadId,
|
|
5198
|
+
parse_mode: "HTML",
|
|
5199
|
+
disable_notification: true
|
|
5200
|
+
}),
|
|
5201
|
+
{ type: "other" }
|
|
5202
|
+
);
|
|
5203
|
+
if (msg) {
|
|
5204
|
+
this.messageId = msg.message_id;
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
} catch {
|
|
5208
|
+
try {
|
|
5209
|
+
if (i === 0 && this.messageId) {
|
|
5210
|
+
await this.sendQueue.enqueue(
|
|
5211
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, mdChunks[i].slice(0, 4096)),
|
|
5212
|
+
{ type: "other" }
|
|
5213
|
+
);
|
|
5214
|
+
} else {
|
|
5215
|
+
const msg = await this.sendQueue.enqueue(
|
|
5216
|
+
() => this.bot.api.sendMessage(this.chatId, mdChunks[i].slice(0, 4096), {
|
|
5217
|
+
message_thread_id: this.threadId,
|
|
5218
|
+
disable_notification: true
|
|
5219
|
+
}),
|
|
5220
|
+
{ type: "other" }
|
|
5221
|
+
);
|
|
5222
|
+
if (msg) {
|
|
5223
|
+
this.messageId = msg.message_id;
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
} catch {
|
|
4811
5227
|
}
|
|
4812
|
-
}
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
originalText + `
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
return this.messageId;
|
|
5231
|
+
}
|
|
5232
|
+
getMessageId() {
|
|
5233
|
+
return this.messageId;
|
|
5234
|
+
}
|
|
5235
|
+
};
|
|
4821
5236
|
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
5237
|
+
// src/adapters/telegram/draft-manager.ts
|
|
5238
|
+
var DraftManager = class {
|
|
5239
|
+
constructor(bot, chatId, sendQueue) {
|
|
5240
|
+
this.bot = bot;
|
|
5241
|
+
this.chatId = chatId;
|
|
5242
|
+
this.sendQueue = sendQueue;
|
|
5243
|
+
}
|
|
5244
|
+
drafts = /* @__PURE__ */ new Map();
|
|
5245
|
+
textBuffers = /* @__PURE__ */ new Map();
|
|
5246
|
+
getOrCreate(sessionId, threadId) {
|
|
5247
|
+
let draft = this.drafts.get(sessionId);
|
|
5248
|
+
if (!draft) {
|
|
5249
|
+
draft = new MessageDraft(
|
|
5250
|
+
this.bot,
|
|
5251
|
+
this.chatId,
|
|
5252
|
+
threadId,
|
|
5253
|
+
this.sendQueue,
|
|
5254
|
+
sessionId
|
|
5255
|
+
);
|
|
5256
|
+
this.drafts.set(sessionId, draft);
|
|
5257
|
+
}
|
|
5258
|
+
return draft;
|
|
5259
|
+
}
|
|
5260
|
+
hasDraft(sessionId) {
|
|
5261
|
+
return this.drafts.has(sessionId);
|
|
5262
|
+
}
|
|
5263
|
+
appendText(sessionId, text) {
|
|
5264
|
+
this.textBuffers.set(
|
|
5265
|
+
sessionId,
|
|
5266
|
+
(this.textBuffers.get(sessionId) ?? "") + text
|
|
5267
|
+
);
|
|
5268
|
+
}
|
|
5269
|
+
/**
|
|
5270
|
+
* Finalize the current draft and return the message ID.
|
|
5271
|
+
* Optionally detects actions in assistant responses.
|
|
5272
|
+
*/
|
|
5273
|
+
async finalize(sessionId, assistantSessionId) {
|
|
5274
|
+
const draft = this.drafts.get(sessionId);
|
|
5275
|
+
if (!draft) return;
|
|
5276
|
+
this.drafts.delete(sessionId);
|
|
5277
|
+
const finalMsgId = await draft.finalize();
|
|
5278
|
+
if (assistantSessionId && sessionId === assistantSessionId) {
|
|
5279
|
+
const fullText = this.textBuffers.get(sessionId);
|
|
5280
|
+
this.textBuffers.delete(sessionId);
|
|
5281
|
+
if (fullText && finalMsgId) {
|
|
5282
|
+
const detected = detectAction(fullText);
|
|
5283
|
+
if (detected) {
|
|
5284
|
+
const actionId = storeAction(detected);
|
|
5285
|
+
const keyboard = buildActionKeyboard(actionId, detected);
|
|
4834
5286
|
try {
|
|
4835
|
-
await
|
|
4836
|
-
|
|
4837
|
-
|
|
5287
|
+
await this.bot.api.editMessageReplyMarkup(
|
|
5288
|
+
this.chatId,
|
|
5289
|
+
finalMsgId,
|
|
5290
|
+
{ reply_markup: keyboard }
|
|
5291
|
+
);
|
|
4838
5292
|
} catch {
|
|
4839
5293
|
}
|
|
4840
5294
|
}
|
|
4841
5295
|
}
|
|
4842
|
-
}
|
|
4843
|
-
|
|
5296
|
+
} else {
|
|
5297
|
+
this.textBuffers.delete(sessionId);
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
cleanup(sessionId) {
|
|
5301
|
+
this.drafts.delete(sessionId);
|
|
5302
|
+
this.textBuffers.delete(sessionId);
|
|
5303
|
+
}
|
|
5304
|
+
};
|
|
5305
|
+
|
|
5306
|
+
// src/adapters/telegram/skill-command-manager.ts
|
|
5307
|
+
var log18 = createChildLogger({ module: "skill-commands" });
|
|
5308
|
+
var SkillCommandManager = class {
|
|
5309
|
+
// sessionId → pinned msgId
|
|
5310
|
+
constructor(bot, chatId, sendQueue, sessionManager) {
|
|
5311
|
+
this.bot = bot;
|
|
5312
|
+
this.chatId = chatId;
|
|
5313
|
+
this.sendQueue = sendQueue;
|
|
5314
|
+
this.sessionManager = sessionManager;
|
|
5315
|
+
}
|
|
5316
|
+
messages = /* @__PURE__ */ new Map();
|
|
5317
|
+
async send(sessionId, threadId, commands) {
|
|
5318
|
+
if (!this.messages.has(sessionId)) {
|
|
5319
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
5320
|
+
const platform = record?.platform;
|
|
5321
|
+
if (platform?.skillMsgId) {
|
|
5322
|
+
this.messages.set(sessionId, platform.skillMsgId);
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
if (commands.length === 0) {
|
|
5326
|
+
await this.cleanup(sessionId);
|
|
5327
|
+
return;
|
|
5328
|
+
}
|
|
5329
|
+
const messages = buildSkillMessages(commands);
|
|
5330
|
+
const existingMsgId = this.messages.get(sessionId);
|
|
5331
|
+
if (existingMsgId) {
|
|
4844
5332
|
try {
|
|
4845
|
-
await
|
|
4846
|
-
|
|
5333
|
+
await this.bot.api.editMessageText(
|
|
5334
|
+
this.chatId,
|
|
5335
|
+
existingMsgId,
|
|
5336
|
+
messages[0],
|
|
5337
|
+
{ parse_mode: "HTML" }
|
|
5338
|
+
);
|
|
5339
|
+
return;
|
|
5340
|
+
} catch (err) {
|
|
5341
|
+
const msg = err instanceof Error ? err.message : "";
|
|
5342
|
+
if (msg.includes("message is not modified")) return;
|
|
5343
|
+
try {
|
|
5344
|
+
await this.bot.api.deleteMessage(this.chatId, existingMsgId);
|
|
5345
|
+
} catch {
|
|
5346
|
+
}
|
|
5347
|
+
this.messages.delete(sessionId);
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
try {
|
|
5351
|
+
let firstMsgId;
|
|
5352
|
+
for (const text of messages) {
|
|
5353
|
+
const msg = await this.sendQueue.enqueue(
|
|
5354
|
+
() => this.bot.api.sendMessage(this.chatId, text, {
|
|
5355
|
+
message_thread_id: threadId,
|
|
5356
|
+
parse_mode: "HTML",
|
|
5357
|
+
disable_notification: true
|
|
5358
|
+
})
|
|
5359
|
+
);
|
|
5360
|
+
if (!firstMsgId) firstMsgId = msg.message_id;
|
|
5361
|
+
}
|
|
5362
|
+
this.messages.set(sessionId, firstMsgId);
|
|
5363
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
5364
|
+
if (record) {
|
|
5365
|
+
await this.sessionManager.patchRecord(sessionId, {
|
|
5366
|
+
platform: { ...record.platform, skillMsgId: firstMsgId }
|
|
4847
5367
|
});
|
|
4848
|
-
} catch {
|
|
4849
5368
|
}
|
|
5369
|
+
await this.bot.api.pinChatMessage(this.chatId, firstMsgId, {
|
|
5370
|
+
disable_notification: true
|
|
5371
|
+
});
|
|
5372
|
+
} catch (err) {
|
|
5373
|
+
log18.error({ err, sessionId }, "Failed to send skill commands");
|
|
4850
5374
|
}
|
|
4851
|
-
}
|
|
4852
|
-
|
|
5375
|
+
}
|
|
5376
|
+
async cleanup(sessionId) {
|
|
5377
|
+
const msgId = this.messages.get(sessionId);
|
|
5378
|
+
if (!msgId) return;
|
|
5379
|
+
try {
|
|
5380
|
+
await this.bot.api.editMessageText(
|
|
5381
|
+
this.chatId,
|
|
5382
|
+
msgId,
|
|
5383
|
+
"\u{1F6E0} <i>Session ended</i>",
|
|
5384
|
+
{ parse_mode: "HTML" }
|
|
5385
|
+
);
|
|
5386
|
+
await this.bot.api.unpinChatMessage(this.chatId, msgId);
|
|
5387
|
+
} catch {
|
|
5388
|
+
}
|
|
5389
|
+
this.messages.delete(sessionId);
|
|
5390
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
5391
|
+
if (record) {
|
|
5392
|
+
const { skillMsgId: _removed, ...rest } = record.platform;
|
|
5393
|
+
await this.sessionManager.patchRecord(sessionId, { platform: rest });
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
};
|
|
4853
5397
|
|
|
4854
5398
|
// src/adapters/telegram/adapter.ts
|
|
4855
|
-
var
|
|
5399
|
+
var log19 = createChildLogger({ module: "telegram" });
|
|
4856
5400
|
function patchedFetch(input, init) {
|
|
4857
5401
|
if (init?.signal && !(init.signal instanceof AbortSignal)) {
|
|
4858
5402
|
const nativeController = new AbortController();
|
|
@@ -4869,18 +5413,16 @@ function patchedFetch(input, init) {
|
|
|
4869
5413
|
var TelegramAdapter = class extends ChannelAdapter {
|
|
4870
5414
|
bot;
|
|
4871
5415
|
telegramConfig;
|
|
4872
|
-
sessionDrafts = /* @__PURE__ */ new Map();
|
|
4873
|
-
sessionTextBuffers = /* @__PURE__ */ new Map();
|
|
4874
|
-
toolCallMessages = /* @__PURE__ */ new Map();
|
|
4875
|
-
// sessionId → (toolCallId → state)
|
|
4876
5416
|
permissionHandler;
|
|
4877
5417
|
assistantSession = null;
|
|
4878
5418
|
assistantInitializing = false;
|
|
4879
5419
|
notificationTopicId;
|
|
4880
5420
|
assistantTopicId;
|
|
4881
|
-
skillMessages = /* @__PURE__ */ new Map();
|
|
4882
|
-
// sessionId → pinned messageId
|
|
4883
5421
|
sendQueue = new TelegramSendQueue(3e3);
|
|
5422
|
+
// Extracted managers
|
|
5423
|
+
toolTracker;
|
|
5424
|
+
draftManager;
|
|
5425
|
+
skillManager;
|
|
4884
5426
|
sessionTrackers = /* @__PURE__ */ new Map();
|
|
4885
5427
|
getOrCreateTracker(sessionId, threadId) {
|
|
4886
5428
|
let tracker = this.sessionTrackers.get(sessionId);
|
|
@@ -4901,9 +5443,17 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4901
5443
|
}
|
|
4902
5444
|
async start() {
|
|
4903
5445
|
this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
|
|
5446
|
+
this.toolTracker = new ToolCallTracker(this.bot, this.telegramConfig.chatId, this.sendQueue);
|
|
5447
|
+
this.draftManager = new DraftManager(this.bot, this.telegramConfig.chatId, this.sendQueue);
|
|
5448
|
+
this.skillManager = new SkillCommandManager(
|
|
5449
|
+
this.bot,
|
|
5450
|
+
this.telegramConfig.chatId,
|
|
5451
|
+
this.sendQueue,
|
|
5452
|
+
this.core.sessionManager
|
|
5453
|
+
);
|
|
4904
5454
|
this.bot.catch((err) => {
|
|
4905
5455
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
4906
|
-
|
|
5456
|
+
log19.error({ err: rootCause }, "Telegram bot error");
|
|
4907
5457
|
});
|
|
4908
5458
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
4909
5459
|
const maxRetries = 3;
|
|
@@ -4917,7 +5467,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4917
5467
|
if (rateLimitedMethods.includes(method)) {
|
|
4918
5468
|
this.sendQueue.onRateLimited();
|
|
4919
5469
|
}
|
|
4920
|
-
|
|
5470
|
+
log19.warn(
|
|
4921
5471
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
4922
5472
|
"Rate limited by Telegram, retrying"
|
|
4923
5473
|
);
|
|
@@ -4969,11 +5519,18 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
4969
5519
|
() => this.assistantSession?.id
|
|
4970
5520
|
);
|
|
4971
5521
|
setupIntegrateCallbacks(this.bot, this.core);
|
|
4972
|
-
|
|
5522
|
+
setupAllCallbacks(
|
|
4973
5523
|
this.bot,
|
|
4974
5524
|
this.core,
|
|
4975
5525
|
this.telegramConfig.chatId,
|
|
4976
|
-
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId }
|
|
5526
|
+
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
|
|
5527
|
+
() => {
|
|
5528
|
+
if (!this.assistantSession) return void 0;
|
|
5529
|
+
return {
|
|
5530
|
+
topicId: this.assistantTopicId,
|
|
5531
|
+
enqueuePrompt: (p) => this.assistantSession.enqueuePrompt(p)
|
|
5532
|
+
};
|
|
5533
|
+
}
|
|
4977
5534
|
);
|
|
4978
5535
|
setupCommands(
|
|
4979
5536
|
this.bot,
|
|
@@ -5042,7 +5599,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5042
5599
|
this.setupRoutes();
|
|
5043
5600
|
this.bot.start({
|
|
5044
5601
|
allowed_updates: ["message", "callback_query"],
|
|
5045
|
-
onStart: () =>
|
|
5602
|
+
onStart: () => log19.info(
|
|
5046
5603
|
{ chatId: this.telegramConfig.chatId },
|
|
5047
5604
|
"Telegram bot started"
|
|
5048
5605
|
)
|
|
@@ -5064,10 +5621,10 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5064
5621
|
reply_markup: buildMenuKeyboard()
|
|
5065
5622
|
});
|
|
5066
5623
|
} catch (err) {
|
|
5067
|
-
|
|
5624
|
+
log19.warn({ err }, "Failed to send welcome message");
|
|
5068
5625
|
}
|
|
5069
5626
|
try {
|
|
5070
|
-
|
|
5627
|
+
log19.info("Spawning assistant session...");
|
|
5071
5628
|
const { session, ready } = await spawnAssistant(
|
|
5072
5629
|
this.core,
|
|
5073
5630
|
this,
|
|
@@ -5075,13 +5632,13 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5075
5632
|
);
|
|
5076
5633
|
this.assistantSession = session;
|
|
5077
5634
|
this.assistantInitializing = true;
|
|
5078
|
-
|
|
5635
|
+
log19.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
|
|
5079
5636
|
ready.then(() => {
|
|
5080
5637
|
this.assistantInitializing = false;
|
|
5081
|
-
|
|
5638
|
+
log19.info({ sessionId: session.id }, "Assistant ready for user messages");
|
|
5082
5639
|
});
|
|
5083
5640
|
} catch (err) {
|
|
5084
|
-
|
|
5641
|
+
log19.error({ err }, "Failed to spawn assistant");
|
|
5085
5642
|
this.bot.api.sendMessage(
|
|
5086
5643
|
this.telegramConfig.chatId,
|
|
5087
5644
|
`\u26A0\uFE0F <b>Failed to start assistant session.</b>
|
|
@@ -5097,7 +5654,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5097
5654
|
await this.assistantSession.destroy();
|
|
5098
5655
|
}
|
|
5099
5656
|
await this.bot.stop();
|
|
5100
|
-
|
|
5657
|
+
log19.info("Telegram bot stopped");
|
|
5101
5658
|
}
|
|
5102
5659
|
setupRoutes() {
|
|
5103
5660
|
this.bot.on("message:text", async (ctx) => {
|
|
@@ -5119,16 +5676,16 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5119
5676
|
await ctx.reply("\u26A0\uFE0F Assistant is not available yet. Please try again shortly.", { parse_mode: "HTML" });
|
|
5120
5677
|
return;
|
|
5121
5678
|
}
|
|
5122
|
-
await this.
|
|
5679
|
+
await this.draftManager.finalize(this.assistantSession.id, this.assistantSession.id);
|
|
5123
5680
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
5124
5681
|
});
|
|
5125
5682
|
handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
|
|
5126
|
-
(err) =>
|
|
5683
|
+
(err) => log19.error({ err }, "Assistant error")
|
|
5127
5684
|
);
|
|
5128
5685
|
return;
|
|
5129
5686
|
}
|
|
5130
5687
|
const sessionId = this.core.sessionManager.getSessionByThread("telegram", String(threadId))?.id;
|
|
5131
|
-
if (sessionId) await this.
|
|
5688
|
+
if (sessionId) await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5132
5689
|
if (sessionId) {
|
|
5133
5690
|
const tracker = this.sessionTrackers.get(sessionId);
|
|
5134
5691
|
if (tracker) await tracker.onNewPrompt();
|
|
@@ -5140,19 +5697,17 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5140
5697
|
threadId: String(threadId),
|
|
5141
5698
|
userId: String(ctx.from.id),
|
|
5142
5699
|
text: ctx.message.text
|
|
5143
|
-
}).catch((err) =>
|
|
5700
|
+
}).catch((err) => log19.error({ err }, "handleMessage error"));
|
|
5144
5701
|
});
|
|
5145
5702
|
}
|
|
5146
5703
|
// --- ChannelAdapter implementations ---
|
|
5147
5704
|
async sendMessage(sessionId, content) {
|
|
5148
5705
|
if (this.assistantInitializing && sessionId === this.assistantSession?.id) return;
|
|
5149
|
-
const session = this.core.sessionManager.getSession(
|
|
5150
|
-
sessionId
|
|
5151
|
-
);
|
|
5706
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
5152
5707
|
if (!session) return;
|
|
5153
5708
|
const threadId = Number(session.threadId);
|
|
5154
5709
|
if (!threadId || isNaN(threadId)) {
|
|
5155
|
-
|
|
5710
|
+
log19.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
|
|
5156
5711
|
return;
|
|
5157
5712
|
}
|
|
5158
5713
|
switch (content.type) {
|
|
@@ -5162,105 +5717,32 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5162
5717
|
break;
|
|
5163
5718
|
}
|
|
5164
5719
|
case "text": {
|
|
5165
|
-
|
|
5166
|
-
if (!draft) {
|
|
5720
|
+
if (!this.draftManager.hasDraft(sessionId)) {
|
|
5167
5721
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
5168
5722
|
await tracker.onTextStart();
|
|
5169
|
-
draft = new MessageDraft(
|
|
5170
|
-
this.bot,
|
|
5171
|
-
this.telegramConfig.chatId,
|
|
5172
|
-
threadId,
|
|
5173
|
-
this.sendQueue,
|
|
5174
|
-
sessionId
|
|
5175
|
-
);
|
|
5176
|
-
this.sessionDrafts.set(sessionId, draft);
|
|
5177
5723
|
}
|
|
5724
|
+
const draft = this.draftManager.getOrCreate(sessionId, threadId);
|
|
5178
5725
|
draft.append(content.text);
|
|
5179
|
-
this.
|
|
5180
|
-
sessionId,
|
|
5181
|
-
(this.sessionTextBuffers.get(sessionId) ?? "") + content.text
|
|
5182
|
-
);
|
|
5726
|
+
this.draftManager.appendText(sessionId, content.text);
|
|
5183
5727
|
break;
|
|
5184
5728
|
}
|
|
5185
5729
|
case "tool_call": {
|
|
5186
5730
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
5187
5731
|
await tracker.onToolCall();
|
|
5188
|
-
await this.
|
|
5732
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5189
5733
|
const meta = content.metadata;
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
let resolveReady;
|
|
5194
|
-
const ready = new Promise((r) => {
|
|
5195
|
-
resolveReady = r;
|
|
5196
|
-
});
|
|
5197
|
-
this.toolCallMessages.get(sessionId).set(meta.id, {
|
|
5198
|
-
msgId: 0,
|
|
5199
|
-
name: meta.name,
|
|
5200
|
-
kind: meta.kind,
|
|
5201
|
-
viewerLinks: meta.viewerLinks,
|
|
5202
|
-
viewerFilePath: content.metadata?.viewerFilePath,
|
|
5203
|
-
ready
|
|
5734
|
+
await this.toolTracker.trackNewCall(sessionId, threadId, {
|
|
5735
|
+
...meta,
|
|
5736
|
+
viewerFilePath: content.metadata?.viewerFilePath
|
|
5204
5737
|
});
|
|
5205
|
-
const msg = await this.sendQueue.enqueue(
|
|
5206
|
-
() => this.bot.api.sendMessage(
|
|
5207
|
-
this.telegramConfig.chatId,
|
|
5208
|
-
formatToolCall(meta),
|
|
5209
|
-
{
|
|
5210
|
-
message_thread_id: threadId,
|
|
5211
|
-
parse_mode: "HTML",
|
|
5212
|
-
disable_notification: true
|
|
5213
|
-
}
|
|
5214
|
-
)
|
|
5215
|
-
);
|
|
5216
|
-
const toolEntry = this.toolCallMessages.get(sessionId).get(meta.id);
|
|
5217
|
-
toolEntry.msgId = msg.message_id;
|
|
5218
|
-
resolveReady();
|
|
5219
5738
|
break;
|
|
5220
5739
|
}
|
|
5221
5740
|
case "tool_update": {
|
|
5222
5741
|
const meta = content.metadata;
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
log12.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
5228
|
-
}
|
|
5229
|
-
const viewerFilePath = content.metadata?.viewerFilePath;
|
|
5230
|
-
if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
|
|
5231
|
-
if (meta.name) toolState.name = meta.name;
|
|
5232
|
-
if (meta.kind) toolState.kind = meta.kind;
|
|
5233
|
-
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
5234
|
-
if (!isTerminal && !meta.viewerLinks) break;
|
|
5235
|
-
await toolState.ready;
|
|
5236
|
-
log12.debug(
|
|
5237
|
-
{ toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
|
|
5238
|
-
"Tool completed, preparing edit"
|
|
5239
|
-
);
|
|
5240
|
-
const merged = {
|
|
5241
|
-
...meta,
|
|
5242
|
-
name: toolState.name,
|
|
5243
|
-
kind: toolState.kind,
|
|
5244
|
-
viewerLinks: toolState.viewerLinks,
|
|
5245
|
-
viewerFilePath: toolState.viewerFilePath
|
|
5246
|
-
};
|
|
5247
|
-
const formattedText = formatToolUpdate(merged);
|
|
5248
|
-
try {
|
|
5249
|
-
await this.sendQueue.enqueue(
|
|
5250
|
-
() => this.bot.api.editMessageText(
|
|
5251
|
-
this.telegramConfig.chatId,
|
|
5252
|
-
toolState.msgId,
|
|
5253
|
-
formattedText,
|
|
5254
|
-
{ parse_mode: "HTML" }
|
|
5255
|
-
)
|
|
5256
|
-
);
|
|
5257
|
-
} catch (err) {
|
|
5258
|
-
log12.warn(
|
|
5259
|
-
{ err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
|
|
5260
|
-
"Tool update edit failed"
|
|
5261
|
-
);
|
|
5262
|
-
}
|
|
5263
|
-
}
|
|
5742
|
+
await this.toolTracker.updateCall(sessionId, {
|
|
5743
|
+
...meta,
|
|
5744
|
+
viewerFilePath: content.metadata?.viewerFilePath
|
|
5745
|
+
});
|
|
5264
5746
|
break;
|
|
5265
5747
|
}
|
|
5266
5748
|
case "plan": {
|
|
@@ -5277,7 +5759,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5277
5759
|
}
|
|
5278
5760
|
case "usage": {
|
|
5279
5761
|
const meta = content.metadata;
|
|
5280
|
-
await this.
|
|
5762
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5281
5763
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
5282
5764
|
await tracker.sendUsage(meta);
|
|
5283
5765
|
if (this.notificationTopicId && sessionId !== this.assistantSession?.id) {
|
|
@@ -5303,10 +5785,10 @@ Task completed.
|
|
|
5303
5785
|
break;
|
|
5304
5786
|
}
|
|
5305
5787
|
case "session_end": {
|
|
5306
|
-
await this.
|
|
5307
|
-
this.
|
|
5308
|
-
this.
|
|
5309
|
-
await this.
|
|
5788
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5789
|
+
this.draftManager.cleanup(sessionId);
|
|
5790
|
+
this.toolTracker.cleanup(sessionId);
|
|
5791
|
+
await this.skillManager.cleanup(sessionId);
|
|
5310
5792
|
const tracker = this.sessionTrackers.get(sessionId);
|
|
5311
5793
|
if (tracker) {
|
|
5312
5794
|
await tracker.onComplete();
|
|
@@ -5328,7 +5810,7 @@ Task completed.
|
|
|
5328
5810
|
break;
|
|
5329
5811
|
}
|
|
5330
5812
|
case "error": {
|
|
5331
|
-
await this.
|
|
5813
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5332
5814
|
const tracker = this.sessionTrackers.get(sessionId);
|
|
5333
5815
|
if (tracker) {
|
|
5334
5816
|
tracker.destroy();
|
|
@@ -5350,15 +5832,13 @@ Task completed.
|
|
|
5350
5832
|
}
|
|
5351
5833
|
}
|
|
5352
5834
|
async sendPermissionRequest(sessionId, request) {
|
|
5353
|
-
|
|
5354
|
-
const session = this.core.sessionManager.getSession(
|
|
5355
|
-
sessionId
|
|
5356
|
-
);
|
|
5835
|
+
log19.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
5836
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
5357
5837
|
if (!session) return;
|
|
5358
5838
|
if (request.description.includes("openacp")) {
|
|
5359
5839
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
5360
5840
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
5361
|
-
|
|
5841
|
+
log19.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
|
|
5362
5842
|
session.permissionGate.resolve(allowOption.id);
|
|
5363
5843
|
}
|
|
5364
5844
|
return;
|
|
@@ -5366,7 +5846,7 @@ Task completed.
|
|
|
5366
5846
|
if (session.dangerousMode) {
|
|
5367
5847
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
5368
5848
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
5369
|
-
|
|
5849
|
+
log19.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
|
|
5370
5850
|
session.permissionGate.resolve(allowOption.id);
|
|
5371
5851
|
}
|
|
5372
5852
|
return;
|
|
@@ -5377,7 +5857,7 @@ Task completed.
|
|
|
5377
5857
|
}
|
|
5378
5858
|
async sendNotification(notification) {
|
|
5379
5859
|
if (notification.sessionId === this.assistantSession?.id) return;
|
|
5380
|
-
|
|
5860
|
+
log19.info(
|
|
5381
5861
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
5382
5862
|
"Notification sent"
|
|
5383
5863
|
);
|
|
@@ -5413,15 +5893,13 @@ Task completed.
|
|
|
5413
5893
|
);
|
|
5414
5894
|
}
|
|
5415
5895
|
async createSessionThread(sessionId, name) {
|
|
5416
|
-
|
|
5896
|
+
log19.info({ sessionId, name }, "Session topic created");
|
|
5417
5897
|
return String(
|
|
5418
5898
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
5419
5899
|
);
|
|
5420
5900
|
}
|
|
5421
5901
|
async renameSessionThread(sessionId, newName) {
|
|
5422
|
-
const session = this.core.sessionManager.getSession(
|
|
5423
|
-
sessionId
|
|
5424
|
-
);
|
|
5902
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
5425
5903
|
if (!session) return;
|
|
5426
5904
|
await renameSessionTopic(
|
|
5427
5905
|
this.bot,
|
|
@@ -5429,10 +5907,7 @@ Task completed.
|
|
|
5429
5907
|
Number(session.threadId),
|
|
5430
5908
|
newName
|
|
5431
5909
|
);
|
|
5432
|
-
await this.core.sessionManager.
|
|
5433
|
-
sessionId,
|
|
5434
|
-
newName
|
|
5435
|
-
);
|
|
5910
|
+
await this.core.sessionManager.patchRecord(sessionId, { name: newName });
|
|
5436
5911
|
}
|
|
5437
5912
|
async deleteSessionThread(sessionId) {
|
|
5438
5913
|
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
@@ -5442,7 +5917,7 @@ Task completed.
|
|
|
5442
5917
|
try {
|
|
5443
5918
|
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
5444
5919
|
} catch (err) {
|
|
5445
|
-
|
|
5920
|
+
log19.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
|
|
5446
5921
|
}
|
|
5447
5922
|
}
|
|
5448
5923
|
async sendSkillCommands(sessionId, commands) {
|
|
@@ -5451,119 +5926,10 @@ Task completed.
|
|
|
5451
5926
|
if (!session) return;
|
|
5452
5927
|
const threadId = Number(session.threadId);
|
|
5453
5928
|
if (!threadId) return;
|
|
5454
|
-
|
|
5455
|
-
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
5456
|
-
const platform = record?.platform;
|
|
5457
|
-
if (platform?.skillMsgId) {
|
|
5458
|
-
this.skillMessages.set(sessionId, platform.skillMsgId);
|
|
5459
|
-
}
|
|
5460
|
-
}
|
|
5461
|
-
if (commands.length === 0) {
|
|
5462
|
-
await this.cleanupSkillCommands(sessionId);
|
|
5463
|
-
return;
|
|
5464
|
-
}
|
|
5465
|
-
const messages = buildSkillMessages(commands);
|
|
5466
|
-
const existingMsgId = this.skillMessages.get(sessionId);
|
|
5467
|
-
if (existingMsgId) {
|
|
5468
|
-
try {
|
|
5469
|
-
await this.bot.api.editMessageText(
|
|
5470
|
-
this.telegramConfig.chatId,
|
|
5471
|
-
existingMsgId,
|
|
5472
|
-
messages[0],
|
|
5473
|
-
{ parse_mode: "HTML" }
|
|
5474
|
-
);
|
|
5475
|
-
return;
|
|
5476
|
-
} catch (err) {
|
|
5477
|
-
const msg = err instanceof Error ? err.message : "";
|
|
5478
|
-
if (msg.includes("message is not modified")) {
|
|
5479
|
-
return;
|
|
5480
|
-
}
|
|
5481
|
-
try {
|
|
5482
|
-
await this.bot.api.deleteMessage(this.telegramConfig.chatId, existingMsgId);
|
|
5483
|
-
} catch {
|
|
5484
|
-
}
|
|
5485
|
-
this.skillMessages.delete(sessionId);
|
|
5486
|
-
}
|
|
5487
|
-
}
|
|
5488
|
-
try {
|
|
5489
|
-
let firstMsgId;
|
|
5490
|
-
for (const text of messages) {
|
|
5491
|
-
const msg = await this.sendQueue.enqueue(
|
|
5492
|
-
() => this.bot.api.sendMessage(
|
|
5493
|
-
this.telegramConfig.chatId,
|
|
5494
|
-
text,
|
|
5495
|
-
{
|
|
5496
|
-
message_thread_id: threadId,
|
|
5497
|
-
parse_mode: "HTML",
|
|
5498
|
-
disable_notification: true
|
|
5499
|
-
}
|
|
5500
|
-
)
|
|
5501
|
-
);
|
|
5502
|
-
if (!firstMsgId) firstMsgId = msg.message_id;
|
|
5503
|
-
}
|
|
5504
|
-
this.skillMessages.set(sessionId, firstMsgId);
|
|
5505
|
-
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
5506
|
-
if (record) {
|
|
5507
|
-
await this.core.sessionManager.updateSessionPlatform(
|
|
5508
|
-
sessionId,
|
|
5509
|
-
{ ...record.platform, skillMsgId: firstMsgId }
|
|
5510
|
-
);
|
|
5511
|
-
}
|
|
5512
|
-
await this.bot.api.pinChatMessage(
|
|
5513
|
-
this.telegramConfig.chatId,
|
|
5514
|
-
firstMsgId,
|
|
5515
|
-
{ disable_notification: true }
|
|
5516
|
-
);
|
|
5517
|
-
} catch (err) {
|
|
5518
|
-
log12.error({ err, sessionId }, "Failed to send skill commands");
|
|
5519
|
-
}
|
|
5929
|
+
await this.skillManager.send(sessionId, threadId, commands);
|
|
5520
5930
|
}
|
|
5521
5931
|
async cleanupSkillCommands(sessionId) {
|
|
5522
|
-
|
|
5523
|
-
if (!msgId) return;
|
|
5524
|
-
try {
|
|
5525
|
-
await this.bot.api.editMessageText(
|
|
5526
|
-
this.telegramConfig.chatId,
|
|
5527
|
-
msgId,
|
|
5528
|
-
"\u{1F6E0} <i>Session ended</i>",
|
|
5529
|
-
{ parse_mode: "HTML" }
|
|
5530
|
-
);
|
|
5531
|
-
await this.bot.api.unpinChatMessage(this.telegramConfig.chatId, msgId);
|
|
5532
|
-
} catch {
|
|
5533
|
-
}
|
|
5534
|
-
this.skillMessages.delete(sessionId);
|
|
5535
|
-
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
5536
|
-
if (record) {
|
|
5537
|
-
const { skillMsgId: _removed, ...rest } = record.platform;
|
|
5538
|
-
await this.core.sessionManager.updateSessionPlatform(sessionId, rest);
|
|
5539
|
-
}
|
|
5540
|
-
}
|
|
5541
|
-
async finalizeDraft(sessionId) {
|
|
5542
|
-
const draft = this.sessionDrafts.get(sessionId);
|
|
5543
|
-
if (!draft) return;
|
|
5544
|
-
this.sessionDrafts.delete(sessionId);
|
|
5545
|
-
const finalMsgId = await draft.finalize();
|
|
5546
|
-
if (sessionId === this.assistantSession?.id) {
|
|
5547
|
-
const fullText = this.sessionTextBuffers.get(sessionId);
|
|
5548
|
-
this.sessionTextBuffers.delete(sessionId);
|
|
5549
|
-
if (fullText && finalMsgId) {
|
|
5550
|
-
const detected = detectAction(fullText);
|
|
5551
|
-
if (detected) {
|
|
5552
|
-
const actionId = storeAction(detected);
|
|
5553
|
-
const keyboard = buildActionKeyboard(actionId, detected);
|
|
5554
|
-
try {
|
|
5555
|
-
await this.bot.api.editMessageReplyMarkup(
|
|
5556
|
-
this.telegramConfig.chatId,
|
|
5557
|
-
finalMsgId,
|
|
5558
|
-
{ reply_markup: keyboard }
|
|
5559
|
-
);
|
|
5560
|
-
} catch {
|
|
5561
|
-
}
|
|
5562
|
-
}
|
|
5563
|
-
}
|
|
5564
|
-
} else {
|
|
5565
|
-
this.sessionTextBuffers.delete(sessionId);
|
|
5566
|
-
}
|
|
5932
|
+
await this.skillManager.cleanup(sessionId);
|
|
5567
5933
|
}
|
|
5568
5934
|
};
|
|
5569
5935
|
|
|
@@ -5578,6 +5944,7 @@ export {
|
|
|
5578
5944
|
PermissionGate,
|
|
5579
5945
|
Session,
|
|
5580
5946
|
SessionManager,
|
|
5947
|
+
SessionBridge,
|
|
5581
5948
|
NotificationManager,
|
|
5582
5949
|
MessageTransformer,
|
|
5583
5950
|
OpenACPCore,
|
|
@@ -5586,4 +5953,4 @@ export {
|
|
|
5586
5953
|
TopicManager,
|
|
5587
5954
|
TelegramAdapter
|
|
5588
5955
|
};
|
|
5589
|
-
//# sourceMappingURL=chunk-
|
|
5956
|
+
//# sourceMappingURL=chunk-66RVSUAR.js.map
|