@openacp/cli 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3UGSZYSQ.js +278 -0
- package/dist/chunk-3UGSZYSQ.js.map +1 -0
- package/dist/{chunk-TKOYKVXH.js → chunk-HTXK4NLG.js} +654 -82
- package/dist/chunk-HTXK4NLG.js.map +1 -0
- package/dist/chunk-ZATQZUJT.js +193 -0
- package/dist/chunk-ZATQZUJT.js.map +1 -0
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +206 -15
- package/dist/index.js +10 -8
- package/dist/{main-C4QWS4PK.js → main-4H43GCXO.js} +16 -5
- package/dist/main-4H43GCXO.js.map +1 -0
- package/dist/{setup-5RQ7YLYW.js → setup-FB4DGR6R.js} +12 -2
- package/dist/{setup-5RQ7YLYW.js.map → setup-FB4DGR6R.js.map} +1 -1
- package/dist/tunnel-service-FPRPBPQ5.js +633 -0
- package/dist/tunnel-service-FPRPBPQ5.js.map +1 -0
- package/package.json +5 -1
- package/dist/chunk-HTUZOMIT.js +0 -421
- package/dist/chunk-HTUZOMIT.js.map +0 -1
- package/dist/chunk-TKOYKVXH.js.map +0 -1
- package/dist/main-C4QWS4PK.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createChildLogger,
|
|
3
3
|
createSessionLogger
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZATQZUJT.js";
|
|
5
5
|
|
|
6
6
|
// src/core/streams.ts
|
|
7
7
|
function nodeToWebWritable(nodeStream) {
|
|
@@ -55,7 +55,14 @@ import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
|
55
55
|
var log = createChildLogger({ module: "agent-instance" });
|
|
56
56
|
function resolveAgentCommand(cmd) {
|
|
57
57
|
const packageDirs = [
|
|
58
|
-
path.resolve(
|
|
58
|
+
path.resolve(
|
|
59
|
+
process.cwd(),
|
|
60
|
+
"node_modules",
|
|
61
|
+
"@zed-industries",
|
|
62
|
+
cmd,
|
|
63
|
+
"dist",
|
|
64
|
+
"index.js"
|
|
65
|
+
),
|
|
59
66
|
path.resolve(process.cwd(), "node_modules", cmd, "dist", "index.js")
|
|
60
67
|
];
|
|
61
68
|
for (const jsPath of packageDirs) {
|
|
@@ -103,19 +110,33 @@ var AgentInstance = class _AgentInstance {
|
|
|
103
110
|
constructor(agentName) {
|
|
104
111
|
this.agentName = agentName;
|
|
105
112
|
}
|
|
106
|
-
static async
|
|
113
|
+
static async spawnSubprocess(agentDef, workingDirectory) {
|
|
107
114
|
const instance = new _AgentInstance(agentDef.name);
|
|
108
115
|
const resolved = resolveAgentCommand(agentDef.command);
|
|
109
|
-
log.debug(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
log.debug(
|
|
117
|
+
{
|
|
118
|
+
agentName: agentDef.name,
|
|
119
|
+
command: resolved.command,
|
|
120
|
+
args: resolved.args
|
|
121
|
+
},
|
|
122
|
+
"Resolved agent command"
|
|
123
|
+
);
|
|
124
|
+
instance.child = spawn(
|
|
125
|
+
resolved.command,
|
|
126
|
+
[...resolved.args, ...agentDef.args],
|
|
127
|
+
{
|
|
128
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
129
|
+
cwd: workingDirectory,
|
|
130
|
+
env: { ...process.env, ...agentDef.env }
|
|
131
|
+
}
|
|
132
|
+
);
|
|
116
133
|
await new Promise((resolve, reject) => {
|
|
117
134
|
instance.child.on("error", (err) => {
|
|
118
|
-
reject(
|
|
135
|
+
reject(
|
|
136
|
+
new Error(
|
|
137
|
+
`Failed to spawn agent "${agentDef.name}": ${err.message}. Is "${agentDef.command}" installed?`
|
|
138
|
+
)
|
|
139
|
+
);
|
|
119
140
|
});
|
|
120
141
|
instance.child.on("spawn", () => resolve());
|
|
121
142
|
});
|
|
@@ -125,14 +146,20 @@ var AgentInstance = class _AgentInstance {
|
|
|
125
146
|
});
|
|
126
147
|
const stdinLogger = new Transform({
|
|
127
148
|
transform(chunk, _enc, cb) {
|
|
128
|
-
log.debug(
|
|
149
|
+
log.debug(
|
|
150
|
+
{ direction: "send", raw: chunk.toString().trimEnd() },
|
|
151
|
+
"ACP raw"
|
|
152
|
+
);
|
|
129
153
|
cb(null, chunk);
|
|
130
154
|
}
|
|
131
155
|
});
|
|
132
156
|
stdinLogger.pipe(instance.child.stdin);
|
|
133
157
|
const stdoutLogger = new Transform({
|
|
134
158
|
transform(chunk, _enc, cb) {
|
|
135
|
-
log.debug(
|
|
159
|
+
log.debug(
|
|
160
|
+
{ direction: "recv", raw: chunk.toString().trimEnd() },
|
|
161
|
+
"ACP raw"
|
|
162
|
+
);
|
|
136
163
|
cb(null, chunk);
|
|
137
164
|
}
|
|
138
165
|
});
|
|
@@ -151,26 +178,82 @@ var AgentInstance = class _AgentInstance {
|
|
|
151
178
|
terminal: true
|
|
152
179
|
}
|
|
153
180
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
181
|
+
return instance;
|
|
182
|
+
}
|
|
183
|
+
setupCrashDetection() {
|
|
184
|
+
this.child.on("exit", (code, signal) => {
|
|
185
|
+
log.info(
|
|
186
|
+
{ sessionId: this.sessionId, exitCode: code, signal },
|
|
187
|
+
"Agent process exited"
|
|
188
|
+
);
|
|
161
189
|
if (code !== 0 && code !== null) {
|
|
162
|
-
const stderr =
|
|
163
|
-
|
|
190
|
+
const stderr = this.stderrCapture.getLastLines();
|
|
191
|
+
this.onSessionUpdate({
|
|
164
192
|
type: "error",
|
|
165
193
|
message: `Agent crashed (exit code ${code})
|
|
166
194
|
${stderr}`
|
|
167
195
|
});
|
|
168
196
|
}
|
|
169
197
|
});
|
|
170
|
-
|
|
171
|
-
log.debug({ sessionId:
|
|
198
|
+
this.connection.closed.then(() => {
|
|
199
|
+
log.debug({ sessionId: this.sessionId }, "ACP connection closed");
|
|
172
200
|
});
|
|
173
|
-
|
|
201
|
+
}
|
|
202
|
+
static async spawn(agentDef, workingDirectory) {
|
|
203
|
+
log.debug(
|
|
204
|
+
{ agentName: agentDef.name, command: agentDef.command },
|
|
205
|
+
"Spawning agent"
|
|
206
|
+
);
|
|
207
|
+
const spawnStart = Date.now();
|
|
208
|
+
const instance = await _AgentInstance.spawnSubprocess(
|
|
209
|
+
agentDef,
|
|
210
|
+
workingDirectory
|
|
211
|
+
);
|
|
212
|
+
const response = await instance.connection.newSession({
|
|
213
|
+
cwd: workingDirectory,
|
|
214
|
+
mcpServers: []
|
|
215
|
+
});
|
|
216
|
+
instance.sessionId = response.sessionId;
|
|
217
|
+
instance.setupCrashDetection();
|
|
218
|
+
log.info(
|
|
219
|
+
{ sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
|
|
220
|
+
"Agent spawn complete"
|
|
221
|
+
);
|
|
222
|
+
return instance;
|
|
223
|
+
}
|
|
224
|
+
static async resume(agentDef, workingDirectory, agentSessionId) {
|
|
225
|
+
log.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
|
|
226
|
+
const spawnStart = Date.now();
|
|
227
|
+
const instance = await _AgentInstance.spawnSubprocess(
|
|
228
|
+
agentDef,
|
|
229
|
+
workingDirectory
|
|
230
|
+
);
|
|
231
|
+
try {
|
|
232
|
+
const response = await instance.connection.unstable_resumeSession({
|
|
233
|
+
sessionId: agentSessionId,
|
|
234
|
+
cwd: workingDirectory
|
|
235
|
+
});
|
|
236
|
+
instance.sessionId = response.sessionId;
|
|
237
|
+
log.info(
|
|
238
|
+
{ sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
|
|
239
|
+
"Agent resume complete"
|
|
240
|
+
);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
log.warn(
|
|
243
|
+
{ err, agentSessionId },
|
|
244
|
+
"Resume failed, falling back to new session"
|
|
245
|
+
);
|
|
246
|
+
const response = await instance.connection.newSession({
|
|
247
|
+
cwd: workingDirectory,
|
|
248
|
+
mcpServers: []
|
|
249
|
+
});
|
|
250
|
+
instance.sessionId = response.sessionId;
|
|
251
|
+
log.info(
|
|
252
|
+
{ sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
|
|
253
|
+
"Agent fallback spawn complete"
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
instance.setupCrashDetection();
|
|
174
257
|
return instance;
|
|
175
258
|
}
|
|
176
259
|
// createClient — implemented in Task 6b
|
|
@@ -223,7 +306,10 @@ ${stderr}`
|
|
|
223
306
|
};
|
|
224
307
|
break;
|
|
225
308
|
case "available_commands_update":
|
|
226
|
-
event = {
|
|
309
|
+
event = {
|
|
310
|
+
type: "commands_update",
|
|
311
|
+
commands: update.availableCommands
|
|
312
|
+
};
|
|
227
313
|
break;
|
|
228
314
|
default:
|
|
229
315
|
return;
|
|
@@ -286,8 +372,14 @@ ${stderr}`
|
|
|
286
372
|
state.output = state.output.slice(excess);
|
|
287
373
|
}
|
|
288
374
|
};
|
|
289
|
-
childProcess.stdout?.on(
|
|
290
|
-
|
|
375
|
+
childProcess.stdout?.on(
|
|
376
|
+
"data",
|
|
377
|
+
(chunk) => appendOutput(chunk.toString())
|
|
378
|
+
);
|
|
379
|
+
childProcess.stderr?.on(
|
|
380
|
+
"data",
|
|
381
|
+
(chunk) => appendOutput(chunk.toString())
|
|
382
|
+
);
|
|
291
383
|
childProcess.on("exit", (code, signal) => {
|
|
292
384
|
state.exitStatus = { exitCode: code, signal };
|
|
293
385
|
});
|
|
@@ -301,7 +393,10 @@ ${stderr}`
|
|
|
301
393
|
return {
|
|
302
394
|
output: state.output,
|
|
303
395
|
truncated: false,
|
|
304
|
-
exitStatus: state.exitStatus ? {
|
|
396
|
+
exitStatus: state.exitStatus ? {
|
|
397
|
+
exitCode: state.exitStatus.exitCode,
|
|
398
|
+
signal: state.exitStatus.signal
|
|
399
|
+
} : void 0
|
|
305
400
|
};
|
|
306
401
|
},
|
|
307
402
|
async waitForTerminalExit(params) {
|
|
@@ -310,7 +405,10 @@ ${stderr}`
|
|
|
310
405
|
throw new Error(`Terminal not found: ${params.terminalId}`);
|
|
311
406
|
}
|
|
312
407
|
if (state.exitStatus !== null) {
|
|
313
|
-
return {
|
|
408
|
+
return {
|
|
409
|
+
exitCode: state.exitStatus.exitCode,
|
|
410
|
+
signal: state.exitStatus.signal
|
|
411
|
+
};
|
|
314
412
|
}
|
|
315
413
|
return new Promise((resolve) => {
|
|
316
414
|
state.process.on("exit", (code, signal) => {
|
|
@@ -381,6 +479,11 @@ var AgentManager = class {
|
|
|
381
479
|
if (!agentDef) throw new Error(`Agent "${agentName}" not found in config`);
|
|
382
480
|
return AgentInstance.spawn(agentDef, workingDirectory);
|
|
383
481
|
}
|
|
482
|
+
async resume(agentName, workingDirectory, agentSessionId) {
|
|
483
|
+
const agentDef = this.getAgent(agentName);
|
|
484
|
+
if (!agentDef) throw new Error(`Agent "${agentName}" not found in config`);
|
|
485
|
+
return AgentInstance.resume(agentDef, workingDirectory, agentSessionId);
|
|
486
|
+
}
|
|
384
487
|
};
|
|
385
488
|
|
|
386
489
|
// src/core/session.ts
|
|
@@ -393,6 +496,7 @@ var Session = class {
|
|
|
393
496
|
agentName;
|
|
394
497
|
workingDirectory;
|
|
395
498
|
agentInstance;
|
|
499
|
+
agentSessionId = "";
|
|
396
500
|
status = "initializing";
|
|
397
501
|
name;
|
|
398
502
|
promptQueue = [];
|
|
@@ -426,7 +530,10 @@ var Session = class {
|
|
|
426
530
|
this.log.debug("Prompt execution started");
|
|
427
531
|
try {
|
|
428
532
|
await this.agentInstance.prompt(text);
|
|
429
|
-
this.log.info(
|
|
533
|
+
this.log.info(
|
|
534
|
+
{ durationMs: Date.now() - promptStart },
|
|
535
|
+
"Prompt execution completed"
|
|
536
|
+
);
|
|
430
537
|
if (!this.name) {
|
|
431
538
|
await this.autoName();
|
|
432
539
|
}
|
|
@@ -499,10 +606,34 @@ var Session = class {
|
|
|
499
606
|
// src/core/session-manager.ts
|
|
500
607
|
var SessionManager = class {
|
|
501
608
|
sessions = /* @__PURE__ */ new Map();
|
|
609
|
+
store;
|
|
610
|
+
constructor(store = null) {
|
|
611
|
+
this.store = store;
|
|
612
|
+
}
|
|
502
613
|
async createSession(channelId, agentName, workingDirectory, agentManager) {
|
|
503
614
|
const agentInstance = await agentManager.spawn(agentName, workingDirectory);
|
|
504
|
-
const session = new Session({
|
|
615
|
+
const session = new Session({
|
|
616
|
+
channelId,
|
|
617
|
+
agentName,
|
|
618
|
+
workingDirectory,
|
|
619
|
+
agentInstance
|
|
620
|
+
});
|
|
505
621
|
this.sessions.set(session.id, session);
|
|
622
|
+
session.agentSessionId = session.agentInstance.sessionId;
|
|
623
|
+
if (this.store) {
|
|
624
|
+
await this.store.save({
|
|
625
|
+
sessionId: session.id,
|
|
626
|
+
agentSessionId: session.agentInstance.sessionId,
|
|
627
|
+
agentName: session.agentName,
|
|
628
|
+
workingDir: session.workingDirectory,
|
|
629
|
+
channelId,
|
|
630
|
+
status: session.status,
|
|
631
|
+
createdAt: session.createdAt.toISOString(),
|
|
632
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
633
|
+
name: session.name,
|
|
634
|
+
platform: {}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
506
637
|
return session;
|
|
507
638
|
}
|
|
508
639
|
getSession(sessionId) {
|
|
@@ -516,9 +647,44 @@ var SessionManager = class {
|
|
|
516
647
|
}
|
|
517
648
|
return void 0;
|
|
518
649
|
}
|
|
650
|
+
registerSession(session) {
|
|
651
|
+
this.sessions.set(session.id, session);
|
|
652
|
+
}
|
|
653
|
+
async updateSessionPlatform(sessionId, platform) {
|
|
654
|
+
if (!this.store) return;
|
|
655
|
+
const record = this.store.get(sessionId);
|
|
656
|
+
if (record) {
|
|
657
|
+
await this.store.save({ ...record, platform });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async updateSessionActivity(sessionId) {
|
|
661
|
+
if (!this.store) return;
|
|
662
|
+
const record = this.store.get(sessionId);
|
|
663
|
+
if (record) {
|
|
664
|
+
await this.store.save({
|
|
665
|
+
...record,
|
|
666
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async updateSessionStatus(sessionId, status) {
|
|
671
|
+
if (!this.store) return;
|
|
672
|
+
const record = this.store.get(sessionId);
|
|
673
|
+
if (record) {
|
|
674
|
+
await this.store.save({ ...record, status });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
519
677
|
async cancelSession(sessionId) {
|
|
520
678
|
const session = this.sessions.get(sessionId);
|
|
521
|
-
if (session)
|
|
679
|
+
if (session) {
|
|
680
|
+
await session.cancel();
|
|
681
|
+
if (this.store) {
|
|
682
|
+
const record = this.store.get(sessionId);
|
|
683
|
+
if (record) {
|
|
684
|
+
await this.store.save({ ...record, status: "cancelled" });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
522
688
|
}
|
|
523
689
|
listSessions(channelId) {
|
|
524
690
|
const all = Array.from(this.sessions.values());
|
|
@@ -526,6 +692,14 @@ var SessionManager = class {
|
|
|
526
692
|
return all;
|
|
527
693
|
}
|
|
528
694
|
async destroyAll() {
|
|
695
|
+
if (this.store) {
|
|
696
|
+
for (const session of this.sessions.values()) {
|
|
697
|
+
const record = this.store.get(session.id);
|
|
698
|
+
if (record) {
|
|
699
|
+
await this.store.save({ ...record, status: "finished" });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
529
703
|
for (const session of this.sessions.values()) {
|
|
530
704
|
await session.destroy();
|
|
531
705
|
}
|
|
@@ -552,18 +726,218 @@ var NotificationManager = class {
|
|
|
552
726
|
};
|
|
553
727
|
|
|
554
728
|
// src/core/core.ts
|
|
555
|
-
|
|
729
|
+
import path3 from "path";
|
|
730
|
+
import os from "os";
|
|
731
|
+
|
|
732
|
+
// src/core/session-store.ts
|
|
733
|
+
import fs2 from "fs";
|
|
734
|
+
import path2 from "path";
|
|
735
|
+
var log2 = createChildLogger({ module: "session-store" });
|
|
736
|
+
var DEBOUNCE_MS = 2e3;
|
|
737
|
+
var JsonFileSessionStore = class {
|
|
738
|
+
records = /* @__PURE__ */ new Map();
|
|
739
|
+
filePath;
|
|
740
|
+
ttlDays;
|
|
741
|
+
debounceTimer = null;
|
|
742
|
+
cleanupInterval = null;
|
|
743
|
+
flushHandler = null;
|
|
744
|
+
constructor(filePath, ttlDays) {
|
|
745
|
+
this.filePath = filePath;
|
|
746
|
+
this.ttlDays = ttlDays;
|
|
747
|
+
this.load();
|
|
748
|
+
this.cleanup();
|
|
749
|
+
this.cleanupInterval = setInterval(
|
|
750
|
+
() => this.cleanup(),
|
|
751
|
+
24 * 60 * 60 * 1e3
|
|
752
|
+
);
|
|
753
|
+
this.flushHandler = () => this.flushSync();
|
|
754
|
+
process.on("SIGTERM", this.flushHandler);
|
|
755
|
+
process.on("SIGINT", this.flushHandler);
|
|
756
|
+
process.on("exit", this.flushHandler);
|
|
757
|
+
}
|
|
758
|
+
async save(record) {
|
|
759
|
+
this.records.set(record.sessionId, { ...record });
|
|
760
|
+
this.scheduleDiskWrite();
|
|
761
|
+
}
|
|
762
|
+
get(sessionId) {
|
|
763
|
+
return this.records.get(sessionId);
|
|
764
|
+
}
|
|
765
|
+
findByPlatform(channelId, predicate) {
|
|
766
|
+
for (const record of this.records.values()) {
|
|
767
|
+
if (record.channelId === channelId && predicate(record.platform)) {
|
|
768
|
+
return record;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return void 0;
|
|
772
|
+
}
|
|
773
|
+
list(channelId) {
|
|
774
|
+
const all = [...this.records.values()];
|
|
775
|
+
if (channelId) return all.filter((r) => r.channelId === channelId);
|
|
776
|
+
return all;
|
|
777
|
+
}
|
|
778
|
+
async remove(sessionId) {
|
|
779
|
+
this.records.delete(sessionId);
|
|
780
|
+
this.scheduleDiskWrite();
|
|
781
|
+
}
|
|
782
|
+
flushSync() {
|
|
783
|
+
if (this.debounceTimer) {
|
|
784
|
+
clearTimeout(this.debounceTimer);
|
|
785
|
+
this.debounceTimer = null;
|
|
786
|
+
}
|
|
787
|
+
const data = {
|
|
788
|
+
version: 1,
|
|
789
|
+
sessions: Object.fromEntries(this.records)
|
|
790
|
+
};
|
|
791
|
+
const dir = path2.dirname(this.filePath);
|
|
792
|
+
if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
|
|
793
|
+
fs2.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
|
|
794
|
+
}
|
|
795
|
+
destroy() {
|
|
796
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
797
|
+
if (this.cleanupInterval) clearInterval(this.cleanupInterval);
|
|
798
|
+
if (this.flushHandler) {
|
|
799
|
+
process.removeListener("SIGTERM", this.flushHandler);
|
|
800
|
+
process.removeListener("SIGINT", this.flushHandler);
|
|
801
|
+
process.removeListener("exit", this.flushHandler);
|
|
802
|
+
this.flushHandler = null;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
load() {
|
|
806
|
+
if (!fs2.existsSync(this.filePath)) return;
|
|
807
|
+
try {
|
|
808
|
+
const raw = JSON.parse(
|
|
809
|
+
fs2.readFileSync(this.filePath, "utf-8")
|
|
810
|
+
);
|
|
811
|
+
if (raw.version !== 1) {
|
|
812
|
+
log2.warn(
|
|
813
|
+
{ version: raw.version },
|
|
814
|
+
"Unknown session store version, skipping load"
|
|
815
|
+
);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
819
|
+
this.records.set(id, record);
|
|
820
|
+
}
|
|
821
|
+
log2.info({ count: this.records.size }, "Loaded session records");
|
|
822
|
+
} catch (err) {
|
|
823
|
+
log2.error({ err }, "Failed to load session store");
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
cleanup() {
|
|
827
|
+
const cutoff = Date.now() - this.ttlDays * 24 * 60 * 60 * 1e3;
|
|
828
|
+
let removed = 0;
|
|
829
|
+
for (const [id, record] of this.records) {
|
|
830
|
+
if (record.status === "active" || record.status === "initializing")
|
|
831
|
+
continue;
|
|
832
|
+
const lastActive = new Date(record.lastActiveAt).getTime();
|
|
833
|
+
if (lastActive < cutoff) {
|
|
834
|
+
this.records.delete(id);
|
|
835
|
+
removed++;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (removed > 0) {
|
|
839
|
+
log2.info({ removed }, "Cleaned up expired session records");
|
|
840
|
+
this.scheduleDiskWrite();
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
scheduleDiskWrite() {
|
|
844
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
845
|
+
this.debounceTimer = setTimeout(() => {
|
|
846
|
+
this.flushSync();
|
|
847
|
+
}, DEBOUNCE_MS);
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/tunnel/extract-file-info.ts
|
|
852
|
+
function extractFileInfo(name, kind, content) {
|
|
853
|
+
if (!content) return null;
|
|
854
|
+
if (kind && !["read", "edit", "write"].includes(kind)) return null;
|
|
855
|
+
const info = parseContent(content);
|
|
856
|
+
if (!info) return null;
|
|
857
|
+
if (!info.filePath) {
|
|
858
|
+
const pathMatch = name.match(/(?:Read|Edit|Write|View)\s+(.+)/i);
|
|
859
|
+
if (pathMatch) info.filePath = pathMatch[1].trim();
|
|
860
|
+
}
|
|
861
|
+
if (!info.filePath || !info.content) return null;
|
|
862
|
+
return info;
|
|
863
|
+
}
|
|
864
|
+
function parseContent(content) {
|
|
865
|
+
if (typeof content === "string") {
|
|
866
|
+
return { content };
|
|
867
|
+
}
|
|
868
|
+
if (Array.isArray(content)) {
|
|
869
|
+
for (const block of content) {
|
|
870
|
+
const result = parseContent(block);
|
|
871
|
+
if (result?.content || result?.filePath) return result;
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
if (typeof content === "object" && content !== null) {
|
|
876
|
+
const c = content;
|
|
877
|
+
if (c.type === "diff" && typeof c.path === "string") {
|
|
878
|
+
const newText = c.newText;
|
|
879
|
+
const oldText = c.oldText;
|
|
880
|
+
if (newText) {
|
|
881
|
+
return {
|
|
882
|
+
filePath: c.path,
|
|
883
|
+
content: newText,
|
|
884
|
+
oldContent: oldText ?? void 0
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (c.type === "content" && c.content) {
|
|
889
|
+
return parseContent(c.content);
|
|
890
|
+
}
|
|
891
|
+
if (c.type === "text" && typeof c.text === "string") {
|
|
892
|
+
return { content: c.text, filePath: c.filePath };
|
|
893
|
+
}
|
|
894
|
+
if (typeof c.text === "string") {
|
|
895
|
+
return { content: c.text, filePath: c.filePath };
|
|
896
|
+
}
|
|
897
|
+
if (typeof c.file_path === "string" || typeof c.filePath === "string" || typeof c.path === "string") {
|
|
898
|
+
const filePath = c.file_path || c.filePath || c.path;
|
|
899
|
+
const fileContent = c.content || c.text || c.output || c.newText;
|
|
900
|
+
if (typeof fileContent === "string") {
|
|
901
|
+
return {
|
|
902
|
+
filePath,
|
|
903
|
+
content: fileContent,
|
|
904
|
+
oldContent: c.old_content || c.oldText
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (c.input) {
|
|
909
|
+
const result = parseContent(c.input);
|
|
910
|
+
if (result) return result;
|
|
911
|
+
}
|
|
912
|
+
if (c.output) {
|
|
913
|
+
const result = parseContent(c.output);
|
|
914
|
+
if (result) return result;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/core/core.ts
|
|
921
|
+
var log3 = createChildLogger({ module: "core" });
|
|
556
922
|
var OpenACPCore = class {
|
|
557
923
|
configManager;
|
|
558
924
|
agentManager;
|
|
559
925
|
sessionManager;
|
|
560
926
|
notificationManager;
|
|
561
927
|
adapters = /* @__PURE__ */ new Map();
|
|
928
|
+
tunnelService;
|
|
929
|
+
sessionStore = null;
|
|
930
|
+
resumeLocks = /* @__PURE__ */ new Map();
|
|
562
931
|
constructor(configManager) {
|
|
563
932
|
this.configManager = configManager;
|
|
564
933
|
const config = configManager.get();
|
|
565
934
|
this.agentManager = new AgentManager(config);
|
|
566
|
-
|
|
935
|
+
const storePath = path3.join(os.homedir(), ".openacp", "sessions.json");
|
|
936
|
+
this.sessionStore = new JsonFileSessionStore(
|
|
937
|
+
storePath,
|
|
938
|
+
config.sessionStore.ttlDays
|
|
939
|
+
);
|
|
940
|
+
this.sessionManager = new SessionManager(this.sessionStore);
|
|
567
941
|
this.notificationManager = new NotificationManager(this.adapters);
|
|
568
942
|
}
|
|
569
943
|
registerAdapter(name, adapter) {
|
|
@@ -591,16 +965,33 @@ var OpenACPCore = class {
|
|
|
591
965
|
// --- Message Routing ---
|
|
592
966
|
async handleMessage(message) {
|
|
593
967
|
const config = this.configManager.get();
|
|
594
|
-
|
|
968
|
+
log3.debug(
|
|
969
|
+
{
|
|
970
|
+
channelId: message.channelId,
|
|
971
|
+
threadId: message.threadId,
|
|
972
|
+
userId: message.userId
|
|
973
|
+
},
|
|
974
|
+
"Incoming message"
|
|
975
|
+
);
|
|
595
976
|
if (config.security.allowedUserIds.length > 0) {
|
|
596
977
|
if (!config.security.allowedUserIds.includes(message.userId)) {
|
|
597
|
-
|
|
978
|
+
log3.warn(
|
|
979
|
+
{ userId: message.userId },
|
|
980
|
+
"Rejected message from unauthorized user"
|
|
981
|
+
);
|
|
598
982
|
return;
|
|
599
983
|
}
|
|
600
984
|
}
|
|
601
985
|
const activeSessions = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
|
|
602
986
|
if (activeSessions.length >= config.security.maxConcurrentSessions) {
|
|
603
|
-
|
|
987
|
+
log3.warn(
|
|
988
|
+
{
|
|
989
|
+
userId: message.userId,
|
|
990
|
+
currentCount: activeSessions.length,
|
|
991
|
+
max: config.security.maxConcurrentSessions
|
|
992
|
+
},
|
|
993
|
+
"Session limit reached"
|
|
994
|
+
);
|
|
604
995
|
const adapter = this.adapters.get(message.channelId);
|
|
605
996
|
if (adapter) {
|
|
606
997
|
await adapter.sendMessage("system", {
|
|
@@ -610,14 +1001,21 @@ var OpenACPCore = class {
|
|
|
610
1001
|
}
|
|
611
1002
|
return;
|
|
612
1003
|
}
|
|
613
|
-
|
|
1004
|
+
let session = this.sessionManager.getSessionByThread(
|
|
1005
|
+
message.channelId,
|
|
1006
|
+
message.threadId
|
|
1007
|
+
);
|
|
1008
|
+
if (!session) {
|
|
1009
|
+
session = await this.lazyResume(message) ?? void 0;
|
|
1010
|
+
}
|
|
614
1011
|
if (!session) return;
|
|
1012
|
+
this.sessionManager.updateSessionActivity(session.id);
|
|
615
1013
|
await session.enqueuePrompt(message.text);
|
|
616
1014
|
}
|
|
617
1015
|
async handleNewSession(channelId, agentName, workspacePath) {
|
|
618
1016
|
const config = this.configManager.get();
|
|
619
1017
|
const resolvedAgent = agentName || config.defaultAgent;
|
|
620
|
-
|
|
1018
|
+
log3.info({ channelId, agentName: resolvedAgent }, "New session request");
|
|
621
1019
|
const resolvedWorkspace = this.configManager.resolveWorkspace(
|
|
622
1020
|
workspacePath || config.agents[resolvedAgent]?.workingDirectory
|
|
623
1021
|
);
|
|
@@ -634,7 +1032,10 @@ var OpenACPCore = class {
|
|
|
634
1032
|
return session;
|
|
635
1033
|
}
|
|
636
1034
|
async handleNewChat(channelId, currentThreadId) {
|
|
637
|
-
const currentSession = this.sessionManager.getSessionByThread(
|
|
1035
|
+
const currentSession = this.sessionManager.getSessionByThread(
|
|
1036
|
+
channelId,
|
|
1037
|
+
currentThreadId
|
|
1038
|
+
);
|
|
638
1039
|
if (!currentSession) return null;
|
|
639
1040
|
return this.handleNewSession(
|
|
640
1041
|
channelId,
|
|
@@ -642,25 +1043,148 @@ var OpenACPCore = class {
|
|
|
642
1043
|
currentSession.workingDirectory
|
|
643
1044
|
);
|
|
644
1045
|
}
|
|
1046
|
+
// --- Lazy Resume ---
|
|
1047
|
+
async lazyResume(message) {
|
|
1048
|
+
const store = this.sessionStore;
|
|
1049
|
+
if (!store) return null;
|
|
1050
|
+
const lockKey = `${message.channelId}:${message.threadId}`;
|
|
1051
|
+
const existing = this.resumeLocks.get(lockKey);
|
|
1052
|
+
if (existing) return existing;
|
|
1053
|
+
const record = store.findByPlatform(
|
|
1054
|
+
message.channelId,
|
|
1055
|
+
(p) => String(p.topicId) === message.threadId
|
|
1056
|
+
);
|
|
1057
|
+
if (!record) return null;
|
|
1058
|
+
if (record.status === "cancelled" || record.status === "error") return null;
|
|
1059
|
+
const resumePromise = (async () => {
|
|
1060
|
+
try {
|
|
1061
|
+
const agentInstance = await this.agentManager.resume(
|
|
1062
|
+
record.agentName,
|
|
1063
|
+
record.workingDir,
|
|
1064
|
+
record.agentSessionId
|
|
1065
|
+
);
|
|
1066
|
+
const session = new Session({
|
|
1067
|
+
id: record.sessionId,
|
|
1068
|
+
channelId: record.channelId,
|
|
1069
|
+
agentName: record.agentName,
|
|
1070
|
+
workingDirectory: record.workingDir,
|
|
1071
|
+
agentInstance
|
|
1072
|
+
});
|
|
1073
|
+
session.threadId = message.threadId;
|
|
1074
|
+
session.agentSessionId = agentInstance.sessionId;
|
|
1075
|
+
session.status = "active";
|
|
1076
|
+
session.name = record.name;
|
|
1077
|
+
this.sessionManager.registerSession(session);
|
|
1078
|
+
const adapter = this.adapters.get(message.channelId);
|
|
1079
|
+
if (adapter) {
|
|
1080
|
+
this.wireSessionEvents(session, adapter);
|
|
1081
|
+
}
|
|
1082
|
+
await store.save({
|
|
1083
|
+
...record,
|
|
1084
|
+
agentSessionId: agentInstance.sessionId,
|
|
1085
|
+
status: "active",
|
|
1086
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1087
|
+
});
|
|
1088
|
+
log3.info(
|
|
1089
|
+
{ sessionId: session.id, threadId: message.threadId },
|
|
1090
|
+
"Lazy resume successful"
|
|
1091
|
+
);
|
|
1092
|
+
return session;
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
log3.error({ err, record }, "Lazy resume failed");
|
|
1095
|
+
return null;
|
|
1096
|
+
} finally {
|
|
1097
|
+
this.resumeLocks.delete(lockKey);
|
|
1098
|
+
}
|
|
1099
|
+
})();
|
|
1100
|
+
this.resumeLocks.set(lockKey, resumePromise);
|
|
1101
|
+
return resumePromise;
|
|
1102
|
+
}
|
|
645
1103
|
// --- Event Wiring ---
|
|
646
|
-
toOutgoingMessage(event) {
|
|
1104
|
+
toOutgoingMessage(event, session) {
|
|
647
1105
|
switch (event.type) {
|
|
648
1106
|
case "text":
|
|
649
1107
|
return { type: "text", text: event.content };
|
|
650
1108
|
case "thought":
|
|
651
1109
|
return { type: "thought", text: event.content };
|
|
652
|
-
case "tool_call":
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1110
|
+
case "tool_call": {
|
|
1111
|
+
const metadata = {
|
|
1112
|
+
id: event.id,
|
|
1113
|
+
kind: event.kind,
|
|
1114
|
+
status: event.status,
|
|
1115
|
+
content: event.content,
|
|
1116
|
+
locations: event.locations
|
|
1117
|
+
};
|
|
1118
|
+
this.enrichWithViewerLinks(event, metadata, session);
|
|
1119
|
+
return { type: "tool_call", text: event.name, metadata };
|
|
1120
|
+
}
|
|
1121
|
+
case "tool_update": {
|
|
1122
|
+
const metadata = {
|
|
1123
|
+
id: event.id,
|
|
1124
|
+
status: event.status,
|
|
1125
|
+
content: event.content
|
|
1126
|
+
};
|
|
1127
|
+
this.enrichWithViewerLinks(event, metadata, session);
|
|
1128
|
+
return { type: "tool_update", text: "", metadata };
|
|
1129
|
+
}
|
|
656
1130
|
case "plan":
|
|
657
1131
|
return { type: "plan", text: "", metadata: { entries: event.entries } };
|
|
658
1132
|
case "usage":
|
|
659
|
-
return {
|
|
1133
|
+
return {
|
|
1134
|
+
type: "usage",
|
|
1135
|
+
text: "",
|
|
1136
|
+
metadata: {
|
|
1137
|
+
tokensUsed: event.tokensUsed,
|
|
1138
|
+
contextSize: event.contextSize,
|
|
1139
|
+
cost: event.cost
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
660
1142
|
default:
|
|
661
1143
|
return { type: "text", text: "" };
|
|
662
1144
|
}
|
|
663
1145
|
}
|
|
1146
|
+
enrichWithViewerLinks(event, metadata, session) {
|
|
1147
|
+
if (!this.tunnelService || !session) return;
|
|
1148
|
+
const name = "name" in event ? event.name || "" : "";
|
|
1149
|
+
const kind = "kind" in event ? event.kind : void 0;
|
|
1150
|
+
log3.debug(
|
|
1151
|
+
{ name, kind, status: event.status, hasContent: !!event.content },
|
|
1152
|
+
"enrichWithViewerLinks: inspecting event"
|
|
1153
|
+
);
|
|
1154
|
+
const fileInfo = extractFileInfo(name, kind, event.content);
|
|
1155
|
+
if (!fileInfo) return;
|
|
1156
|
+
log3.info(
|
|
1157
|
+
{
|
|
1158
|
+
name,
|
|
1159
|
+
kind,
|
|
1160
|
+
filePath: fileInfo.filePath,
|
|
1161
|
+
hasOldContent: !!fileInfo.oldContent
|
|
1162
|
+
},
|
|
1163
|
+
"enrichWithViewerLinks: extracted file info"
|
|
1164
|
+
);
|
|
1165
|
+
const store = this.tunnelService.getStore();
|
|
1166
|
+
const viewerLinks = {};
|
|
1167
|
+
if (fileInfo.oldContent) {
|
|
1168
|
+
const id2 = store.storeDiff(
|
|
1169
|
+
session.id,
|
|
1170
|
+
fileInfo.filePath,
|
|
1171
|
+
fileInfo.oldContent,
|
|
1172
|
+
fileInfo.content,
|
|
1173
|
+
session.workingDirectory
|
|
1174
|
+
);
|
|
1175
|
+
if (id2) viewerLinks.diff = this.tunnelService.diffUrl(id2);
|
|
1176
|
+
}
|
|
1177
|
+
const id = store.storeFile(
|
|
1178
|
+
session.id,
|
|
1179
|
+
fileInfo.filePath,
|
|
1180
|
+
fileInfo.content,
|
|
1181
|
+
session.workingDirectory
|
|
1182
|
+
);
|
|
1183
|
+
if (id) viewerLinks.file = this.tunnelService.fileUrl(id);
|
|
1184
|
+
if (Object.keys(viewerLinks).length > 0) {
|
|
1185
|
+
metadata.viewerLinks = viewerLinks;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
664
1188
|
// Public — adapters call this for assistant session wiring
|
|
665
1189
|
wireSessionEvents(session, adapter) {
|
|
666
1190
|
session.adapter = adapter;
|
|
@@ -672,12 +1196,19 @@ var OpenACPCore = class {
|
|
|
672
1196
|
case "tool_update":
|
|
673
1197
|
case "plan":
|
|
674
1198
|
case "usage":
|
|
675
|
-
adapter.sendMessage(
|
|
1199
|
+
adapter.sendMessage(
|
|
1200
|
+
session.id,
|
|
1201
|
+
this.toOutgoingMessage(event, session)
|
|
1202
|
+
);
|
|
676
1203
|
break;
|
|
677
1204
|
case "session_end":
|
|
678
1205
|
session.status = "finished";
|
|
1206
|
+
this.sessionManager.updateSessionStatus(session.id, "finished");
|
|
679
1207
|
adapter.cleanupSkillCommands(session.id);
|
|
680
|
-
adapter.sendMessage(session.id, {
|
|
1208
|
+
adapter.sendMessage(session.id, {
|
|
1209
|
+
type: "session_end",
|
|
1210
|
+
text: `Done (${event.reason})`
|
|
1211
|
+
});
|
|
681
1212
|
this.notificationManager.notify(session.channelId, {
|
|
682
1213
|
sessionId: session.id,
|
|
683
1214
|
sessionName: session.name,
|
|
@@ -686,8 +1217,12 @@ var OpenACPCore = class {
|
|
|
686
1217
|
});
|
|
687
1218
|
break;
|
|
688
1219
|
case "error":
|
|
1220
|
+
this.sessionManager.updateSessionStatus(session.id, "error");
|
|
689
1221
|
adapter.cleanupSkillCommands(session.id);
|
|
690
|
-
adapter.sendMessage(session.id, {
|
|
1222
|
+
adapter.sendMessage(session.id, {
|
|
1223
|
+
type: "error",
|
|
1224
|
+
text: event.message
|
|
1225
|
+
});
|
|
691
1226
|
this.notificationManager.notify(session.channelId, {
|
|
692
1227
|
sessionId: session.id,
|
|
693
1228
|
sessionName: session.name,
|
|
@@ -696,7 +1231,7 @@ var OpenACPCore = class {
|
|
|
696
1231
|
});
|
|
697
1232
|
break;
|
|
698
1233
|
case "commands_update":
|
|
699
|
-
|
|
1234
|
+
log3.debug({ commands: event.commands }, "Commands available");
|
|
700
1235
|
adapter.sendSkillCommands(session.id, event.commands);
|
|
701
1236
|
break;
|
|
702
1237
|
}
|
|
@@ -808,6 +1343,7 @@ function formatToolCall(tool) {
|
|
|
808
1343
|
text += `
|
|
809
1344
|
<pre>${escapeHtml(truncateContent(details))}</pre>`;
|
|
810
1345
|
}
|
|
1346
|
+
text += formatViewerLinks(tool.viewerLinks);
|
|
811
1347
|
return text;
|
|
812
1348
|
}
|
|
813
1349
|
function formatToolUpdate(update) {
|
|
@@ -820,6 +1356,16 @@ function formatToolUpdate(update) {
|
|
|
820
1356
|
text += `
|
|
821
1357
|
<pre>${escapeHtml(truncateContent(details))}</pre>`;
|
|
822
1358
|
}
|
|
1359
|
+
text += formatViewerLinks(update.viewerLinks);
|
|
1360
|
+
return text;
|
|
1361
|
+
}
|
|
1362
|
+
function formatViewerLinks(links) {
|
|
1363
|
+
if (!links) return "";
|
|
1364
|
+
let text = "";
|
|
1365
|
+
if (links.file) text += `
|
|
1366
|
+
\u{1F4C4} <a href="${escapeHtml(links.file)}">View file</a>`;
|
|
1367
|
+
if (links.diff) text += `
|
|
1368
|
+
\u{1F4DD} <a href="${escapeHtml(links.diff)}">View diff</a>`;
|
|
823
1369
|
return text;
|
|
824
1370
|
}
|
|
825
1371
|
function formatPlan(plan) {
|
|
@@ -999,7 +1545,7 @@ function buildDeepLink(chatId, messageId) {
|
|
|
999
1545
|
// src/adapters/telegram/commands.ts
|
|
1000
1546
|
import { InlineKeyboard } from "grammy";
|
|
1001
1547
|
import { nanoid as nanoid2 } from "nanoid";
|
|
1002
|
-
var
|
|
1548
|
+
var log5 = createChildLogger({ module: "telegram-commands" });
|
|
1003
1549
|
function setupCommands(bot, core, chatId) {
|
|
1004
1550
|
bot.command("new", (ctx) => handleNew(ctx, core, chatId));
|
|
1005
1551
|
bot.command("new_chat", (ctx) => handleNewChat(ctx, core, chatId));
|
|
@@ -1054,7 +1600,7 @@ async function handleNew(ctx, core, chatId) {
|
|
|
1054
1600
|
const args = matchStr.split(" ").filter(Boolean);
|
|
1055
1601
|
const agentName = args[0];
|
|
1056
1602
|
const workspace = args[1];
|
|
1057
|
-
|
|
1603
|
+
log5.info({ userId: ctx.from?.id, agentName }, "New session command");
|
|
1058
1604
|
let threadId;
|
|
1059
1605
|
try {
|
|
1060
1606
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -1069,6 +1615,9 @@ async function handleNew(ctx, core, chatId) {
|
|
|
1069
1615
|
workspace
|
|
1070
1616
|
);
|
|
1071
1617
|
session.threadId = String(threadId);
|
|
1618
|
+
await core.sessionManager.updateSessionPlatform(session.id, {
|
|
1619
|
+
topicId: threadId
|
|
1620
|
+
});
|
|
1072
1621
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
1073
1622
|
try {
|
|
1074
1623
|
await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
|
|
@@ -1084,7 +1633,7 @@ async function handleNew(ctx, core, chatId) {
|
|
|
1084
1633
|
parse_mode: "HTML"
|
|
1085
1634
|
}
|
|
1086
1635
|
);
|
|
1087
|
-
session.warmup().catch((err) =>
|
|
1636
|
+
session.warmup().catch((err) => log5.error({ err }, "Warm-up error"));
|
|
1088
1637
|
} catch (err) {
|
|
1089
1638
|
if (threadId) {
|
|
1090
1639
|
try {
|
|
@@ -1124,6 +1673,9 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
1124
1673
|
parse_mode: "HTML"
|
|
1125
1674
|
});
|
|
1126
1675
|
session.threadId = String(newThreadId);
|
|
1676
|
+
await core.sessionManager.updateSessionPlatform(session.id, {
|
|
1677
|
+
topicId: newThreadId
|
|
1678
|
+
});
|
|
1127
1679
|
await ctx.api.sendMessage(
|
|
1128
1680
|
chatId,
|
|
1129
1681
|
`\u2705 New chat (same agent & workspace)
|
|
@@ -1134,7 +1686,7 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
1134
1686
|
parse_mode: "HTML"
|
|
1135
1687
|
}
|
|
1136
1688
|
);
|
|
1137
|
-
session.warmup().catch((err) =>
|
|
1689
|
+
session.warmup().catch((err) => log5.error({ err }, "Warm-up error"));
|
|
1138
1690
|
} catch (err) {
|
|
1139
1691
|
const message = err instanceof Error ? err.message : String(err);
|
|
1140
1692
|
await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
|
|
@@ -1148,7 +1700,7 @@ async function handleCancel(ctx, core) {
|
|
|
1148
1700
|
String(threadId)
|
|
1149
1701
|
);
|
|
1150
1702
|
if (session) {
|
|
1151
|
-
|
|
1703
|
+
log5.info({ sessionId: session.id }, "Cancel session command");
|
|
1152
1704
|
await session.cancel();
|
|
1153
1705
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
1154
1706
|
}
|
|
@@ -1269,7 +1821,7 @@ var STATIC_COMMANDS = [
|
|
|
1269
1821
|
// src/adapters/telegram/permissions.ts
|
|
1270
1822
|
import { InlineKeyboard as InlineKeyboard2 } from "grammy";
|
|
1271
1823
|
import { nanoid as nanoid3 } from "nanoid";
|
|
1272
|
-
var
|
|
1824
|
+
var log6 = createChildLogger({ module: "telegram-permissions" });
|
|
1273
1825
|
var PermissionHandler = class {
|
|
1274
1826
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
1275
1827
|
this.bot = bot;
|
|
@@ -1329,7 +1881,7 @@ ${escapeHtml(request.description)}`,
|
|
|
1329
1881
|
}
|
|
1330
1882
|
const session = this.getSession(pending.sessionId);
|
|
1331
1883
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
1332
|
-
|
|
1884
|
+
log6.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
1333
1885
|
if (session?.pendingPermission?.requestId === pending.requestId) {
|
|
1334
1886
|
session.pendingPermission.resolve(optionId);
|
|
1335
1887
|
session.pendingPermission = void 0;
|
|
@@ -1396,7 +1948,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
|
|
|
1396
1948
|
}
|
|
1397
1949
|
|
|
1398
1950
|
// src/adapters/telegram/adapter.ts
|
|
1399
|
-
var
|
|
1951
|
+
var log7 = createChildLogger({ module: "telegram" });
|
|
1400
1952
|
var TelegramAdapter = class extends ChannelAdapter {
|
|
1401
1953
|
bot;
|
|
1402
1954
|
telegramConfig;
|
|
@@ -1414,10 +1966,10 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
1414
1966
|
this.telegramConfig = config;
|
|
1415
1967
|
}
|
|
1416
1968
|
async start() {
|
|
1417
|
-
this.bot = new Bot(this.telegramConfig.botToken);
|
|
1969
|
+
this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch } });
|
|
1418
1970
|
this.bot.catch((err) => {
|
|
1419
1971
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
1420
|
-
|
|
1972
|
+
log7.error({ err: rootCause }, "Telegram bot error");
|
|
1421
1973
|
});
|
|
1422
1974
|
this.bot.api.config.use((prev, method, payload, signal) => {
|
|
1423
1975
|
if (method === "getUpdates") {
|
|
@@ -1470,7 +2022,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
1470
2022
|
this.setupRoutes();
|
|
1471
2023
|
this.bot.start({
|
|
1472
2024
|
allowed_updates: ["message", "callback_query"],
|
|
1473
|
-
onStart: () =>
|
|
2025
|
+
onStart: () => log7.info(
|
|
1474
2026
|
{ chatId: this.telegramConfig.chatId },
|
|
1475
2027
|
"Telegram bot started"
|
|
1476
2028
|
)
|
|
@@ -1482,7 +2034,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
1482
2034
|
this.assistantTopicId
|
|
1483
2035
|
);
|
|
1484
2036
|
} catch (err) {
|
|
1485
|
-
|
|
2037
|
+
log7.error({ err }, "Failed to spawn assistant");
|
|
1486
2038
|
}
|
|
1487
2039
|
try {
|
|
1488
2040
|
const config = this.core.configManager.get();
|
|
@@ -1503,7 +2055,7 @@ Workspace: <code>${workspace}</code>
|
|
|
1503
2055
|
reply_markup: buildMenuKeyboard()
|
|
1504
2056
|
});
|
|
1505
2057
|
} catch (err) {
|
|
1506
|
-
|
|
2058
|
+
log7.warn({ err }, "Failed to send welcome message");
|
|
1507
2059
|
}
|
|
1508
2060
|
}
|
|
1509
2061
|
async stop() {
|
|
@@ -1511,7 +2063,7 @@ Workspace: <code>${workspace}</code>
|
|
|
1511
2063
|
await this.assistantSession.destroy();
|
|
1512
2064
|
}
|
|
1513
2065
|
await this.bot.stop();
|
|
1514
|
-
|
|
2066
|
+
log7.info("Telegram bot stopped");
|
|
1515
2067
|
}
|
|
1516
2068
|
setupRoutes() {
|
|
1517
2069
|
this.bot.on("message:text", async (ctx) => {
|
|
@@ -1529,7 +2081,7 @@ Workspace: <code>${workspace}</code>
|
|
|
1529
2081
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
1530
2082
|
});
|
|
1531
2083
|
handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
|
|
1532
|
-
(err) =>
|
|
2084
|
+
(err) => log7.error({ err }, "Assistant error")
|
|
1533
2085
|
);
|
|
1534
2086
|
return;
|
|
1535
2087
|
}
|
|
@@ -1540,7 +2092,7 @@ Workspace: <code>${workspace}</code>
|
|
|
1540
2092
|
threadId: String(threadId),
|
|
1541
2093
|
userId: String(ctx.from.id),
|
|
1542
2094
|
text: ctx.message.text
|
|
1543
|
-
}).catch((err) =>
|
|
2095
|
+
}).catch((err) => log7.error({ err }, "handleMessage error"));
|
|
1544
2096
|
});
|
|
1545
2097
|
}
|
|
1546
2098
|
// --- ChannelAdapter implementations ---
|
|
@@ -1585,7 +2137,8 @@ Workspace: <code>${workspace}</code>
|
|
|
1585
2137
|
this.toolCallMessages.get(sessionId).set(meta.id, {
|
|
1586
2138
|
msgId: msg.message_id,
|
|
1587
2139
|
name: meta.name,
|
|
1588
|
-
kind: meta.kind
|
|
2140
|
+
kind: meta.kind,
|
|
2141
|
+
viewerLinks: meta.viewerLinks
|
|
1589
2142
|
});
|
|
1590
2143
|
break;
|
|
1591
2144
|
}
|
|
@@ -1593,10 +2146,13 @@ Workspace: <code>${workspace}</code>
|
|
|
1593
2146
|
const meta = content.metadata;
|
|
1594
2147
|
const toolState = this.toolCallMessages.get(sessionId)?.get(meta.id);
|
|
1595
2148
|
if (toolState) {
|
|
2149
|
+
const viewerLinks = meta.viewerLinks || toolState.viewerLinks;
|
|
2150
|
+
if (meta.viewerLinks) toolState.viewerLinks = meta.viewerLinks;
|
|
1596
2151
|
const merged = {
|
|
1597
2152
|
...meta,
|
|
1598
2153
|
name: meta.name || toolState.name,
|
|
1599
|
-
kind: meta.kind || toolState.kind
|
|
2154
|
+
kind: meta.kind || toolState.kind,
|
|
2155
|
+
viewerLinks
|
|
1600
2156
|
};
|
|
1601
2157
|
try {
|
|
1602
2158
|
await this.bot.api.editMessageText(
|
|
@@ -1614,7 +2170,9 @@ Workspace: <code>${workspace}</code>
|
|
|
1614
2170
|
await this.finalizeDraft(sessionId);
|
|
1615
2171
|
await this.bot.api.sendMessage(
|
|
1616
2172
|
this.telegramConfig.chatId,
|
|
1617
|
-
formatPlan(
|
|
2173
|
+
formatPlan(
|
|
2174
|
+
content.metadata
|
|
2175
|
+
),
|
|
1618
2176
|
{
|
|
1619
2177
|
message_thread_id: threadId,
|
|
1620
2178
|
parse_mode: "HTML",
|
|
@@ -1626,7 +2184,9 @@ Workspace: <code>${workspace}</code>
|
|
|
1626
2184
|
case "usage": {
|
|
1627
2185
|
await this.bot.api.sendMessage(
|
|
1628
2186
|
this.telegramConfig.chatId,
|
|
1629
|
-
formatUsage(
|
|
2187
|
+
formatUsage(
|
|
2188
|
+
content.metadata
|
|
2189
|
+
),
|
|
1630
2190
|
{
|
|
1631
2191
|
message_thread_id: threadId,
|
|
1632
2192
|
parse_mode: "HTML",
|
|
@@ -1667,7 +2227,7 @@ Workspace: <code>${workspace}</code>
|
|
|
1667
2227
|
}
|
|
1668
2228
|
}
|
|
1669
2229
|
async sendPermissionRequest(sessionId, request) {
|
|
1670
|
-
|
|
2230
|
+
log7.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
1671
2231
|
const session = this.core.sessionManager.getSession(
|
|
1672
2232
|
sessionId
|
|
1673
2233
|
);
|
|
@@ -1675,7 +2235,7 @@ Workspace: <code>${workspace}</code>
|
|
|
1675
2235
|
await this.permissionHandler.sendPermissionRequest(session, request);
|
|
1676
2236
|
}
|
|
1677
2237
|
async sendNotification(notification) {
|
|
1678
|
-
|
|
2238
|
+
log7.info(
|
|
1679
2239
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
1680
2240
|
"Notification sent"
|
|
1681
2241
|
);
|
|
@@ -1701,7 +2261,7 @@ Workspace: <code>${workspace}</code>
|
|
|
1701
2261
|
});
|
|
1702
2262
|
}
|
|
1703
2263
|
async createSessionThread(sessionId, name) {
|
|
1704
|
-
|
|
2264
|
+
log7.info({ sessionId, name }, "Session topic created");
|
|
1705
2265
|
return String(
|
|
1706
2266
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
1707
2267
|
);
|
|
@@ -1719,7 +2279,9 @@ Workspace: <code>${workspace}</code>
|
|
|
1719
2279
|
);
|
|
1720
2280
|
}
|
|
1721
2281
|
async sendSkillCommands(sessionId, commands) {
|
|
1722
|
-
const session = this.core.sessionManager.getSession(
|
|
2282
|
+
const session = this.core.sessionManager.getSession(
|
|
2283
|
+
sessionId
|
|
2284
|
+
);
|
|
1723
2285
|
if (!session) return;
|
|
1724
2286
|
const threadId = Number(session.threadId);
|
|
1725
2287
|
if (!threadId) return;
|
|
@@ -1755,11 +2317,15 @@ Workspace: <code>${workspace}</code>
|
|
|
1755
2317
|
}
|
|
1756
2318
|
);
|
|
1757
2319
|
this.skillMessages.set(sessionId, msg.message_id);
|
|
1758
|
-
await this.bot.api.pinChatMessage(
|
|
1759
|
-
|
|
1760
|
-
|
|
2320
|
+
await this.bot.api.pinChatMessage(
|
|
2321
|
+
this.telegramConfig.chatId,
|
|
2322
|
+
msg.message_id,
|
|
2323
|
+
{
|
|
2324
|
+
disable_notification: true
|
|
2325
|
+
}
|
|
2326
|
+
);
|
|
1761
2327
|
} catch (err) {
|
|
1762
|
-
|
|
2328
|
+
log7.error({ err, sessionId }, "Failed to send skill commands");
|
|
1763
2329
|
}
|
|
1764
2330
|
await this.updateCommandAutocomplete(session.agentName, commands);
|
|
1765
2331
|
}
|
|
@@ -1790,9 +2356,15 @@ Workspace: <code>${workspace}</code>
|
|
|
1790
2356
|
await this.bot.api.setMyCommands(all, {
|
|
1791
2357
|
scope: { type: "chat", chat_id: this.telegramConfig.chatId }
|
|
1792
2358
|
});
|
|
1793
|
-
|
|
2359
|
+
log7.info(
|
|
2360
|
+
{ count: all.length, skills: validSkills.length },
|
|
2361
|
+
"Updated command autocomplete"
|
|
2362
|
+
);
|
|
1794
2363
|
} catch (err) {
|
|
1795
|
-
|
|
2364
|
+
log7.error(
|
|
2365
|
+
{ err, commands: all },
|
|
2366
|
+
"Failed to update command autocomplete"
|
|
2367
|
+
);
|
|
1796
2368
|
}
|
|
1797
2369
|
}
|
|
1798
2370
|
async finalizeDraft(sessionId) {
|
|
@@ -1817,4 +2389,4 @@ export {
|
|
|
1817
2389
|
ChannelAdapter,
|
|
1818
2390
|
TelegramAdapter
|
|
1819
2391
|
};
|
|
1820
|
-
//# sourceMappingURL=chunk-
|
|
2392
|
+
//# sourceMappingURL=chunk-HTXK4NLG.js.map
|