@openacp/cli 0.4.10 → 0.5.0
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 +57 -4
- package/dist/agent-catalog-4IAJ7HEG.js +10 -0
- package/dist/agent-registry-B5YAMA4T.js +8 -0
- package/dist/agent-store-ZBXGOFPH.js +8 -0
- package/dist/api-client-UN7BXQOQ.js +11 -0
- package/dist/{autostart-DZ3MHHMM.js → autostart-K73RQZVV.js} +3 -3
- package/dist/chunk-5HGXUCMX.js +83 -0
- package/dist/chunk-5HGXUCMX.js.map +1 -0
- package/dist/{chunk-UAUTLC4E.js → chunk-D73LCTPF.js} +75 -37
- package/dist/chunk-D73LCTPF.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-KPI4HGJC.js → chunk-FWN3UIRT.js} +1631 -970
- package/dist/chunk-FWN3UIRT.js.map +1 -0
- package/dist/chunk-IRGYTNLP.js +650 -0
- package/dist/chunk-IRGYTNLP.js.map +1 -0
- package/dist/{chunk-ZRFBLD3W.js → chunk-JRF4G4X7.js} +71 -25
- package/dist/chunk-JRF4G4X7.js.map +1 -0
- package/dist/{chunk-6MJLVZXV.js → chunk-LAFKARV3.js} +58 -21
- package/dist/{chunk-6MJLVZXV.js.map → chunk-LAFKARV3.js.map} +1 -1
- package/dist/chunk-NAMYZIS5.js +1 -0
- package/dist/{chunk-HZD3CGPK.js → chunk-NDR5JCS7.js} +3 -3
- 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-S3DRLJPM.js +422 -0
- package/dist/chunk-S3DRLJPM.js.map +1 -0
- package/dist/chunk-UG6X672R.js +90 -0
- package/dist/chunk-UG6X672R.js.map +1 -0
- package/dist/{chunk-C6YIUTGR.js → chunk-VBEWSWVL.js} +2 -2
- package/dist/{chunk-MRKYJ422.js → chunk-X6LLG7XN.js} +2 -2
- package/dist/chunk-XJJ7LPXP.js +85 -0
- package/dist/chunk-XJJ7LPXP.js.map +1 -0
- package/dist/chunk-Z46LGZ7R.js +110 -0
- package/dist/chunk-Z46LGZ7R.js.map +1 -0
- package/dist/cli.js +313 -52
- package/dist/cli.js.map +1 -1
- package/dist/{config-H2DSEHNW.js → config-PCPIBPUA.js} +3 -3
- package/dist/config-editor-5L7AJ5AF.js +12 -0
- package/dist/config-editor-5L7AJ5AF.js.map +1 -0
- package/dist/config-registry-SNKA2EH2.js +17 -0
- package/dist/config-registry-SNKA2EH2.js.map +1 -0
- package/dist/{daemon-VF6HJQXD.js → daemon-JZLFRUW6.js} +4 -4
- package/dist/daemon-JZLFRUW6.js.map +1 -0
- package/dist/data/registry-snapshot.json +876 -0
- package/dist/doctor-N2HKKUUQ.js +9 -0
- package/dist/doctor-N2HKKUUQ.js.map +1 -0
- package/dist/index.d.ts +212 -43
- package/dist/index.js +43 -14
- 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-G6XDM7EZ.js → main-37GLOJ7G.js} +21 -15
- package/dist/{main-G6XDM7EZ.js.map → main-37GLOJ7G.js.map} +1 -1
- package/dist/menu-6RCPBVGQ.js +15 -0
- package/dist/menu-6RCPBVGQ.js.map +1 -0
- package/dist/{setup-FCVL75K6.js → setup-QAS3QW3M.js} +5 -4
- package/dist/setup-QAS3QW3M.js.map +1 -0
- package/dist/{tunnel-service-DASSH7OA.js → tunnel-service-LEVPLXAZ.js} +3 -3
- package/package.json +10 -2
- package/dist/agent-registry-7HC6D4CH.js +0 -7
- package/dist/chunk-KPI4HGJC.js.map +0 -1
- package/dist/chunk-LYKCQTH5.js.map +0 -1
- package/dist/chunk-UAUTLC4E.js.map +0 -1
- package/dist/chunk-VA2M52CM.js +0 -15
- package/dist/chunk-VA2M52CM.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/{agent-registry-7HC6D4CH.js.map → agent-catalog-4IAJ7HEG.js.map} +0 -0
- /package/dist/{autostart-DZ3MHHMM.js.map → agent-registry-B5YAMA4T.js.map} +0 -0
- /package/dist/{config-H2DSEHNW.js.map → agent-store-ZBXGOFPH.js.map} +0 -0
- /package/dist/{config-editor-SKS4LJLT.js.map → api-client-UN7BXQOQ.js.map} +0 -0
- /package/dist/{daemon-VF6HJQXD.js.map → autostart-K73RQZVV.js.map} +0 -0
- /package/dist/{install-cloudflared-ILUXKLAC.js.map → chunk-NAMYZIS5.js.map} +0 -0
- /package/dist/{chunk-HZD3CGPK.js.map → chunk-NDR5JCS7.js.map} +0 -0
- /package/dist/{chunk-V3BA2MJ6.js.map → chunk-RF3DUYFO.js.map} +0 -0
- /package/dist/{chunk-C6YIUTGR.js.map → chunk-VBEWSWVL.js.map} +0 -0
- /package/dist/{chunk-MRKYJ422.js.map → chunk-X6LLG7XN.js.map} +0 -0
- /package/dist/{setup-FCVL75K6.js.map → config-PCPIBPUA.js.map} +0 -0
- /package/dist/{tunnel-service-DASSH7OA.js.map → tunnel-service-LEVPLXAZ.js.map} +0 -0
|
@@ -1,10 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildMenuKeyboard,
|
|
3
|
+
buildSkillMessages,
|
|
4
|
+
handleClear,
|
|
5
|
+
handleHelp,
|
|
6
|
+
handleMenu
|
|
7
|
+
} from "./chunk-UG6X672R.js";
|
|
8
|
+
import {
|
|
9
|
+
AgentCatalog
|
|
10
|
+
} from "./chunk-S3DRLJPM.js";
|
|
1
11
|
import {
|
|
2
12
|
getAgentCapabilities
|
|
3
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XJJ7LPXP.js";
|
|
14
|
+
import {
|
|
15
|
+
DoctorEngine
|
|
16
|
+
} from "./chunk-IRGYTNLP.js";
|
|
17
|
+
import {
|
|
18
|
+
getConfigValue,
|
|
19
|
+
getSafeFields,
|
|
20
|
+
isHotReloadable,
|
|
21
|
+
resolveOptions
|
|
22
|
+
} from "./chunk-Z46LGZ7R.js";
|
|
4
23
|
import {
|
|
5
24
|
createChildLogger,
|
|
6
25
|
createSessionLogger
|
|
7
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ESOPMQAY.js";
|
|
8
27
|
|
|
9
28
|
// src/core/streams.ts
|
|
10
29
|
function nodeToWebWritable(nodeStream) {
|
|
@@ -49,7 +68,7 @@ var StderrCapture = class {
|
|
|
49
68
|
};
|
|
50
69
|
|
|
51
70
|
// src/core/agent-instance.ts
|
|
52
|
-
import { spawn,
|
|
71
|
+
import { spawn, execFileSync } from "child_process";
|
|
53
72
|
import { Transform } from "stream";
|
|
54
73
|
import fs from "fs";
|
|
55
74
|
import path from "path";
|
|
@@ -100,7 +119,7 @@ function resolveAgentCommand(cmd) {
|
|
|
100
119
|
}
|
|
101
120
|
}
|
|
102
121
|
try {
|
|
103
|
-
const fullPath =
|
|
122
|
+
const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim();
|
|
104
123
|
if (fullPath) {
|
|
105
124
|
const content = fs.readFileSync(fullPath, "utf-8");
|
|
106
125
|
if (content.startsWith("#!/usr/bin/env node")) {
|
|
@@ -478,31 +497,29 @@ ${stderr}`
|
|
|
478
497
|
|
|
479
498
|
// src/core/agent-manager.ts
|
|
480
499
|
var AgentManager = class {
|
|
481
|
-
constructor(
|
|
482
|
-
this.
|
|
500
|
+
constructor(catalog) {
|
|
501
|
+
this.catalog = catalog;
|
|
483
502
|
}
|
|
484
503
|
getAvailableAgents() {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
env:
|
|
504
|
+
const installed = this.catalog.getInstalledEntries();
|
|
505
|
+
return Object.entries(installed).map(([key, agent]) => ({
|
|
506
|
+
name: key,
|
|
507
|
+
command: agent.command,
|
|
508
|
+
args: agent.args,
|
|
509
|
+
env: agent.env
|
|
491
510
|
}));
|
|
492
511
|
}
|
|
493
512
|
getAgent(name) {
|
|
494
|
-
|
|
495
|
-
if (!cfg) return void 0;
|
|
496
|
-
return { name, ...cfg };
|
|
513
|
+
return this.catalog.resolve(name);
|
|
497
514
|
}
|
|
498
515
|
async spawn(agentName, workingDirectory) {
|
|
499
516
|
const agentDef = this.getAgent(agentName);
|
|
500
|
-
if (!agentDef) throw new Error(`Agent "${agentName}" not
|
|
517
|
+
if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
|
|
501
518
|
return AgentInstance.spawn(agentDef, workingDirectory);
|
|
502
519
|
}
|
|
503
520
|
async resume(agentName, workingDirectory, agentSessionId) {
|
|
504
521
|
const agentDef = this.getAgent(agentName);
|
|
505
|
-
if (!agentDef) throw new Error(`Agent "${agentName}" not
|
|
522
|
+
if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
|
|
506
523
|
return AgentInstance.resume(agentDef, workingDirectory, agentSessionId);
|
|
507
524
|
}
|
|
508
525
|
};
|
|
@@ -629,28 +646,40 @@ var PromptQueue = class {
|
|
|
629
646
|
};
|
|
630
647
|
|
|
631
648
|
// src/core/permission-gate.ts
|
|
649
|
+
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
632
650
|
var PermissionGate = class {
|
|
633
651
|
request;
|
|
634
652
|
resolveFn;
|
|
635
653
|
rejectFn;
|
|
636
654
|
settled = false;
|
|
655
|
+
timeoutTimer;
|
|
656
|
+
timeoutMs;
|
|
657
|
+
constructor(timeoutMs) {
|
|
658
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
659
|
+
}
|
|
637
660
|
setPending(request) {
|
|
638
661
|
this.request = request;
|
|
639
662
|
this.settled = false;
|
|
663
|
+
this.clearTimeout();
|
|
640
664
|
return new Promise((resolve2, reject) => {
|
|
641
665
|
this.resolveFn = resolve2;
|
|
642
666
|
this.rejectFn = reject;
|
|
667
|
+
this.timeoutTimer = setTimeout(() => {
|
|
668
|
+
this.reject("Permission request timed out (no response received)");
|
|
669
|
+
}, this.timeoutMs);
|
|
643
670
|
});
|
|
644
671
|
}
|
|
645
672
|
resolve(optionId) {
|
|
646
673
|
if (this.settled || !this.resolveFn) return;
|
|
647
674
|
this.settled = true;
|
|
675
|
+
this.clearTimeout();
|
|
648
676
|
this.resolveFn(optionId);
|
|
649
677
|
this.cleanup();
|
|
650
678
|
}
|
|
651
679
|
reject(reason) {
|
|
652
680
|
if (this.settled || !this.rejectFn) return;
|
|
653
681
|
this.settled = true;
|
|
682
|
+
this.clearTimeout();
|
|
654
683
|
this.rejectFn(new Error(reason ?? "Permission rejected"));
|
|
655
684
|
this.cleanup();
|
|
656
685
|
}
|
|
@@ -664,6 +693,12 @@ var PermissionGate = class {
|
|
|
664
693
|
get requestId() {
|
|
665
694
|
return this.request?.id;
|
|
666
695
|
}
|
|
696
|
+
clearTimeout() {
|
|
697
|
+
if (this.timeoutTimer) {
|
|
698
|
+
clearTimeout(this.timeoutTimer);
|
|
699
|
+
this.timeoutTimer = void 0;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
667
702
|
cleanup() {
|
|
668
703
|
this.request = void 0;
|
|
669
704
|
this.resolveFn = void 0;
|
|
@@ -674,6 +709,13 @@ var PermissionGate = class {
|
|
|
674
709
|
// src/core/session.ts
|
|
675
710
|
import { nanoid } from "nanoid";
|
|
676
711
|
var moduleLog = createChildLogger({ module: "session" });
|
|
712
|
+
var VALID_TRANSITIONS = {
|
|
713
|
+
initializing: /* @__PURE__ */ new Set(["active", "error"]),
|
|
714
|
+
active: /* @__PURE__ */ new Set(["error", "finished", "cancelled"]),
|
|
715
|
+
error: /* @__PURE__ */ new Set(["active"]),
|
|
716
|
+
cancelled: /* @__PURE__ */ new Set(["active"]),
|
|
717
|
+
finished: /* @__PURE__ */ new Set()
|
|
718
|
+
};
|
|
677
719
|
var Session = class extends TypedEmitter {
|
|
678
720
|
id;
|
|
679
721
|
channelId;
|
|
@@ -682,11 +724,9 @@ var Session = class extends TypedEmitter {
|
|
|
682
724
|
workingDirectory;
|
|
683
725
|
agentInstance;
|
|
684
726
|
agentSessionId = "";
|
|
685
|
-
|
|
727
|
+
_status = "initializing";
|
|
686
728
|
name;
|
|
687
729
|
createdAt = /* @__PURE__ */ new Date();
|
|
688
|
-
adapter;
|
|
689
|
-
// Set by wireSessionEvents for renaming
|
|
690
730
|
dangerousMode = false;
|
|
691
731
|
log;
|
|
692
732
|
permissionGate = new PermissionGate();
|
|
@@ -703,21 +743,44 @@ var Session = class extends TypedEmitter {
|
|
|
703
743
|
this.queue = new PromptQueue(
|
|
704
744
|
(text) => this.processPrompt(text),
|
|
705
745
|
(err) => {
|
|
706
|
-
this.
|
|
746
|
+
this.fail("Prompt execution failed");
|
|
707
747
|
this.log.error({ err }, "Prompt execution failed");
|
|
708
748
|
}
|
|
709
749
|
);
|
|
710
750
|
}
|
|
711
|
-
// ---
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
751
|
+
// --- State Machine ---
|
|
752
|
+
get status() {
|
|
753
|
+
return this._status;
|
|
754
|
+
}
|
|
755
|
+
/** Transition to active — from initializing, error, or cancelled */
|
|
756
|
+
activate() {
|
|
757
|
+
this.transition("active");
|
|
758
|
+
}
|
|
759
|
+
/** Transition to error — from initializing or active */
|
|
760
|
+
fail(reason) {
|
|
761
|
+
this.transition("error");
|
|
762
|
+
this.emit("error", new Error(reason));
|
|
763
|
+
}
|
|
764
|
+
/** Transition to finished — from active only. Emits session_end for backward compat. */
|
|
765
|
+
finish(reason) {
|
|
766
|
+
this.transition("finished");
|
|
767
|
+
this.emit("session_end", reason ?? "completed");
|
|
768
|
+
}
|
|
769
|
+
/** Transition to cancelled — from active only (terminal session cancel) */
|
|
770
|
+
markCancelled() {
|
|
771
|
+
this.transition("cancelled");
|
|
772
|
+
}
|
|
773
|
+
transition(to) {
|
|
774
|
+
const from = this._status;
|
|
775
|
+
const allowed = VALID_TRANSITIONS[from];
|
|
776
|
+
if (!allowed?.has(to)) {
|
|
777
|
+
throw new Error(
|
|
778
|
+
`Invalid session transition: ${from} \u2192 ${to}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
this._status = to;
|
|
782
|
+
this.log.debug({ from, to }, "Session status transition");
|
|
783
|
+
this.emit("status_change", from, to);
|
|
721
784
|
}
|
|
722
785
|
/** Number of prompts waiting in queue */
|
|
723
786
|
get queueDepth() {
|
|
@@ -735,7 +798,9 @@ var Session = class extends TypedEmitter {
|
|
|
735
798
|
await this.runWarmup();
|
|
736
799
|
return;
|
|
737
800
|
}
|
|
738
|
-
this.
|
|
801
|
+
if (this._status === "initializing") {
|
|
802
|
+
this.activate();
|
|
803
|
+
}
|
|
739
804
|
const promptStart = Date.now();
|
|
740
805
|
this.log.debug("Prompt execution started");
|
|
741
806
|
await this.agentInstance.prompt(text);
|
|
@@ -760,9 +825,7 @@ var Session = class extends TypedEmitter {
|
|
|
760
825
|
);
|
|
761
826
|
this.name = title.trim().slice(0, 50) || `Session ${this.id.slice(0, 6)}`;
|
|
762
827
|
this.log.info({ name: this.name }, "Session auto-named");
|
|
763
|
-
|
|
764
|
-
await this.adapter.renameSessionThread(this.id, this.name);
|
|
765
|
-
}
|
|
828
|
+
this.emit("named", this.name);
|
|
766
829
|
} catch {
|
|
767
830
|
this.name = `Session ${this.id.slice(0, 6)}`;
|
|
768
831
|
} finally {
|
|
@@ -781,7 +844,7 @@ var Session = class extends TypedEmitter {
|
|
|
781
844
|
try {
|
|
782
845
|
const start = Date.now();
|
|
783
846
|
await this.agentInstance.prompt('Reply with only "ready".');
|
|
784
|
-
this.
|
|
847
|
+
this.activate();
|
|
785
848
|
this.log.info({ durationMs: Date.now() - start }, "Warm-up complete");
|
|
786
849
|
} catch (err) {
|
|
787
850
|
this.log.error({ err }, "Warm-up failed");
|
|
@@ -790,11 +853,11 @@ var Session = class extends TypedEmitter {
|
|
|
790
853
|
this.resume();
|
|
791
854
|
}
|
|
792
855
|
}
|
|
793
|
-
|
|
856
|
+
/** Cancel the current prompt and clear the queue. Stays in active state. */
|
|
857
|
+
async abortPrompt() {
|
|
794
858
|
this.queue.clear();
|
|
795
|
-
this.log.info("
|
|
859
|
+
this.log.info("Prompt aborted");
|
|
796
860
|
await this.agentInstance.cancel();
|
|
797
|
-
this.status = "active";
|
|
798
861
|
}
|
|
799
862
|
async destroy() {
|
|
800
863
|
this.log.info("Session destroyed");
|
|
@@ -867,42 +930,13 @@ var SessionManager = class {
|
|
|
867
930
|
registerSession(session) {
|
|
868
931
|
this.sessions.set(session.id, session);
|
|
869
932
|
}
|
|
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) {
|
|
933
|
+
async patchRecord(sessionId, patch) {
|
|
902
934
|
if (!this.store) return;
|
|
903
935
|
const record = this.store.get(sessionId);
|
|
904
936
|
if (record) {
|
|
905
|
-
await this.store.save({ ...record,
|
|
937
|
+
await this.store.save({ ...record, ...patch });
|
|
938
|
+
} else if (patch.sessionId) {
|
|
939
|
+
await this.store.save(patch);
|
|
906
940
|
}
|
|
907
941
|
}
|
|
908
942
|
getSessionRecord(sessionId) {
|
|
@@ -911,7 +945,8 @@ var SessionManager = class {
|
|
|
911
945
|
async cancelSession(sessionId) {
|
|
912
946
|
const session = this.sessions.get(sessionId);
|
|
913
947
|
if (session) {
|
|
914
|
-
await session.
|
|
948
|
+
await session.abortPrompt();
|
|
949
|
+
session.markCancelled();
|
|
915
950
|
}
|
|
916
951
|
if (this.store) {
|
|
917
952
|
const record = this.store.get(sessionId);
|
|
@@ -953,6 +988,133 @@ var SessionManager = class {
|
|
|
953
988
|
}
|
|
954
989
|
};
|
|
955
990
|
|
|
991
|
+
// src/core/session-bridge.ts
|
|
992
|
+
var log2 = createChildLogger({ module: "session-bridge" });
|
|
993
|
+
var SessionBridge = class {
|
|
994
|
+
constructor(session, adapter, deps) {
|
|
995
|
+
this.session = session;
|
|
996
|
+
this.adapter = adapter;
|
|
997
|
+
this.deps = deps;
|
|
998
|
+
}
|
|
999
|
+
connected = false;
|
|
1000
|
+
agentEventHandler;
|
|
1001
|
+
statusChangeHandler;
|
|
1002
|
+
namedHandler;
|
|
1003
|
+
connect() {
|
|
1004
|
+
if (this.connected) return;
|
|
1005
|
+
this.connected = true;
|
|
1006
|
+
this.wireAgentToSession();
|
|
1007
|
+
this.wireSessionToAdapter();
|
|
1008
|
+
this.wirePermissions();
|
|
1009
|
+
this.wireLifecycle();
|
|
1010
|
+
}
|
|
1011
|
+
disconnect() {
|
|
1012
|
+
if (!this.connected) return;
|
|
1013
|
+
this.connected = false;
|
|
1014
|
+
if (this.agentEventHandler) {
|
|
1015
|
+
this.session.off("agent_event", this.agentEventHandler);
|
|
1016
|
+
}
|
|
1017
|
+
if (this.statusChangeHandler) {
|
|
1018
|
+
this.session.off("status_change", this.statusChangeHandler);
|
|
1019
|
+
}
|
|
1020
|
+
if (this.namedHandler) {
|
|
1021
|
+
this.session.off("named", this.namedHandler);
|
|
1022
|
+
}
|
|
1023
|
+
this.session.agentInstance.onSessionUpdate = () => {
|
|
1024
|
+
};
|
|
1025
|
+
this.session.agentInstance.onPermissionRequest = async () => "";
|
|
1026
|
+
}
|
|
1027
|
+
wireAgentToSession() {
|
|
1028
|
+
this.session.agentInstance.onSessionUpdate = (event) => {
|
|
1029
|
+
this.session.emit("agent_event", event);
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
wireSessionToAdapter() {
|
|
1033
|
+
const session = this.session;
|
|
1034
|
+
const ctx = {
|
|
1035
|
+
get id() {
|
|
1036
|
+
return session.id;
|
|
1037
|
+
},
|
|
1038
|
+
get workingDirectory() {
|
|
1039
|
+
return session.workingDirectory;
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
this.agentEventHandler = (event) => {
|
|
1043
|
+
switch (event.type) {
|
|
1044
|
+
case "text":
|
|
1045
|
+
case "thought":
|
|
1046
|
+
case "tool_call":
|
|
1047
|
+
case "tool_update":
|
|
1048
|
+
case "plan":
|
|
1049
|
+
case "usage":
|
|
1050
|
+
this.adapter.sendMessage(
|
|
1051
|
+
this.session.id,
|
|
1052
|
+
this.deps.messageTransformer.transform(event, ctx)
|
|
1053
|
+
);
|
|
1054
|
+
break;
|
|
1055
|
+
case "session_end":
|
|
1056
|
+
this.session.finish(event.reason);
|
|
1057
|
+
this.adapter.cleanupSkillCommands(this.session.id);
|
|
1058
|
+
this.adapter.sendMessage(
|
|
1059
|
+
this.session.id,
|
|
1060
|
+
this.deps.messageTransformer.transform(event)
|
|
1061
|
+
);
|
|
1062
|
+
this.deps.notificationManager.notify(this.session.channelId, {
|
|
1063
|
+
sessionId: this.session.id,
|
|
1064
|
+
sessionName: this.session.name,
|
|
1065
|
+
type: "completed",
|
|
1066
|
+
summary: `Session "${this.session.name || this.session.id}" completed`
|
|
1067
|
+
});
|
|
1068
|
+
break;
|
|
1069
|
+
case "error":
|
|
1070
|
+
this.session.fail(event.message);
|
|
1071
|
+
this.adapter.cleanupSkillCommands(this.session.id);
|
|
1072
|
+
this.adapter.sendMessage(
|
|
1073
|
+
this.session.id,
|
|
1074
|
+
this.deps.messageTransformer.transform(event)
|
|
1075
|
+
);
|
|
1076
|
+
this.deps.notificationManager.notify(this.session.channelId, {
|
|
1077
|
+
sessionId: this.session.id,
|
|
1078
|
+
sessionName: this.session.name,
|
|
1079
|
+
type: "error",
|
|
1080
|
+
summary: event.message
|
|
1081
|
+
});
|
|
1082
|
+
break;
|
|
1083
|
+
case "commands_update":
|
|
1084
|
+
log2.debug({ commands: event.commands }, "Commands available");
|
|
1085
|
+
this.adapter.sendSkillCommands(this.session.id, event.commands);
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
this.session.on("agent_event", this.agentEventHandler);
|
|
1090
|
+
}
|
|
1091
|
+
wirePermissions() {
|
|
1092
|
+
this.session.agentInstance.onPermissionRequest = async (request) => {
|
|
1093
|
+
this.session.emit("permission_request", request);
|
|
1094
|
+
const promise = this.session.permissionGate.setPending(request);
|
|
1095
|
+
await this.adapter.sendPermissionRequest(this.session.id, request);
|
|
1096
|
+
return promise;
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
wireLifecycle() {
|
|
1100
|
+
this.statusChangeHandler = (from, to) => {
|
|
1101
|
+
this.deps.sessionManager.patchRecord(this.session.id, {
|
|
1102
|
+
status: to,
|
|
1103
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1104
|
+
});
|
|
1105
|
+
if (to === "finished" || to === "cancelled") {
|
|
1106
|
+
queueMicrotask(() => this.disconnect());
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
this.session.on("status_change", this.statusChangeHandler);
|
|
1110
|
+
this.namedHandler = (name) => {
|
|
1111
|
+
this.deps.sessionManager.patchRecord(this.session.id, { name });
|
|
1112
|
+
this.adapter.renameSessionThread(this.session.id, name);
|
|
1113
|
+
};
|
|
1114
|
+
this.session.on("named", this.namedHandler);
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
|
|
956
1118
|
// src/core/notification.ts
|
|
957
1119
|
var NotificationManager = class {
|
|
958
1120
|
constructor(adapters) {
|
|
@@ -1062,7 +1224,7 @@ function parseContent(content) {
|
|
|
1062
1224
|
}
|
|
1063
1225
|
|
|
1064
1226
|
// src/core/message-transformer.ts
|
|
1065
|
-
var
|
|
1227
|
+
var log3 = createChildLogger({ module: "message-transformer" });
|
|
1066
1228
|
var MessageTransformer = class {
|
|
1067
1229
|
constructor(tunnelService) {
|
|
1068
1230
|
this.tunnelService = tunnelService;
|
|
@@ -1124,7 +1286,7 @@ var MessageTransformer = class {
|
|
|
1124
1286
|
if (!this.tunnelService || !sessionContext) return;
|
|
1125
1287
|
const name = "name" in event ? event.name || "" : "";
|
|
1126
1288
|
const kind = "kind" in event ? event.kind : void 0;
|
|
1127
|
-
|
|
1289
|
+
log3.debug(
|
|
1128
1290
|
{ name, kind, status: event.status, hasContent: !!event.content },
|
|
1129
1291
|
"enrichWithViewerLinks: inspecting event"
|
|
1130
1292
|
);
|
|
@@ -1136,7 +1298,7 @@ var MessageTransformer = class {
|
|
|
1136
1298
|
event.meta
|
|
1137
1299
|
);
|
|
1138
1300
|
if (!fileInfo) return;
|
|
1139
|
-
|
|
1301
|
+
log3.info(
|
|
1140
1302
|
{
|
|
1141
1303
|
name,
|
|
1142
1304
|
kind,
|
|
@@ -1178,7 +1340,7 @@ import os from "os";
|
|
|
1178
1340
|
// src/core/session-store.ts
|
|
1179
1341
|
import fs2 from "fs";
|
|
1180
1342
|
import path2 from "path";
|
|
1181
|
-
var
|
|
1343
|
+
var log4 = createChildLogger({ module: "session-store" });
|
|
1182
1344
|
var DEBOUNCE_MS = 2e3;
|
|
1183
1345
|
var JsonFileSessionStore = class {
|
|
1184
1346
|
records = /* @__PURE__ */ new Map();
|
|
@@ -1260,7 +1422,7 @@ var JsonFileSessionStore = class {
|
|
|
1260
1422
|
fs2.readFileSync(this.filePath, "utf-8")
|
|
1261
1423
|
);
|
|
1262
1424
|
if (raw.version !== 1) {
|
|
1263
|
-
|
|
1425
|
+
log4.warn(
|
|
1264
1426
|
{ version: raw.version },
|
|
1265
1427
|
"Unknown session store version, skipping load"
|
|
1266
1428
|
);
|
|
@@ -1269,9 +1431,9 @@ var JsonFileSessionStore = class {
|
|
|
1269
1431
|
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
1270
1432
|
this.records.set(id, record);
|
|
1271
1433
|
}
|
|
1272
|
-
|
|
1434
|
+
log4.info({ count: this.records.size }, "Loaded session records");
|
|
1273
1435
|
} catch (err) {
|
|
1274
|
-
|
|
1436
|
+
log4.error({ err }, "Failed to load session store");
|
|
1275
1437
|
}
|
|
1276
1438
|
}
|
|
1277
1439
|
cleanup() {
|
|
@@ -1287,7 +1449,7 @@ var JsonFileSessionStore = class {
|
|
|
1287
1449
|
}
|
|
1288
1450
|
}
|
|
1289
1451
|
if (removed > 0) {
|
|
1290
|
-
|
|
1452
|
+
log4.info({ removed }, "Cleaned up expired session records");
|
|
1291
1453
|
this.scheduleDiskWrite();
|
|
1292
1454
|
}
|
|
1293
1455
|
}
|
|
@@ -1300,9 +1462,10 @@ var JsonFileSessionStore = class {
|
|
|
1300
1462
|
};
|
|
1301
1463
|
|
|
1302
1464
|
// src/core/core.ts
|
|
1303
|
-
var
|
|
1465
|
+
var log5 = createChildLogger({ module: "core" });
|
|
1304
1466
|
var OpenACPCore = class {
|
|
1305
1467
|
configManager;
|
|
1468
|
+
agentCatalog;
|
|
1306
1469
|
agentManager;
|
|
1307
1470
|
sessionManager;
|
|
1308
1471
|
notificationManager;
|
|
@@ -1316,7 +1479,9 @@ var OpenACPCore = class {
|
|
|
1316
1479
|
constructor(configManager) {
|
|
1317
1480
|
this.configManager = configManager;
|
|
1318
1481
|
const config = configManager.get();
|
|
1319
|
-
this.
|
|
1482
|
+
this.agentCatalog = new AgentCatalog();
|
|
1483
|
+
this.agentCatalog.load();
|
|
1484
|
+
this.agentManager = new AgentManager(this.agentCatalog);
|
|
1320
1485
|
const storePath = path3.join(os.homedir(), ".openacp", "sessions.json");
|
|
1321
1486
|
this.sessionStore = new JsonFileSessionStore(
|
|
1322
1487
|
storePath,
|
|
@@ -1325,6 +1490,13 @@ var OpenACPCore = class {
|
|
|
1325
1490
|
this.sessionManager = new SessionManager(this.sessionStore);
|
|
1326
1491
|
this.notificationManager = new NotificationManager(this.adapters);
|
|
1327
1492
|
this.messageTransformer = new MessageTransformer();
|
|
1493
|
+
this.configManager.on("config:changed", async ({ path: configPath, value }) => {
|
|
1494
|
+
if (configPath === "logging.level" && typeof value === "string") {
|
|
1495
|
+
const { setLogLevel: setLogLevel2 } = await import("./log-SPS2S6FO.js");
|
|
1496
|
+
setLogLevel2(value);
|
|
1497
|
+
log5.info({ level: value }, "Log level changed at runtime");
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1328
1500
|
}
|
|
1329
1501
|
get tunnelService() {
|
|
1330
1502
|
return this._tunnelService;
|
|
@@ -1337,6 +1509,9 @@ var OpenACPCore = class {
|
|
|
1337
1509
|
this.adapters.set(name, adapter);
|
|
1338
1510
|
}
|
|
1339
1511
|
async start() {
|
|
1512
|
+
this.agentCatalog.refreshRegistryIfStale().catch((err) => {
|
|
1513
|
+
log5.warn({ err }, "Background registry refresh failed");
|
|
1514
|
+
});
|
|
1340
1515
|
for (const adapter of this.adapters.values()) {
|
|
1341
1516
|
await adapter.start();
|
|
1342
1517
|
}
|
|
@@ -1358,7 +1533,7 @@ var OpenACPCore = class {
|
|
|
1358
1533
|
// --- Message Routing ---
|
|
1359
1534
|
async handleMessage(message) {
|
|
1360
1535
|
const config = this.configManager.get();
|
|
1361
|
-
|
|
1536
|
+
log5.debug(
|
|
1362
1537
|
{
|
|
1363
1538
|
channelId: message.channelId,
|
|
1364
1539
|
threadId: message.threadId,
|
|
@@ -1368,7 +1543,7 @@ var OpenACPCore = class {
|
|
|
1368
1543
|
);
|
|
1369
1544
|
if (config.security.allowedUserIds.length > 0) {
|
|
1370
1545
|
if (!config.security.allowedUserIds.includes(message.userId)) {
|
|
1371
|
-
|
|
1546
|
+
log5.warn(
|
|
1372
1547
|
{ userId: message.userId },
|
|
1373
1548
|
"Rejected message from unauthorized user"
|
|
1374
1549
|
);
|
|
@@ -1377,7 +1552,7 @@ var OpenACPCore = class {
|
|
|
1377
1552
|
}
|
|
1378
1553
|
const activeSessions = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
|
|
1379
1554
|
if (activeSessions.length >= config.security.maxConcurrentSessions) {
|
|
1380
|
-
|
|
1555
|
+
log5.warn(
|
|
1381
1556
|
{
|
|
1382
1557
|
userId: message.userId,
|
|
1383
1558
|
currentCount: activeSessions.length,
|
|
@@ -1401,28 +1576,88 @@ var OpenACPCore = class {
|
|
|
1401
1576
|
if (!session) {
|
|
1402
1577
|
session = await this.lazyResume(message) ?? void 0;
|
|
1403
1578
|
}
|
|
1404
|
-
if (!session)
|
|
1405
|
-
|
|
1579
|
+
if (!session) {
|
|
1580
|
+
log5.warn(
|
|
1581
|
+
{ channelId: message.channelId, threadId: message.threadId },
|
|
1582
|
+
"No session found for thread (in-memory miss + lazy resume returned null)"
|
|
1583
|
+
);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
this.sessionManager.patchRecord(session.id, { lastActiveAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1406
1587
|
await session.enqueuePrompt(message.text);
|
|
1407
1588
|
}
|
|
1589
|
+
// --- Unified Session Creation Pipeline ---
|
|
1590
|
+
async createSession(params) {
|
|
1591
|
+
const agentInstance = params.resumeAgentSessionId ? await this.agentManager.resume(
|
|
1592
|
+
params.agentName,
|
|
1593
|
+
params.workingDirectory,
|
|
1594
|
+
params.resumeAgentSessionId
|
|
1595
|
+
) : await this.agentManager.spawn(
|
|
1596
|
+
params.agentName,
|
|
1597
|
+
params.workingDirectory
|
|
1598
|
+
);
|
|
1599
|
+
const session = new Session({
|
|
1600
|
+
id: params.existingSessionId,
|
|
1601
|
+
channelId: params.channelId,
|
|
1602
|
+
agentName: params.agentName,
|
|
1603
|
+
workingDirectory: params.workingDirectory,
|
|
1604
|
+
agentInstance
|
|
1605
|
+
});
|
|
1606
|
+
session.agentSessionId = agentInstance.sessionId;
|
|
1607
|
+
if (params.initialName) {
|
|
1608
|
+
session.name = params.initialName;
|
|
1609
|
+
}
|
|
1610
|
+
this.sessionManager.registerSession(session);
|
|
1611
|
+
const adapter = this.adapters.get(params.channelId);
|
|
1612
|
+
if (params.createThread && adapter) {
|
|
1613
|
+
const threadId = await adapter.createSessionThread(
|
|
1614
|
+
session.id,
|
|
1615
|
+
params.initialName ?? `\u{1F504} ${params.agentName} \u2014 New Session`
|
|
1616
|
+
);
|
|
1617
|
+
session.threadId = threadId;
|
|
1618
|
+
}
|
|
1619
|
+
if (adapter) {
|
|
1620
|
+
const bridge = this.createBridge(session, adapter);
|
|
1621
|
+
bridge.connect();
|
|
1622
|
+
}
|
|
1623
|
+
const existingRecord = this.sessionStore?.get(session.id);
|
|
1624
|
+
const platform = {
|
|
1625
|
+
...existingRecord?.platform ?? {}
|
|
1626
|
+
};
|
|
1627
|
+
if (session.threadId) {
|
|
1628
|
+
platform.topicId = Number(session.threadId);
|
|
1629
|
+
}
|
|
1630
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
1631
|
+
sessionId: session.id,
|
|
1632
|
+
agentSessionId: agentInstance.sessionId,
|
|
1633
|
+
agentName: params.agentName,
|
|
1634
|
+
workingDir: params.workingDirectory,
|
|
1635
|
+
channelId: params.channelId,
|
|
1636
|
+
status: session.status,
|
|
1637
|
+
createdAt: session.createdAt.toISOString(),
|
|
1638
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1639
|
+
name: session.name,
|
|
1640
|
+
platform
|
|
1641
|
+
});
|
|
1642
|
+
log5.info(
|
|
1643
|
+
{ sessionId: session.id, agentName: params.agentName },
|
|
1644
|
+
"Session created via pipeline"
|
|
1645
|
+
);
|
|
1646
|
+
return session;
|
|
1647
|
+
}
|
|
1408
1648
|
async handleNewSession(channelId, agentName, workspacePath) {
|
|
1409
1649
|
const config = this.configManager.get();
|
|
1410
1650
|
const resolvedAgent = agentName || config.defaultAgent;
|
|
1411
|
-
|
|
1651
|
+
log5.info({ channelId, agentName: resolvedAgent }, "New session request");
|
|
1652
|
+
const agentDef = this.agentCatalog.resolve(resolvedAgent);
|
|
1412
1653
|
const resolvedWorkspace = this.configManager.resolveWorkspace(
|
|
1413
|
-
workspacePath ||
|
|
1654
|
+
workspacePath || agentDef?.workingDirectory
|
|
1414
1655
|
);
|
|
1415
|
-
|
|
1656
|
+
return this.createSession({
|
|
1416
1657
|
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;
|
|
1658
|
+
agentName: resolvedAgent,
|
|
1659
|
+
workingDirectory: resolvedWorkspace
|
|
1660
|
+
});
|
|
1426
1661
|
}
|
|
1427
1662
|
async adoptSession(agentName, agentSessionId, cwd) {
|
|
1428
1663
|
const caps = getAgentCapabilities(agentName);
|
|
@@ -1445,10 +1680,10 @@ var OpenACPCore = class {
|
|
|
1445
1680
|
if (existingRecord) {
|
|
1446
1681
|
const platform = existingRecord.platform;
|
|
1447
1682
|
if (platform?.topicId) {
|
|
1448
|
-
const
|
|
1449
|
-
if (
|
|
1683
|
+
const adapter = this.adapters.values().next().value;
|
|
1684
|
+
if (adapter) {
|
|
1450
1685
|
try {
|
|
1451
|
-
await
|
|
1686
|
+
await adapter.sendMessage(existingRecord.sessionId, {
|
|
1452
1687
|
type: "text",
|
|
1453
1688
|
text: "Session resumed from CLI."
|
|
1454
1689
|
});
|
|
@@ -1463,9 +1698,21 @@ var OpenACPCore = class {
|
|
|
1463
1698
|
};
|
|
1464
1699
|
}
|
|
1465
1700
|
}
|
|
1466
|
-
|
|
1701
|
+
const firstEntry = this.adapters.entries().next().value;
|
|
1702
|
+
if (!firstEntry) {
|
|
1703
|
+
return { ok: false, error: "no_adapter", message: "No channel adapter registered" };
|
|
1704
|
+
}
|
|
1705
|
+
const [adapterChannelId] = firstEntry;
|
|
1706
|
+
let session;
|
|
1467
1707
|
try {
|
|
1468
|
-
|
|
1708
|
+
session = await this.createSession({
|
|
1709
|
+
channelId: adapterChannelId,
|
|
1710
|
+
agentName,
|
|
1711
|
+
workingDirectory: cwd,
|
|
1712
|
+
resumeAgentSessionId: agentSessionId,
|
|
1713
|
+
createThread: true,
|
|
1714
|
+
initialName: "Adopted session"
|
|
1715
|
+
});
|
|
1469
1716
|
} catch (err) {
|
|
1470
1717
|
return {
|
|
1471
1718
|
ok: false,
|
|
@@ -1473,43 +1720,14 @@ var OpenACPCore = class {
|
|
|
1473
1720
|
message: `Failed to resume session: ${err instanceof Error ? err.message : String(err)}`
|
|
1474
1721
|
};
|
|
1475
1722
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
workingDirectory: cwd,
|
|
1480
|
-
agentInstance
|
|
1723
|
+
await this.sessionManager.patchRecord(session.id, {
|
|
1724
|
+
originalAgentSessionId: agentSessionId,
|
|
1725
|
+
platform: { topicId: Number(session.threadId) }
|
|
1481
1726
|
});
|
|
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
1727
|
return {
|
|
1510
1728
|
ok: true,
|
|
1511
1729
|
sessionId: session.id,
|
|
1512
|
-
threadId,
|
|
1730
|
+
threadId: session.threadId,
|
|
1513
1731
|
status: "adopted"
|
|
1514
1732
|
};
|
|
1515
1733
|
}
|
|
@@ -1544,45 +1762,54 @@ var OpenACPCore = class {
|
|
|
1544
1762
|
message.channelId,
|
|
1545
1763
|
(p) => String(p.topicId) === message.threadId
|
|
1546
1764
|
);
|
|
1547
|
-
if (!record)
|
|
1548
|
-
|
|
1765
|
+
if (!record) {
|
|
1766
|
+
log5.debug(
|
|
1767
|
+
{ threadId: message.threadId, channelId: message.channelId },
|
|
1768
|
+
"No session record found for thread"
|
|
1769
|
+
);
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
if (record.status === "cancelled" || record.status === "error") {
|
|
1773
|
+
log5.debug(
|
|
1774
|
+
{ threadId: message.threadId, sessionId: record.sessionId, status: record.status },
|
|
1775
|
+
"Skipping resume of cancelled/error session"
|
|
1776
|
+
);
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
log5.info(
|
|
1780
|
+
{ threadId: message.threadId, sessionId: record.sessionId, status: record.status },
|
|
1781
|
+
"Lazy resume: found record, attempting resume"
|
|
1782
|
+
);
|
|
1549
1783
|
const resumePromise = (async () => {
|
|
1550
1784
|
try {
|
|
1551
|
-
const
|
|
1552
|
-
record.agentName,
|
|
1553
|
-
record.workingDir,
|
|
1554
|
-
record.agentSessionId
|
|
1555
|
-
);
|
|
1556
|
-
const session = new Session({
|
|
1557
|
-
id: record.sessionId,
|
|
1785
|
+
const session = await this.createSession({
|
|
1558
1786
|
channelId: record.channelId,
|
|
1559
1787
|
agentName: record.agentName,
|
|
1560
1788
|
workingDirectory: record.workingDir,
|
|
1561
|
-
|
|
1789
|
+
resumeAgentSessionId: record.agentSessionId,
|
|
1790
|
+
existingSessionId: record.sessionId,
|
|
1791
|
+
initialName: record.name
|
|
1562
1792
|
});
|
|
1563
1793
|
session.threadId = message.threadId;
|
|
1564
|
-
session.
|
|
1565
|
-
session.status = "active";
|
|
1566
|
-
session.name = record.name;
|
|
1794
|
+
session.activate();
|
|
1567
1795
|
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(
|
|
1796
|
+
log5.info(
|
|
1580
1797
|
{ sessionId: session.id, threadId: message.threadId },
|
|
1581
1798
|
"Lazy resume successful"
|
|
1582
1799
|
);
|
|
1583
1800
|
return session;
|
|
1584
1801
|
} catch (err) {
|
|
1585
|
-
|
|
1802
|
+
log5.error({ err, record }, "Lazy resume failed");
|
|
1803
|
+
const adapter = this.adapters.get(message.channelId);
|
|
1804
|
+
if (adapter) {
|
|
1805
|
+
try {
|
|
1806
|
+
await adapter.sendMessage(message.threadId, {
|
|
1807
|
+
type: "error",
|
|
1808
|
+
text: `\u26A0\uFE0F Failed to resume session: ${err instanceof Error ? err.message : String(err)}`
|
|
1809
|
+
});
|
|
1810
|
+
} catch {
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1586
1813
|
return null;
|
|
1587
1814
|
} finally {
|
|
1588
1815
|
this.resumeLocks.delete(lockKey);
|
|
@@ -1592,73 +1819,12 @@ var OpenACPCore = class {
|
|
|
1592
1819
|
return resumePromise;
|
|
1593
1820
|
}
|
|
1594
1821
|
// --- 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
|
-
}
|
|
1822
|
+
/** Create a SessionBridge for the given session and adapter */
|
|
1823
|
+
createBridge(session, adapter) {
|
|
1824
|
+
return new SessionBridge(session, adapter, {
|
|
1825
|
+
messageTransformer: this.messageTransformer,
|
|
1826
|
+
notificationManager: this.notificationManager,
|
|
1827
|
+
sessionManager: this.sessionManager
|
|
1662
1828
|
});
|
|
1663
1829
|
}
|
|
1664
1830
|
};
|
|
@@ -1684,7 +1850,7 @@ import * as fs3 from "fs";
|
|
|
1684
1850
|
import * as path4 from "path";
|
|
1685
1851
|
import * as os2 from "os";
|
|
1686
1852
|
import { fileURLToPath } from "url";
|
|
1687
|
-
var
|
|
1853
|
+
var log6 = createChildLogger({ module: "api-server" });
|
|
1688
1854
|
var DEFAULT_PORT_FILE = path4.join(os2.homedir(), ".openacp", "api.port");
|
|
1689
1855
|
var cachedVersion;
|
|
1690
1856
|
function getVersion() {
|
|
@@ -1730,7 +1896,7 @@ var ApiServer = class {
|
|
|
1730
1896
|
await new Promise((resolve2, reject) => {
|
|
1731
1897
|
this.server.on("error", (err) => {
|
|
1732
1898
|
if (err.code === "EADDRINUSE") {
|
|
1733
|
-
|
|
1899
|
+
log6.warn({ port: this.config.port }, "API port in use, continuing without API server");
|
|
1734
1900
|
this.server = null;
|
|
1735
1901
|
resolve2();
|
|
1736
1902
|
} else {
|
|
@@ -1743,7 +1909,7 @@ var ApiServer = class {
|
|
|
1743
1909
|
this.actualPort = addr.port;
|
|
1744
1910
|
}
|
|
1745
1911
|
this.writePortFile();
|
|
1746
|
-
|
|
1912
|
+
log6.info({ host: this.config.host, port: this.actualPort }, "API server listening");
|
|
1747
1913
|
resolve2();
|
|
1748
1914
|
});
|
|
1749
1915
|
});
|
|
@@ -1799,6 +1965,8 @@ var ApiServer = class {
|
|
|
1799
1965
|
await this.handleHealth(res);
|
|
1800
1966
|
} else if (method === "GET" && url === "/api/version") {
|
|
1801
1967
|
await this.handleVersion(res);
|
|
1968
|
+
} else if (method === "GET" && url === "/api/config/editable") {
|
|
1969
|
+
await this.handleGetEditableConfig(res);
|
|
1802
1970
|
} else if (method === "GET" && url === "/api/config") {
|
|
1803
1971
|
await this.handleGetConfig(res);
|
|
1804
1972
|
} else if (method === "PATCH" && url === "/api/config") {
|
|
@@ -1822,7 +1990,7 @@ var ApiServer = class {
|
|
|
1822
1990
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1823
1991
|
}
|
|
1824
1992
|
} catch (err) {
|
|
1825
|
-
|
|
1993
|
+
log6.error({ err }, "API request error");
|
|
1826
1994
|
this.sendJson(res, 500, { error: "Internal server error" });
|
|
1827
1995
|
}
|
|
1828
1996
|
}
|
|
@@ -1850,24 +2018,25 @@ var ApiServer = class {
|
|
|
1850
2018
|
}
|
|
1851
2019
|
const [adapterId, adapter] = this.core.adapters.entries().next().value ?? [null, null];
|
|
1852
2020
|
const channelId = adapterId ?? "api";
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2021
|
+
const resolvedAgent = agent || config.defaultAgent;
|
|
2022
|
+
const resolvedWorkspace = this.core.configManager.resolveWorkspace(
|
|
2023
|
+
workspace || config.agents[resolvedAgent]?.workingDirectory
|
|
2024
|
+
);
|
|
2025
|
+
const session = await this.core.createSession({
|
|
2026
|
+
channelId,
|
|
2027
|
+
agentName: resolvedAgent,
|
|
2028
|
+
workingDirectory: resolvedWorkspace,
|
|
2029
|
+
createThread: !!adapter,
|
|
2030
|
+
initialName: `\u{1F504} ${resolvedAgent} \u2014 New Session`
|
|
2031
|
+
});
|
|
1863
2032
|
if (!adapter) {
|
|
1864
2033
|
session.agentInstance.onPermissionRequest = async (request) => {
|
|
1865
2034
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
1866
|
-
|
|
2035
|
+
log6.debug({ sessionId: session.id, permissionId: request.id, option: allowOption?.id }, "Auto-approving permission for API session");
|
|
1867
2036
|
return allowOption?.id ?? request.options[0]?.id ?? "";
|
|
1868
2037
|
};
|
|
1869
2038
|
}
|
|
1870
|
-
session.warmup().catch((err) =>
|
|
2039
|
+
session.warmup().catch((err) => log6.warn({ err, sessionId: session.id }, "API session warmup failed"));
|
|
1871
2040
|
this.sendJson(res, 200, {
|
|
1872
2041
|
sessionId: session.id,
|
|
1873
2042
|
agent: session.agentName,
|
|
@@ -1949,7 +2118,7 @@ var ApiServer = class {
|
|
|
1949
2118
|
return;
|
|
1950
2119
|
}
|
|
1951
2120
|
session.dangerousMode = enabled;
|
|
1952
|
-
await this.core.sessionManager.
|
|
2121
|
+
await this.core.sessionManager.patchRecord(sessionId, { dangerousMode: enabled });
|
|
1953
2122
|
this.sendJson(res, 200, { ok: true, dangerousMode: enabled });
|
|
1954
2123
|
}
|
|
1955
2124
|
async handleHealth(res) {
|
|
@@ -1977,6 +2146,21 @@ var ApiServer = class {
|
|
|
1977
2146
|
async handleVersion(res) {
|
|
1978
2147
|
this.sendJson(res, 200, { version: getVersion() });
|
|
1979
2148
|
}
|
|
2149
|
+
async handleGetEditableConfig(res) {
|
|
2150
|
+
const { getSafeFields: getSafeFields2, resolveOptions: resolveOptions2, getConfigValue: getConfigValue2 } = await import("./config-registry-SNKA2EH2.js");
|
|
2151
|
+
const config = this.core.configManager.get();
|
|
2152
|
+
const safeFields = getSafeFields2();
|
|
2153
|
+
const fields = safeFields.map((def) => ({
|
|
2154
|
+
path: def.path,
|
|
2155
|
+
displayName: def.displayName,
|
|
2156
|
+
group: def.group,
|
|
2157
|
+
type: def.type,
|
|
2158
|
+
options: resolveOptions2(def, config),
|
|
2159
|
+
value: getConfigValue2(config, def.path),
|
|
2160
|
+
hotReload: def.hotReload
|
|
2161
|
+
}));
|
|
2162
|
+
this.sendJson(res, 200, { fields });
|
|
2163
|
+
}
|
|
1980
2164
|
async handleGetConfig(res) {
|
|
1981
2165
|
const config = this.core.configManager.get();
|
|
1982
2166
|
this.sendJson(res, 200, { config: redactConfig(config) });
|
|
@@ -2017,7 +2201,7 @@ var ApiServer = class {
|
|
|
2017
2201
|
return;
|
|
2018
2202
|
}
|
|
2019
2203
|
target[lastKey] = value;
|
|
2020
|
-
const { ConfigSchema } = await import("./config-
|
|
2204
|
+
const { ConfigSchema } = await import("./config-PCPIBPUA.js");
|
|
2021
2205
|
const result = ConfigSchema.safeParse(cloned);
|
|
2022
2206
|
if (!result.success) {
|
|
2023
2207
|
this.sendJson(res, 400, {
|
|
@@ -2033,12 +2217,9 @@ var ApiServer = class {
|
|
|
2033
2217
|
updateTarget = updateTarget[parts[i]];
|
|
2034
2218
|
}
|
|
2035
2219
|
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
|
-
);
|
|
2220
|
+
await this.core.configManager.save(updates, configPath);
|
|
2221
|
+
const { isHotReloadable: isHotReloadable2 } = await import("./config-registry-SNKA2EH2.js");
|
|
2222
|
+
const needsRestart = !isHotReloadable2(configPath);
|
|
2042
2223
|
this.sendJson(res, 200, {
|
|
2043
2224
|
ok: true,
|
|
2044
2225
|
needsRestart,
|
|
@@ -2097,7 +2278,7 @@ var ApiServer = class {
|
|
|
2097
2278
|
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
2098
2279
|
return;
|
|
2099
2280
|
}
|
|
2100
|
-
await session.
|
|
2281
|
+
await session.abortPrompt();
|
|
2101
2282
|
this.sendJson(res, 200, { ok: true });
|
|
2102
2283
|
}
|
|
2103
2284
|
async handleListSessions(res) {
|
|
@@ -2206,7 +2387,7 @@ var ApiServer = class {
|
|
|
2206
2387
|
};
|
|
2207
2388
|
|
|
2208
2389
|
// src/core/topic-manager.ts
|
|
2209
|
-
var
|
|
2390
|
+
var log7 = createChildLogger({ module: "topic-manager" });
|
|
2210
2391
|
var TopicManager = class {
|
|
2211
2392
|
constructor(sessionManager, adapter, systemTopicIds) {
|
|
2212
2393
|
this.sessionManager = sessionManager;
|
|
@@ -2245,7 +2426,7 @@ var TopicManager = class {
|
|
|
2245
2426
|
try {
|
|
2246
2427
|
await this.adapter.deleteSessionThread(sessionId);
|
|
2247
2428
|
} catch (err) {
|
|
2248
|
-
|
|
2429
|
+
log7.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
|
|
2249
2430
|
}
|
|
2250
2431
|
}
|
|
2251
2432
|
await this.sessionManager.removeRecord(sessionId);
|
|
@@ -2268,7 +2449,7 @@ var TopicManager = class {
|
|
|
2268
2449
|
try {
|
|
2269
2450
|
await this.adapter.deleteSessionThread(record.sessionId);
|
|
2270
2451
|
} catch (err) {
|
|
2271
|
-
|
|
2452
|
+
log7.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
|
|
2272
2453
|
}
|
|
2273
2454
|
}
|
|
2274
2455
|
await this.sessionManager.removeRecord(record.sessionId);
|
|
@@ -2289,6 +2470,40 @@ var TopicManager = class {
|
|
|
2289
2470
|
// src/adapters/telegram/adapter.ts
|
|
2290
2471
|
import { Bot } from "grammy";
|
|
2291
2472
|
|
|
2473
|
+
// src/adapters/telegram/topics.ts
|
|
2474
|
+
async function ensureTopics(bot, chatId, config, saveConfig) {
|
|
2475
|
+
let notificationTopicId = config.notificationTopicId;
|
|
2476
|
+
let assistantTopicId = config.assistantTopicId;
|
|
2477
|
+
if (notificationTopicId === null) {
|
|
2478
|
+
const topic = await bot.api.createForumTopic(chatId, "\u{1F4CB} Notifications");
|
|
2479
|
+
notificationTopicId = topic.message_thread_id;
|
|
2480
|
+
await saveConfig({ notificationTopicId });
|
|
2481
|
+
}
|
|
2482
|
+
if (assistantTopicId === null) {
|
|
2483
|
+
const topic = await bot.api.createForumTopic(chatId, "\u{1F916} Assistant");
|
|
2484
|
+
assistantTopicId = topic.message_thread_id;
|
|
2485
|
+
await saveConfig({ assistantTopicId });
|
|
2486
|
+
}
|
|
2487
|
+
return { notificationTopicId, assistantTopicId };
|
|
2488
|
+
}
|
|
2489
|
+
async function createSessionTopic(bot, chatId, name) {
|
|
2490
|
+
const topic = await bot.api.createForumTopic(chatId, name);
|
|
2491
|
+
return topic.message_thread_id;
|
|
2492
|
+
}
|
|
2493
|
+
async function renameSessionTopic(bot, chatId, threadId, name) {
|
|
2494
|
+
try {
|
|
2495
|
+
await bot.api.editForumTopic(chatId, threadId, { name });
|
|
2496
|
+
} catch {
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
function buildDeepLink(chatId, messageId) {
|
|
2500
|
+
const cleanId = String(chatId).replace("-100", "");
|
|
2501
|
+
return `https://t.me/c/${cleanId}/${messageId}`;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// src/adapters/telegram/commands/new-session.ts
|
|
2505
|
+
import { InlineKeyboard as InlineKeyboard2 } from "grammy";
|
|
2506
|
+
|
|
2292
2507
|
// src/adapters/telegram/formatting.ts
|
|
2293
2508
|
function escapeHtml(text) {
|
|
2294
2509
|
if (!text) return "";
|
|
@@ -2452,224 +2667,9 @@ function splitMessage(text, maxLength = 3800) {
|
|
|
2452
2667
|
return chunks;
|
|
2453
2668
|
}
|
|
2454
2669
|
|
|
2455
|
-
// src/adapters/telegram/streaming.ts
|
|
2456
|
-
var FLUSH_INTERVAL = 5e3;
|
|
2457
|
-
var MessageDraft = class {
|
|
2458
|
-
constructor(bot, chatId, threadId, sendQueue, sessionId) {
|
|
2459
|
-
this.bot = bot;
|
|
2460
|
-
this.chatId = chatId;
|
|
2461
|
-
this.threadId = threadId;
|
|
2462
|
-
this.sendQueue = sendQueue;
|
|
2463
|
-
this.sessionId = sessionId;
|
|
2464
|
-
}
|
|
2465
|
-
buffer = "";
|
|
2466
|
-
messageId;
|
|
2467
|
-
firstFlushPending = false;
|
|
2468
|
-
flushTimer;
|
|
2469
|
-
flushPromise = Promise.resolve();
|
|
2470
|
-
lastSentBuffer = "";
|
|
2471
|
-
displayTruncated = false;
|
|
2472
|
-
append(text) {
|
|
2473
|
-
if (!text) return;
|
|
2474
|
-
this.buffer += text;
|
|
2475
|
-
this.scheduleFlush();
|
|
2476
|
-
}
|
|
2477
|
-
scheduleFlush() {
|
|
2478
|
-
if (this.flushTimer) return;
|
|
2479
|
-
this.flushTimer = setTimeout(() => {
|
|
2480
|
-
this.flushTimer = void 0;
|
|
2481
|
-
this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
|
|
2482
|
-
});
|
|
2483
|
-
}, FLUSH_INTERVAL);
|
|
2484
|
-
}
|
|
2485
|
-
async flush() {
|
|
2486
|
-
if (!this.buffer) return;
|
|
2487
|
-
if (this.firstFlushPending) return;
|
|
2488
|
-
const snapshot = this.buffer;
|
|
2489
|
-
let html = markdownToTelegramHtml(snapshot);
|
|
2490
|
-
if (!html) return;
|
|
2491
|
-
let truncated = false;
|
|
2492
|
-
if (html.length > 4096) {
|
|
2493
|
-
const ratio = 4e3 / html.length;
|
|
2494
|
-
const targetLen = Math.floor(snapshot.length * ratio);
|
|
2495
|
-
let cutAt = snapshot.lastIndexOf("\n", targetLen);
|
|
2496
|
-
if (cutAt < targetLen * 0.5) cutAt = targetLen;
|
|
2497
|
-
html = markdownToTelegramHtml(snapshot.slice(0, cutAt) + "\n\u2026");
|
|
2498
|
-
truncated = true;
|
|
2499
|
-
if (html.length > 4096) {
|
|
2500
|
-
html = html.slice(0, 4090) + "\n\u2026";
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
if (!this.messageId) {
|
|
2504
|
-
this.firstFlushPending = true;
|
|
2505
|
-
try {
|
|
2506
|
-
const result = await this.sendQueue.enqueue(
|
|
2507
|
-
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
2508
|
-
message_thread_id: this.threadId,
|
|
2509
|
-
parse_mode: "HTML",
|
|
2510
|
-
disable_notification: true
|
|
2511
|
-
}),
|
|
2512
|
-
{ type: "other" }
|
|
2513
|
-
);
|
|
2514
|
-
if (result) {
|
|
2515
|
-
this.messageId = result.message_id;
|
|
2516
|
-
if (!truncated) {
|
|
2517
|
-
this.lastSentBuffer = snapshot;
|
|
2518
|
-
this.displayTruncated = false;
|
|
2519
|
-
} else {
|
|
2520
|
-
this.displayTruncated = true;
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
} catch {
|
|
2524
|
-
} finally {
|
|
2525
|
-
this.firstFlushPending = false;
|
|
2526
|
-
}
|
|
2527
|
-
} else {
|
|
2528
|
-
try {
|
|
2529
|
-
const result = await this.sendQueue.enqueue(
|
|
2530
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
2531
|
-
parse_mode: "HTML"
|
|
2532
|
-
}),
|
|
2533
|
-
{ type: "text", key: this.sessionId }
|
|
2534
|
-
);
|
|
2535
|
-
if (result !== void 0) {
|
|
2536
|
-
if (!truncated) {
|
|
2537
|
-
this.lastSentBuffer = snapshot;
|
|
2538
|
-
this.displayTruncated = false;
|
|
2539
|
-
} else {
|
|
2540
|
-
this.displayTruncated = true;
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
} catch {
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
async finalize() {
|
|
2548
|
-
if (this.flushTimer) {
|
|
2549
|
-
clearTimeout(this.flushTimer);
|
|
2550
|
-
this.flushTimer = void 0;
|
|
2551
|
-
}
|
|
2552
|
-
await this.flushPromise;
|
|
2553
|
-
if (!this.buffer) return this.messageId;
|
|
2554
|
-
if (this.messageId && this.buffer === this.lastSentBuffer && !this.displayTruncated) {
|
|
2555
|
-
return this.messageId;
|
|
2556
|
-
}
|
|
2557
|
-
const fullHtml = markdownToTelegramHtml(this.buffer);
|
|
2558
|
-
if (fullHtml.length <= 4096) {
|
|
2559
|
-
try {
|
|
2560
|
-
if (this.messageId) {
|
|
2561
|
-
await this.sendQueue.enqueue(
|
|
2562
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId, fullHtml, {
|
|
2563
|
-
parse_mode: "HTML"
|
|
2564
|
-
}),
|
|
2565
|
-
{ type: "other" }
|
|
2566
|
-
);
|
|
2567
|
-
} else {
|
|
2568
|
-
const msg = await this.sendQueue.enqueue(
|
|
2569
|
-
() => this.bot.api.sendMessage(this.chatId, fullHtml, {
|
|
2570
|
-
message_thread_id: this.threadId,
|
|
2571
|
-
parse_mode: "HTML",
|
|
2572
|
-
disable_notification: true
|
|
2573
|
-
}),
|
|
2574
|
-
{ type: "other" }
|
|
2575
|
-
);
|
|
2576
|
-
if (msg) this.messageId = msg.message_id;
|
|
2577
|
-
}
|
|
2578
|
-
return this.messageId;
|
|
2579
|
-
} catch {
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
const mdChunks = splitMessage(this.buffer);
|
|
2583
|
-
for (let i = 0; i < mdChunks.length; i++) {
|
|
2584
|
-
const html = markdownToTelegramHtml(mdChunks[i]);
|
|
2585
|
-
try {
|
|
2586
|
-
if (i === 0 && this.messageId) {
|
|
2587
|
-
await this.sendQueue.enqueue(
|
|
2588
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
2589
|
-
parse_mode: "HTML"
|
|
2590
|
-
}),
|
|
2591
|
-
{ type: "other" }
|
|
2592
|
-
);
|
|
2593
|
-
} else {
|
|
2594
|
-
const msg = await this.sendQueue.enqueue(
|
|
2595
|
-
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
2596
|
-
message_thread_id: this.threadId,
|
|
2597
|
-
parse_mode: "HTML",
|
|
2598
|
-
disable_notification: true
|
|
2599
|
-
}),
|
|
2600
|
-
{ type: "other" }
|
|
2601
|
-
);
|
|
2602
|
-
if (msg) {
|
|
2603
|
-
this.messageId = msg.message_id;
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
} catch {
|
|
2607
|
-
try {
|
|
2608
|
-
if (i === 0 && this.messageId) {
|
|
2609
|
-
await this.sendQueue.enqueue(
|
|
2610
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId, mdChunks[i].slice(0, 4096)),
|
|
2611
|
-
{ type: "other" }
|
|
2612
|
-
);
|
|
2613
|
-
} else {
|
|
2614
|
-
const msg = await this.sendQueue.enqueue(
|
|
2615
|
-
() => this.bot.api.sendMessage(this.chatId, mdChunks[i].slice(0, 4096), {
|
|
2616
|
-
message_thread_id: this.threadId,
|
|
2617
|
-
disable_notification: true
|
|
2618
|
-
}),
|
|
2619
|
-
{ type: "other" }
|
|
2620
|
-
);
|
|
2621
|
-
if (msg) {
|
|
2622
|
-
this.messageId = msg.message_id;
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
} catch {
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
return this.messageId;
|
|
2630
|
-
}
|
|
2631
|
-
getMessageId() {
|
|
2632
|
-
return this.messageId;
|
|
2633
|
-
}
|
|
2634
|
-
};
|
|
2635
|
-
|
|
2636
|
-
// src/adapters/telegram/topics.ts
|
|
2637
|
-
async function ensureTopics(bot, chatId, config, saveConfig) {
|
|
2638
|
-
let notificationTopicId = config.notificationTopicId;
|
|
2639
|
-
let assistantTopicId = config.assistantTopicId;
|
|
2640
|
-
if (notificationTopicId === null) {
|
|
2641
|
-
const topic = await bot.api.createForumTopic(chatId, "\u{1F4CB} Notifications");
|
|
2642
|
-
notificationTopicId = topic.message_thread_id;
|
|
2643
|
-
await saveConfig({ notificationTopicId });
|
|
2644
|
-
}
|
|
2645
|
-
if (assistantTopicId === null) {
|
|
2646
|
-
const topic = await bot.api.createForumTopic(chatId, "\u{1F916} Assistant");
|
|
2647
|
-
assistantTopicId = topic.message_thread_id;
|
|
2648
|
-
await saveConfig({ assistantTopicId });
|
|
2649
|
-
}
|
|
2650
|
-
return { notificationTopicId, assistantTopicId };
|
|
2651
|
-
}
|
|
2652
|
-
async function createSessionTopic(bot, chatId, name) {
|
|
2653
|
-
const topic = await bot.api.createForumTopic(chatId, name);
|
|
2654
|
-
return topic.message_thread_id;
|
|
2655
|
-
}
|
|
2656
|
-
async function renameSessionTopic(bot, chatId, threadId, name) {
|
|
2657
|
-
try {
|
|
2658
|
-
await bot.api.editForumTopic(chatId, threadId, { name });
|
|
2659
|
-
} catch {
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
function buildDeepLink(chatId, messageId) {
|
|
2663
|
-
const cleanId = String(chatId).replace("-100", "");
|
|
2664
|
-
return `https://t.me/c/${cleanId}/${messageId}`;
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
// src/adapters/telegram/commands/new-session.ts
|
|
2668
|
-
import { InlineKeyboard as InlineKeyboard2 } from "grammy";
|
|
2669
|
-
|
|
2670
2670
|
// src/adapters/telegram/commands/admin.ts
|
|
2671
2671
|
import { InlineKeyboard } from "grammy";
|
|
2672
|
-
var
|
|
2672
|
+
var log9 = createChildLogger({ module: "telegram-cmd-admin" });
|
|
2673
2673
|
function buildDangerousModeKeyboard(sessionId, enabled) {
|
|
2674
2674
|
return new InlineKeyboard().text(
|
|
2675
2675
|
enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
|
|
@@ -2682,8 +2682,8 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
2682
2682
|
const session = core.sessionManager.getSession(sessionId);
|
|
2683
2683
|
if (session) {
|
|
2684
2684
|
session.dangerousMode = !session.dangerousMode;
|
|
2685
|
-
|
|
2686
|
-
core.sessionManager.
|
|
2685
|
+
log9.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
|
|
2686
|
+
core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {
|
|
2687
2687
|
});
|
|
2688
2688
|
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2689
2689
|
try {
|
|
@@ -2707,9 +2707,9 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
2707
2707
|
return;
|
|
2708
2708
|
}
|
|
2709
2709
|
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
2710
|
-
core.sessionManager.
|
|
2710
|
+
core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {
|
|
2711
2711
|
});
|
|
2712
|
-
|
|
2712
|
+
log9.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
|
|
2713
2713
|
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2714
2714
|
try {
|
|
2715
2715
|
await ctx.answerCallbackQuery({ text: toastText });
|
|
@@ -2736,7 +2736,7 @@ async function handleEnableDangerous(ctx, core) {
|
|
|
2736
2736
|
return;
|
|
2737
2737
|
}
|
|
2738
2738
|
session.dangerousMode = true;
|
|
2739
|
-
core.sessionManager.
|
|
2739
|
+
core.sessionManager.patchRecord(session.id, { dangerousMode: true }).catch(() => {
|
|
2740
2740
|
});
|
|
2741
2741
|
} else {
|
|
2742
2742
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
@@ -2748,7 +2748,7 @@ async function handleEnableDangerous(ctx, core) {
|
|
|
2748
2748
|
await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
|
|
2749
2749
|
return;
|
|
2750
2750
|
}
|
|
2751
|
-
core.sessionManager.
|
|
2751
|
+
core.sessionManager.patchRecord(record.sessionId, { dangerousMode: true }).catch(() => {
|
|
2752
2752
|
});
|
|
2753
2753
|
}
|
|
2754
2754
|
await ctx.reply(
|
|
@@ -2773,7 +2773,7 @@ async function handleDisableDangerous(ctx, core) {
|
|
|
2773
2773
|
return;
|
|
2774
2774
|
}
|
|
2775
2775
|
session.dangerousMode = false;
|
|
2776
|
-
core.sessionManager.
|
|
2776
|
+
core.sessionManager.patchRecord(session.id, { dangerousMode: false }).catch(() => {
|
|
2777
2777
|
});
|
|
2778
2778
|
} else {
|
|
2779
2779
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
@@ -2785,7 +2785,7 @@ async function handleDisableDangerous(ctx, core) {
|
|
|
2785
2785
|
await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
|
|
2786
2786
|
return;
|
|
2787
2787
|
}
|
|
2788
|
-
core.sessionManager.
|
|
2788
|
+
core.sessionManager.patchRecord(record.sessionId, { dangerousMode: false }).catch(() => {
|
|
2789
2789
|
});
|
|
2790
2790
|
}
|
|
2791
2791
|
await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
|
|
@@ -2838,7 +2838,7 @@ async function handleRestart(ctx, core) {
|
|
|
2838
2838
|
}
|
|
2839
2839
|
|
|
2840
2840
|
// src/adapters/telegram/commands/new-session.ts
|
|
2841
|
-
var
|
|
2841
|
+
var log10 = createChildLogger({ module: "telegram-cmd-new-session" });
|
|
2842
2842
|
var pendingNewSessions = /* @__PURE__ */ new Map();
|
|
2843
2843
|
var PENDING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2844
2844
|
function cleanupPending(userId) {
|
|
@@ -2870,37 +2870,12 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2870
2870
|
return;
|
|
2871
2871
|
}
|
|
2872
2872
|
}
|
|
2873
|
-
|
|
2874
|
-
if (!userId) return;
|
|
2875
|
-
const agents = core.agentManager.getAvailableAgents();
|
|
2876
|
-
const config = core.configManager.get();
|
|
2877
|
-
if (agentName || agents.length === 1) {
|
|
2878
|
-
const selectedAgent = agentName || config.defaultAgent;
|
|
2879
|
-
await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
|
|
2880
|
-
return;
|
|
2881
|
-
}
|
|
2882
|
-
const keyboard = new InlineKeyboard2();
|
|
2883
|
-
for (const agent of agents) {
|
|
2884
|
-
const label = agent.name === config.defaultAgent ? `${agent.name} (default)` : agent.name;
|
|
2885
|
-
keyboard.text(label, `m:new:agent:${agent.name}`).row();
|
|
2886
|
-
}
|
|
2887
|
-
keyboard.text("\u274C Cancel", "m:new:cancel");
|
|
2888
|
-
const msg = await ctx.reply(
|
|
2889
|
-
`\u{1F916} <b>Choose an agent:</b>`,
|
|
2890
|
-
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
2891
|
-
);
|
|
2892
|
-
cleanupPending(userId);
|
|
2893
|
-
pendingNewSessions.set(userId, {
|
|
2894
|
-
step: "agent",
|
|
2895
|
-
messageId: msg.message_id,
|
|
2896
|
-
threadId: currentThreadId,
|
|
2897
|
-
timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
|
|
2898
|
-
});
|
|
2873
|
+
await showAgentPicker(ctx, core, chatId, agentName);
|
|
2899
2874
|
}
|
|
2900
2875
|
async function startWorkspaceStep(ctx, core, chatId, userId, agentName) {
|
|
2901
2876
|
const config = core.configManager.get();
|
|
2902
2877
|
const baseDir = config.workspace.baseDir;
|
|
2903
|
-
const keyboard = new InlineKeyboard2().text(`\u{1F4C1} Use ${baseDir}`, "m:new:ws:default").row().text("\u270F\uFE0F Enter project path", "m:new:ws:custom")
|
|
2878
|
+
const keyboard = new InlineKeyboard2().text(`\u{1F4C1} Use ${baseDir}`, "m:new:ws:default").row().text("\u270F\uFE0F Enter project path", "m:new:ws:custom");
|
|
2904
2879
|
const text = `\u{1F4C1} <b>Where should ${escapeHtml(agentName)} work?</b>
|
|
2905
2880
|
|
|
2906
2881
|
Enter the path to your project folder \u2014 the agent will read, write, and run code there.
|
|
@@ -2962,7 +2937,7 @@ async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
|
|
|
2962
2937
|
});
|
|
2963
2938
|
}
|
|
2964
2939
|
async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
|
|
2965
|
-
|
|
2940
|
+
log10.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
|
|
2966
2941
|
let threadId;
|
|
2967
2942
|
try {
|
|
2968
2943
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -2973,7 +2948,7 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
|
|
|
2973
2948
|
});
|
|
2974
2949
|
const session = await core.handleNewSession("telegram", agentName, workspace);
|
|
2975
2950
|
session.threadId = String(threadId);
|
|
2976
|
-
await core.sessionManager.
|
|
2951
|
+
await core.sessionManager.patchRecord(session.id, { platform: { topicId: threadId } });
|
|
2977
2952
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
2978
2953
|
try {
|
|
2979
2954
|
await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
|
|
@@ -2992,10 +2967,10 @@ This is your coding session \u2014 chat here to work with the agent.`,
|
|
|
2992
2967
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2993
2968
|
}
|
|
2994
2969
|
);
|
|
2995
|
-
session.warmup().catch((err) =>
|
|
2970
|
+
session.warmup().catch((err) => log10.error({ err }, "Warm-up error"));
|
|
2996
2971
|
return threadId ?? null;
|
|
2997
2972
|
} catch (err) {
|
|
2998
|
-
|
|
2973
|
+
log10.error({ err }, "Session creation failed");
|
|
2999
2974
|
if (threadId) {
|
|
3000
2975
|
try {
|
|
3001
2976
|
await ctx.api.deleteForumTopic(chatId, threadId);
|
|
@@ -3059,9 +3034,9 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
3059
3034
|
workspace
|
|
3060
3035
|
);
|
|
3061
3036
|
session.threadId = String(newThreadId);
|
|
3062
|
-
await core.sessionManager.
|
|
3037
|
+
await core.sessionManager.patchRecord(session.id, { platform: {
|
|
3063
3038
|
topicId: newThreadId
|
|
3064
|
-
});
|
|
3039
|
+
} });
|
|
3065
3040
|
await ctx.api.sendMessage(
|
|
3066
3041
|
chatId,
|
|
3067
3042
|
`\u2705 New chat (same agent & workspace)
|
|
@@ -3073,7 +3048,7 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
3073
3048
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
3074
3049
|
}
|
|
3075
3050
|
);
|
|
3076
|
-
session.warmup().catch((err) =>
|
|
3051
|
+
session.warmup().catch((err) => log10.error({ err }, "Warm-up error"));
|
|
3077
3052
|
} catch (err) {
|
|
3078
3053
|
if (newThreadId) {
|
|
3079
3054
|
try {
|
|
@@ -3099,12 +3074,12 @@ async function executeNewSession(bot, core, chatId, agentName, workspace) {
|
|
|
3099
3074
|
workspace
|
|
3100
3075
|
);
|
|
3101
3076
|
session.threadId = String(threadId);
|
|
3102
|
-
await core.sessionManager.
|
|
3077
|
+
await core.sessionManager.patchRecord(session.id, { platform: {
|
|
3103
3078
|
topicId: threadId
|
|
3104
|
-
});
|
|
3079
|
+
} });
|
|
3105
3080
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
3106
3081
|
await renameSessionTopic(bot, chatId, threadId, finalName);
|
|
3107
|
-
session.warmup().catch((err) =>
|
|
3082
|
+
session.warmup().catch((err) => log10.error({ err }, "Warm-up error"));
|
|
3108
3083
|
return { session, threadId, firstMsgId };
|
|
3109
3084
|
} catch (err) {
|
|
3110
3085
|
try {
|
|
@@ -3135,30 +3110,35 @@ async function handlePendingWorkspaceInput(ctx, core, chatId, assistantTopicId)
|
|
|
3135
3110
|
return true;
|
|
3136
3111
|
}
|
|
3137
3112
|
async function startInteractiveNewSession(ctx, core, chatId, agentName) {
|
|
3113
|
+
await showAgentPicker(ctx, core, chatId, agentName);
|
|
3114
|
+
}
|
|
3115
|
+
async function showAgentPicker(ctx, core, chatId, agentName) {
|
|
3138
3116
|
const userId = ctx.from?.id;
|
|
3139
3117
|
if (!userId) return;
|
|
3140
|
-
const
|
|
3118
|
+
const installedEntries = core.agentCatalog.getInstalledEntries();
|
|
3119
|
+
const agentKeys = Object.keys(installedEntries);
|
|
3141
3120
|
const config = core.configManager.get();
|
|
3142
|
-
if (agentName ||
|
|
3121
|
+
if (agentName || agentKeys.length === 1) {
|
|
3143
3122
|
const selectedAgent = agentName || config.defaultAgent;
|
|
3144
3123
|
await startWorkspaceStep(ctx, core, chatId, userId, selectedAgent);
|
|
3145
3124
|
return;
|
|
3146
3125
|
}
|
|
3147
3126
|
const keyboard = new InlineKeyboard2();
|
|
3148
|
-
for (const
|
|
3149
|
-
const
|
|
3150
|
-
|
|
3127
|
+
for (const key of agentKeys) {
|
|
3128
|
+
const agent = installedEntries[key];
|
|
3129
|
+
const label = key === config.defaultAgent ? `${agent.name} (default)` : agent.name;
|
|
3130
|
+
keyboard.text(label, `m:new:agent:${key}`).row();
|
|
3151
3131
|
}
|
|
3152
|
-
keyboard.text("\u274C Cancel", "m:new:cancel");
|
|
3153
3132
|
const msg = await ctx.reply(
|
|
3154
3133
|
`\u{1F916} <b>Choose an agent:</b>`,
|
|
3155
3134
|
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3156
3135
|
);
|
|
3157
3136
|
cleanupPending(userId);
|
|
3137
|
+
const threadId = ctx.message?.message_thread_id ?? ctx.callbackQuery?.message?.message_thread_id;
|
|
3158
3138
|
pendingNewSessions.set(userId, {
|
|
3159
3139
|
step: "agent",
|
|
3160
3140
|
messageId: msg.message_id,
|
|
3161
|
-
threadId
|
|
3141
|
+
threadId,
|
|
3162
3142
|
timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
|
|
3163
3143
|
});
|
|
3164
3144
|
}
|
|
@@ -3247,7 +3227,7 @@ Or just the folder name like <code>my-project</code> (will use ${core.configMana
|
|
|
3247
3227
|
|
|
3248
3228
|
// src/adapters/telegram/commands/session.ts
|
|
3249
3229
|
import { InlineKeyboard as InlineKeyboard3 } from "grammy";
|
|
3250
|
-
var
|
|
3230
|
+
var log11 = createChildLogger({ module: "telegram-cmd-session" });
|
|
3251
3231
|
async function handleCancel(ctx, core, assistant) {
|
|
3252
3232
|
const threadId = ctx.message?.message_thread_id;
|
|
3253
3233
|
if (!threadId) return;
|
|
@@ -3265,14 +3245,14 @@ async function handleCancel(ctx, core, assistant) {
|
|
|
3265
3245
|
String(threadId)
|
|
3266
3246
|
);
|
|
3267
3247
|
if (session) {
|
|
3268
|
-
|
|
3269
|
-
await session.
|
|
3248
|
+
log11.info({ sessionId: session.id }, "Cancel session command");
|
|
3249
|
+
await session.abortPrompt();
|
|
3270
3250
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
3271
3251
|
return;
|
|
3272
3252
|
}
|
|
3273
3253
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
3274
3254
|
if (record && record.status !== "cancelled" && record.status !== "error") {
|
|
3275
|
-
|
|
3255
|
+
log11.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
|
|
3276
3256
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
3277
3257
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
3278
3258
|
}
|
|
@@ -3356,8 +3336,8 @@ async function handleTopics(ctx, core) {
|
|
|
3356
3336
|
const truncated = records.length > MAX_DISPLAY ? `
|
|
3357
3337
|
|
|
3358
3338
|
<i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
|
|
3359
|
-
const finishedCount =
|
|
3360
|
-
const errorCount =
|
|
3339
|
+
const finishedCount = allRecords.filter((r) => r.status === "finished").length;
|
|
3340
|
+
const errorCount = allRecords.filter((r) => r.status === "error" || r.status === "cancelled").length;
|
|
3361
3341
|
const keyboard = new InlineKeyboard3();
|
|
3362
3342
|
if (finishedCount > 0) {
|
|
3363
3343
|
keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
|
|
@@ -3368,7 +3348,7 @@ async function handleTopics(ctx, core) {
|
|
|
3368
3348
|
if (finishedCount + errorCount > 0) {
|
|
3369
3349
|
keyboard.text(`Cleanup all non-active (${finishedCount + errorCount})`, "m:cleanup:all").row();
|
|
3370
3350
|
}
|
|
3371
|
-
keyboard.text(`\u26A0\uFE0F Cleanup ALL (${
|
|
3351
|
+
keyboard.text(`\u26A0\uFE0F Cleanup ALL (${allRecords.length})`, "m:cleanup:everything").row();
|
|
3372
3352
|
keyboard.text("Refresh", "m:topics");
|
|
3373
3353
|
await ctx.reply(
|
|
3374
3354
|
`${header}
|
|
@@ -3377,17 +3357,14 @@ ${lines.join("\n")}${truncated}`,
|
|
|
3377
3357
|
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3378
3358
|
);
|
|
3379
3359
|
} catch (err) {
|
|
3380
|
-
|
|
3360
|
+
log11.error({ err }, "handleTopics error");
|
|
3381
3361
|
await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
|
|
3382
3362
|
});
|
|
3383
3363
|
}
|
|
3384
3364
|
}
|
|
3385
3365
|
async function handleCleanup(ctx, core, chatId, statuses) {
|
|
3386
3366
|
const allRecords = core.sessionManager.listRecords();
|
|
3387
|
-
const cleanable = allRecords.filter((r) =>
|
|
3388
|
-
const platform = r.platform;
|
|
3389
|
-
return !!platform?.topicId && statuses.includes(r.status);
|
|
3390
|
-
});
|
|
3367
|
+
const cleanable = allRecords.filter((r) => statuses.includes(r.status));
|
|
3391
3368
|
if (cleanable.length === 0) {
|
|
3392
3369
|
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3393
3370
|
return;
|
|
@@ -3401,13 +3378,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
|
|
|
3401
3378
|
try {
|
|
3402
3379
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3403
3380
|
} catch (err) {
|
|
3404
|
-
|
|
3381
|
+
log11.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3405
3382
|
}
|
|
3406
3383
|
}
|
|
3407
3384
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
3408
3385
|
deleted++;
|
|
3409
3386
|
} catch (err) {
|
|
3410
|
-
|
|
3387
|
+
log11.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3411
3388
|
failed++;
|
|
3412
3389
|
}
|
|
3413
3390
|
}
|
|
@@ -3420,8 +3397,7 @@ async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
|
|
|
3420
3397
|
const allRecords = core.sessionManager.listRecords();
|
|
3421
3398
|
const cleanable = allRecords.filter((r) => {
|
|
3422
3399
|
const platform = r.platform;
|
|
3423
|
-
if (
|
|
3424
|
-
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3400
|
+
if (systemTopicIds && platform?.topicId && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3425
3401
|
return true;
|
|
3426
3402
|
});
|
|
3427
3403
|
if (cleanable.length === 0) {
|
|
@@ -3464,8 +3440,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
3464
3440
|
const allRecords = core.sessionManager.listRecords();
|
|
3465
3441
|
const cleanable = allRecords.filter((r) => {
|
|
3466
3442
|
const platform = r.platform;
|
|
3467
|
-
if (
|
|
3468
|
-
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3443
|
+
if (systemTopicIds && platform?.topicId && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3469
3444
|
return true;
|
|
3470
3445
|
});
|
|
3471
3446
|
if (cleanable.length === 0) {
|
|
@@ -3480,7 +3455,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
3480
3455
|
try {
|
|
3481
3456
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
3482
3457
|
} catch (err) {
|
|
3483
|
-
|
|
3458
|
+
log11.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
3484
3459
|
}
|
|
3485
3460
|
}
|
|
3486
3461
|
const topicId = record.platform?.topicId;
|
|
@@ -3488,13 +3463,13 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
3488
3463
|
try {
|
|
3489
3464
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3490
3465
|
} catch (err) {
|
|
3491
|
-
|
|
3466
|
+
log11.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3492
3467
|
}
|
|
3493
3468
|
}
|
|
3494
3469
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
3495
3470
|
deleted++;
|
|
3496
3471
|
} catch (err) {
|
|
3497
|
-
|
|
3472
|
+
log11.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3498
3473
|
failed++;
|
|
3499
3474
|
}
|
|
3500
3475
|
}
|
|
@@ -3507,7 +3482,7 @@ async function executeCancelSession(core, excludeSessionId) {
|
|
|
3507
3482
|
const sessions = core.sessionManager.listSessions("telegram").filter((s) => s.status === "active" && s.id !== excludeSessionId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
3508
3483
|
const session = sessions[0];
|
|
3509
3484
|
if (!session) return null;
|
|
3510
|
-
await session.
|
|
3485
|
+
await session.abortPrompt();
|
|
3511
3486
|
return session;
|
|
3512
3487
|
}
|
|
3513
3488
|
function setupSessionCallbacks(bot, core, chatId, systemTopicIds) {
|
|
@@ -3537,100 +3512,213 @@ function setupSessionCallbacks(bot, core, chatId, systemTopicIds) {
|
|
|
3537
3512
|
});
|
|
3538
3513
|
}
|
|
3539
3514
|
|
|
3540
|
-
// src/adapters/telegram/commands/
|
|
3515
|
+
// src/adapters/telegram/commands/agents.ts
|
|
3541
3516
|
import { InlineKeyboard as InlineKeyboard4 } from "grammy";
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3517
|
+
var AGENTS_PER_PAGE = 6;
|
|
3518
|
+
async function handleAgents(ctx, core, page = 0) {
|
|
3519
|
+
const catalog = core.agentCatalog;
|
|
3520
|
+
const items = catalog.getAvailable();
|
|
3521
|
+
const installed = items.filter((i) => i.installed);
|
|
3522
|
+
const available = items.filter((i) => !i.installed);
|
|
3523
|
+
let text = "<b>\u{1F916} Agents</b>\n\n";
|
|
3524
|
+
if (installed.length > 0) {
|
|
3525
|
+
text += "<b>Installed:</b>\n";
|
|
3526
|
+
for (const item of installed) {
|
|
3527
|
+
text += `\u2705 <b>${escapeHtml(item.name)}</b>`;
|
|
3528
|
+
if (item.description) {
|
|
3529
|
+
text += ` \u2014 <i>${escapeHtml(truncate(item.description, 50))}</i>`;
|
|
3530
|
+
}
|
|
3531
|
+
text += "\n";
|
|
3532
|
+
}
|
|
3533
|
+
text += "\n";
|
|
3534
|
+
}
|
|
3535
|
+
if (available.length > 0) {
|
|
3536
|
+
const totalPages = Math.ceil(available.length / AGENTS_PER_PAGE);
|
|
3537
|
+
const safePage = Math.max(0, Math.min(page, totalPages - 1));
|
|
3538
|
+
const pageItems = available.slice(safePage * AGENTS_PER_PAGE, (safePage + 1) * AGENTS_PER_PAGE);
|
|
3539
|
+
text += `<b>Available to install:</b>`;
|
|
3540
|
+
if (totalPages > 1) {
|
|
3541
|
+
text += ` (${safePage + 1}/${totalPages})`;
|
|
3542
|
+
}
|
|
3543
|
+
text += "\n";
|
|
3544
|
+
for (const item of pageItems) {
|
|
3545
|
+
if (item.available) {
|
|
3546
|
+
text += `\u2B07\uFE0F <b>${escapeHtml(item.name)}</b>`;
|
|
3547
|
+
} else {
|
|
3548
|
+
const deps = item.missingDeps?.join(", ") ?? "requirements not met";
|
|
3549
|
+
text += `\u26A0\uFE0F <b>${escapeHtml(item.name)}</b> <i>(needs: ${escapeHtml(deps)})</i>`;
|
|
3550
|
+
}
|
|
3551
|
+
if (item.description) {
|
|
3552
|
+
text += `
|
|
3553
|
+
<i>${escapeHtml(truncate(item.description, 60))}</i>`;
|
|
3554
|
+
}
|
|
3555
|
+
text += "\n";
|
|
3556
|
+
}
|
|
3557
|
+
const keyboard = new InlineKeyboard4();
|
|
3558
|
+
const installable = pageItems.filter((i) => i.available);
|
|
3559
|
+
for (let i = 0; i < installable.length; i += 2) {
|
|
3560
|
+
const row = installable.slice(i, i + 2);
|
|
3561
|
+
for (const item of row) {
|
|
3562
|
+
keyboard.text(`\u2B07\uFE0F ${item.name}`, `ag:install:${item.key}`);
|
|
3563
|
+
}
|
|
3564
|
+
keyboard.row();
|
|
3565
|
+
}
|
|
3566
|
+
if (totalPages > 1) {
|
|
3567
|
+
if (safePage > 0) {
|
|
3568
|
+
keyboard.text("\u25C0\uFE0F Prev", `ag:page:${safePage - 1}`);
|
|
3569
|
+
}
|
|
3570
|
+
if (safePage < totalPages - 1) {
|
|
3571
|
+
keyboard.text("Next \u25B6\uFE0F", `ag:page:${safePage + 1}`);
|
|
3572
|
+
}
|
|
3573
|
+
keyboard.row();
|
|
3574
|
+
}
|
|
3575
|
+
if (available.some((i) => !i.available)) {
|
|
3576
|
+
text += "\n\u{1F4A1} <i>Agents marked \u26A0\uFE0F need additional setup. Use</i> <code>openacp agents info <name></code> <i>for details.</i>\n";
|
|
3577
|
+
}
|
|
3578
|
+
await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
3579
|
+
} else {
|
|
3580
|
+
text += "<i>All agents are already installed!</i>";
|
|
3581
|
+
await ctx.reply(text, { parse_mode: "HTML" });
|
|
3582
|
+
}
|
|
3551
3583
|
}
|
|
3552
|
-
async function
|
|
3553
|
-
const
|
|
3554
|
-
const
|
|
3555
|
-
const
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
await ctx.reply(text, { parse_mode: "HTML" });
|
|
3584
|
+
async function handleInstall(ctx, core) {
|
|
3585
|
+
const text = (ctx.message?.text ?? "").trim();
|
|
3586
|
+
const parts = text.split(/\s+/);
|
|
3587
|
+
const nameOrId = parts[1];
|
|
3588
|
+
if (!nameOrId) {
|
|
3589
|
+
await ctx.reply(
|
|
3590
|
+
"\u{1F4E6} <b>Install an agent</b>\n\nUsage: <code>/install <agent-name></code>\nExample: <code>/install gemini</code>\n\nUse /agents to browse available agents.",
|
|
3591
|
+
{ parse_mode: "HTML" }
|
|
3592
|
+
);
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
await installAgentWithProgress(ctx, core, nameOrId);
|
|
3565
3596
|
}
|
|
3566
|
-
async function
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
Each session gets its own topic \u2014 chat there to work with the agent.
|
|
3573
|
-
|
|
3574
|
-
\u{1F4A1} <b>Common Tasks</b>
|
|
3575
|
-
/new [agent] [workspace] \u2014 Create new session
|
|
3576
|
-
/cancel \u2014 Cancel session (in session topic)
|
|
3577
|
-
/status \u2014 Show session or system status
|
|
3578
|
-
/sessions \u2014 List all sessions
|
|
3579
|
-
/agents \u2014 List available agents
|
|
3580
|
-
|
|
3581
|
-
\u2699\uFE0F <b>System</b>
|
|
3582
|
-
/restart \u2014 Restart OpenACP
|
|
3583
|
-
/update \u2014 Update to latest version
|
|
3584
|
-
/integrate \u2014 Manage agent integrations
|
|
3585
|
-
/menu \u2014 Show action menu
|
|
3586
|
-
|
|
3587
|
-
\u{1F512} <b>Session Options</b>
|
|
3588
|
-
/enable_dangerous \u2014 Auto-approve permissions
|
|
3589
|
-
/disable_dangerous \u2014 Restore permission prompts
|
|
3590
|
-
/handoff \u2014 Continue session in terminal
|
|
3591
|
-
/clear \u2014 Clear assistant history
|
|
3592
|
-
|
|
3593
|
-
\u{1F4AC} Need help? Just ask me in this topic!`,
|
|
3594
|
-
{ parse_mode: "HTML" }
|
|
3595
|
-
);
|
|
3596
|
-
}
|
|
3597
|
-
async function handleClear(ctx, assistant) {
|
|
3598
|
-
if (!assistant) {
|
|
3599
|
-
await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
|
|
3600
|
-
return;
|
|
3601
|
-
}
|
|
3602
|
-
const threadId = ctx.message?.message_thread_id;
|
|
3603
|
-
if (threadId !== assistant.topicId) {
|
|
3604
|
-
await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
|
|
3597
|
+
async function handleAgentCallback(ctx, core) {
|
|
3598
|
+
const data = ctx.callbackQuery?.data ?? "";
|
|
3599
|
+
await ctx.answerCallbackQuery();
|
|
3600
|
+
if (data.startsWith("ag:install:")) {
|
|
3601
|
+
const nameOrId = data.replace("ag:install:", "");
|
|
3602
|
+
await installAgentWithProgress(ctx, core, nameOrId);
|
|
3605
3603
|
return;
|
|
3606
3604
|
}
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3605
|
+
if (data.startsWith("ag:page:")) {
|
|
3606
|
+
const page = parseInt(data.replace("ag:page:", ""), 10);
|
|
3607
|
+
try {
|
|
3608
|
+
const catalog = core.agentCatalog;
|
|
3609
|
+
const items = catalog.getAvailable();
|
|
3610
|
+
const installed = items.filter((i) => i.installed);
|
|
3611
|
+
const available = items.filter((i) => !i.installed);
|
|
3612
|
+
let text = "<b>\u{1F916} Agents</b>\n\n";
|
|
3613
|
+
if (installed.length > 0) {
|
|
3614
|
+
text += "<b>Installed:</b>\n";
|
|
3615
|
+
for (const item of installed) {
|
|
3616
|
+
text += `\u2705 <b>${escapeHtml(item.name)}</b>`;
|
|
3617
|
+
if (item.description) {
|
|
3618
|
+
text += ` \u2014 <i>${escapeHtml(truncate(item.description, 50))}</i>`;
|
|
3619
|
+
}
|
|
3620
|
+
text += "\n";
|
|
3621
|
+
}
|
|
3622
|
+
text += "\n";
|
|
3623
|
+
}
|
|
3624
|
+
const totalPages = Math.ceil(available.length / AGENTS_PER_PAGE);
|
|
3625
|
+
const safePage = Math.max(0, Math.min(page, totalPages - 1));
|
|
3626
|
+
const pageItems = available.slice(safePage * AGENTS_PER_PAGE, (safePage + 1) * AGENTS_PER_PAGE);
|
|
3627
|
+
text += `<b>Available to install:</b>`;
|
|
3628
|
+
if (totalPages > 1) {
|
|
3629
|
+
text += ` (${safePage + 1}/${totalPages})`;
|
|
3630
|
+
}
|
|
3631
|
+
text += "\n";
|
|
3632
|
+
for (const item of pageItems) {
|
|
3633
|
+
if (item.available) {
|
|
3634
|
+
text += `\u2B07\uFE0F <b>${escapeHtml(item.name)}</b>`;
|
|
3635
|
+
} else {
|
|
3636
|
+
const deps = item.missingDeps?.join(", ") ?? "requirements not met";
|
|
3637
|
+
text += `\u26A0\uFE0F <b>${escapeHtml(item.name)}</b> <i>(needs: ${escapeHtml(deps)})</i>`;
|
|
3638
|
+
}
|
|
3639
|
+
if (item.description) {
|
|
3640
|
+
text += `
|
|
3641
|
+
<i>${escapeHtml(truncate(item.description, 60))}</i>`;
|
|
3642
|
+
}
|
|
3643
|
+
text += "\n";
|
|
3644
|
+
}
|
|
3645
|
+
const keyboard = new InlineKeyboard4();
|
|
3646
|
+
const installable = pageItems.filter((i) => i.available);
|
|
3647
|
+
for (let i = 0; i < installable.length; i += 2) {
|
|
3648
|
+
const row = installable.slice(i, i + 2);
|
|
3649
|
+
for (const item of row) {
|
|
3650
|
+
keyboard.text(`\u2B07\uFE0F ${item.name}`, `ag:install:${item.key}`);
|
|
3651
|
+
}
|
|
3652
|
+
keyboard.row();
|
|
3653
|
+
}
|
|
3654
|
+
if (totalPages > 1) {
|
|
3655
|
+
if (safePage > 0) {
|
|
3656
|
+
keyboard.text("\u25C0\uFE0F Prev", `ag:page:${safePage - 1}`);
|
|
3657
|
+
}
|
|
3658
|
+
if (safePage < totalPages - 1) {
|
|
3659
|
+
keyboard.text("Next \u25B6\uFE0F", `ag:page:${safePage + 1}`);
|
|
3660
|
+
}
|
|
3661
|
+
keyboard.row();
|
|
3662
|
+
}
|
|
3663
|
+
await ctx.editMessageText(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
3664
|
+
} catch {
|
|
3665
|
+
}
|
|
3614
3666
|
}
|
|
3615
3667
|
}
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
const
|
|
3619
|
-
|
|
3620
|
-
const
|
|
3621
|
-
const
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3668
|
+
async function installAgentWithProgress(ctx, core, nameOrId) {
|
|
3669
|
+
const catalog = core.agentCatalog;
|
|
3670
|
+
const msg = await ctx.reply(`\u23F3 Installing <b>${escapeHtml(nameOrId)}</b>...`, { parse_mode: "HTML" });
|
|
3671
|
+
let lastEdit = 0;
|
|
3672
|
+
const EDIT_THROTTLE_MS = 1500;
|
|
3673
|
+
const progress = {
|
|
3674
|
+
onStart(_id, _name) {
|
|
3675
|
+
},
|
|
3676
|
+
async onStep(step) {
|
|
3677
|
+
const now = Date.now();
|
|
3678
|
+
if (now - lastEdit > EDIT_THROTTLE_MS) {
|
|
3679
|
+
lastEdit = now;
|
|
3680
|
+
try {
|
|
3681
|
+
await ctx.api.editMessageText(msg.chat.id, msg.message_id, `\u23F3 <b>${escapeHtml(nameOrId)}</b>: ${escapeHtml(step)}`, { parse_mode: "HTML" });
|
|
3682
|
+
} catch {
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
},
|
|
3686
|
+
async onDownloadProgress(percent) {
|
|
3687
|
+
const now = Date.now();
|
|
3688
|
+
if (now - lastEdit > EDIT_THROTTLE_MS) {
|
|
3689
|
+
lastEdit = now;
|
|
3690
|
+
try {
|
|
3691
|
+
const bar = buildProgressBar(percent);
|
|
3692
|
+
await ctx.api.editMessageText(msg.chat.id, msg.message_id, `\u23F3 <b>${escapeHtml(nameOrId)}</b>
|
|
3693
|
+
Downloading... ${bar} ${percent}%`, { parse_mode: "HTML" });
|
|
3694
|
+
} catch {
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
},
|
|
3698
|
+
async onSuccess(name) {
|
|
3699
|
+
try {
|
|
3700
|
+
const keyboard = new InlineKeyboard4().text(`\u{1F680} Start session with ${name}`, `na:${nameOrId}`);
|
|
3701
|
+
await ctx.api.editMessageText(msg.chat.id, msg.message_id, `\u2705 <b>${escapeHtml(name)}</b> installed!`, { parse_mode: "HTML", reply_markup: keyboard });
|
|
3702
|
+
} catch {
|
|
3703
|
+
}
|
|
3704
|
+
},
|
|
3705
|
+
async onError(error) {
|
|
3706
|
+
try {
|
|
3707
|
+
await ctx.api.editMessageText(msg.chat.id, msg.message_id, `\u274C ${escapeHtml(error)}`, { parse_mode: "HTML" });
|
|
3708
|
+
} catch {
|
|
3709
|
+
}
|
|
3630
3710
|
}
|
|
3631
|
-
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3711
|
+
};
|
|
3712
|
+
await catalog.install(nameOrId, progress);
|
|
3713
|
+
}
|
|
3714
|
+
function truncate(text, maxLen) {
|
|
3715
|
+
if (text.length <= maxLen) return text;
|
|
3716
|
+
return text.slice(0, maxLen - 1) + "\u2026";
|
|
3717
|
+
}
|
|
3718
|
+
function buildProgressBar(percent) {
|
|
3719
|
+
const filled = Math.round(percent / 10);
|
|
3720
|
+
const empty = 10 - filled;
|
|
3721
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
3634
3722
|
}
|
|
3635
3723
|
|
|
3636
3724
|
// src/adapters/telegram/commands/integrate.ts
|
|
@@ -3751,6 +3839,298 @@ ${resultText}`,
|
|
|
3751
3839
|
});
|
|
3752
3840
|
}
|
|
3753
3841
|
|
|
3842
|
+
// src/adapters/telegram/commands/settings.ts
|
|
3843
|
+
import { InlineKeyboard as InlineKeyboard6 } from "grammy";
|
|
3844
|
+
var log12 = createChildLogger({ module: "telegram-settings" });
|
|
3845
|
+
function buildSettingsKeyboard(core) {
|
|
3846
|
+
const config = core.configManager.get();
|
|
3847
|
+
const fields = getSafeFields();
|
|
3848
|
+
const kb = new InlineKeyboard6();
|
|
3849
|
+
for (const field of fields) {
|
|
3850
|
+
const value = getConfigValue(config, field.path);
|
|
3851
|
+
const label = formatFieldLabel(field, value);
|
|
3852
|
+
if (field.type === "toggle") {
|
|
3853
|
+
kb.text(`${label}`, `s:toggle:${field.path}`).row();
|
|
3854
|
+
} else if (field.type === "select") {
|
|
3855
|
+
kb.text(`${label}`, `s:select:${field.path}`).row();
|
|
3856
|
+
} else {
|
|
3857
|
+
kb.text(`${label}`, `s:input:${field.path}`).row();
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
kb.text("\u25C0\uFE0F Back to Menu", "s:back");
|
|
3861
|
+
return kb;
|
|
3862
|
+
}
|
|
3863
|
+
function formatFieldLabel(field, value) {
|
|
3864
|
+
const icons = {
|
|
3865
|
+
agent: "\u{1F916}",
|
|
3866
|
+
logging: "\u{1F4DD}",
|
|
3867
|
+
tunnel: "\u{1F517}",
|
|
3868
|
+
security: "\u{1F512}",
|
|
3869
|
+
workspace: "\u{1F4C1}",
|
|
3870
|
+
storage: "\u{1F4BE}"
|
|
3871
|
+
};
|
|
3872
|
+
const icon = icons[field.group] ?? "\u2699\uFE0F";
|
|
3873
|
+
if (field.type === "toggle") {
|
|
3874
|
+
return `${icon} ${field.displayName}: ${value ? "ON" : "OFF"}`;
|
|
3875
|
+
}
|
|
3876
|
+
return `${icon} ${field.displayName}: ${String(value)}`;
|
|
3877
|
+
}
|
|
3878
|
+
async function handleSettings(ctx, core) {
|
|
3879
|
+
const kb = buildSettingsKeyboard(core);
|
|
3880
|
+
await ctx.reply(`<b>\u2699\uFE0F Settings</b>
|
|
3881
|
+
Tap to change:`, {
|
|
3882
|
+
parse_mode: "HTML",
|
|
3883
|
+
reply_markup: kb
|
|
3884
|
+
});
|
|
3885
|
+
}
|
|
3886
|
+
function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
3887
|
+
bot.callbackQuery(/^s:toggle:/, async (ctx) => {
|
|
3888
|
+
const fieldPath = ctx.callbackQuery.data.replace("s:toggle:", "");
|
|
3889
|
+
const config = core.configManager.get();
|
|
3890
|
+
const currentValue = getConfigValue(config, fieldPath);
|
|
3891
|
+
const newValue = !currentValue;
|
|
3892
|
+
try {
|
|
3893
|
+
const updates = buildNestedUpdate(fieldPath, newValue);
|
|
3894
|
+
await core.configManager.save(updates, fieldPath);
|
|
3895
|
+
const toast = isHotReloadable(fieldPath) ? `\u2705 ${fieldPath} = ${newValue}` : `\u2705 ${fieldPath} = ${newValue} (restart needed)`;
|
|
3896
|
+
try {
|
|
3897
|
+
await ctx.answerCallbackQuery({ text: toast });
|
|
3898
|
+
} catch {
|
|
3899
|
+
}
|
|
3900
|
+
try {
|
|
3901
|
+
await ctx.editMessageReplyMarkup({ reply_markup: buildSettingsKeyboard(core) });
|
|
3902
|
+
} catch {
|
|
3903
|
+
}
|
|
3904
|
+
} catch (err) {
|
|
3905
|
+
log12.error({ err, fieldPath }, "Failed to toggle config");
|
|
3906
|
+
try {
|
|
3907
|
+
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
3908
|
+
} catch {
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
});
|
|
3912
|
+
bot.callbackQuery(/^s:select:/, async (ctx) => {
|
|
3913
|
+
const fieldPath = ctx.callbackQuery.data.replace("s:select:", "");
|
|
3914
|
+
const config = core.configManager.get();
|
|
3915
|
+
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
3916
|
+
if (!fieldDef) return;
|
|
3917
|
+
const options = resolveOptions(fieldDef, config) ?? [];
|
|
3918
|
+
const currentValue = getConfigValue(config, fieldPath);
|
|
3919
|
+
const kb = new InlineKeyboard6();
|
|
3920
|
+
for (const opt of options) {
|
|
3921
|
+
const marker = opt === String(currentValue) ? " \u2713" : "";
|
|
3922
|
+
kb.text(`${opt}${marker}`, `s:pick:${fieldPath}:${opt}`).row();
|
|
3923
|
+
}
|
|
3924
|
+
kb.text("\u25C0\uFE0F Back", "s:back:refresh");
|
|
3925
|
+
try {
|
|
3926
|
+
await ctx.answerCallbackQuery();
|
|
3927
|
+
} catch {
|
|
3928
|
+
}
|
|
3929
|
+
try {
|
|
3930
|
+
await ctx.editMessageText(`<b>\u2699\uFE0F ${fieldDef.displayName}</b>
|
|
3931
|
+
Select a value:`, {
|
|
3932
|
+
parse_mode: "HTML",
|
|
3933
|
+
reply_markup: kb
|
|
3934
|
+
});
|
|
3935
|
+
} catch {
|
|
3936
|
+
}
|
|
3937
|
+
});
|
|
3938
|
+
bot.callbackQuery(/^s:pick:/, async (ctx) => {
|
|
3939
|
+
const parts = ctx.callbackQuery.data.replace("s:pick:", "").split(":");
|
|
3940
|
+
const fieldPath = parts.slice(0, -1).join(":");
|
|
3941
|
+
const newValue = parts[parts.length - 1];
|
|
3942
|
+
try {
|
|
3943
|
+
const updates = buildNestedUpdate(fieldPath, newValue);
|
|
3944
|
+
await core.configManager.save(updates, fieldPath);
|
|
3945
|
+
try {
|
|
3946
|
+
await ctx.answerCallbackQuery({ text: `\u2705 ${fieldPath} = ${newValue}` });
|
|
3947
|
+
} catch {
|
|
3948
|
+
}
|
|
3949
|
+
try {
|
|
3950
|
+
await ctx.editMessageText(`<b>\u2699\uFE0F Settings</b>
|
|
3951
|
+
Tap to change:`, {
|
|
3952
|
+
parse_mode: "HTML",
|
|
3953
|
+
reply_markup: buildSettingsKeyboard(core)
|
|
3954
|
+
});
|
|
3955
|
+
} catch {
|
|
3956
|
+
}
|
|
3957
|
+
} catch (err) {
|
|
3958
|
+
log12.error({ err, fieldPath }, "Failed to set config");
|
|
3959
|
+
try {
|
|
3960
|
+
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
3961
|
+
} catch {
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
});
|
|
3965
|
+
bot.callbackQuery(/^s:input:/, async (ctx) => {
|
|
3966
|
+
const fieldPath = ctx.callbackQuery.data.replace("s:input:", "");
|
|
3967
|
+
const config = core.configManager.get();
|
|
3968
|
+
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
3969
|
+
if (!fieldDef) return;
|
|
3970
|
+
const currentValue = getConfigValue(config, fieldPath);
|
|
3971
|
+
const assistant = getAssistantSession();
|
|
3972
|
+
if (!assistant) {
|
|
3973
|
+
try {
|
|
3974
|
+
await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Start the assistant first (/assistant)" });
|
|
3975
|
+
} catch {
|
|
3976
|
+
}
|
|
3977
|
+
return;
|
|
3978
|
+
}
|
|
3979
|
+
try {
|
|
3980
|
+
await ctx.answerCallbackQuery({ text: `Delegating to assistant...` });
|
|
3981
|
+
} catch {
|
|
3982
|
+
}
|
|
3983
|
+
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>`;
|
|
3984
|
+
await assistant.enqueuePrompt(prompt);
|
|
3985
|
+
});
|
|
3986
|
+
bot.callbackQuery("s:back", async (ctx) => {
|
|
3987
|
+
try {
|
|
3988
|
+
await ctx.answerCallbackQuery();
|
|
3989
|
+
} catch {
|
|
3990
|
+
}
|
|
3991
|
+
const { buildMenuKeyboard: buildMenuKeyboard3 } = await import("./menu-6RCPBVGQ.js");
|
|
3992
|
+
try {
|
|
3993
|
+
await ctx.editMessageText(`<b>OpenACP Menu</b>
|
|
3994
|
+
Choose an action:`, {
|
|
3995
|
+
parse_mode: "HTML",
|
|
3996
|
+
reply_markup: buildMenuKeyboard3()
|
|
3997
|
+
});
|
|
3998
|
+
} catch {
|
|
3999
|
+
}
|
|
4000
|
+
});
|
|
4001
|
+
bot.callbackQuery("s:back:refresh", async (ctx) => {
|
|
4002
|
+
try {
|
|
4003
|
+
await ctx.answerCallbackQuery();
|
|
4004
|
+
} catch {
|
|
4005
|
+
}
|
|
4006
|
+
try {
|
|
4007
|
+
await ctx.editMessageText(`<b>\u2699\uFE0F Settings</b>
|
|
4008
|
+
Tap to change:`, {
|
|
4009
|
+
parse_mode: "HTML",
|
|
4010
|
+
reply_markup: buildSettingsKeyboard(core)
|
|
4011
|
+
});
|
|
4012
|
+
} catch {
|
|
4013
|
+
}
|
|
4014
|
+
});
|
|
4015
|
+
}
|
|
4016
|
+
function buildNestedUpdate(dotPath, value) {
|
|
4017
|
+
const parts = dotPath.split(".");
|
|
4018
|
+
const result = {};
|
|
4019
|
+
let target = result;
|
|
4020
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
4021
|
+
target[parts[i]] = {};
|
|
4022
|
+
target = target[parts[i]];
|
|
4023
|
+
}
|
|
4024
|
+
target[parts[parts.length - 1]] = value;
|
|
4025
|
+
return result;
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
// src/adapters/telegram/commands/doctor.ts
|
|
4029
|
+
import { InlineKeyboard as InlineKeyboard7 } from "grammy";
|
|
4030
|
+
var log13 = createChildLogger({ module: "telegram-cmd-doctor" });
|
|
4031
|
+
var pendingFixesStore = /* @__PURE__ */ new Map();
|
|
4032
|
+
function renderReport(report) {
|
|
4033
|
+
const icons = { pass: "\u2705", warn: "\u26A0\uFE0F", fail: "\u274C" };
|
|
4034
|
+
const lines = ["\u{1FA7A} <b>OpenACP Doctor</b>\n"];
|
|
4035
|
+
for (const category of report.categories) {
|
|
4036
|
+
lines.push(`<b>${category.name}</b>`);
|
|
4037
|
+
for (const result of category.results) {
|
|
4038
|
+
lines.push(` ${icons[result.status]} ${escapeHtml2(result.message)}`);
|
|
4039
|
+
}
|
|
4040
|
+
lines.push("");
|
|
4041
|
+
}
|
|
4042
|
+
const { passed, warnings, failed, fixed } = report.summary;
|
|
4043
|
+
const fixedStr = fixed > 0 ? `, ${fixed} fixed` : "";
|
|
4044
|
+
lines.push(`<b>Result:</b> ${passed} passed, ${warnings} warnings, ${failed} failed${fixedStr}`);
|
|
4045
|
+
let keyboard;
|
|
4046
|
+
if (report.pendingFixes.length > 0) {
|
|
4047
|
+
keyboard = new InlineKeyboard7();
|
|
4048
|
+
for (let i = 0; i < report.pendingFixes.length; i++) {
|
|
4049
|
+
const label = `\u{1F527} Fix: ${report.pendingFixes[i].message.slice(0, 30)}`;
|
|
4050
|
+
keyboard.text(label, `m:doctor:fix:${i}`).row();
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
return { text: lines.join("\n"), keyboard };
|
|
4054
|
+
}
|
|
4055
|
+
function escapeHtml2(text) {
|
|
4056
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4057
|
+
}
|
|
4058
|
+
async function handleDoctor(ctx) {
|
|
4059
|
+
const statusMsg = await ctx.reply("\u{1FA7A} Running diagnostics...", { parse_mode: "HTML" });
|
|
4060
|
+
try {
|
|
4061
|
+
const engine = new DoctorEngine();
|
|
4062
|
+
const report = await engine.runAll();
|
|
4063
|
+
const { text, keyboard } = renderReport(report);
|
|
4064
|
+
const storeKey = `${ctx.chat.id}:${statusMsg.message_id}`;
|
|
4065
|
+
if (report.pendingFixes.length > 0) {
|
|
4066
|
+
pendingFixesStore.set(storeKey, report.pendingFixes);
|
|
4067
|
+
}
|
|
4068
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, text, {
|
|
4069
|
+
parse_mode: "HTML",
|
|
4070
|
+
reply_markup: keyboard
|
|
4071
|
+
});
|
|
4072
|
+
} catch (err) {
|
|
4073
|
+
log13.error({ err }, "Doctor command failed");
|
|
4074
|
+
await ctx.api.editMessageText(
|
|
4075
|
+
ctx.chat.id,
|
|
4076
|
+
statusMsg.message_id,
|
|
4077
|
+
`\u274C Doctor failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
4078
|
+
{ parse_mode: "HTML" }
|
|
4079
|
+
);
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
function setupDoctorCallbacks(bot) {
|
|
4083
|
+
bot.callbackQuery(/^m:doctor:fix:/, async (ctx) => {
|
|
4084
|
+
const data = ctx.callbackQuery.data;
|
|
4085
|
+
const index = parseInt(data.replace("m:doctor:fix:", ""), 10);
|
|
4086
|
+
const chatId = ctx.callbackQuery.message?.chat.id;
|
|
4087
|
+
const messageId = ctx.callbackQuery.message?.message_id;
|
|
4088
|
+
try {
|
|
4089
|
+
await ctx.answerCallbackQuery({ text: "Applying fix..." });
|
|
4090
|
+
} catch {
|
|
4091
|
+
}
|
|
4092
|
+
if (chatId === void 0 || messageId === void 0) return;
|
|
4093
|
+
const storeKey = `${chatId}:${messageId}`;
|
|
4094
|
+
const fixes = pendingFixesStore.get(storeKey);
|
|
4095
|
+
if (!fixes || index < 0 || index >= fixes.length) {
|
|
4096
|
+
try {
|
|
4097
|
+
await ctx.answerCallbackQuery({ text: "Fix no longer available" });
|
|
4098
|
+
} catch {
|
|
4099
|
+
}
|
|
4100
|
+
return;
|
|
4101
|
+
}
|
|
4102
|
+
const pending = fixes[index];
|
|
4103
|
+
try {
|
|
4104
|
+
const result = await pending.fix();
|
|
4105
|
+
if (result.success) {
|
|
4106
|
+
const engine = new DoctorEngine();
|
|
4107
|
+
const report = await engine.runAll();
|
|
4108
|
+
const { text, keyboard } = renderReport(report);
|
|
4109
|
+
if (report.pendingFixes.length > 0) {
|
|
4110
|
+
pendingFixesStore.set(storeKey, report.pendingFixes);
|
|
4111
|
+
} else {
|
|
4112
|
+
pendingFixesStore.delete(storeKey);
|
|
4113
|
+
}
|
|
4114
|
+
await ctx.editMessageText(text, { parse_mode: "HTML", reply_markup: keyboard });
|
|
4115
|
+
} else {
|
|
4116
|
+
try {
|
|
4117
|
+
await ctx.answerCallbackQuery({ text: `Fix failed: ${result.message}` });
|
|
4118
|
+
} catch {
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
} catch (err) {
|
|
4122
|
+
log13.error({ err, index }, "Doctor fix callback failed");
|
|
4123
|
+
}
|
|
4124
|
+
});
|
|
4125
|
+
bot.callbackQuery("m:doctor", async (ctx) => {
|
|
4126
|
+
try {
|
|
4127
|
+
await ctx.answerCallbackQuery();
|
|
4128
|
+
} catch {
|
|
4129
|
+
}
|
|
4130
|
+
await handleDoctor(ctx);
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4133
|
+
|
|
3754
4134
|
// src/adapters/telegram/commands/index.ts
|
|
3755
4135
|
function setupCommands(bot, core, chatId, assistant) {
|
|
3756
4136
|
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
@@ -3759,6 +4139,7 @@ function setupCommands(bot, core, chatId, assistant) {
|
|
|
3759
4139
|
bot.command("status", (ctx) => handleStatus(ctx, core));
|
|
3760
4140
|
bot.command("sessions", (ctx) => handleTopics(ctx, core));
|
|
3761
4141
|
bot.command("agents", (ctx) => handleAgents(ctx, core));
|
|
4142
|
+
bot.command("install", (ctx) => handleInstall(ctx, core));
|
|
3762
4143
|
bot.command("help", (ctx) => handleHelp(ctx));
|
|
3763
4144
|
bot.command("menu", (ctx) => handleMenu(ctx));
|
|
3764
4145
|
bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
|
|
@@ -3767,10 +4148,19 @@ function setupCommands(bot, core, chatId, assistant) {
|
|
|
3767
4148
|
bot.command("update", (ctx) => handleUpdate(ctx, core));
|
|
3768
4149
|
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
3769
4150
|
bot.command("clear", (ctx) => handleClear(ctx, assistant));
|
|
4151
|
+
bot.command("doctor", (ctx) => handleDoctor(ctx));
|
|
3770
4152
|
}
|
|
3771
|
-
function setupAllCallbacks(bot, core, chatId, systemTopicIds) {
|
|
4153
|
+
function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession) {
|
|
3772
4154
|
setupNewSessionCallbacks(bot, core, chatId);
|
|
3773
4155
|
setupSessionCallbacks(bot, core, chatId, systemTopicIds);
|
|
4156
|
+
setupSettingsCallbacks(bot, core, getAssistantSession ?? (() => void 0));
|
|
4157
|
+
setupDoctorCallbacks(bot);
|
|
4158
|
+
bot.callbackQuery(/^ag:/, (ctx) => handleAgentCallback(ctx, core));
|
|
4159
|
+
bot.callbackQuery(/^na:/, async (ctx) => {
|
|
4160
|
+
const agentKey = ctx.callbackQuery.data.replace("na:", "");
|
|
4161
|
+
await ctx.answerCallbackQuery();
|
|
4162
|
+
await createSessionDirect(ctx, core, chatId, agentKey, core.configManager.get().workspace.baseDir);
|
|
4163
|
+
});
|
|
3774
4164
|
bot.callbackQuery(/^m:/, async (ctx) => {
|
|
3775
4165
|
const data = ctx.callbackQuery.data;
|
|
3776
4166
|
try {
|
|
@@ -3802,6 +4192,9 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds) {
|
|
|
3802
4192
|
case "m:topics":
|
|
3803
4193
|
await handleTopics(ctx, core);
|
|
3804
4194
|
break;
|
|
4195
|
+
case "m:settings":
|
|
4196
|
+
await handleSettings(ctx, core);
|
|
4197
|
+
break;
|
|
3805
4198
|
}
|
|
3806
4199
|
});
|
|
3807
4200
|
}
|
|
@@ -3812,6 +4205,7 @@ var STATIC_COMMANDS = [
|
|
|
3812
4205
|
{ command: "status", description: "Show status" },
|
|
3813
4206
|
{ command: "sessions", description: "List all sessions" },
|
|
3814
4207
|
{ command: "agents", description: "List available agents" },
|
|
4208
|
+
{ command: "install", description: "Install a new agent" },
|
|
3815
4209
|
{ command: "help", description: "Help" },
|
|
3816
4210
|
{ command: "menu", description: "Show menu" },
|
|
3817
4211
|
{ command: "enable_dangerous", description: "Auto-approve all permission requests (session only)" },
|
|
@@ -3820,13 +4214,14 @@ var STATIC_COMMANDS = [
|
|
|
3820
4214
|
{ command: "handoff", description: "Continue this session in your terminal" },
|
|
3821
4215
|
{ command: "clear", description: "Clear assistant history" },
|
|
3822
4216
|
{ command: "restart", description: "Restart OpenACP" },
|
|
3823
|
-
{ command: "update", description: "Update to latest version and restart" }
|
|
4217
|
+
{ command: "update", description: "Update to latest version and restart" },
|
|
4218
|
+
{ command: "doctor", description: "Run system diagnostics" }
|
|
3824
4219
|
];
|
|
3825
4220
|
|
|
3826
4221
|
// src/adapters/telegram/permissions.ts
|
|
3827
|
-
import { InlineKeyboard as
|
|
4222
|
+
import { InlineKeyboard as InlineKeyboard8 } from "grammy";
|
|
3828
4223
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3829
|
-
var
|
|
4224
|
+
var log14 = createChildLogger({ module: "telegram-permissions" });
|
|
3830
4225
|
var PermissionHandler = class {
|
|
3831
4226
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
3832
4227
|
this.bot = bot;
|
|
@@ -3843,7 +4238,7 @@ var PermissionHandler = class {
|
|
|
3843
4238
|
requestId: request.id,
|
|
3844
4239
|
options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
|
|
3845
4240
|
});
|
|
3846
|
-
const keyboard = new
|
|
4241
|
+
const keyboard = new InlineKeyboard8();
|
|
3847
4242
|
for (const option of request.options) {
|
|
3848
4243
|
const emoji = option.isAllow ? "\u2705" : "\u274C";
|
|
3849
4244
|
keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
|
|
@@ -3886,7 +4281,7 @@ ${escapeHtml(request.description)}`,
|
|
|
3886
4281
|
}
|
|
3887
4282
|
const session = this.getSession(pending.sessionId);
|
|
3888
4283
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
3889
|
-
|
|
4284
|
+
log14.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
3890
4285
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
3891
4286
|
session.permissionGate.resolve(optionId);
|
|
3892
4287
|
}
|
|
@@ -4268,20 +4663,19 @@ Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetenti
|
|
|
4268
4663
|
`;
|
|
4269
4664
|
|
|
4270
4665
|
// src/adapters/telegram/assistant.ts
|
|
4271
|
-
var
|
|
4666
|
+
var log15 = createChildLogger({ module: "telegram-assistant" });
|
|
4272
4667
|
async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
4273
4668
|
const config = core.configManager.get();
|
|
4274
|
-
|
|
4275
|
-
const session = await core.
|
|
4276
|
-
"telegram",
|
|
4277
|
-
config.defaultAgent,
|
|
4278
|
-
core.configManager.resolveWorkspace(),
|
|
4279
|
-
|
|
4280
|
-
|
|
4669
|
+
log15.info({ agent: config.defaultAgent }, "Creating assistant session...");
|
|
4670
|
+
const session = await core.createSession({
|
|
4671
|
+
channelId: "telegram",
|
|
4672
|
+
agentName: config.defaultAgent,
|
|
4673
|
+
workingDirectory: core.configManager.resolveWorkspace(),
|
|
4674
|
+
initialName: "Assistant"
|
|
4675
|
+
// Prevent auto-naming from triggering after system prompt
|
|
4676
|
+
});
|
|
4281
4677
|
session.threadId = String(assistantTopicId);
|
|
4282
|
-
session.
|
|
4283
|
-
log12.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
4284
|
-
core.wireSessionEvents(session, adapter);
|
|
4678
|
+
log15.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
4285
4679
|
const allRecords = core.sessionManager.listRecords();
|
|
4286
4680
|
const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
4287
4681
|
const statusCounts = /* @__PURE__ */ new Map();
|
|
@@ -4297,9 +4691,9 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
4297
4691
|
};
|
|
4298
4692
|
const systemPrompt = buildAssistantSystemPrompt(ctx);
|
|
4299
4693
|
const ready = session.enqueuePrompt(systemPrompt).then(() => {
|
|
4300
|
-
|
|
4694
|
+
log15.info({ sessionId: session.id }, "Assistant system prompt completed");
|
|
4301
4695
|
}).catch((err) => {
|
|
4302
|
-
|
|
4696
|
+
log15.warn({ err }, "Assistant system prompt failed");
|
|
4303
4697
|
});
|
|
4304
4698
|
return { session, ready };
|
|
4305
4699
|
}
|
|
@@ -4367,8 +4761,10 @@ function buildAssistantSystemPrompt(ctx) {
|
|
|
4367
4761
|
- Execute: \`openacp api cleanup --status <statuses>\`
|
|
4368
4762
|
|
|
4369
4763
|
### Configuration
|
|
4370
|
-
- View: \`openacp api config\`
|
|
4371
|
-
- Update: \`openacp
|
|
4764
|
+
- View: \`openacp config\` (or \`openacp api config\` \u2014 deprecated)
|
|
4765
|
+
- Update: \`openacp config set <key> <value>\`
|
|
4766
|
+
- When user asks about "settings" or "config", use \`openacp config set\` directly
|
|
4767
|
+
- When receiving a delegated request from the Settings menu, ask user for the new value, then apply with \`openacp config set <path> <value>\`
|
|
4372
4768
|
|
|
4373
4769
|
### Restart / Update
|
|
4374
4770
|
- Always ask for confirmation \u2014 these are disruptive actions
|
|
@@ -4398,8 +4794,10 @@ openacp api cleanup --status finished,error
|
|
|
4398
4794
|
|
|
4399
4795
|
# System
|
|
4400
4796
|
openacp api health # System health
|
|
4401
|
-
openacp
|
|
4402
|
-
openacp
|
|
4797
|
+
openacp config # Edit config (interactive)
|
|
4798
|
+
openacp config set <key> <value> # Update config value
|
|
4799
|
+
openacp api config # Show config (deprecated)
|
|
4800
|
+
openacp api config set <key> <value> # Update config (deprecated)
|
|
4403
4801
|
openacp api adapters # List adapters
|
|
4404
4802
|
openacp api tunnel # Tunnel status
|
|
4405
4803
|
openacp api notify "message" # Send notification
|
|
@@ -4432,7 +4830,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
|
|
|
4432
4830
|
}
|
|
4433
4831
|
|
|
4434
4832
|
// src/adapters/telegram/activity.ts
|
|
4435
|
-
var
|
|
4833
|
+
var log16 = createChildLogger({ module: "telegram:activity" });
|
|
4436
4834
|
var THINKING_REFRESH_MS = 15e3;
|
|
4437
4835
|
var THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
4438
4836
|
var ThinkingIndicator = class {
|
|
@@ -4464,7 +4862,7 @@ var ThinkingIndicator = class {
|
|
|
4464
4862
|
this.startRefreshTimer();
|
|
4465
4863
|
}
|
|
4466
4864
|
} catch (err) {
|
|
4467
|
-
|
|
4865
|
+
log16.warn({ err }, "ThinkingIndicator.show() failed");
|
|
4468
4866
|
} finally {
|
|
4469
4867
|
this.sending = false;
|
|
4470
4868
|
}
|
|
@@ -4537,7 +4935,7 @@ var UsageMessage = class {
|
|
|
4537
4935
|
if (result) this.msgId = result.message_id;
|
|
4538
4936
|
}
|
|
4539
4937
|
} catch (err) {
|
|
4540
|
-
|
|
4938
|
+
log16.warn({ err }, "UsageMessage.send() failed");
|
|
4541
4939
|
}
|
|
4542
4940
|
}
|
|
4543
4941
|
getMsgId() {
|
|
@@ -4550,7 +4948,7 @@ var UsageMessage = class {
|
|
|
4550
4948
|
try {
|
|
4551
4949
|
await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
|
|
4552
4950
|
} catch (err) {
|
|
4553
|
-
|
|
4951
|
+
log16.warn({ err }, "UsageMessage.delete() failed");
|
|
4554
4952
|
}
|
|
4555
4953
|
}
|
|
4556
4954
|
};
|
|
@@ -4636,7 +5034,7 @@ var PlanCard = class {
|
|
|
4636
5034
|
if (result) this.msgId = result.message_id;
|
|
4637
5035
|
}
|
|
4638
5036
|
} catch (err) {
|
|
4639
|
-
|
|
5037
|
+
log16.warn({ err }, "PlanCard flush failed");
|
|
4640
5038
|
}
|
|
4641
5039
|
}
|
|
4642
5040
|
};
|
|
@@ -4699,7 +5097,7 @@ var ActivityTracker = class {
|
|
|
4699
5097
|
})
|
|
4700
5098
|
);
|
|
4701
5099
|
} catch (err) {
|
|
4702
|
-
|
|
5100
|
+
log16.warn({ err }, "ActivityTracker.onComplete() Done send failed");
|
|
4703
5101
|
}
|
|
4704
5102
|
}
|
|
4705
5103
|
}
|
|
@@ -4782,7 +5180,7 @@ var TelegramSendQueue = class {
|
|
|
4782
5180
|
|
|
4783
5181
|
// src/adapters/telegram/action-detect.ts
|
|
4784
5182
|
import { nanoid as nanoid3 } from "nanoid";
|
|
4785
|
-
import { InlineKeyboard as
|
|
5183
|
+
import { InlineKeyboard as InlineKeyboard9 } from "grammy";
|
|
4786
5184
|
var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
|
|
4787
5185
|
var CMD_CANCEL_RE = /\/cancel\b/;
|
|
4788
5186
|
var KW_NEW_RE = /(?:create|new)\s+session/i;
|
|
@@ -4829,7 +5227,7 @@ function removeAction(id) {
|
|
|
4829
5227
|
actionMap.delete(id);
|
|
4830
5228
|
}
|
|
4831
5229
|
function buildActionKeyboard(actionId, action) {
|
|
4832
|
-
const keyboard = new
|
|
5230
|
+
const keyboard = new InlineKeyboard9();
|
|
4833
5231
|
if (action.action === "new_session") {
|
|
4834
5232
|
keyboard.text("\u2705 Create session", `a:${actionId}`);
|
|
4835
5233
|
keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
|
|
@@ -4936,79 +5334,525 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
4936
5334
|
});
|
|
4937
5335
|
}
|
|
4938
5336
|
|
|
4939
|
-
// src/adapters/telegram/
|
|
4940
|
-
var
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
nativeController.abort();
|
|
4947
|
-
} else {
|
|
4948
|
-
polyfillSignal.addEventListener("abort", () => nativeController.abort());
|
|
4949
|
-
}
|
|
4950
|
-
init = { ...init, signal: nativeController.signal };
|
|
5337
|
+
// src/adapters/telegram/tool-call-tracker.ts
|
|
5338
|
+
var log17 = createChildLogger({ module: "tool-call-tracker" });
|
|
5339
|
+
var ToolCallTracker = class {
|
|
5340
|
+
constructor(bot, chatId, sendQueue) {
|
|
5341
|
+
this.bot = bot;
|
|
5342
|
+
this.chatId = chatId;
|
|
5343
|
+
this.sendQueue = sendQueue;
|
|
4951
5344
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5345
|
+
sessions = /* @__PURE__ */ new Map();
|
|
5346
|
+
async trackNewCall(sessionId, threadId, meta) {
|
|
5347
|
+
if (!this.sessions.has(sessionId)) {
|
|
5348
|
+
this.sessions.set(sessionId, /* @__PURE__ */ new Map());
|
|
5349
|
+
}
|
|
5350
|
+
let resolveReady;
|
|
5351
|
+
const ready = new Promise((r) => {
|
|
5352
|
+
resolveReady = r;
|
|
5353
|
+
});
|
|
5354
|
+
this.sessions.get(sessionId).set(meta.id, {
|
|
5355
|
+
msgId: 0,
|
|
5356
|
+
name: meta.name,
|
|
5357
|
+
kind: meta.kind,
|
|
5358
|
+
viewerLinks: meta.viewerLinks,
|
|
5359
|
+
viewerFilePath: meta.viewerFilePath,
|
|
5360
|
+
ready
|
|
5361
|
+
});
|
|
5362
|
+
const msg = await this.sendQueue.enqueue(
|
|
5363
|
+
() => this.bot.api.sendMessage(
|
|
5364
|
+
this.chatId,
|
|
5365
|
+
formatToolCall(meta),
|
|
5366
|
+
{
|
|
5367
|
+
message_thread_id: threadId,
|
|
5368
|
+
parse_mode: "HTML",
|
|
5369
|
+
disable_notification: true
|
|
5370
|
+
}
|
|
5371
|
+
)
|
|
5372
|
+
);
|
|
5373
|
+
const toolEntry = this.sessions.get(sessionId).get(meta.id);
|
|
5374
|
+
toolEntry.msgId = msg.message_id;
|
|
5375
|
+
resolveReady();
|
|
5376
|
+
}
|
|
5377
|
+
async updateCall(sessionId, meta) {
|
|
5378
|
+
const toolState = this.sessions.get(sessionId)?.get(meta.id);
|
|
5379
|
+
if (!toolState) return;
|
|
5380
|
+
if (meta.viewerLinks) {
|
|
5381
|
+
toolState.viewerLinks = meta.viewerLinks;
|
|
5382
|
+
log17.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
5383
|
+
}
|
|
5384
|
+
if (meta.viewerFilePath) toolState.viewerFilePath = meta.viewerFilePath;
|
|
5385
|
+
if (meta.name) toolState.name = meta.name;
|
|
5386
|
+
if (meta.kind) toolState.kind = meta.kind;
|
|
5387
|
+
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
5388
|
+
if (!isTerminal) return;
|
|
5389
|
+
await toolState.ready;
|
|
5390
|
+
log17.debug(
|
|
5391
|
+
{
|
|
5392
|
+
toolId: meta.id,
|
|
5393
|
+
status: meta.status,
|
|
5394
|
+
hasViewerLinks: !!toolState.viewerLinks,
|
|
5395
|
+
viewerLinks: toolState.viewerLinks,
|
|
5396
|
+
name: toolState.name,
|
|
5397
|
+
msgId: toolState.msgId
|
|
5398
|
+
},
|
|
5399
|
+
"Tool completed, preparing edit"
|
|
5400
|
+
);
|
|
5401
|
+
const merged = {
|
|
5402
|
+
...meta,
|
|
5403
|
+
name: toolState.name,
|
|
5404
|
+
kind: toolState.kind,
|
|
5405
|
+
viewerLinks: toolState.viewerLinks,
|
|
5406
|
+
viewerFilePath: toolState.viewerFilePath
|
|
5407
|
+
};
|
|
5408
|
+
const formattedText = formatToolUpdate(merged);
|
|
5409
|
+
try {
|
|
5410
|
+
await this.sendQueue.enqueue(
|
|
5411
|
+
() => this.bot.api.editMessageText(
|
|
5412
|
+
this.chatId,
|
|
5413
|
+
toolState.msgId,
|
|
5414
|
+
formattedText,
|
|
5415
|
+
{ parse_mode: "HTML" }
|
|
5416
|
+
)
|
|
5417
|
+
);
|
|
5418
|
+
} catch (err) {
|
|
5419
|
+
log17.warn(
|
|
5420
|
+
{
|
|
5421
|
+
err,
|
|
5422
|
+
msgId: toolState.msgId,
|
|
5423
|
+
textLen: formattedText.length,
|
|
5424
|
+
hasViewerLinks: !!merged.viewerLinks
|
|
5425
|
+
},
|
|
5426
|
+
"Tool update edit failed"
|
|
4978
5427
|
);
|
|
4979
|
-
this.sessionTrackers.set(sessionId, tracker);
|
|
4980
5428
|
}
|
|
4981
|
-
return tracker;
|
|
4982
5429
|
}
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
this.telegramConfig = config;
|
|
5430
|
+
cleanup(sessionId) {
|
|
5431
|
+
this.sessions.delete(sessionId);
|
|
4986
5432
|
}
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
this.bot
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5433
|
+
};
|
|
5434
|
+
|
|
5435
|
+
// src/adapters/telegram/streaming.ts
|
|
5436
|
+
var FLUSH_INTERVAL = 5e3;
|
|
5437
|
+
var MessageDraft = class {
|
|
5438
|
+
constructor(bot, chatId, threadId, sendQueue, sessionId) {
|
|
5439
|
+
this.bot = bot;
|
|
5440
|
+
this.chatId = chatId;
|
|
5441
|
+
this.threadId = threadId;
|
|
5442
|
+
this.sendQueue = sendQueue;
|
|
5443
|
+
this.sessionId = sessionId;
|
|
5444
|
+
}
|
|
5445
|
+
buffer = "";
|
|
5446
|
+
messageId;
|
|
5447
|
+
firstFlushPending = false;
|
|
5448
|
+
flushTimer;
|
|
5449
|
+
flushPromise = Promise.resolve();
|
|
5450
|
+
lastSentBuffer = "";
|
|
5451
|
+
displayTruncated = false;
|
|
5452
|
+
append(text) {
|
|
5453
|
+
if (!text) return;
|
|
5454
|
+
this.buffer += text;
|
|
5455
|
+
this.scheduleFlush();
|
|
5456
|
+
}
|
|
5457
|
+
scheduleFlush() {
|
|
5458
|
+
if (this.flushTimer) return;
|
|
5459
|
+
this.flushTimer = setTimeout(() => {
|
|
5460
|
+
this.flushTimer = void 0;
|
|
5461
|
+
this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
|
|
5462
|
+
});
|
|
5463
|
+
}, FLUSH_INTERVAL);
|
|
5464
|
+
}
|
|
5465
|
+
async flush() {
|
|
5466
|
+
if (!this.buffer) return;
|
|
5467
|
+
if (this.firstFlushPending) return;
|
|
5468
|
+
const snapshot = this.buffer;
|
|
5469
|
+
let html = markdownToTelegramHtml(snapshot);
|
|
5470
|
+
if (!html) return;
|
|
5471
|
+
let truncated = false;
|
|
5472
|
+
if (html.length > 4096) {
|
|
5473
|
+
const ratio = 4e3 / html.length;
|
|
5474
|
+
const targetLen = Math.floor(snapshot.length * ratio);
|
|
5475
|
+
let cutAt = snapshot.lastIndexOf("\n", targetLen);
|
|
5476
|
+
if (cutAt < targetLen * 0.5) cutAt = targetLen;
|
|
5477
|
+
html = markdownToTelegramHtml(snapshot.slice(0, cutAt) + "\n\u2026");
|
|
5478
|
+
truncated = true;
|
|
5479
|
+
if (html.length > 4096) {
|
|
5480
|
+
html = html.slice(0, 4090) + "\n\u2026";
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
5483
|
+
if (!this.messageId) {
|
|
5484
|
+
this.firstFlushPending = true;
|
|
5485
|
+
try {
|
|
5486
|
+
const result = await this.sendQueue.enqueue(
|
|
5487
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
5488
|
+
message_thread_id: this.threadId,
|
|
5489
|
+
parse_mode: "HTML",
|
|
5490
|
+
disable_notification: true
|
|
5491
|
+
}),
|
|
5492
|
+
{ type: "other" }
|
|
5493
|
+
);
|
|
5494
|
+
if (result) {
|
|
5495
|
+
this.messageId = result.message_id;
|
|
5496
|
+
if (!truncated) {
|
|
5497
|
+
this.lastSentBuffer = snapshot;
|
|
5498
|
+
this.displayTruncated = false;
|
|
5499
|
+
} else {
|
|
5500
|
+
this.displayTruncated = true;
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
} catch {
|
|
5504
|
+
} finally {
|
|
5505
|
+
this.firstFlushPending = false;
|
|
5506
|
+
}
|
|
5507
|
+
} else {
|
|
5508
|
+
try {
|
|
5509
|
+
const result = await this.sendQueue.enqueue(
|
|
5510
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
5511
|
+
parse_mode: "HTML"
|
|
5512
|
+
}),
|
|
5513
|
+
{ type: "text", key: this.sessionId }
|
|
5514
|
+
);
|
|
5515
|
+
if (result !== void 0) {
|
|
5516
|
+
if (!truncated) {
|
|
5517
|
+
this.lastSentBuffer = snapshot;
|
|
5518
|
+
this.displayTruncated = false;
|
|
5519
|
+
} else {
|
|
5520
|
+
this.displayTruncated = true;
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
} catch {
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
async finalize() {
|
|
5528
|
+
if (this.flushTimer) {
|
|
5529
|
+
clearTimeout(this.flushTimer);
|
|
5530
|
+
this.flushTimer = void 0;
|
|
5531
|
+
}
|
|
5532
|
+
await this.flushPromise;
|
|
5533
|
+
if (!this.buffer) return this.messageId;
|
|
5534
|
+
if (this.messageId && this.buffer === this.lastSentBuffer && !this.displayTruncated) {
|
|
5535
|
+
return this.messageId;
|
|
5536
|
+
}
|
|
5537
|
+
const fullHtml = markdownToTelegramHtml(this.buffer);
|
|
5538
|
+
if (fullHtml.length <= 4096) {
|
|
5539
|
+
try {
|
|
5540
|
+
if (this.messageId) {
|
|
5541
|
+
await this.sendQueue.enqueue(
|
|
5542
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, fullHtml, {
|
|
5543
|
+
parse_mode: "HTML"
|
|
5544
|
+
}),
|
|
5545
|
+
{ type: "other" }
|
|
5546
|
+
);
|
|
5547
|
+
} else {
|
|
5548
|
+
const msg = await this.sendQueue.enqueue(
|
|
5549
|
+
() => this.bot.api.sendMessage(this.chatId, fullHtml, {
|
|
5550
|
+
message_thread_id: this.threadId,
|
|
5551
|
+
parse_mode: "HTML",
|
|
5552
|
+
disable_notification: true
|
|
5553
|
+
}),
|
|
5554
|
+
{ type: "other" }
|
|
5555
|
+
);
|
|
5556
|
+
if (msg) this.messageId = msg.message_id;
|
|
5557
|
+
}
|
|
5558
|
+
return this.messageId;
|
|
5559
|
+
} catch {
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
const mdChunks = splitMessage(this.buffer);
|
|
5563
|
+
for (let i = 0; i < mdChunks.length; i++) {
|
|
5564
|
+
const html = markdownToTelegramHtml(mdChunks[i]);
|
|
5565
|
+
try {
|
|
5566
|
+
if (i === 0 && this.messageId) {
|
|
5567
|
+
await this.sendQueue.enqueue(
|
|
5568
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
5569
|
+
parse_mode: "HTML"
|
|
5570
|
+
}),
|
|
5571
|
+
{ type: "other" }
|
|
5572
|
+
);
|
|
5573
|
+
} else {
|
|
5574
|
+
const msg = await this.sendQueue.enqueue(
|
|
5575
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
5576
|
+
message_thread_id: this.threadId,
|
|
5577
|
+
parse_mode: "HTML",
|
|
5578
|
+
disable_notification: true
|
|
5579
|
+
}),
|
|
5580
|
+
{ type: "other" }
|
|
5581
|
+
);
|
|
5582
|
+
if (msg) {
|
|
5583
|
+
this.messageId = msg.message_id;
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
} catch {
|
|
5587
|
+
try {
|
|
5588
|
+
if (i === 0 && this.messageId) {
|
|
5589
|
+
await this.sendQueue.enqueue(
|
|
5590
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, mdChunks[i].slice(0, 4096)),
|
|
5591
|
+
{ type: "other" }
|
|
5592
|
+
);
|
|
5593
|
+
} else {
|
|
5594
|
+
const msg = await this.sendQueue.enqueue(
|
|
5595
|
+
() => this.bot.api.sendMessage(this.chatId, mdChunks[i].slice(0, 4096), {
|
|
5596
|
+
message_thread_id: this.threadId,
|
|
5597
|
+
disable_notification: true
|
|
5598
|
+
}),
|
|
5599
|
+
{ type: "other" }
|
|
5600
|
+
);
|
|
5601
|
+
if (msg) {
|
|
5602
|
+
this.messageId = msg.message_id;
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
} catch {
|
|
5606
|
+
}
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
return this.messageId;
|
|
5610
|
+
}
|
|
5611
|
+
getMessageId() {
|
|
5612
|
+
return this.messageId;
|
|
5613
|
+
}
|
|
5614
|
+
};
|
|
5615
|
+
|
|
5616
|
+
// src/adapters/telegram/draft-manager.ts
|
|
5617
|
+
var DraftManager = class {
|
|
5618
|
+
constructor(bot, chatId, sendQueue) {
|
|
5619
|
+
this.bot = bot;
|
|
5620
|
+
this.chatId = chatId;
|
|
5621
|
+
this.sendQueue = sendQueue;
|
|
5622
|
+
}
|
|
5623
|
+
drafts = /* @__PURE__ */ new Map();
|
|
5624
|
+
textBuffers = /* @__PURE__ */ new Map();
|
|
5625
|
+
getOrCreate(sessionId, threadId) {
|
|
5626
|
+
let draft = this.drafts.get(sessionId);
|
|
5627
|
+
if (!draft) {
|
|
5628
|
+
draft = new MessageDraft(
|
|
5629
|
+
this.bot,
|
|
5630
|
+
this.chatId,
|
|
5631
|
+
threadId,
|
|
5632
|
+
this.sendQueue,
|
|
5633
|
+
sessionId
|
|
5634
|
+
);
|
|
5635
|
+
this.drafts.set(sessionId, draft);
|
|
5636
|
+
}
|
|
5637
|
+
return draft;
|
|
5638
|
+
}
|
|
5639
|
+
hasDraft(sessionId) {
|
|
5640
|
+
return this.drafts.has(sessionId);
|
|
5641
|
+
}
|
|
5642
|
+
appendText(sessionId, text) {
|
|
5643
|
+
this.textBuffers.set(
|
|
5644
|
+
sessionId,
|
|
5645
|
+
(this.textBuffers.get(sessionId) ?? "") + text
|
|
5646
|
+
);
|
|
5647
|
+
}
|
|
5648
|
+
/**
|
|
5649
|
+
* Finalize the current draft and return the message ID.
|
|
5650
|
+
* Optionally detects actions in assistant responses.
|
|
5651
|
+
*/
|
|
5652
|
+
async finalize(sessionId, assistantSessionId) {
|
|
5653
|
+
const draft = this.drafts.get(sessionId);
|
|
5654
|
+
if (!draft) return;
|
|
5655
|
+
this.drafts.delete(sessionId);
|
|
5656
|
+
const finalMsgId = await draft.finalize();
|
|
5657
|
+
if (assistantSessionId && sessionId === assistantSessionId) {
|
|
5658
|
+
const fullText = this.textBuffers.get(sessionId);
|
|
5659
|
+
this.textBuffers.delete(sessionId);
|
|
5660
|
+
if (fullText && finalMsgId) {
|
|
5661
|
+
const detected = detectAction(fullText);
|
|
5662
|
+
if (detected) {
|
|
5663
|
+
const actionId = storeAction(detected);
|
|
5664
|
+
const keyboard = buildActionKeyboard(actionId, detected);
|
|
5665
|
+
try {
|
|
5666
|
+
await this.bot.api.editMessageReplyMarkup(
|
|
5667
|
+
this.chatId,
|
|
5668
|
+
finalMsgId,
|
|
5669
|
+
{ reply_markup: keyboard }
|
|
5670
|
+
);
|
|
5671
|
+
} catch {
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
} else {
|
|
5676
|
+
this.textBuffers.delete(sessionId);
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
cleanup(sessionId) {
|
|
5680
|
+
this.drafts.delete(sessionId);
|
|
5681
|
+
this.textBuffers.delete(sessionId);
|
|
5682
|
+
}
|
|
5683
|
+
};
|
|
5684
|
+
|
|
5685
|
+
// src/adapters/telegram/skill-command-manager.ts
|
|
5686
|
+
var log18 = createChildLogger({ module: "skill-commands" });
|
|
5687
|
+
var SkillCommandManager = class {
|
|
5688
|
+
// sessionId → pinned msgId
|
|
5689
|
+
constructor(bot, chatId, sendQueue, sessionManager) {
|
|
5690
|
+
this.bot = bot;
|
|
5691
|
+
this.chatId = chatId;
|
|
5692
|
+
this.sendQueue = sendQueue;
|
|
5693
|
+
this.sessionManager = sessionManager;
|
|
5694
|
+
}
|
|
5695
|
+
messages = /* @__PURE__ */ new Map();
|
|
5696
|
+
async send(sessionId, threadId, commands) {
|
|
5697
|
+
if (!this.messages.has(sessionId)) {
|
|
5698
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
5699
|
+
const platform = record?.platform;
|
|
5700
|
+
if (platform?.skillMsgId) {
|
|
5701
|
+
this.messages.set(sessionId, platform.skillMsgId);
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
if (commands.length === 0) {
|
|
5705
|
+
await this.cleanup(sessionId);
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
const messages = buildSkillMessages(commands);
|
|
5709
|
+
const existingMsgId = this.messages.get(sessionId);
|
|
5710
|
+
if (existingMsgId) {
|
|
5711
|
+
try {
|
|
5712
|
+
await this.bot.api.editMessageText(
|
|
5713
|
+
this.chatId,
|
|
5714
|
+
existingMsgId,
|
|
5715
|
+
messages[0],
|
|
5716
|
+
{ parse_mode: "HTML" }
|
|
5717
|
+
);
|
|
5718
|
+
return;
|
|
5719
|
+
} catch (err) {
|
|
5720
|
+
const msg = err instanceof Error ? err.message : "";
|
|
5721
|
+
if (msg.includes("message is not modified")) return;
|
|
5722
|
+
try {
|
|
5723
|
+
await this.bot.api.deleteMessage(this.chatId, existingMsgId);
|
|
5724
|
+
} catch {
|
|
5725
|
+
}
|
|
5726
|
+
this.messages.delete(sessionId);
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
try {
|
|
5730
|
+
let firstMsgId;
|
|
5731
|
+
for (const text of messages) {
|
|
5732
|
+
const msg = await this.sendQueue.enqueue(
|
|
5733
|
+
() => this.bot.api.sendMessage(this.chatId, text, {
|
|
5734
|
+
message_thread_id: threadId,
|
|
5735
|
+
parse_mode: "HTML",
|
|
5736
|
+
disable_notification: true
|
|
5737
|
+
})
|
|
5738
|
+
);
|
|
5739
|
+
if (!firstMsgId) firstMsgId = msg.message_id;
|
|
5740
|
+
}
|
|
5741
|
+
this.messages.set(sessionId, firstMsgId);
|
|
5742
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
5743
|
+
if (record) {
|
|
5744
|
+
await this.sessionManager.patchRecord(sessionId, {
|
|
5745
|
+
platform: { ...record.platform, skillMsgId: firstMsgId }
|
|
5746
|
+
});
|
|
5747
|
+
}
|
|
5748
|
+
await this.bot.api.pinChatMessage(this.chatId, firstMsgId, {
|
|
5749
|
+
disable_notification: true
|
|
5750
|
+
});
|
|
5751
|
+
} catch (err) {
|
|
5752
|
+
log18.error({ err, sessionId }, "Failed to send skill commands");
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
async cleanup(sessionId) {
|
|
5756
|
+
const msgId = this.messages.get(sessionId);
|
|
5757
|
+
if (!msgId) return;
|
|
5758
|
+
try {
|
|
5759
|
+
await this.bot.api.editMessageText(
|
|
5760
|
+
this.chatId,
|
|
5761
|
+
msgId,
|
|
5762
|
+
"\u{1F6E0} <i>Session ended</i>",
|
|
5763
|
+
{ parse_mode: "HTML" }
|
|
5764
|
+
);
|
|
5765
|
+
await this.bot.api.unpinChatMessage(this.chatId, msgId);
|
|
5766
|
+
} catch {
|
|
5767
|
+
}
|
|
5768
|
+
this.messages.delete(sessionId);
|
|
5769
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
5770
|
+
if (record) {
|
|
5771
|
+
const { skillMsgId: _removed, ...rest } = record.platform;
|
|
5772
|
+
await this.sessionManager.patchRecord(sessionId, { platform: rest });
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
};
|
|
5776
|
+
|
|
5777
|
+
// src/adapters/telegram/adapter.ts
|
|
5778
|
+
var log19 = createChildLogger({ module: "telegram" });
|
|
5779
|
+
function patchedFetch(input, init) {
|
|
5780
|
+
if (init?.signal && !(init.signal instanceof AbortSignal)) {
|
|
5781
|
+
const nativeController = new AbortController();
|
|
5782
|
+
const polyfillSignal = init.signal;
|
|
5783
|
+
if (polyfillSignal.aborted) {
|
|
5784
|
+
nativeController.abort();
|
|
5785
|
+
} else {
|
|
5786
|
+
polyfillSignal.addEventListener("abort", () => nativeController.abort());
|
|
5787
|
+
}
|
|
5788
|
+
init = { ...init, signal: nativeController.signal };
|
|
5789
|
+
}
|
|
5790
|
+
return fetch(input, init);
|
|
5791
|
+
}
|
|
5792
|
+
var TelegramAdapter = class extends ChannelAdapter {
|
|
5793
|
+
bot;
|
|
5794
|
+
telegramConfig;
|
|
5795
|
+
permissionHandler;
|
|
5796
|
+
assistantSession = null;
|
|
5797
|
+
assistantInitializing = false;
|
|
5798
|
+
notificationTopicId;
|
|
5799
|
+
assistantTopicId;
|
|
5800
|
+
sendQueue = new TelegramSendQueue(3e3);
|
|
5801
|
+
// Extracted managers
|
|
5802
|
+
toolTracker;
|
|
5803
|
+
draftManager;
|
|
5804
|
+
skillManager;
|
|
5805
|
+
sessionTrackers = /* @__PURE__ */ new Map();
|
|
5806
|
+
getOrCreateTracker(sessionId, threadId) {
|
|
5807
|
+
let tracker = this.sessionTrackers.get(sessionId);
|
|
5808
|
+
if (!tracker) {
|
|
5809
|
+
tracker = new ActivityTracker(
|
|
5810
|
+
this.bot.api,
|
|
5811
|
+
this.telegramConfig.chatId,
|
|
5812
|
+
threadId,
|
|
5813
|
+
this.sendQueue
|
|
5814
|
+
);
|
|
5815
|
+
this.sessionTrackers.set(sessionId, tracker);
|
|
5816
|
+
}
|
|
5817
|
+
return tracker;
|
|
5818
|
+
}
|
|
5819
|
+
constructor(core, config) {
|
|
5820
|
+
super(core, config);
|
|
5821
|
+
this.telegramConfig = config;
|
|
5822
|
+
}
|
|
5823
|
+
async start() {
|
|
5824
|
+
this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
|
|
5825
|
+
this.toolTracker = new ToolCallTracker(this.bot, this.telegramConfig.chatId, this.sendQueue);
|
|
5826
|
+
this.draftManager = new DraftManager(this.bot, this.telegramConfig.chatId, this.sendQueue);
|
|
5827
|
+
this.skillManager = new SkillCommandManager(
|
|
5828
|
+
this.bot,
|
|
5829
|
+
this.telegramConfig.chatId,
|
|
5830
|
+
this.sendQueue,
|
|
5831
|
+
this.core.sessionManager
|
|
5832
|
+
);
|
|
5833
|
+
this.bot.catch((err) => {
|
|
5834
|
+
const rootCause = err.error instanceof Error ? err.error : err;
|
|
5835
|
+
log19.error({ err: rootCause }, "Telegram bot error");
|
|
5836
|
+
});
|
|
5837
|
+
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
5838
|
+
const maxRetries = 3;
|
|
5839
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
5840
|
+
const result = await prev(method, payload, signal);
|
|
5841
|
+
if (result.ok || result.error_code !== 429 || attempt === maxRetries) {
|
|
5842
|
+
return result;
|
|
5843
|
+
}
|
|
5844
|
+
const retryAfter = (result.parameters?.retry_after ?? 5) + 1;
|
|
5845
|
+
const rateLimitedMethods = ["sendMessage", "editMessageText", "editMessageReplyMarkup"];
|
|
5846
|
+
if (rateLimitedMethods.includes(method)) {
|
|
5847
|
+
this.sendQueue.onRateLimited();
|
|
5848
|
+
}
|
|
5849
|
+
log19.warn(
|
|
5850
|
+
{ method, retryAfter, attempt: attempt + 1 },
|
|
5851
|
+
"Rate limited by Telegram, retrying"
|
|
5852
|
+
);
|
|
5853
|
+
await new Promise((r) => setTimeout(r, retryAfter * 1e3));
|
|
5854
|
+
}
|
|
5855
|
+
return prev(method, payload, signal);
|
|
5012
5856
|
});
|
|
5013
5857
|
this.bot.api.config.use((prev, method, payload, signal) => {
|
|
5014
5858
|
if (method === "getUpdates") {
|
|
@@ -5058,7 +5902,14 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5058
5902
|
this.bot,
|
|
5059
5903
|
this.core,
|
|
5060
5904
|
this.telegramConfig.chatId,
|
|
5061
|
-
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId }
|
|
5905
|
+
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
|
|
5906
|
+
() => {
|
|
5907
|
+
if (!this.assistantSession) return void 0;
|
|
5908
|
+
return {
|
|
5909
|
+
topicId: this.assistantTopicId,
|
|
5910
|
+
enqueuePrompt: (p) => this.assistantSession.enqueuePrompt(p)
|
|
5911
|
+
};
|
|
5912
|
+
}
|
|
5062
5913
|
);
|
|
5063
5914
|
setupCommands(
|
|
5064
5915
|
this.bot,
|
|
@@ -5105,7 +5956,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5105
5956
|
});
|
|
5106
5957
|
return;
|
|
5107
5958
|
}
|
|
5108
|
-
const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-registry-
|
|
5959
|
+
const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-registry-B5YAMA4T.js");
|
|
5109
5960
|
const caps = getAgentCapabilities2(agentName);
|
|
5110
5961
|
if (!caps.supportsResume || !caps.resumeCommand) {
|
|
5111
5962
|
await ctx.reply("This agent does not support session transfer.", {
|
|
@@ -5127,7 +5978,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5127
5978
|
this.setupRoutes();
|
|
5128
5979
|
this.bot.start({
|
|
5129
5980
|
allowed_updates: ["message", "callback_query"],
|
|
5130
|
-
onStart: () =>
|
|
5981
|
+
onStart: () => log19.info(
|
|
5131
5982
|
{ chatId: this.telegramConfig.chatId },
|
|
5132
5983
|
"Telegram bot started"
|
|
5133
5984
|
)
|
|
@@ -5149,10 +6000,10 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5149
6000
|
reply_markup: buildMenuKeyboard()
|
|
5150
6001
|
});
|
|
5151
6002
|
} catch (err) {
|
|
5152
|
-
|
|
6003
|
+
log19.warn({ err }, "Failed to send welcome message");
|
|
5153
6004
|
}
|
|
5154
6005
|
try {
|
|
5155
|
-
|
|
6006
|
+
log19.info("Spawning assistant session...");
|
|
5156
6007
|
const { session, ready } = await spawnAssistant(
|
|
5157
6008
|
this.core,
|
|
5158
6009
|
this,
|
|
@@ -5160,13 +6011,13 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5160
6011
|
);
|
|
5161
6012
|
this.assistantSession = session;
|
|
5162
6013
|
this.assistantInitializing = true;
|
|
5163
|
-
|
|
6014
|
+
log19.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
|
|
5164
6015
|
ready.then(() => {
|
|
5165
6016
|
this.assistantInitializing = false;
|
|
5166
|
-
|
|
6017
|
+
log19.info({ sessionId: session.id }, "Assistant ready for user messages");
|
|
5167
6018
|
});
|
|
5168
6019
|
} catch (err) {
|
|
5169
|
-
|
|
6020
|
+
log19.error({ err }, "Failed to spawn assistant");
|
|
5170
6021
|
this.bot.api.sendMessage(
|
|
5171
6022
|
this.telegramConfig.chatId,
|
|
5172
6023
|
`\u26A0\uFE0F <b>Failed to start assistant session.</b>
|
|
@@ -5182,7 +6033,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5182
6033
|
await this.assistantSession.destroy();
|
|
5183
6034
|
}
|
|
5184
6035
|
await this.bot.stop();
|
|
5185
|
-
|
|
6036
|
+
log19.info("Telegram bot stopped");
|
|
5186
6037
|
}
|
|
5187
6038
|
setupRoutes() {
|
|
5188
6039
|
this.bot.on("message:text", async (ctx) => {
|
|
@@ -5204,16 +6055,16 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5204
6055
|
await ctx.reply("\u26A0\uFE0F Assistant is not available yet. Please try again shortly.", { parse_mode: "HTML" });
|
|
5205
6056
|
return;
|
|
5206
6057
|
}
|
|
5207
|
-
await this.
|
|
6058
|
+
await this.draftManager.finalize(this.assistantSession.id, this.assistantSession.id);
|
|
5208
6059
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
5209
6060
|
});
|
|
5210
6061
|
handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
|
|
5211
|
-
(err) =>
|
|
6062
|
+
(err) => log19.error({ err }, "Assistant error")
|
|
5212
6063
|
);
|
|
5213
6064
|
return;
|
|
5214
6065
|
}
|
|
5215
6066
|
const sessionId = this.core.sessionManager.getSessionByThread("telegram", String(threadId))?.id;
|
|
5216
|
-
if (sessionId) await this.
|
|
6067
|
+
if (sessionId) await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5217
6068
|
if (sessionId) {
|
|
5218
6069
|
const tracker = this.sessionTrackers.get(sessionId);
|
|
5219
6070
|
if (tracker) await tracker.onNewPrompt();
|
|
@@ -5225,19 +6076,17 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5225
6076
|
threadId: String(threadId),
|
|
5226
6077
|
userId: String(ctx.from.id),
|
|
5227
6078
|
text: ctx.message.text
|
|
5228
|
-
}).catch((err) =>
|
|
6079
|
+
}).catch((err) => log19.error({ err }, "handleMessage error"));
|
|
5229
6080
|
});
|
|
5230
6081
|
}
|
|
5231
6082
|
// --- ChannelAdapter implementations ---
|
|
5232
6083
|
async sendMessage(sessionId, content) {
|
|
5233
6084
|
if (this.assistantInitializing && sessionId === this.assistantSession?.id) return;
|
|
5234
|
-
const session = this.core.sessionManager.getSession(
|
|
5235
|
-
sessionId
|
|
5236
|
-
);
|
|
6085
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
5237
6086
|
if (!session) return;
|
|
5238
6087
|
const threadId = Number(session.threadId);
|
|
5239
6088
|
if (!threadId || isNaN(threadId)) {
|
|
5240
|
-
|
|
6089
|
+
log19.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
|
|
5241
6090
|
return;
|
|
5242
6091
|
}
|
|
5243
6092
|
switch (content.type) {
|
|
@@ -5247,105 +6096,32 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5247
6096
|
break;
|
|
5248
6097
|
}
|
|
5249
6098
|
case "text": {
|
|
5250
|
-
|
|
5251
|
-
if (!draft) {
|
|
6099
|
+
if (!this.draftManager.hasDraft(sessionId)) {
|
|
5252
6100
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
5253
6101
|
await tracker.onTextStart();
|
|
5254
|
-
draft = new MessageDraft(
|
|
5255
|
-
this.bot,
|
|
5256
|
-
this.telegramConfig.chatId,
|
|
5257
|
-
threadId,
|
|
5258
|
-
this.sendQueue,
|
|
5259
|
-
sessionId
|
|
5260
|
-
);
|
|
5261
|
-
this.sessionDrafts.set(sessionId, draft);
|
|
5262
6102
|
}
|
|
6103
|
+
const draft = this.draftManager.getOrCreate(sessionId, threadId);
|
|
5263
6104
|
draft.append(content.text);
|
|
5264
|
-
this.
|
|
5265
|
-
sessionId,
|
|
5266
|
-
(this.sessionTextBuffers.get(sessionId) ?? "") + content.text
|
|
5267
|
-
);
|
|
6105
|
+
this.draftManager.appendText(sessionId, content.text);
|
|
5268
6106
|
break;
|
|
5269
6107
|
}
|
|
5270
6108
|
case "tool_call": {
|
|
5271
6109
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
5272
6110
|
await tracker.onToolCall();
|
|
5273
|
-
await this.
|
|
6111
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5274
6112
|
const meta = content.metadata;
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
let resolveReady;
|
|
5279
|
-
const ready = new Promise((r) => {
|
|
5280
|
-
resolveReady = r;
|
|
5281
|
-
});
|
|
5282
|
-
this.toolCallMessages.get(sessionId).set(meta.id, {
|
|
5283
|
-
msgId: 0,
|
|
5284
|
-
name: meta.name,
|
|
5285
|
-
kind: meta.kind,
|
|
5286
|
-
viewerLinks: meta.viewerLinks,
|
|
5287
|
-
viewerFilePath: content.metadata?.viewerFilePath,
|
|
5288
|
-
ready
|
|
6113
|
+
await this.toolTracker.trackNewCall(sessionId, threadId, {
|
|
6114
|
+
...meta,
|
|
6115
|
+
viewerFilePath: content.metadata?.viewerFilePath
|
|
5289
6116
|
});
|
|
5290
|
-
const msg = await this.sendQueue.enqueue(
|
|
5291
|
-
() => this.bot.api.sendMessage(
|
|
5292
|
-
this.telegramConfig.chatId,
|
|
5293
|
-
formatToolCall(meta),
|
|
5294
|
-
{
|
|
5295
|
-
message_thread_id: threadId,
|
|
5296
|
-
parse_mode: "HTML",
|
|
5297
|
-
disable_notification: true
|
|
5298
|
-
}
|
|
5299
|
-
)
|
|
5300
|
-
);
|
|
5301
|
-
const toolEntry = this.toolCallMessages.get(sessionId).get(meta.id);
|
|
5302
|
-
toolEntry.msgId = msg.message_id;
|
|
5303
|
-
resolveReady();
|
|
5304
6117
|
break;
|
|
5305
6118
|
}
|
|
5306
6119
|
case "tool_update": {
|
|
5307
6120
|
const meta = content.metadata;
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
log14.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
5313
|
-
}
|
|
5314
|
-
const viewerFilePath = content.metadata?.viewerFilePath;
|
|
5315
|
-
if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
|
|
5316
|
-
if (meta.name) toolState.name = meta.name;
|
|
5317
|
-
if (meta.kind) toolState.kind = meta.kind;
|
|
5318
|
-
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
5319
|
-
if (!isTerminal) break;
|
|
5320
|
-
await toolState.ready;
|
|
5321
|
-
log14.debug(
|
|
5322
|
-
{ toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
|
|
5323
|
-
"Tool completed, preparing edit"
|
|
5324
|
-
);
|
|
5325
|
-
const merged = {
|
|
5326
|
-
...meta,
|
|
5327
|
-
name: toolState.name,
|
|
5328
|
-
kind: toolState.kind,
|
|
5329
|
-
viewerLinks: toolState.viewerLinks,
|
|
5330
|
-
viewerFilePath: toolState.viewerFilePath
|
|
5331
|
-
};
|
|
5332
|
-
const formattedText = formatToolUpdate(merged);
|
|
5333
|
-
try {
|
|
5334
|
-
await this.sendQueue.enqueue(
|
|
5335
|
-
() => this.bot.api.editMessageText(
|
|
5336
|
-
this.telegramConfig.chatId,
|
|
5337
|
-
toolState.msgId,
|
|
5338
|
-
formattedText,
|
|
5339
|
-
{ parse_mode: "HTML" }
|
|
5340
|
-
)
|
|
5341
|
-
);
|
|
5342
|
-
} catch (err) {
|
|
5343
|
-
log14.warn(
|
|
5344
|
-
{ err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
|
|
5345
|
-
"Tool update edit failed"
|
|
5346
|
-
);
|
|
5347
|
-
}
|
|
5348
|
-
}
|
|
6121
|
+
await this.toolTracker.updateCall(sessionId, {
|
|
6122
|
+
...meta,
|
|
6123
|
+
viewerFilePath: content.metadata?.viewerFilePath
|
|
6124
|
+
});
|
|
5349
6125
|
break;
|
|
5350
6126
|
}
|
|
5351
6127
|
case "plan": {
|
|
@@ -5362,7 +6138,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
5362
6138
|
}
|
|
5363
6139
|
case "usage": {
|
|
5364
6140
|
const meta = content.metadata;
|
|
5365
|
-
await this.
|
|
6141
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5366
6142
|
const tracker = this.getOrCreateTracker(sessionId, threadId);
|
|
5367
6143
|
await tracker.sendUsage(meta);
|
|
5368
6144
|
if (this.notificationTopicId && sessionId !== this.assistantSession?.id) {
|
|
@@ -5388,10 +6164,10 @@ Task completed.
|
|
|
5388
6164
|
break;
|
|
5389
6165
|
}
|
|
5390
6166
|
case "session_end": {
|
|
5391
|
-
await this.
|
|
5392
|
-
this.
|
|
5393
|
-
this.
|
|
5394
|
-
await this.
|
|
6167
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
6168
|
+
this.draftManager.cleanup(sessionId);
|
|
6169
|
+
this.toolTracker.cleanup(sessionId);
|
|
6170
|
+
await this.skillManager.cleanup(sessionId);
|
|
5395
6171
|
const tracker = this.sessionTrackers.get(sessionId);
|
|
5396
6172
|
if (tracker) {
|
|
5397
6173
|
await tracker.onComplete();
|
|
@@ -5413,7 +6189,7 @@ Task completed.
|
|
|
5413
6189
|
break;
|
|
5414
6190
|
}
|
|
5415
6191
|
case "error": {
|
|
5416
|
-
await this.
|
|
6192
|
+
await this.draftManager.finalize(sessionId, this.assistantSession?.id);
|
|
5417
6193
|
const tracker = this.sessionTrackers.get(sessionId);
|
|
5418
6194
|
if (tracker) {
|
|
5419
6195
|
tracker.destroy();
|
|
@@ -5435,15 +6211,13 @@ Task completed.
|
|
|
5435
6211
|
}
|
|
5436
6212
|
}
|
|
5437
6213
|
async sendPermissionRequest(sessionId, request) {
|
|
5438
|
-
|
|
5439
|
-
const session = this.core.sessionManager.getSession(
|
|
5440
|
-
sessionId
|
|
5441
|
-
);
|
|
6214
|
+
log19.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
6215
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
5442
6216
|
if (!session) return;
|
|
5443
6217
|
if (request.description.includes("openacp")) {
|
|
5444
6218
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
5445
6219
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
5446
|
-
|
|
6220
|
+
log19.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
|
|
5447
6221
|
session.permissionGate.resolve(allowOption.id);
|
|
5448
6222
|
}
|
|
5449
6223
|
return;
|
|
@@ -5451,7 +6225,7 @@ Task completed.
|
|
|
5451
6225
|
if (session.dangerousMode) {
|
|
5452
6226
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
5453
6227
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
5454
|
-
|
|
6228
|
+
log19.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
|
|
5455
6229
|
session.permissionGate.resolve(allowOption.id);
|
|
5456
6230
|
}
|
|
5457
6231
|
return;
|
|
@@ -5462,7 +6236,7 @@ Task completed.
|
|
|
5462
6236
|
}
|
|
5463
6237
|
async sendNotification(notification) {
|
|
5464
6238
|
if (notification.sessionId === this.assistantSession?.id) return;
|
|
5465
|
-
|
|
6239
|
+
log19.info(
|
|
5466
6240
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
5467
6241
|
"Notification sent"
|
|
5468
6242
|
);
|
|
@@ -5498,15 +6272,13 @@ Task completed.
|
|
|
5498
6272
|
);
|
|
5499
6273
|
}
|
|
5500
6274
|
async createSessionThread(sessionId, name) {
|
|
5501
|
-
|
|
6275
|
+
log19.info({ sessionId, name }, "Session topic created");
|
|
5502
6276
|
return String(
|
|
5503
6277
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
5504
6278
|
);
|
|
5505
6279
|
}
|
|
5506
6280
|
async renameSessionThread(sessionId, newName) {
|
|
5507
|
-
const session = this.core.sessionManager.getSession(
|
|
5508
|
-
sessionId
|
|
5509
|
-
);
|
|
6281
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
5510
6282
|
if (!session) return;
|
|
5511
6283
|
await renameSessionTopic(
|
|
5512
6284
|
this.bot,
|
|
@@ -5514,10 +6286,7 @@ Task completed.
|
|
|
5514
6286
|
Number(session.threadId),
|
|
5515
6287
|
newName
|
|
5516
6288
|
);
|
|
5517
|
-
await this.core.sessionManager.
|
|
5518
|
-
sessionId,
|
|
5519
|
-
newName
|
|
5520
|
-
);
|
|
6289
|
+
await this.core.sessionManager.patchRecord(sessionId, { name: newName });
|
|
5521
6290
|
}
|
|
5522
6291
|
async deleteSessionThread(sessionId) {
|
|
5523
6292
|
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
@@ -5527,7 +6296,7 @@ Task completed.
|
|
|
5527
6296
|
try {
|
|
5528
6297
|
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
5529
6298
|
} catch (err) {
|
|
5530
|
-
|
|
6299
|
+
log19.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
|
|
5531
6300
|
}
|
|
5532
6301
|
}
|
|
5533
6302
|
async sendSkillCommands(sessionId, commands) {
|
|
@@ -5536,119 +6305,10 @@ Task completed.
|
|
|
5536
6305
|
if (!session) return;
|
|
5537
6306
|
const threadId = Number(session.threadId);
|
|
5538
6307
|
if (!threadId) return;
|
|
5539
|
-
|
|
5540
|
-
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
5541
|
-
const platform = record?.platform;
|
|
5542
|
-
if (platform?.skillMsgId) {
|
|
5543
|
-
this.skillMessages.set(sessionId, platform.skillMsgId);
|
|
5544
|
-
}
|
|
5545
|
-
}
|
|
5546
|
-
if (commands.length === 0) {
|
|
5547
|
-
await this.cleanupSkillCommands(sessionId);
|
|
5548
|
-
return;
|
|
5549
|
-
}
|
|
5550
|
-
const messages = buildSkillMessages(commands);
|
|
5551
|
-
const existingMsgId = this.skillMessages.get(sessionId);
|
|
5552
|
-
if (existingMsgId) {
|
|
5553
|
-
try {
|
|
5554
|
-
await this.bot.api.editMessageText(
|
|
5555
|
-
this.telegramConfig.chatId,
|
|
5556
|
-
existingMsgId,
|
|
5557
|
-
messages[0],
|
|
5558
|
-
{ parse_mode: "HTML" }
|
|
5559
|
-
);
|
|
5560
|
-
return;
|
|
5561
|
-
} catch (err) {
|
|
5562
|
-
const msg = err instanceof Error ? err.message : "";
|
|
5563
|
-
if (msg.includes("message is not modified")) {
|
|
5564
|
-
return;
|
|
5565
|
-
}
|
|
5566
|
-
try {
|
|
5567
|
-
await this.bot.api.deleteMessage(this.telegramConfig.chatId, existingMsgId);
|
|
5568
|
-
} catch {
|
|
5569
|
-
}
|
|
5570
|
-
this.skillMessages.delete(sessionId);
|
|
5571
|
-
}
|
|
5572
|
-
}
|
|
5573
|
-
try {
|
|
5574
|
-
let firstMsgId;
|
|
5575
|
-
for (const text of messages) {
|
|
5576
|
-
const msg = await this.sendQueue.enqueue(
|
|
5577
|
-
() => this.bot.api.sendMessage(
|
|
5578
|
-
this.telegramConfig.chatId,
|
|
5579
|
-
text,
|
|
5580
|
-
{
|
|
5581
|
-
message_thread_id: threadId,
|
|
5582
|
-
parse_mode: "HTML",
|
|
5583
|
-
disable_notification: true
|
|
5584
|
-
}
|
|
5585
|
-
)
|
|
5586
|
-
);
|
|
5587
|
-
if (!firstMsgId) firstMsgId = msg.message_id;
|
|
5588
|
-
}
|
|
5589
|
-
this.skillMessages.set(sessionId, firstMsgId);
|
|
5590
|
-
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
5591
|
-
if (record) {
|
|
5592
|
-
await this.core.sessionManager.updateSessionPlatform(
|
|
5593
|
-
sessionId,
|
|
5594
|
-
{ ...record.platform, skillMsgId: firstMsgId }
|
|
5595
|
-
);
|
|
5596
|
-
}
|
|
5597
|
-
await this.bot.api.pinChatMessage(
|
|
5598
|
-
this.telegramConfig.chatId,
|
|
5599
|
-
firstMsgId,
|
|
5600
|
-
{ disable_notification: true }
|
|
5601
|
-
);
|
|
5602
|
-
} catch (err) {
|
|
5603
|
-
log14.error({ err, sessionId }, "Failed to send skill commands");
|
|
5604
|
-
}
|
|
6308
|
+
await this.skillManager.send(sessionId, threadId, commands);
|
|
5605
6309
|
}
|
|
5606
6310
|
async cleanupSkillCommands(sessionId) {
|
|
5607
|
-
|
|
5608
|
-
if (!msgId) return;
|
|
5609
|
-
try {
|
|
5610
|
-
await this.bot.api.editMessageText(
|
|
5611
|
-
this.telegramConfig.chatId,
|
|
5612
|
-
msgId,
|
|
5613
|
-
"\u{1F6E0} <i>Session ended</i>",
|
|
5614
|
-
{ parse_mode: "HTML" }
|
|
5615
|
-
);
|
|
5616
|
-
await this.bot.api.unpinChatMessage(this.telegramConfig.chatId, msgId);
|
|
5617
|
-
} catch {
|
|
5618
|
-
}
|
|
5619
|
-
this.skillMessages.delete(sessionId);
|
|
5620
|
-
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
5621
|
-
if (record) {
|
|
5622
|
-
const { skillMsgId: _removed, ...rest } = record.platform;
|
|
5623
|
-
await this.core.sessionManager.updateSessionPlatform(sessionId, rest);
|
|
5624
|
-
}
|
|
5625
|
-
}
|
|
5626
|
-
async finalizeDraft(sessionId) {
|
|
5627
|
-
const draft = this.sessionDrafts.get(sessionId);
|
|
5628
|
-
if (!draft) return;
|
|
5629
|
-
this.sessionDrafts.delete(sessionId);
|
|
5630
|
-
const finalMsgId = await draft.finalize();
|
|
5631
|
-
if (sessionId === this.assistantSession?.id) {
|
|
5632
|
-
const fullText = this.sessionTextBuffers.get(sessionId);
|
|
5633
|
-
this.sessionTextBuffers.delete(sessionId);
|
|
5634
|
-
if (fullText && finalMsgId) {
|
|
5635
|
-
const detected = detectAction(fullText);
|
|
5636
|
-
if (detected) {
|
|
5637
|
-
const actionId = storeAction(detected);
|
|
5638
|
-
const keyboard = buildActionKeyboard(actionId, detected);
|
|
5639
|
-
try {
|
|
5640
|
-
await this.bot.api.editMessageReplyMarkup(
|
|
5641
|
-
this.telegramConfig.chatId,
|
|
5642
|
-
finalMsgId,
|
|
5643
|
-
{ reply_markup: keyboard }
|
|
5644
|
-
);
|
|
5645
|
-
} catch {
|
|
5646
|
-
}
|
|
5647
|
-
}
|
|
5648
|
-
}
|
|
5649
|
-
} else {
|
|
5650
|
-
this.sessionTextBuffers.delete(sessionId);
|
|
5651
|
-
}
|
|
6311
|
+
await this.skillManager.cleanup(sessionId);
|
|
5652
6312
|
}
|
|
5653
6313
|
};
|
|
5654
6314
|
|
|
@@ -5663,6 +6323,7 @@ export {
|
|
|
5663
6323
|
PermissionGate,
|
|
5664
6324
|
Session,
|
|
5665
6325
|
SessionManager,
|
|
6326
|
+
SessionBridge,
|
|
5666
6327
|
NotificationManager,
|
|
5667
6328
|
MessageTransformer,
|
|
5668
6329
|
OpenACPCore,
|
|
@@ -5671,4 +6332,4 @@ export {
|
|
|
5671
6332
|
TopicManager,
|
|
5672
6333
|
TelegramAdapter
|
|
5673
6334
|
};
|
|
5674
|
-
//# sourceMappingURL=chunk-
|
|
6335
|
+
//# sourceMappingURL=chunk-FWN3UIRT.js.map
|