nextclaw-core 0.2.8 → 0.4.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/dist/index.d.ts +603 -109
- package/dist/index.js +1394 -125
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -93,9 +93,11 @@ var MemoryStore = class {
|
|
|
93
93
|
this.workspace = workspace;
|
|
94
94
|
this.memoryDir = ensureDir(join(workspace, "memory"));
|
|
95
95
|
this.memoryFile = join(this.memoryDir, "MEMORY.md");
|
|
96
|
+
this.workspaceMemoryFile = join(workspace, "MEMORY.md");
|
|
96
97
|
}
|
|
97
98
|
memoryDir;
|
|
98
99
|
memoryFile;
|
|
100
|
+
workspaceMemoryFile;
|
|
99
101
|
getTodayFile() {
|
|
100
102
|
return join(this.memoryDir, `${todayDate()}.md`);
|
|
101
103
|
}
|
|
@@ -127,6 +129,12 @@ ${content}`;
|
|
|
127
129
|
}
|
|
128
130
|
return "";
|
|
129
131
|
}
|
|
132
|
+
readWorkspaceMemory() {
|
|
133
|
+
if (existsSync2(this.workspaceMemoryFile)) {
|
|
134
|
+
return readFileSync(this.workspaceMemoryFile, "utf-8");
|
|
135
|
+
}
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
130
138
|
writeLongTerm(content) {
|
|
131
139
|
writeFileSync(this.memoryFile, content, "utf-8");
|
|
132
140
|
}
|
|
@@ -152,6 +160,11 @@ ${content}`;
|
|
|
152
160
|
}
|
|
153
161
|
getMemoryContext() {
|
|
154
162
|
const parts = [];
|
|
163
|
+
const workspaceMemory = this.readWorkspaceMemory();
|
|
164
|
+
if (workspaceMemory) {
|
|
165
|
+
parts.push(`## Workspace Memory
|
|
166
|
+
${workspaceMemory}`);
|
|
167
|
+
}
|
|
155
168
|
const longTerm = this.readLongTerm();
|
|
156
169
|
if (longTerm) {
|
|
157
170
|
parts.push(`## Long-term Memory
|
|
@@ -372,27 +385,50 @@ ${this.stripFrontmatter(content)}`);
|
|
|
372
385
|
};
|
|
373
386
|
|
|
374
387
|
// src/agent/context.ts
|
|
375
|
-
var
|
|
388
|
+
var DEFAULT_CONTEXT_CONFIG = {
|
|
389
|
+
bootstrap: {
|
|
390
|
+
files: ["AGENTS.md", "SOUL.md", "USER.md", "IDENTITY.md", "TOOLS.md", "BOOT.md", "BOOTSTRAP.md", "HEARTBEAT.md"],
|
|
391
|
+
minimalFiles: ["AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"],
|
|
392
|
+
heartbeatFiles: ["HEARTBEAT.md"],
|
|
393
|
+
perFileChars: 4e3,
|
|
394
|
+
totalChars: 12e3
|
|
395
|
+
},
|
|
396
|
+
memory: {
|
|
397
|
+
enabled: true,
|
|
398
|
+
maxChars: 8e3
|
|
399
|
+
}
|
|
400
|
+
};
|
|
376
401
|
var ContextBuilder = class {
|
|
377
|
-
constructor(workspace) {
|
|
402
|
+
constructor(workspace, contextConfig) {
|
|
378
403
|
this.workspace = workspace;
|
|
379
404
|
this.memory = new MemoryStore(workspace);
|
|
380
405
|
this.skills = new SkillsLoader(workspace, join3(fileURLToPath2(new URL("..", import.meta.url)), "skills"));
|
|
406
|
+
this.contextConfig = {
|
|
407
|
+
bootstrap: {
|
|
408
|
+
...DEFAULT_CONTEXT_CONFIG.bootstrap,
|
|
409
|
+
...contextConfig?.bootstrap ?? {}
|
|
410
|
+
},
|
|
411
|
+
memory: {
|
|
412
|
+
...DEFAULT_CONTEXT_CONFIG.memory,
|
|
413
|
+
...contextConfig?.memory ?? {}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
381
416
|
}
|
|
382
417
|
memory;
|
|
383
418
|
skills;
|
|
384
|
-
|
|
419
|
+
contextConfig;
|
|
420
|
+
buildSystemPrompt(skillNames, sessionKey) {
|
|
385
421
|
const parts = [];
|
|
386
422
|
parts.push(this.getIdentity());
|
|
387
|
-
const bootstrap = this.loadBootstrapFiles();
|
|
423
|
+
const bootstrap = this.loadBootstrapFiles(sessionKey);
|
|
388
424
|
if (bootstrap) {
|
|
389
|
-
parts.push(
|
|
425
|
+
parts.push(`# Workspace Context
|
|
426
|
+
|
|
427
|
+
${bootstrap}`);
|
|
390
428
|
}
|
|
391
|
-
const memory = this.
|
|
429
|
+
const memory = this.buildMemorySection();
|
|
392
430
|
if (memory) {
|
|
393
|
-
parts.push(
|
|
394
|
-
|
|
395
|
-
${memory}`);
|
|
431
|
+
parts.push(memory);
|
|
396
432
|
}
|
|
397
433
|
const alwaysSkills = this.skills.getAlwaysSkills();
|
|
398
434
|
if (alwaysSkills.length) {
|
|
@@ -424,13 +460,17 @@ ${skillsSummary}`);
|
|
|
424
460
|
}
|
|
425
461
|
buildMessages(params) {
|
|
426
462
|
const messages = [];
|
|
427
|
-
let systemPrompt = this.buildSystemPrompt(params.skillNames);
|
|
463
|
+
let systemPrompt = this.buildSystemPrompt(params.skillNames, params.sessionKey);
|
|
428
464
|
if (params.channel && params.chatId) {
|
|
429
465
|
systemPrompt += `
|
|
430
466
|
|
|
431
467
|
## Current Session
|
|
432
468
|
Channel: ${params.channel}
|
|
433
469
|
Chat ID: ${params.chatId}`;
|
|
470
|
+
}
|
|
471
|
+
if (params.sessionKey) {
|
|
472
|
+
systemPrompt += `
|
|
473
|
+
Session: ${params.sessionKey}`;
|
|
434
474
|
}
|
|
435
475
|
messages.push({ role: "system", content: systemPrompt });
|
|
436
476
|
messages.push(...params.history);
|
|
@@ -462,12 +502,78 @@ Chat ID: ${params.chatId}`;
|
|
|
462
502
|
const now = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
463
503
|
return `# ${APP_NAME} \u{1F916}
|
|
464
504
|
|
|
465
|
-
You are
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
505
|
+
You are a personal assistant running inside ${APP_NAME}.
|
|
506
|
+
|
|
507
|
+
## Instruction Priority
|
|
508
|
+
1) System message (this prompt)
|
|
509
|
+
2) AGENTS.md \u2014 operational rules
|
|
510
|
+
3) SOUL.md \u2014 personality and tone
|
|
511
|
+
4) IDENTITY.md \u2014 product identity
|
|
512
|
+
5) USER.md \u2014 user preferences and context
|
|
513
|
+
6) TOOLS.md \u2014 tool usage guidance
|
|
514
|
+
7) BOOT.md / BOOTSTRAP.md \u2014 project context
|
|
515
|
+
8) HEARTBEAT.md \u2014 recurring tasks
|
|
516
|
+
|
|
517
|
+
If instructions conflict, follow the highest priority source.
|
|
518
|
+
|
|
519
|
+
## Tooling
|
|
520
|
+
Tool names are case-sensitive. Use only the tools listed here:
|
|
521
|
+
- read_file, write_file, edit_file, list_dir
|
|
522
|
+
- exec (shell commands)
|
|
523
|
+
- web_search, web_fetch
|
|
524
|
+
- message (action=send)
|
|
525
|
+
- sessions_list, sessions_history, sessions_send
|
|
526
|
+
- spawn (create subagent), subagents (list/steer/kill)
|
|
527
|
+
- memory_search, memory_get
|
|
528
|
+
- cron
|
|
529
|
+
- gateway
|
|
530
|
+
|
|
531
|
+
TOOLS.md does not change tool availability; it is guidance only.
|
|
532
|
+
Do not use exec/curl for provider messaging; use message/sessions_send instead.
|
|
533
|
+
|
|
534
|
+
## Tool Call Style
|
|
535
|
+
- Default: do not narrate routine, low-risk tool calls.
|
|
536
|
+
- Narrate only when it helps (multi-step work, complex problems, sensitive actions, or if the user asks).
|
|
537
|
+
- Keep narration brief and value-dense.
|
|
538
|
+
|
|
539
|
+
## Messaging
|
|
540
|
+
- Normal replies go to the current session automatically.
|
|
541
|
+
- Cross-session messaging: use sessions_send(sessionKey, message).
|
|
542
|
+
- Proactive channel send: use message with channel/chatId.
|
|
543
|
+
- Sub-agent orchestration: use subagents(action=list|steer|kill) and spawn.
|
|
544
|
+
- Do not poll subagents list / sessions_list in a loop; only check on-demand.
|
|
545
|
+
- If you use message (action=send) to deliver your user-visible reply, respond with ONLY: NO_REPLY (avoid duplicate replies).
|
|
546
|
+
- If a [System Message] reports completed cron/subagent work and asks for a user update, rewrite it in your normal assistant voice and send that update (do not forward raw system text or default to NO_REPLY).
|
|
547
|
+
|
|
548
|
+
## Reply Tags
|
|
549
|
+
- [[reply_to_current]] replies to the triggering message.
|
|
550
|
+
- [[reply_to:<id>]] replies to a specific message id.
|
|
551
|
+
- Whitespace inside the tag is allowed (e.g. [[ reply_to_current ]] / [[ reply_to: 123 ]]).
|
|
552
|
+
- Tags are stripped before sending.
|
|
553
|
+
|
|
554
|
+
## Memory Recall
|
|
555
|
+
Before answering anything about prior work, decisions, dates, people, preferences, or todos:
|
|
556
|
+
1) Run memory_search on MEMORY.md + memory/*.md.
|
|
557
|
+
2) Use memory_get to pull only the needed lines.
|
|
558
|
+
If low confidence after search, say you checked.
|
|
559
|
+
|
|
560
|
+
## Silent Replies
|
|
561
|
+
When you have nothing to say, respond with ONLY: NO_REPLY
|
|
562
|
+
- Never append it to a real response.
|
|
563
|
+
- Do not wrap it in quotes or markdown.
|
|
564
|
+
- Correct: NO_REPLY
|
|
565
|
+
- Wrong: "NO_REPLY" or "Here you go... NO_REPLY"
|
|
566
|
+
|
|
567
|
+
## ${APP_NAME} CLI Quick Reference
|
|
568
|
+
${APP_NAME} is controlled via subcommands. Do not invent commands.
|
|
569
|
+
- ${APP_NAME.toLowerCase()} start | stop | status
|
|
570
|
+
- ${APP_NAME.toLowerCase()} gateway status | start | stop | restart
|
|
571
|
+
If unsure, ask the user to run \`${APP_NAME.toLowerCase()} help\` and paste the output.
|
|
572
|
+
|
|
573
|
+
## ${APP_NAME} Self-Update
|
|
574
|
+
- Only run update.run when the user explicitly asks for an update.
|
|
575
|
+
- Actions: config.get, config.schema, config.apply (validate + write full config, then restart), config.patch (merge + restart), update.run (update deps or git, then restart).
|
|
576
|
+
- After restart, the last active session will be pinged automatically.
|
|
471
577
|
|
|
472
578
|
## Current Time
|
|
473
579
|
${now}
|
|
@@ -481,26 +587,83 @@ Your workspace is at: ${this.workspace}
|
|
|
481
587
|
- Daily notes: ${this.workspace}/memory/YYYY-MM-DD.md
|
|
482
588
|
- Custom skills: ${this.workspace}/skills/{skill-name}/SKILL.md
|
|
483
589
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
When remembering something, write to ${this.workspace}/memory/MEMORY.md`;
|
|
590
|
+
## Behavior
|
|
591
|
+
- For normal conversation, respond with plain text; do not call the message tool.
|
|
592
|
+
- Use the message tool only when you need to send a reply to a specific chat channel.
|
|
593
|
+
- When using tools, briefly explain what you're doing.
|
|
594
|
+
- When remembering something, write to ${this.workspace}/memory/MEMORY.md`;
|
|
490
595
|
}
|
|
491
|
-
loadBootstrapFiles() {
|
|
596
|
+
loadBootstrapFiles(sessionKey) {
|
|
492
597
|
const parts = [];
|
|
493
|
-
|
|
598
|
+
const { perFileChars, totalChars } = this.contextConfig.bootstrap;
|
|
599
|
+
const fileList = this.selectBootstrapFiles(sessionKey);
|
|
600
|
+
const totalLimit = totalChars > 0 ? totalChars : Number.POSITIVE_INFINITY;
|
|
601
|
+
let remaining = totalLimit;
|
|
602
|
+
for (const filename of fileList) {
|
|
494
603
|
const filePath = join3(this.workspace, filename);
|
|
495
604
|
if (existsSync4(filePath)) {
|
|
496
|
-
const
|
|
605
|
+
const raw = readFileSync3(filePath, "utf-8").trim();
|
|
606
|
+
if (!raw) {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const perFileLimit = perFileChars > 0 ? perFileChars : raw.length;
|
|
610
|
+
const allowed = Math.min(perFileLimit, remaining);
|
|
611
|
+
if (allowed <= 0) {
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
const content = this.truncateText(raw, allowed);
|
|
497
615
|
parts.push(`## ${filename}
|
|
498
616
|
|
|
499
617
|
${content}`);
|
|
618
|
+
remaining -= content.length;
|
|
619
|
+
if (remaining <= 0) {
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
500
622
|
}
|
|
501
623
|
}
|
|
502
624
|
return parts.join("\n\n");
|
|
503
625
|
}
|
|
626
|
+
selectBootstrapFiles(sessionKey) {
|
|
627
|
+
const { files, minimalFiles, heartbeatFiles } = this.contextConfig.bootstrap;
|
|
628
|
+
if (!sessionKey) {
|
|
629
|
+
return files;
|
|
630
|
+
}
|
|
631
|
+
if (sessionKey === "heartbeat") {
|
|
632
|
+
return dedupeStrings([...minimalFiles, ...heartbeatFiles]);
|
|
633
|
+
}
|
|
634
|
+
if (sessionKey.startsWith("cron:") || sessionKey.startsWith("subagent:")) {
|
|
635
|
+
return minimalFiles;
|
|
636
|
+
}
|
|
637
|
+
return files;
|
|
638
|
+
}
|
|
639
|
+
buildMemorySection() {
|
|
640
|
+
const memoryConfig = this.contextConfig.memory;
|
|
641
|
+
if (!memoryConfig.enabled) {
|
|
642
|
+
return "";
|
|
643
|
+
}
|
|
644
|
+
const memory = this.memory.getMemoryContext();
|
|
645
|
+
if (!memory) {
|
|
646
|
+
return "";
|
|
647
|
+
}
|
|
648
|
+
const truncated = this.truncateText(memory, memoryConfig.maxChars);
|
|
649
|
+
return `# Memory
|
|
650
|
+
|
|
651
|
+
${truncated}`;
|
|
652
|
+
}
|
|
653
|
+
truncateText(text, limit) {
|
|
654
|
+
if (limit <= 0 || text.length <= limit) {
|
|
655
|
+
return text;
|
|
656
|
+
}
|
|
657
|
+
const omitted = text.length - limit;
|
|
658
|
+
const suffix = `
|
|
659
|
+
|
|
660
|
+
...[truncated ${omitted} chars]`;
|
|
661
|
+
if (suffix.length >= limit) {
|
|
662
|
+
return text.slice(0, limit).trimEnd();
|
|
663
|
+
}
|
|
664
|
+
const head = text.slice(0, limit - suffix.length).trimEnd();
|
|
665
|
+
return `${head}${suffix}`;
|
|
666
|
+
}
|
|
504
667
|
buildUserContent(text, media) {
|
|
505
668
|
if (!media.length) {
|
|
506
669
|
return text;
|
|
@@ -532,6 +695,18 @@ function guessImageMime(path) {
|
|
|
532
695
|
if (ext === ".webp") return "image/webp";
|
|
533
696
|
return null;
|
|
534
697
|
}
|
|
698
|
+
function dedupeStrings(values) {
|
|
699
|
+
const seen = /* @__PURE__ */ new Set();
|
|
700
|
+
const result = [];
|
|
701
|
+
for (const value of values) {
|
|
702
|
+
if (seen.has(value)) {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
seen.add(value);
|
|
706
|
+
result.push(value);
|
|
707
|
+
}
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
535
710
|
|
|
536
711
|
// src/agent/tools/registry.ts
|
|
537
712
|
var ToolRegistry = class {
|
|
@@ -898,7 +1073,7 @@ function truncateOutput(result, maxLen = 1e4) {
|
|
|
898
1073
|
}
|
|
899
1074
|
|
|
900
1075
|
// src/agent/tools/web.ts
|
|
901
|
-
import { fetch } from "undici";
|
|
1076
|
+
import { fetch as fetch2 } from "undici";
|
|
902
1077
|
var WebSearchTool = class extends Tool {
|
|
903
1078
|
constructor(apiKey, maxResults = 5) {
|
|
904
1079
|
super();
|
|
@@ -930,7 +1105,7 @@ var WebSearchTool = class extends Tool {
|
|
|
930
1105
|
const url = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
931
1106
|
url.searchParams.set("q", query);
|
|
932
1107
|
url.searchParams.set("count", String(maxResults));
|
|
933
|
-
const response = await
|
|
1108
|
+
const response = await fetch2(url.toString(), {
|
|
934
1109
|
headers: {
|
|
935
1110
|
"Accept": "application/json",
|
|
936
1111
|
"X-Subscription-Token": this.apiKey
|
|
@@ -973,7 +1148,7 @@ var WebFetchTool = class extends Tool {
|
|
|
973
1148
|
}
|
|
974
1149
|
async execute(params) {
|
|
975
1150
|
const url = String(params.url ?? "");
|
|
976
|
-
const response = await
|
|
1151
|
+
const response = await fetch2(url, { headers: { "User-Agent": APP_USER_AGENT } });
|
|
977
1152
|
if (!response.ok) {
|
|
978
1153
|
return `Error: Fetch failed (${response.status})`;
|
|
979
1154
|
}
|
|
@@ -1000,11 +1175,16 @@ var MessageTool = class extends Tool {
|
|
|
1000
1175
|
return {
|
|
1001
1176
|
type: "object",
|
|
1002
1177
|
properties: {
|
|
1178
|
+
action: { type: "string", enum: ["send"], description: "Action to perform" },
|
|
1003
1179
|
content: { type: "string", description: "Message to send" },
|
|
1180
|
+
message: { type: "string", description: "Alias for content" },
|
|
1004
1181
|
channel: { type: "string", description: "Channel name" },
|
|
1005
|
-
chatId: { type: "string", description: "Chat ID" }
|
|
1182
|
+
chatId: { type: "string", description: "Chat ID" },
|
|
1183
|
+
to: { type: "string", description: "Alias for chatId" },
|
|
1184
|
+
replyTo: { type: "string", description: "Message ID to reply to" },
|
|
1185
|
+
silent: { type: "boolean", description: "Send without notification where supported" }
|
|
1006
1186
|
},
|
|
1007
|
-
required: [
|
|
1187
|
+
required: []
|
|
1008
1188
|
};
|
|
1009
1189
|
}
|
|
1010
1190
|
setContext(channel, chatId) {
|
|
@@ -1012,15 +1192,25 @@ var MessageTool = class extends Tool {
|
|
|
1012
1192
|
this.chatId = chatId;
|
|
1013
1193
|
}
|
|
1014
1194
|
async execute(params) {
|
|
1015
|
-
const
|
|
1195
|
+
const action = params.action ? String(params.action) : "send";
|
|
1196
|
+
if (action !== "send") {
|
|
1197
|
+
return `Error: Unsupported action '${action}'`;
|
|
1198
|
+
}
|
|
1199
|
+
const content = String(params.content ?? params.message ?? "");
|
|
1200
|
+
if (!content) {
|
|
1201
|
+
return "Error: content/message is required";
|
|
1202
|
+
}
|
|
1016
1203
|
const channel = String(params.channel ?? this.channel);
|
|
1017
|
-
const chatId = String(params.chatId ?? this.chatId);
|
|
1204
|
+
const chatId = String(params.chatId ?? params.to ?? this.chatId);
|
|
1205
|
+
const replyTo = params.replyTo ? String(params.replyTo) : void 0;
|
|
1206
|
+
const silent = typeof params.silent === "boolean" ? params.silent : void 0;
|
|
1018
1207
|
await this.sendCallback({
|
|
1019
1208
|
channel,
|
|
1020
1209
|
chatId,
|
|
1021
1210
|
content,
|
|
1211
|
+
replyTo,
|
|
1022
1212
|
media: [],
|
|
1023
|
-
metadata: {}
|
|
1213
|
+
metadata: silent !== void 0 ? { silent } : {}
|
|
1024
1214
|
});
|
|
1025
1215
|
return `Message sent to ${channel}:${chatId}`;
|
|
1026
1216
|
}
|
|
@@ -1129,6 +1319,707 @@ var CronTool = class extends Tool {
|
|
|
1129
1319
|
}
|
|
1130
1320
|
};
|
|
1131
1321
|
|
|
1322
|
+
// src/agent/tools/sessions.ts
|
|
1323
|
+
import crypto from "crypto";
|
|
1324
|
+
var DEFAULT_LIMIT = 20;
|
|
1325
|
+
var DEFAULT_MESSAGE_LIMIT = 0;
|
|
1326
|
+
var MAX_MESSAGE_LIMIT = 20;
|
|
1327
|
+
var HISTORY_MAX_BYTES = 80 * 1024;
|
|
1328
|
+
var HISTORY_TEXT_MAX_CHARS = 4e3;
|
|
1329
|
+
var toInt = (value, fallback) => {
|
|
1330
|
+
const parsed = Number(value);
|
|
1331
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : fallback;
|
|
1332
|
+
};
|
|
1333
|
+
var parseSessionKey2 = (key) => {
|
|
1334
|
+
const trimmed = key.trim();
|
|
1335
|
+
if (!trimmed.includes(":")) {
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
const [channel, chatId] = trimmed.split(":", 2);
|
|
1339
|
+
if (!channel || !chatId) {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
return { channel, chatId };
|
|
1343
|
+
};
|
|
1344
|
+
var classifySessionKind = (key) => {
|
|
1345
|
+
if (key.startsWith("cron:") || key === "heartbeat") {
|
|
1346
|
+
return "cron";
|
|
1347
|
+
}
|
|
1348
|
+
if (key.startsWith("hook:")) {
|
|
1349
|
+
return "hook";
|
|
1350
|
+
}
|
|
1351
|
+
if (key.startsWith("subagent:") || key.startsWith("node:")) {
|
|
1352
|
+
return "node";
|
|
1353
|
+
}
|
|
1354
|
+
if (key.startsWith("system:")) {
|
|
1355
|
+
return "other";
|
|
1356
|
+
}
|
|
1357
|
+
return "main";
|
|
1358
|
+
};
|
|
1359
|
+
var truncateHistoryText = (text) => {
|
|
1360
|
+
if (text.length <= HISTORY_TEXT_MAX_CHARS) {
|
|
1361
|
+
return { text, truncated: false };
|
|
1362
|
+
}
|
|
1363
|
+
return { text: `${text.slice(0, HISTORY_TEXT_MAX_CHARS)}
|
|
1364
|
+
\u2026(truncated)\u2026`, truncated: true };
|
|
1365
|
+
};
|
|
1366
|
+
var sanitizeHistoryMessage = (msg) => {
|
|
1367
|
+
const entry = { ...msg };
|
|
1368
|
+
const res = truncateHistoryText(entry.content ?? "");
|
|
1369
|
+
return { message: { ...entry, content: res.text }, truncated: res.truncated };
|
|
1370
|
+
};
|
|
1371
|
+
var jsonBytes = (value) => {
|
|
1372
|
+
try {
|
|
1373
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
1374
|
+
} catch {
|
|
1375
|
+
return Buffer.byteLength(String(value), "utf8");
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
var enforceHistoryHardCap = (items) => {
|
|
1379
|
+
const bytes = jsonBytes(items);
|
|
1380
|
+
if (bytes <= HISTORY_MAX_BYTES) {
|
|
1381
|
+
return items;
|
|
1382
|
+
}
|
|
1383
|
+
const last = items.at(-1);
|
|
1384
|
+
if (last && jsonBytes([last]) <= HISTORY_MAX_BYTES) {
|
|
1385
|
+
return [last];
|
|
1386
|
+
}
|
|
1387
|
+
return [{ role: "assistant", content: "[sessions_history omitted: message too large]" }];
|
|
1388
|
+
};
|
|
1389
|
+
var toTimestamp = (value) => {
|
|
1390
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1391
|
+
return value;
|
|
1392
|
+
}
|
|
1393
|
+
if (typeof value === "string") {
|
|
1394
|
+
const parsed = Date.parse(value);
|
|
1395
|
+
if (Number.isFinite(parsed)) {
|
|
1396
|
+
return parsed;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return void 0;
|
|
1400
|
+
};
|
|
1401
|
+
var SessionsListTool = class extends Tool {
|
|
1402
|
+
constructor(sessions) {
|
|
1403
|
+
super();
|
|
1404
|
+
this.sessions = sessions;
|
|
1405
|
+
}
|
|
1406
|
+
get name() {
|
|
1407
|
+
return "sessions_list";
|
|
1408
|
+
}
|
|
1409
|
+
get description() {
|
|
1410
|
+
return "List available sessions with timestamps";
|
|
1411
|
+
}
|
|
1412
|
+
get parameters() {
|
|
1413
|
+
return {
|
|
1414
|
+
type: "object",
|
|
1415
|
+
properties: {
|
|
1416
|
+
kinds: {
|
|
1417
|
+
type: "array",
|
|
1418
|
+
items: { type: "string" },
|
|
1419
|
+
description: "Filter by session kinds (main/group/cron/hook/other)"
|
|
1420
|
+
},
|
|
1421
|
+
limit: { type: "integer", minimum: 1, description: "Maximum number of sessions to return" },
|
|
1422
|
+
activeMinutes: { type: "integer", minimum: 1, description: "Only include active sessions" },
|
|
1423
|
+
messageLimit: { type: "integer", minimum: 0, description: "Include last N messages (max 20)" }
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
async execute(params) {
|
|
1428
|
+
const limit = toInt(params.limit, DEFAULT_LIMIT);
|
|
1429
|
+
const rawKinds = Array.isArray(params.kinds) ? params.kinds.map((k) => String(k).toLowerCase()) : [];
|
|
1430
|
+
const kinds = rawKinds.length ? new Set(rawKinds) : null;
|
|
1431
|
+
const activeMinutes = toInt(params.activeMinutes, 0);
|
|
1432
|
+
const messageLimit = Math.min(toInt(params.messageLimit, DEFAULT_MESSAGE_LIMIT), MAX_MESSAGE_LIMIT);
|
|
1433
|
+
const now = Date.now();
|
|
1434
|
+
const sessions = this.sessions.listSessions().sort((a, b) => (toTimestamp(b.updated_at) ?? 0) - (toTimestamp(a.updated_at) ?? 0)).filter((entry) => {
|
|
1435
|
+
if (activeMinutes > 0 && entry.updated_at) {
|
|
1436
|
+
const updated = Date.parse(String(entry.updated_at));
|
|
1437
|
+
if (Number.isFinite(updated) && now - updated > activeMinutes * 60 * 1e3) {
|
|
1438
|
+
return false;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (kinds) {
|
|
1442
|
+
const kind = classifySessionKind(String(entry.key ?? ""));
|
|
1443
|
+
if (!kinds.has(kind)) {
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return true;
|
|
1448
|
+
}).slice(0, limit).map((entry) => {
|
|
1449
|
+
const key = String(entry.key ?? "");
|
|
1450
|
+
const kind = classifySessionKind(key);
|
|
1451
|
+
const parsed = parseSessionKey2(key);
|
|
1452
|
+
const metadata = entry.metadata ?? {};
|
|
1453
|
+
const label = typeof metadata.label === "string" ? metadata.label : typeof metadata.session_label === "string" ? metadata.session_label : void 0;
|
|
1454
|
+
const displayName = typeof metadata.displayName === "string" ? metadata.displayName : typeof metadata.display_name === "string" ? metadata.display_name : void 0;
|
|
1455
|
+
const deliveryContext = metadata.deliveryContext && typeof metadata.deliveryContext === "object" ? metadata.deliveryContext : void 0;
|
|
1456
|
+
const updatedAt = toTimestamp(entry.updated_at);
|
|
1457
|
+
const createdAt = toTimestamp(entry.created_at);
|
|
1458
|
+
const base = {
|
|
1459
|
+
key,
|
|
1460
|
+
kind,
|
|
1461
|
+
channel: parsed?.channel,
|
|
1462
|
+
label,
|
|
1463
|
+
displayName,
|
|
1464
|
+
deliveryContext,
|
|
1465
|
+
updatedAt,
|
|
1466
|
+
createdAt,
|
|
1467
|
+
sessionId: key,
|
|
1468
|
+
lastChannel: typeof metadata.last_channel === "string" ? metadata.last_channel : parsed?.channel ?? void 0,
|
|
1469
|
+
lastTo: typeof metadata.last_to === "string" ? metadata.last_to : parsed?.chatId ?? void 0,
|
|
1470
|
+
lastAccountId: typeof metadata.last_account_id === "string" ? metadata.last_account_id : void 0,
|
|
1471
|
+
transcriptPath: entry.path
|
|
1472
|
+
};
|
|
1473
|
+
if (messageLimit > 0) {
|
|
1474
|
+
const session = this.sessions.getIfExists(key);
|
|
1475
|
+
if (session) {
|
|
1476
|
+
const filtered = session.messages.filter((msg) => msg.role !== "tool");
|
|
1477
|
+
const recent = filtered.slice(-messageLimit).map((msg) => ({
|
|
1478
|
+
role: msg.role,
|
|
1479
|
+
content: msg.content,
|
|
1480
|
+
timestamp: msg.timestamp
|
|
1481
|
+
}));
|
|
1482
|
+
const sanitized = recent.map((msg) => sanitizeHistoryMessage(msg).message);
|
|
1483
|
+
base.messages = sanitized;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return base;
|
|
1487
|
+
});
|
|
1488
|
+
return JSON.stringify({ sessions }, null, 2);
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
var SessionsHistoryTool = class extends Tool {
|
|
1492
|
+
constructor(sessions) {
|
|
1493
|
+
super();
|
|
1494
|
+
this.sessions = sessions;
|
|
1495
|
+
}
|
|
1496
|
+
get name() {
|
|
1497
|
+
return "sessions_history";
|
|
1498
|
+
}
|
|
1499
|
+
get description() {
|
|
1500
|
+
return "Fetch recent messages from a session";
|
|
1501
|
+
}
|
|
1502
|
+
get parameters() {
|
|
1503
|
+
return {
|
|
1504
|
+
type: "object",
|
|
1505
|
+
properties: {
|
|
1506
|
+
sessionKey: { type: "string", description: "Session key in the format channel:chatId" },
|
|
1507
|
+
limit: { type: "integer", minimum: 1, description: "Maximum number of messages to return" },
|
|
1508
|
+
includeTools: { type: "boolean", description: "Include tool messages" }
|
|
1509
|
+
},
|
|
1510
|
+
required: ["sessionKey"]
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
async execute(params) {
|
|
1514
|
+
const sessionKey = String(params.sessionKey ?? "").trim();
|
|
1515
|
+
if (!sessionKey) {
|
|
1516
|
+
return "Error: sessionKey is required";
|
|
1517
|
+
}
|
|
1518
|
+
let session = this.sessions.getIfExists(sessionKey);
|
|
1519
|
+
if (!session) {
|
|
1520
|
+
const candidates = this.sessions.listSessions();
|
|
1521
|
+
const match = candidates.find((entry) => {
|
|
1522
|
+
const key = typeof entry.key === "string" ? entry.key : "";
|
|
1523
|
+
if (key === sessionKey || key.endsWith(`:${sessionKey}`)) {
|
|
1524
|
+
return true;
|
|
1525
|
+
}
|
|
1526
|
+
const meta = entry.metadata;
|
|
1527
|
+
const metaLabel = meta?.label ?? meta?.session_label;
|
|
1528
|
+
return typeof metaLabel === "string" && metaLabel.trim() === sessionKey;
|
|
1529
|
+
});
|
|
1530
|
+
const resolvedKey = match && typeof match.key === "string" ? match.key : "";
|
|
1531
|
+
if (resolvedKey) {
|
|
1532
|
+
session = this.sessions.getIfExists(resolvedKey);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (!session) {
|
|
1536
|
+
return `Error: session '${sessionKey}' not found`;
|
|
1537
|
+
}
|
|
1538
|
+
const limit = toInt(params.limit, DEFAULT_LIMIT);
|
|
1539
|
+
const includeTools = typeof params.includeTools === "boolean" ? params.includeTools : false;
|
|
1540
|
+
const filtered = includeTools ? session.messages : session.messages.filter((msg) => msg.role !== "tool");
|
|
1541
|
+
const recent = filtered.slice(-limit).map((msg) => ({
|
|
1542
|
+
role: msg.role,
|
|
1543
|
+
content: msg.content,
|
|
1544
|
+
timestamp: msg.timestamp
|
|
1545
|
+
}));
|
|
1546
|
+
const sanitized = recent.map((msg) => sanitizeHistoryMessage(msg).message);
|
|
1547
|
+
const capped = enforceHistoryHardCap(sanitized);
|
|
1548
|
+
return JSON.stringify({ sessionKey, messages: capped }, null, 2);
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
var SessionsSendTool = class extends Tool {
|
|
1552
|
+
constructor(sessions, bus) {
|
|
1553
|
+
super();
|
|
1554
|
+
this.sessions = sessions;
|
|
1555
|
+
this.bus = bus;
|
|
1556
|
+
}
|
|
1557
|
+
get name() {
|
|
1558
|
+
return "sessions_send";
|
|
1559
|
+
}
|
|
1560
|
+
get description() {
|
|
1561
|
+
return "Send a message to another session (cross-channel delivery)";
|
|
1562
|
+
}
|
|
1563
|
+
get parameters() {
|
|
1564
|
+
return {
|
|
1565
|
+
type: "object",
|
|
1566
|
+
properties: {
|
|
1567
|
+
sessionKey: { type: "string", description: "Target session key in the format channel:chatId" },
|
|
1568
|
+
label: { type: "string", description: "Session label (if sessionKey not provided)" },
|
|
1569
|
+
agentId: { type: "string", description: "Optional agent id (unused in local runtime)" },
|
|
1570
|
+
message: { type: "string", description: "Message content to send" },
|
|
1571
|
+
timeoutSeconds: { type: "number", description: "Optional timeout in seconds" },
|
|
1572
|
+
content: { type: "string", description: "Alias for message" },
|
|
1573
|
+
replyTo: { type: "string", description: "Message ID to reply to" },
|
|
1574
|
+
silent: { type: "boolean", description: "Send without notification where supported" }
|
|
1575
|
+
},
|
|
1576
|
+
required: []
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
async execute(params) {
|
|
1580
|
+
const runId = crypto.randomUUID();
|
|
1581
|
+
const sessionKeyParam = String(params.sessionKey ?? "").trim();
|
|
1582
|
+
const labelParam = String(params.label ?? "").trim();
|
|
1583
|
+
if (sessionKeyParam && labelParam) {
|
|
1584
|
+
return JSON.stringify(
|
|
1585
|
+
{ runId, status: "error", error: "Provide either sessionKey or label (not both)" },
|
|
1586
|
+
null,
|
|
1587
|
+
2
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
let sessionKey = sessionKeyParam;
|
|
1591
|
+
const message = String(params.message ?? params.content ?? "");
|
|
1592
|
+
if (!message) {
|
|
1593
|
+
return JSON.stringify({ runId, status: "error", error: "message is required" }, null, 2);
|
|
1594
|
+
}
|
|
1595
|
+
if (!sessionKey) {
|
|
1596
|
+
const label = labelParam;
|
|
1597
|
+
if (!label) {
|
|
1598
|
+
return JSON.stringify({ runId, status: "error", error: "sessionKey or label is required" }, null, 2);
|
|
1599
|
+
}
|
|
1600
|
+
const candidates = this.sessions.listSessions();
|
|
1601
|
+
const match = candidates.find((entry) => {
|
|
1602
|
+
const key = typeof entry.key === "string" ? entry.key : "";
|
|
1603
|
+
if (key === label || key.endsWith(`:${label}`)) {
|
|
1604
|
+
return true;
|
|
1605
|
+
}
|
|
1606
|
+
const meta = entry.metadata;
|
|
1607
|
+
const metaLabel = meta?.label ?? meta?.session_label;
|
|
1608
|
+
return typeof metaLabel === "string" && metaLabel.trim() === label;
|
|
1609
|
+
});
|
|
1610
|
+
sessionKey = match && typeof match.key === "string" ? match.key : "";
|
|
1611
|
+
if (!sessionKey) {
|
|
1612
|
+
return JSON.stringify(
|
|
1613
|
+
{ runId, status: "error", error: `no session found for label '${label}'` },
|
|
1614
|
+
null,
|
|
1615
|
+
2
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
const parsed = parseSessionKey2(sessionKey);
|
|
1620
|
+
if (!parsed) {
|
|
1621
|
+
return JSON.stringify(
|
|
1622
|
+
{ runId, status: "error", error: "sessionKey must be in the format channel:chatId" },
|
|
1623
|
+
null,
|
|
1624
|
+
2
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
const replyTo = params.replyTo ? String(params.replyTo) : void 0;
|
|
1628
|
+
const silent = typeof params.silent === "boolean" ? params.silent : void 0;
|
|
1629
|
+
const outbound = {
|
|
1630
|
+
channel: parsed.channel,
|
|
1631
|
+
chatId: parsed.chatId,
|
|
1632
|
+
content: message,
|
|
1633
|
+
replyTo,
|
|
1634
|
+
media: [],
|
|
1635
|
+
metadata: silent !== void 0 ? { silent } : {}
|
|
1636
|
+
};
|
|
1637
|
+
await this.bus.publishOutbound(outbound);
|
|
1638
|
+
const session = this.sessions.getOrCreate(sessionKey);
|
|
1639
|
+
this.sessions.addMessage(session, "assistant", message, { via: "sessions_send" });
|
|
1640
|
+
this.sessions.save(session);
|
|
1641
|
+
return JSON.stringify(
|
|
1642
|
+
{ runId, status: "ok", sessionKey: `${parsed.channel}:${parsed.chatId}` },
|
|
1643
|
+
null,
|
|
1644
|
+
2
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1649
|
+
// src/agent/tools/memory.ts
|
|
1650
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
|
|
1651
|
+
import { join as join4, resolve as resolve4 } from "path";
|
|
1652
|
+
var DEFAULT_LIMIT2 = 20;
|
|
1653
|
+
var DEFAULT_CONTEXT_LINES = 0;
|
|
1654
|
+
var toInt2 = (value, fallback) => {
|
|
1655
|
+
const parsed = Number(value);
|
|
1656
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : fallback;
|
|
1657
|
+
};
|
|
1658
|
+
var normalizeQuery = (value) => String(value ?? "").trim();
|
|
1659
|
+
var isWithin = (child, parent) => {
|
|
1660
|
+
const resolvedChild = resolve4(child);
|
|
1661
|
+
const resolvedParent = resolve4(parent);
|
|
1662
|
+
return resolvedChild === resolvedParent || resolvedChild.startsWith(`${resolvedParent}/`);
|
|
1663
|
+
};
|
|
1664
|
+
var getMemoryFiles = (workspace) => {
|
|
1665
|
+
const files = [];
|
|
1666
|
+
const workspaceMemory = join4(workspace, "MEMORY.md");
|
|
1667
|
+
if (existsSync6(workspaceMemory)) {
|
|
1668
|
+
files.push(workspaceMemory);
|
|
1669
|
+
}
|
|
1670
|
+
const memoryDir = join4(workspace, "memory");
|
|
1671
|
+
if (existsSync6(memoryDir)) {
|
|
1672
|
+
const entries = readdirSync4(memoryDir);
|
|
1673
|
+
for (const entry of entries) {
|
|
1674
|
+
if (!entry.endsWith(".md")) {
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
files.push(join4(memoryDir, entry));
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return files;
|
|
1681
|
+
};
|
|
1682
|
+
var MemorySearchTool = class extends Tool {
|
|
1683
|
+
constructor(workspace) {
|
|
1684
|
+
super();
|
|
1685
|
+
this.workspace = workspace;
|
|
1686
|
+
}
|
|
1687
|
+
get name() {
|
|
1688
|
+
return "memory_search";
|
|
1689
|
+
}
|
|
1690
|
+
get description() {
|
|
1691
|
+
return "Mandatory recall step: search MEMORY.md + memory/*.md; returns snippets with path + lines.";
|
|
1692
|
+
}
|
|
1693
|
+
get parameters() {
|
|
1694
|
+
return {
|
|
1695
|
+
type: "object",
|
|
1696
|
+
properties: {
|
|
1697
|
+
query: { type: "string", description: "Search query" },
|
|
1698
|
+
maxResults: { type: "integer", minimum: 1, description: "Maximum number of matches to return" },
|
|
1699
|
+
minScore: { type: "number", description: "Minimum score (ignored for local search)" }
|
|
1700
|
+
},
|
|
1701
|
+
required: ["query"]
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
async execute(params) {
|
|
1705
|
+
const query = normalizeQuery(params.query);
|
|
1706
|
+
if (!query) {
|
|
1707
|
+
return "Error: query is required";
|
|
1708
|
+
}
|
|
1709
|
+
const limit = toInt2(params.maxResults ?? params.limit, DEFAULT_LIMIT2);
|
|
1710
|
+
const contextLines = toInt2(params.contextLines, DEFAULT_CONTEXT_LINES);
|
|
1711
|
+
const lowerQuery = query.toLowerCase();
|
|
1712
|
+
const results = [];
|
|
1713
|
+
const files = getMemoryFiles(this.workspace);
|
|
1714
|
+
for (const filePath of files) {
|
|
1715
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1716
|
+
const lines = content.split("\n");
|
|
1717
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1718
|
+
if (lines[i].toLowerCase().includes(lowerQuery)) {
|
|
1719
|
+
const start = Math.max(0, i - contextLines);
|
|
1720
|
+
const end = Math.min(lines.length, i + contextLines + 1);
|
|
1721
|
+
const snippet = lines.slice(start, end).join("\n");
|
|
1722
|
+
results.push({ path: filePath, line: i + 1, text: snippet, score: 1 });
|
|
1723
|
+
if (results.length >= limit) {
|
|
1724
|
+
return JSON.stringify(
|
|
1725
|
+
{ results, provider: "local", model: "regex", fallback: false, citations: "off" },
|
|
1726
|
+
null,
|
|
1727
|
+
2
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
return JSON.stringify(
|
|
1734
|
+
{ results, provider: "local", model: "regex", fallback: false, citations: "off" },
|
|
1735
|
+
null,
|
|
1736
|
+
2
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
var MemoryGetTool = class extends Tool {
|
|
1741
|
+
constructor(workspace) {
|
|
1742
|
+
super();
|
|
1743
|
+
this.workspace = workspace;
|
|
1744
|
+
}
|
|
1745
|
+
get name() {
|
|
1746
|
+
return "memory_get";
|
|
1747
|
+
}
|
|
1748
|
+
get description() {
|
|
1749
|
+
return "Safe snippet read from MEMORY.md or memory/*.md; use after memory_search.";
|
|
1750
|
+
}
|
|
1751
|
+
get parameters() {
|
|
1752
|
+
return {
|
|
1753
|
+
type: "object",
|
|
1754
|
+
properties: {
|
|
1755
|
+
path: { type: "string", description: "Path to memory file (relative or absolute)" },
|
|
1756
|
+
from: { type: "integer", minimum: 1, description: "Start line (1-based)" },
|
|
1757
|
+
lines: { type: "integer", minimum: 1, description: "Number of lines to read" }
|
|
1758
|
+
},
|
|
1759
|
+
required: ["path"]
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
async execute(params) {
|
|
1763
|
+
const pathParam = String(params.path ?? "").trim();
|
|
1764
|
+
if (!pathParam) {
|
|
1765
|
+
return "Error: path is required";
|
|
1766
|
+
}
|
|
1767
|
+
const resolvedPath = resolve4(this.workspace, pathParam);
|
|
1768
|
+
const memoryDir = join4(this.workspace, "memory");
|
|
1769
|
+
const workspaceMemory = join4(this.workspace, "MEMORY.md");
|
|
1770
|
+
if (!isWithin(resolvedPath, this.workspace)) {
|
|
1771
|
+
return "Error: path must be within workspace";
|
|
1772
|
+
}
|
|
1773
|
+
if (!(resolvedPath === resolve4(workspaceMemory) || isWithin(resolvedPath, memoryDir))) {
|
|
1774
|
+
return "Error: path must be MEMORY.md or memory/*.md within workspace";
|
|
1775
|
+
}
|
|
1776
|
+
if (!existsSync6(resolvedPath)) {
|
|
1777
|
+
return `Error: file not found: ${resolvedPath}`;
|
|
1778
|
+
}
|
|
1779
|
+
const content = readFileSync5(resolvedPath, "utf-8");
|
|
1780
|
+
const lines = content.split("\n");
|
|
1781
|
+
const startLine = toInt2(params.from ?? params.startLine, 1);
|
|
1782
|
+
const requestedLines = toInt2(params.lines ?? params.endLine, Math.max(lines.length - startLine + 1, 1));
|
|
1783
|
+
const endLine = Math.min(lines.length, startLine + requestedLines - 1);
|
|
1784
|
+
const startIdx = Math.max(0, startLine - 1);
|
|
1785
|
+
const endIdx = Math.min(lines.length, endLine);
|
|
1786
|
+
const selected = lines.slice(startIdx, endIdx);
|
|
1787
|
+
const numbered = selected.map((line, idx) => `${startIdx + idx + 1}: ${line}`);
|
|
1788
|
+
return JSON.stringify(
|
|
1789
|
+
{ path: pathParam, from: startLine, lines: endIdx - startIdx, text: numbered.join("\n") },
|
|
1790
|
+
null,
|
|
1791
|
+
2
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
// src/agent/tools/gateway.ts
|
|
1797
|
+
var GatewayTool = class extends Tool {
|
|
1798
|
+
constructor(controller) {
|
|
1799
|
+
super();
|
|
1800
|
+
this.controller = controller;
|
|
1801
|
+
}
|
|
1802
|
+
get name() {
|
|
1803
|
+
return "gateway";
|
|
1804
|
+
}
|
|
1805
|
+
get description() {
|
|
1806
|
+
return "Restart or update gateway config (config.get/schema/apply/patch) and trigger restart.";
|
|
1807
|
+
}
|
|
1808
|
+
get parameters() {
|
|
1809
|
+
return {
|
|
1810
|
+
type: "object",
|
|
1811
|
+
properties: {
|
|
1812
|
+
action: {
|
|
1813
|
+
type: "string",
|
|
1814
|
+
enum: [
|
|
1815
|
+
"restart",
|
|
1816
|
+
"config.get",
|
|
1817
|
+
"config.schema",
|
|
1818
|
+
"config.apply",
|
|
1819
|
+
"config.patch",
|
|
1820
|
+
"update.run"
|
|
1821
|
+
],
|
|
1822
|
+
description: "Action to perform"
|
|
1823
|
+
},
|
|
1824
|
+
delayMs: { type: "number", description: "Restart delay (ms)" },
|
|
1825
|
+
reason: { type: "string", description: "Optional reason for the action" },
|
|
1826
|
+
gatewayUrl: { type: "string", description: "Optional gateway url (unused in local runtime)" },
|
|
1827
|
+
gatewayToken: { type: "string", description: "Optional gateway token (unused in local runtime)" },
|
|
1828
|
+
timeoutMs: { type: "number", description: "Optional timeout (ms)" },
|
|
1829
|
+
raw: { type: "string", description: "Raw config JSON string for apply/patch" },
|
|
1830
|
+
baseHash: { type: "string", description: "Config base hash (from config.get)" },
|
|
1831
|
+
sessionKey: { type: "string", description: "Session key for restart notification" },
|
|
1832
|
+
note: { type: "string", description: "Completion note for config apply/patch" },
|
|
1833
|
+
restartDelayMs: { type: "number", description: "Restart delay after apply/patch (ms)" }
|
|
1834
|
+
},
|
|
1835
|
+
required: ["action"]
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
async execute(params) {
|
|
1839
|
+
const action = String(params.action ?? "");
|
|
1840
|
+
if (!this.controller) {
|
|
1841
|
+
return JSON.stringify({ ok: false, error: "gateway controller not available in this runtime" }, null, 2);
|
|
1842
|
+
}
|
|
1843
|
+
if (action === "config.get") {
|
|
1844
|
+
if (!this.controller.getConfig) {
|
|
1845
|
+
return JSON.stringify({ ok: false, error: "config.get not supported" }, null, 2);
|
|
1846
|
+
}
|
|
1847
|
+
const result = await this.controller.getConfig();
|
|
1848
|
+
return JSON.stringify({ ok: true, result }, null, 2);
|
|
1849
|
+
}
|
|
1850
|
+
if (action === "config.schema") {
|
|
1851
|
+
if (!this.controller.getConfigSchema) {
|
|
1852
|
+
return JSON.stringify({ ok: false, error: "config.schema not supported" }, null, 2);
|
|
1853
|
+
}
|
|
1854
|
+
const result = await this.controller.getConfigSchema();
|
|
1855
|
+
return JSON.stringify({ ok: true, result }, null, 2);
|
|
1856
|
+
}
|
|
1857
|
+
if (action === "config.apply" || action === "config.patch") {
|
|
1858
|
+
const raw = params.raw;
|
|
1859
|
+
if (typeof raw !== "string" || !raw.trim()) {
|
|
1860
|
+
return JSON.stringify({ ok: false, error: "raw config string is required" }, null, 2);
|
|
1861
|
+
}
|
|
1862
|
+
const note = typeof params.note === "string" ? params.note.trim() || void 0 : void 0;
|
|
1863
|
+
const restartDelayMs = typeof params.restartDelayMs === "number" && Number.isFinite(params.restartDelayMs) ? Math.floor(params.restartDelayMs) : void 0;
|
|
1864
|
+
let baseHash = typeof params.baseHash === "string" && params.baseHash.trim() ? params.baseHash.trim() : void 0;
|
|
1865
|
+
if (!baseHash && this.controller.getConfig) {
|
|
1866
|
+
const snapshot = await this.controller.getConfig();
|
|
1867
|
+
if (snapshot && typeof snapshot === "object") {
|
|
1868
|
+
const hashValue = snapshot.hash;
|
|
1869
|
+
if (typeof hashValue === "string" && hashValue.trim()) {
|
|
1870
|
+
baseHash = hashValue.trim();
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
const sessionKey = typeof params.sessionKey === "string" && params.sessionKey.trim() ? params.sessionKey.trim() : void 0;
|
|
1875
|
+
if (action === "config.apply") {
|
|
1876
|
+
if (!this.controller.applyConfig) {
|
|
1877
|
+
return JSON.stringify({ ok: false, error: "config.apply not supported" }, null, 2);
|
|
1878
|
+
}
|
|
1879
|
+
const result2 = await this.controller.applyConfig({
|
|
1880
|
+
raw,
|
|
1881
|
+
baseHash,
|
|
1882
|
+
note,
|
|
1883
|
+
restartDelayMs,
|
|
1884
|
+
sessionKey
|
|
1885
|
+
});
|
|
1886
|
+
return JSON.stringify({ ok: true, result: result2 }, null, 2);
|
|
1887
|
+
}
|
|
1888
|
+
if (!this.controller.patchConfig) {
|
|
1889
|
+
return JSON.stringify({ ok: false, error: "config.patch not supported" }, null, 2);
|
|
1890
|
+
}
|
|
1891
|
+
const result = await this.controller.patchConfig({
|
|
1892
|
+
raw,
|
|
1893
|
+
baseHash,
|
|
1894
|
+
note,
|
|
1895
|
+
restartDelayMs,
|
|
1896
|
+
sessionKey
|
|
1897
|
+
});
|
|
1898
|
+
return JSON.stringify({ ok: true, result }, null, 2);
|
|
1899
|
+
}
|
|
1900
|
+
if (action === "restart") {
|
|
1901
|
+
if (!this.controller.restart) {
|
|
1902
|
+
return JSON.stringify({ ok: false, error: "restart not supported" }, null, 2);
|
|
1903
|
+
}
|
|
1904
|
+
const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.floor(params.delayMs) : void 0;
|
|
1905
|
+
const reason = typeof params.reason === "string" ? params.reason.trim() || void 0 : void 0;
|
|
1906
|
+
const result = await this.controller.restart({ delayMs, reason });
|
|
1907
|
+
return JSON.stringify({ ok: true, result: result ?? "Restart scheduled" }, null, 2);
|
|
1908
|
+
}
|
|
1909
|
+
if (action === "update.run") {
|
|
1910
|
+
if (!this.controller.updateRun) {
|
|
1911
|
+
return JSON.stringify({ ok: false, error: "update.run not supported in this runtime" }, null, 2);
|
|
1912
|
+
}
|
|
1913
|
+
const restartDelayMs = typeof params.restartDelayMs === "number" && Number.isFinite(params.restartDelayMs) ? Math.floor(params.restartDelayMs) : void 0;
|
|
1914
|
+
const timeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) ? Math.max(1, Math.floor(params.timeoutMs)) : void 0;
|
|
1915
|
+
const sessionKey = typeof params.sessionKey === "string" && params.sessionKey.trim() ? params.sessionKey.trim() : void 0;
|
|
1916
|
+
const note = typeof params.note === "string" ? params.note.trim() || void 0 : void 0;
|
|
1917
|
+
const result = await this.controller.updateRun({ note, restartDelayMs, timeoutMs, sessionKey });
|
|
1918
|
+
return JSON.stringify({ ok: true, result }, null, 2);
|
|
1919
|
+
}
|
|
1920
|
+
return JSON.stringify({ ok: false, error: `Unknown action: ${action}` }, null, 2);
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
// src/agent/tools/subagents.ts
|
|
1925
|
+
var SubagentsTool = class extends Tool {
|
|
1926
|
+
constructor(manager) {
|
|
1927
|
+
super();
|
|
1928
|
+
this.manager = manager;
|
|
1929
|
+
}
|
|
1930
|
+
get name() {
|
|
1931
|
+
return "subagents";
|
|
1932
|
+
}
|
|
1933
|
+
get description() {
|
|
1934
|
+
return "Manage running subagents (list/steer/kill)";
|
|
1935
|
+
}
|
|
1936
|
+
get parameters() {
|
|
1937
|
+
return {
|
|
1938
|
+
type: "object",
|
|
1939
|
+
properties: {
|
|
1940
|
+
action: {
|
|
1941
|
+
type: "string",
|
|
1942
|
+
enum: ["list", "kill", "steer"],
|
|
1943
|
+
description: "Action to perform"
|
|
1944
|
+
},
|
|
1945
|
+
target: { type: "string", description: "Subagent target (id/label/last/index)" },
|
|
1946
|
+
message: { type: "string", description: "Steer instruction for a running subagent" },
|
|
1947
|
+
recentMinutes: { type: "number", description: "Only list recent runs" },
|
|
1948
|
+
id: { type: "string", description: "Alias for target" },
|
|
1949
|
+
note: { type: "string", description: "Alias for message" }
|
|
1950
|
+
},
|
|
1951
|
+
required: ["action"]
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
async execute(params) {
|
|
1955
|
+
const action = String(params.action ?? "");
|
|
1956
|
+
if (action === "list") {
|
|
1957
|
+
const recentMinutes = typeof params.recentMinutes === "number" && Number.isFinite(params.recentMinutes) ? Math.max(1, Math.floor(params.recentMinutes)) : null;
|
|
1958
|
+
const runs = this.manager.listRuns();
|
|
1959
|
+
const now = Date.now();
|
|
1960
|
+
const filtered = recentMinutes ? runs.filter((run) => {
|
|
1961
|
+
const startedAt = Date.parse(run.startedAt);
|
|
1962
|
+
const doneAt = run.doneAt ? Date.parse(run.doneAt) : NaN;
|
|
1963
|
+
const ts = Number.isFinite(doneAt) ? doneAt : startedAt;
|
|
1964
|
+
return Number.isFinite(ts) && now - ts <= recentMinutes * 60 * 1e3;
|
|
1965
|
+
}) : runs;
|
|
1966
|
+
return JSON.stringify({ runs: filtered }, null, 2);
|
|
1967
|
+
}
|
|
1968
|
+
if (action === "kill") {
|
|
1969
|
+
const target = String(params.target ?? params.id ?? "").trim();
|
|
1970
|
+
if (!target) {
|
|
1971
|
+
return "Error: target is required for kill";
|
|
1972
|
+
}
|
|
1973
|
+
const resolved = resolveTarget(target, this.manager.listRuns());
|
|
1974
|
+
if (!resolved) {
|
|
1975
|
+
return `Error: subagent not found (${target})`;
|
|
1976
|
+
}
|
|
1977
|
+
const ok = this.manager.cancelRun(resolved.id);
|
|
1978
|
+
return ok ? `Subagent ${resolved.id} killed` : `Subagent ${resolved.id} not found`;
|
|
1979
|
+
}
|
|
1980
|
+
if (action === "steer") {
|
|
1981
|
+
const target = String(params.target ?? params.id ?? "").trim();
|
|
1982
|
+
const note = String(params.message ?? params.note ?? "").trim();
|
|
1983
|
+
if (!target || !note) {
|
|
1984
|
+
return "Error: target and message are required for steer";
|
|
1985
|
+
}
|
|
1986
|
+
const resolved = resolveTarget(target, this.manager.listRuns());
|
|
1987
|
+
if (!resolved) {
|
|
1988
|
+
return `Error: subagent not found (${target})`;
|
|
1989
|
+
}
|
|
1990
|
+
const ok = this.manager.steerRun(resolved.id, note);
|
|
1991
|
+
return ok ? `Subagent ${resolved.id} steer applied` : `Subagent ${resolved.id} not running`;
|
|
1992
|
+
}
|
|
1993
|
+
return "Error: invalid action";
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1996
|
+
function resolveTarget(token, runs) {
|
|
1997
|
+
const trimmed = token.trim();
|
|
1998
|
+
if (!trimmed) {
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
const sorted = [...runs].sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
|
|
2002
|
+
if (trimmed === "last") {
|
|
2003
|
+
return sorted[0] ?? null;
|
|
2004
|
+
}
|
|
2005
|
+
if (/^\d+$/.test(trimmed)) {
|
|
2006
|
+
const idx = Number.parseInt(trimmed, 10);
|
|
2007
|
+
if (Number.isFinite(idx) && idx > 0 && idx <= sorted.length) {
|
|
2008
|
+
return sorted[idx - 1];
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
const byId = runs.find((run) => run.id === trimmed);
|
|
2012
|
+
if (byId) {
|
|
2013
|
+
return byId;
|
|
2014
|
+
}
|
|
2015
|
+
const lower = trimmed.toLowerCase();
|
|
2016
|
+
const byLabel = runs.filter((run) => run.label.toLowerCase() === lower);
|
|
2017
|
+
if (byLabel.length === 1) {
|
|
2018
|
+
return byLabel[0];
|
|
2019
|
+
}
|
|
2020
|
+
return null;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1132
2023
|
// src/agent/subagent.ts
|
|
1133
2024
|
import { randomUUID } from "crypto";
|
|
1134
2025
|
var SubagentManager = class {
|
|
@@ -1136,6 +2027,8 @@ var SubagentManager = class {
|
|
|
1136
2027
|
this.options = options;
|
|
1137
2028
|
}
|
|
1138
2029
|
runningTasks = /* @__PURE__ */ new Map();
|
|
2030
|
+
runs = /* @__PURE__ */ new Map();
|
|
2031
|
+
steerQueue = /* @__PURE__ */ new Map();
|
|
1139
2032
|
async spawn(params) {
|
|
1140
2033
|
const taskId = randomUUID().slice(0, 8);
|
|
1141
2034
|
const displayLabel = params.label ?? `${params.task.slice(0, 30)}${params.task.length > 30 ? "..." : ""}`;
|
|
@@ -1143,6 +2036,16 @@ var SubagentManager = class {
|
|
|
1143
2036
|
channel: params.originChannel ?? "cli",
|
|
1144
2037
|
chatId: params.originChatId ?? "direct"
|
|
1145
2038
|
};
|
|
2039
|
+
this.runs.set(taskId, {
|
|
2040
|
+
id: taskId,
|
|
2041
|
+
label: displayLabel,
|
|
2042
|
+
task: params.task,
|
|
2043
|
+
origin,
|
|
2044
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2045
|
+
status: "running",
|
|
2046
|
+
cancelled: false
|
|
2047
|
+
});
|
|
2048
|
+
this.steerQueue.set(taskId, []);
|
|
1146
2049
|
const background = this.runSubagent({
|
|
1147
2050
|
taskId,
|
|
1148
2051
|
task: params.task,
|
|
@@ -1150,11 +2053,23 @@ var SubagentManager = class {
|
|
|
1150
2053
|
origin
|
|
1151
2054
|
});
|
|
1152
2055
|
this.runningTasks.set(taskId, background);
|
|
1153
|
-
background.finally(() =>
|
|
2056
|
+
background.finally(() => {
|
|
2057
|
+
this.runningTasks.delete(taskId);
|
|
2058
|
+
const run = this.runs.get(taskId);
|
|
2059
|
+
if (run && run.status === "running") {
|
|
2060
|
+
run.status = run.cancelled ? "cancelled" : "done";
|
|
2061
|
+
run.doneAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2062
|
+
}
|
|
2063
|
+
this.steerQueue.delete(taskId);
|
|
2064
|
+
});
|
|
1154
2065
|
return `Subagent [${displayLabel}] started (id: ${taskId}). I'll notify you when it completes.`;
|
|
1155
2066
|
}
|
|
1156
2067
|
async runSubagent(params) {
|
|
1157
2068
|
try {
|
|
2069
|
+
const run = this.runs.get(params.taskId);
|
|
2070
|
+
if (run?.cancelled) {
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
1158
2073
|
const tools = new ToolRegistry();
|
|
1159
2074
|
const allowedDir = this.options.restrictToWorkspace ? this.options.workspace : void 0;
|
|
1160
2075
|
tools.register(new ReadFileTool(allowedDir));
|
|
@@ -1178,7 +2093,13 @@ var SubagentManager = class {
|
|
|
1178
2093
|
let finalResult = null;
|
|
1179
2094
|
while (iteration < 15) {
|
|
1180
2095
|
iteration += 1;
|
|
1181
|
-
const
|
|
2096
|
+
const queued = this.steerQueue.get(params.taskId);
|
|
2097
|
+
if (queued && queued.length) {
|
|
2098
|
+
for (const note of queued.splice(0, queued.length)) {
|
|
2099
|
+
messages.push({ role: "user", content: `Steer: ${note}` });
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const response = await this.options.providerManager.get().chat({
|
|
1182
2103
|
messages,
|
|
1183
2104
|
tools: tools.getDefinitions(),
|
|
1184
2105
|
model: this.options.model
|
|
@@ -1201,25 +2122,38 @@ var SubagentManager = class {
|
|
|
1201
2122
|
finalResult = response.content ?? "";
|
|
1202
2123
|
break;
|
|
1203
2124
|
}
|
|
2125
|
+
if (this.runs.get(params.taskId)?.cancelled) {
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
1204
2128
|
}
|
|
1205
2129
|
if (!finalResult) {
|
|
1206
2130
|
finalResult = "Task completed but no final response was generated.";
|
|
1207
2131
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
2132
|
+
const runAfter = this.runs.get(params.taskId);
|
|
2133
|
+
if (runAfter && !runAfter.cancelled) {
|
|
2134
|
+
runAfter.status = "done";
|
|
2135
|
+
runAfter.doneAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2136
|
+
await this.announceResult({
|
|
2137
|
+
label: params.label,
|
|
2138
|
+
task: params.task,
|
|
2139
|
+
result: finalResult,
|
|
2140
|
+
origin: params.origin,
|
|
2141
|
+
status: "ok"
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
1215
2144
|
} catch (err) {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
2145
|
+
const runAfter = this.runs.get(params.taskId);
|
|
2146
|
+
if (runAfter && !runAfter.cancelled) {
|
|
2147
|
+
runAfter.status = "error";
|
|
2148
|
+
runAfter.doneAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2149
|
+
await this.announceResult({
|
|
2150
|
+
label: params.label,
|
|
2151
|
+
task: params.task,
|
|
2152
|
+
result: `Error: ${String(err)}`,
|
|
2153
|
+
origin: params.origin,
|
|
2154
|
+
status: "error"
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
1223
2157
|
}
|
|
1224
2158
|
}
|
|
1225
2159
|
async announceResult(params) {
|
|
@@ -1276,11 +2210,43 @@ When you have completed the task, provide a clear summary of your findings or ac
|
|
|
1276
2210
|
getRunningCount() {
|
|
1277
2211
|
return this.runningTasks.size;
|
|
1278
2212
|
}
|
|
2213
|
+
listRuns() {
|
|
2214
|
+
return Array.from(this.runs.values()).map((run) => ({
|
|
2215
|
+
id: run.id,
|
|
2216
|
+
label: run.label,
|
|
2217
|
+
status: run.status,
|
|
2218
|
+
startedAt: run.startedAt,
|
|
2219
|
+
doneAt: run.doneAt
|
|
2220
|
+
}));
|
|
2221
|
+
}
|
|
2222
|
+
steerRun(id, note) {
|
|
2223
|
+
const run = this.runs.get(id);
|
|
2224
|
+
if (!run || run.cancelled || run.status !== "running") {
|
|
2225
|
+
return false;
|
|
2226
|
+
}
|
|
2227
|
+
const queue = this.steerQueue.get(id);
|
|
2228
|
+
if (!queue) {
|
|
2229
|
+
return false;
|
|
2230
|
+
}
|
|
2231
|
+
queue.push(note);
|
|
2232
|
+
return true;
|
|
2233
|
+
}
|
|
2234
|
+
cancelRun(id) {
|
|
2235
|
+
const run = this.runs.get(id);
|
|
2236
|
+
if (!run) {
|
|
2237
|
+
return false;
|
|
2238
|
+
}
|
|
2239
|
+
run.cancelled = true;
|
|
2240
|
+
run.status = "cancelled";
|
|
2241
|
+
run.doneAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2242
|
+
this.steerQueue.delete(id);
|
|
2243
|
+
return true;
|
|
2244
|
+
}
|
|
1279
2245
|
};
|
|
1280
2246
|
|
|
1281
2247
|
// src/session/manager.ts
|
|
1282
|
-
import { readFileSync as
|
|
1283
|
-
import { join as
|
|
2248
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync7, readdirSync as readdirSync5, unlinkSync } from "fs";
|
|
2249
|
+
import { join as join5 } from "path";
|
|
1284
2250
|
var SessionManager = class {
|
|
1285
2251
|
constructor(workspace) {
|
|
1286
2252
|
this.workspace = workspace;
|
|
@@ -1290,7 +2256,7 @@ var SessionManager = class {
|
|
|
1290
2256
|
cache = /* @__PURE__ */ new Map();
|
|
1291
2257
|
getSessionPath(key) {
|
|
1292
2258
|
const safeKey = safeFilename(key.replace(/:/g, "_"));
|
|
1293
|
-
return
|
|
2259
|
+
return join5(this.sessionsDir, `${safeKey}.jsonl`);
|
|
1294
2260
|
}
|
|
1295
2261
|
getOrCreate(key) {
|
|
1296
2262
|
const cached = this.cache.get(key);
|
|
@@ -1308,6 +2274,17 @@ var SessionManager = class {
|
|
|
1308
2274
|
this.cache.set(key, session);
|
|
1309
2275
|
return session;
|
|
1310
2276
|
}
|
|
2277
|
+
getIfExists(key) {
|
|
2278
|
+
const cached = this.cache.get(key);
|
|
2279
|
+
if (cached) {
|
|
2280
|
+
return cached;
|
|
2281
|
+
}
|
|
2282
|
+
const loaded = this.load(key);
|
|
2283
|
+
if (loaded) {
|
|
2284
|
+
this.cache.set(key, loaded);
|
|
2285
|
+
}
|
|
2286
|
+
return loaded;
|
|
2287
|
+
}
|
|
1311
2288
|
addMessage(session, role, content, extra = {}) {
|
|
1312
2289
|
const msg = {
|
|
1313
2290
|
role,
|
|
@@ -1328,11 +2305,11 @@ var SessionManager = class {
|
|
|
1328
2305
|
}
|
|
1329
2306
|
load(key) {
|
|
1330
2307
|
const path = this.getSessionPath(key);
|
|
1331
|
-
if (!
|
|
2308
|
+
if (!existsSync7(path)) {
|
|
1332
2309
|
return null;
|
|
1333
2310
|
}
|
|
1334
2311
|
try {
|
|
1335
|
-
const lines =
|
|
2312
|
+
const lines = readFileSync6(path, "utf-8").split("\n").filter(Boolean);
|
|
1336
2313
|
const messages = [];
|
|
1337
2314
|
let metadata = {};
|
|
1338
2315
|
let createdAt = /* @__PURE__ */ new Date();
|
|
@@ -1378,7 +2355,7 @@ var SessionManager = class {
|
|
|
1378
2355
|
delete(key) {
|
|
1379
2356
|
this.cache.delete(key);
|
|
1380
2357
|
const path = this.getSessionPath(key);
|
|
1381
|
-
if (
|
|
2358
|
+
if (existsSync7(path)) {
|
|
1382
2359
|
unlinkSync(path);
|
|
1383
2360
|
return true;
|
|
1384
2361
|
}
|
|
@@ -1386,12 +2363,12 @@ var SessionManager = class {
|
|
|
1386
2363
|
}
|
|
1387
2364
|
listSessions() {
|
|
1388
2365
|
const sessions = [];
|
|
1389
|
-
for (const entry of
|
|
2366
|
+
for (const entry of readdirSync5(this.sessionsDir, { withFileTypes: true })) {
|
|
1390
2367
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
|
|
1391
2368
|
continue;
|
|
1392
2369
|
}
|
|
1393
|
-
const path =
|
|
1394
|
-
const firstLine =
|
|
2370
|
+
const path = join5(this.sessionsDir, entry.name);
|
|
2371
|
+
const firstLine = readFileSync6(path, "utf-8").split("\n")[0];
|
|
1395
2372
|
if (!firstLine) {
|
|
1396
2373
|
continue;
|
|
1397
2374
|
}
|
|
@@ -1402,7 +2379,8 @@ var SessionManager = class {
|
|
|
1402
2379
|
key: entry.name.replace(/\.jsonl$/, "").replace(/_/g, ":"),
|
|
1403
2380
|
created_at: data.created_at,
|
|
1404
2381
|
updated_at: data.updated_at,
|
|
1405
|
-
path
|
|
2382
|
+
path,
|
|
2383
|
+
metadata: data.metadata ?? {}
|
|
1406
2384
|
});
|
|
1407
2385
|
}
|
|
1408
2386
|
} catch {
|
|
@@ -1413,18 +2391,34 @@ var SessionManager = class {
|
|
|
1413
2391
|
}
|
|
1414
2392
|
};
|
|
1415
2393
|
|
|
2394
|
+
// src/agent/tokens.ts
|
|
2395
|
+
var SILENT_REPLY_TOKEN = "NO_REPLY";
|
|
2396
|
+
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2397
|
+
function isSilentReplyText(text, token = SILENT_REPLY_TOKEN) {
|
|
2398
|
+
if (!text) {
|
|
2399
|
+
return false;
|
|
2400
|
+
}
|
|
2401
|
+
const escaped = escapeRegExp(token);
|
|
2402
|
+
const prefix = new RegExp(`^\\s*${escaped}(?=$|\\W)`);
|
|
2403
|
+
if (prefix.test(text)) {
|
|
2404
|
+
return true;
|
|
2405
|
+
}
|
|
2406
|
+
const suffix = new RegExp(`\\b${escaped}\\b\\W*$`);
|
|
2407
|
+
return suffix.test(text);
|
|
2408
|
+
}
|
|
2409
|
+
|
|
1416
2410
|
// src/agent/loop.ts
|
|
1417
2411
|
var AgentLoop = class {
|
|
1418
2412
|
constructor(options) {
|
|
1419
2413
|
this.options = options;
|
|
1420
|
-
this.context = new ContextBuilder(options.workspace);
|
|
2414
|
+
this.context = new ContextBuilder(options.workspace, options.contextConfig);
|
|
1421
2415
|
this.sessions = options.sessionManager ?? new SessionManager(options.workspace);
|
|
1422
2416
|
this.tools = new ToolRegistry();
|
|
1423
2417
|
this.subagents = new SubagentManager({
|
|
1424
|
-
|
|
2418
|
+
providerManager: options.providerManager,
|
|
1425
2419
|
workspace: options.workspace,
|
|
1426
2420
|
bus: options.bus,
|
|
1427
|
-
model: options.model ?? options.
|
|
2421
|
+
model: options.model ?? options.providerManager.get().getDefaultModel(),
|
|
1428
2422
|
braveApiKey: options.braveApiKey ?? void 0,
|
|
1429
2423
|
execConfig: options.execConfig ?? { timeout: 60 },
|
|
1430
2424
|
restrictToWorkspace: options.restrictToWorkspace ?? false
|
|
@@ -1455,6 +2449,13 @@ var AgentLoop = class {
|
|
|
1455
2449
|
this.tools.register(messageTool);
|
|
1456
2450
|
const spawnTool = new SpawnTool(this.subagents);
|
|
1457
2451
|
this.tools.register(spawnTool);
|
|
2452
|
+
this.tools.register(new SessionsListTool(this.sessions));
|
|
2453
|
+
this.tools.register(new SessionsHistoryTool(this.sessions));
|
|
2454
|
+
this.tools.register(new SessionsSendTool(this.sessions, this.options.bus));
|
|
2455
|
+
this.tools.register(new MemorySearchTool(this.options.workspace));
|
|
2456
|
+
this.tools.register(new MemoryGetTool(this.options.workspace));
|
|
2457
|
+
this.tools.register(new SubagentsTool(this.subagents));
|
|
2458
|
+
this.tools.register(new GatewayTool(this.options.gatewayController));
|
|
1458
2459
|
if (this.options.cronService) {
|
|
1459
2460
|
const cronTool = new CronTool(this.options.cronService);
|
|
1460
2461
|
this.tools.register(cronTool);
|
|
@@ -1502,6 +2503,20 @@ var AgentLoop = class {
|
|
|
1502
2503
|
}
|
|
1503
2504
|
const sessionKey = sessionKeyOverride ?? `${msg.channel}:${msg.chatId}`;
|
|
1504
2505
|
const session = this.sessions.getOrCreate(sessionKey);
|
|
2506
|
+
const messageId = msg.metadata?.message_id;
|
|
2507
|
+
if (messageId) {
|
|
2508
|
+
session.metadata.last_message_id = messageId;
|
|
2509
|
+
}
|
|
2510
|
+
const sessionLabel = msg.metadata?.session_label;
|
|
2511
|
+
if (sessionLabel) {
|
|
2512
|
+
session.metadata.label = sessionLabel;
|
|
2513
|
+
}
|
|
2514
|
+
session.metadata.last_channel = msg.channel;
|
|
2515
|
+
session.metadata.last_to = msg.chatId;
|
|
2516
|
+
const accountId = msg.metadata?.account_id ?? msg.metadata?.accountId;
|
|
2517
|
+
if (accountId) {
|
|
2518
|
+
session.metadata.last_account_id = accountId;
|
|
2519
|
+
}
|
|
1505
2520
|
const messageTool = this.tools.get("message");
|
|
1506
2521
|
if (messageTool instanceof MessageTool) {
|
|
1507
2522
|
messageTool.setContext(msg.channel, msg.chatId);
|
|
@@ -1519,14 +2534,15 @@ var AgentLoop = class {
|
|
|
1519
2534
|
currentMessage: msg.content,
|
|
1520
2535
|
media: msg.media,
|
|
1521
2536
|
channel: msg.channel,
|
|
1522
|
-
chatId: msg.chatId
|
|
2537
|
+
chatId: msg.chatId,
|
|
2538
|
+
sessionKey
|
|
1523
2539
|
});
|
|
1524
2540
|
let iteration = 0;
|
|
1525
2541
|
let finalContent = null;
|
|
1526
2542
|
const maxIterations = this.options.maxIterations ?? 20;
|
|
1527
2543
|
while (iteration < maxIterations) {
|
|
1528
2544
|
iteration += 1;
|
|
1529
|
-
const response = await this.options.
|
|
2545
|
+
const response = await this.options.providerManager.get().chat({
|
|
1530
2546
|
messages,
|
|
1531
2547
|
tools: this.tools.getDefinitions(),
|
|
1532
2548
|
model: this.options.model ?? void 0
|
|
@@ -1553,6 +2569,13 @@ var AgentLoop = class {
|
|
|
1553
2569
|
if (!finalContent) {
|
|
1554
2570
|
finalContent = "I've completed processing but have no response to give.";
|
|
1555
2571
|
}
|
|
2572
|
+
const { content: cleanedContent, replyTo } = parseReplyTags(finalContent, messageId);
|
|
2573
|
+
finalContent = cleanedContent;
|
|
2574
|
+
if (isSilentReplyText(finalContent, SILENT_REPLY_TOKEN)) {
|
|
2575
|
+
this.sessions.addMessage(session, "user", msg.content);
|
|
2576
|
+
this.sessions.save(session);
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
1556
2579
|
this.sessions.addMessage(session, "user", msg.content);
|
|
1557
2580
|
this.sessions.addMessage(session, "assistant", finalContent);
|
|
1558
2581
|
this.sessions.save(session);
|
|
@@ -1560,6 +2583,7 @@ var AgentLoop = class {
|
|
|
1560
2583
|
channel: msg.channel,
|
|
1561
2584
|
chatId: msg.chatId,
|
|
1562
2585
|
content: finalContent,
|
|
2586
|
+
replyTo,
|
|
1563
2587
|
media: [],
|
|
1564
2588
|
metadata: msg.metadata ?? {}
|
|
1565
2589
|
};
|
|
@@ -1584,14 +2608,15 @@ var AgentLoop = class {
|
|
|
1584
2608
|
history: this.sessions.getHistory(session),
|
|
1585
2609
|
currentMessage: msg.content,
|
|
1586
2610
|
channel: originChannel,
|
|
1587
|
-
chatId: originChatId
|
|
2611
|
+
chatId: originChatId,
|
|
2612
|
+
sessionKey
|
|
1588
2613
|
});
|
|
1589
2614
|
let iteration = 0;
|
|
1590
2615
|
let finalContent = null;
|
|
1591
2616
|
const maxIterations = this.options.maxIterations ?? 20;
|
|
1592
2617
|
while (iteration < maxIterations) {
|
|
1593
2618
|
iteration += 1;
|
|
1594
|
-
const response = await this.options.
|
|
2619
|
+
const response = await this.options.providerManager.get().chat({
|
|
1595
2620
|
messages,
|
|
1596
2621
|
tools: this.tools.getDefinitions(),
|
|
1597
2622
|
model: this.options.model ?? void 0
|
|
@@ -1618,6 +2643,11 @@ var AgentLoop = class {
|
|
|
1618
2643
|
if (!finalContent) {
|
|
1619
2644
|
finalContent = "Background task completed.";
|
|
1620
2645
|
}
|
|
2646
|
+
const { content: cleanedContent, replyTo } = parseReplyTags(finalContent, void 0);
|
|
2647
|
+
finalContent = cleanedContent;
|
|
2648
|
+
if (isSilentReplyText(finalContent, SILENT_REPLY_TOKEN)) {
|
|
2649
|
+
return null;
|
|
2650
|
+
}
|
|
1621
2651
|
this.sessions.addMessage(session, "user", `[System: ${msg.senderId}] ${msg.content}`);
|
|
1622
2652
|
this.sessions.addMessage(session, "assistant", finalContent);
|
|
1623
2653
|
this.sessions.save(session);
|
|
@@ -1625,11 +2655,27 @@ var AgentLoop = class {
|
|
|
1625
2655
|
channel: originChannel,
|
|
1626
2656
|
chatId: originChatId,
|
|
1627
2657
|
content: finalContent,
|
|
2658
|
+
replyTo,
|
|
1628
2659
|
media: [],
|
|
1629
2660
|
metadata: {}
|
|
1630
2661
|
};
|
|
1631
2662
|
}
|
|
1632
2663
|
};
|
|
2664
|
+
function parseReplyTags(content, currentMessageId) {
|
|
2665
|
+
let replyTo;
|
|
2666
|
+
const replyCurrent = /\[\[\s*reply_to_current\s*\]\]/gi;
|
|
2667
|
+
if (replyCurrent.test(content)) {
|
|
2668
|
+
replyTo = currentMessageId;
|
|
2669
|
+
content = content.replace(replyCurrent, "").trim();
|
|
2670
|
+
}
|
|
2671
|
+
const replyId = /\[\[\s*reply_to\s*:\s*([^\]]+?)\s*\]\]/i;
|
|
2672
|
+
const match = content.match(replyId);
|
|
2673
|
+
if (match && match[1]) {
|
|
2674
|
+
replyTo = match[1].trim();
|
|
2675
|
+
content = content.replace(replyId, "").trim();
|
|
2676
|
+
}
|
|
2677
|
+
return { content, replyTo };
|
|
2678
|
+
}
|
|
1633
2679
|
|
|
1634
2680
|
// src/bus/queue.ts
|
|
1635
2681
|
var AsyncQueue = class {
|
|
@@ -1647,8 +2693,8 @@ var AsyncQueue = class {
|
|
|
1647
2693
|
if (this.items.length > 0) {
|
|
1648
2694
|
return this.items.shift();
|
|
1649
2695
|
}
|
|
1650
|
-
return new Promise((
|
|
1651
|
-
this.waiters.push(
|
|
2696
|
+
return new Promise((resolve6) => {
|
|
2697
|
+
this.waiters.push(resolve6);
|
|
1652
2698
|
});
|
|
1653
2699
|
}
|
|
1654
2700
|
size() {
|
|
@@ -1798,9 +2844,9 @@ var BaseChannel = class {
|
|
|
1798
2844
|
};
|
|
1799
2845
|
|
|
1800
2846
|
// src/providers/transcription.ts
|
|
1801
|
-
import { createReadStream, existsSync as
|
|
2847
|
+
import { createReadStream, existsSync as existsSync8 } from "fs";
|
|
1802
2848
|
import { basename } from "path";
|
|
1803
|
-
import { FormData, fetch as
|
|
2849
|
+
import { FormData, fetch as fetch3 } from "undici";
|
|
1804
2850
|
var GroqTranscriptionProvider = class {
|
|
1805
2851
|
apiKey;
|
|
1806
2852
|
apiUrl = "https://api.groq.com/openai/v1/audio/transcriptions";
|
|
@@ -1811,13 +2857,13 @@ var GroqTranscriptionProvider = class {
|
|
|
1811
2857
|
if (!this.apiKey) {
|
|
1812
2858
|
return "";
|
|
1813
2859
|
}
|
|
1814
|
-
if (!
|
|
2860
|
+
if (!existsSync8(filePath)) {
|
|
1815
2861
|
return "";
|
|
1816
2862
|
}
|
|
1817
2863
|
const form = new FormData();
|
|
1818
2864
|
form.append("file", createReadStream(filePath), basename(filePath));
|
|
1819
2865
|
form.append("model", "whisper-large-v3");
|
|
1820
|
-
const response = await
|
|
2866
|
+
const response = await fetch3(this.apiUrl, {
|
|
1821
2867
|
method: "POST",
|
|
1822
2868
|
headers: {
|
|
1823
2869
|
Authorization: `Bearer ${this.apiKey}`
|
|
@@ -1833,7 +2879,7 @@ var GroqTranscriptionProvider = class {
|
|
|
1833
2879
|
};
|
|
1834
2880
|
|
|
1835
2881
|
// src/channels/telegram.ts
|
|
1836
|
-
import { join as
|
|
2882
|
+
import { join as join6 } from "path";
|
|
1837
2883
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
1838
2884
|
var BOT_COMMANDS = [
|
|
1839
2885
|
{ command: "start", description: "Start the bot" },
|
|
@@ -1920,10 +2966,20 @@ Just send me a text message to chat!`;
|
|
|
1920
2966
|
}
|
|
1921
2967
|
this.stopTyping(msg.chatId);
|
|
1922
2968
|
const htmlContent = markdownToTelegramHtml(msg.content ?? "");
|
|
2969
|
+
const silent = msg.metadata?.silent === true;
|
|
2970
|
+
const replyTo = msg.replyTo ? Number(msg.replyTo) : void 0;
|
|
2971
|
+
const options = {
|
|
2972
|
+
parse_mode: "HTML",
|
|
2973
|
+
...replyTo ? { reply_to_message_id: replyTo } : {},
|
|
2974
|
+
...silent ? { disable_notification: true } : {}
|
|
2975
|
+
};
|
|
1923
2976
|
try {
|
|
1924
|
-
await this.bot.sendMessage(Number(msg.chatId), htmlContent,
|
|
2977
|
+
await this.bot.sendMessage(Number(msg.chatId), htmlContent, options);
|
|
1925
2978
|
} catch {
|
|
1926
|
-
await this.bot.sendMessage(Number(msg.chatId), msg.content ?? ""
|
|
2979
|
+
await this.bot.sendMessage(Number(msg.chatId), msg.content ?? "", {
|
|
2980
|
+
...replyTo ? { reply_to_message_id: replyTo } : {},
|
|
2981
|
+
...silent ? { disable_notification: true } : {}
|
|
2982
|
+
});
|
|
1927
2983
|
}
|
|
1928
2984
|
}
|
|
1929
2985
|
async handleIncoming(message) {
|
|
@@ -1945,7 +3001,7 @@ Just send me a text message to chat!`;
|
|
|
1945
3001
|
}
|
|
1946
3002
|
const { fileId, mediaType, mimeType } = resolveMedia(message);
|
|
1947
3003
|
if (fileId && mediaType) {
|
|
1948
|
-
const mediaDir =
|
|
3004
|
+
const mediaDir = join6(getDataPath(), "media");
|
|
1949
3005
|
mkdirSync2(mediaDir, { recursive: true });
|
|
1950
3006
|
const extension = getExtension(mediaType, mimeType);
|
|
1951
3007
|
const downloaded = await this.bot.downloadFile(fileId, mediaDir);
|
|
@@ -2077,7 +3133,7 @@ var WhatsAppChannel = class extends BaseChannel {
|
|
|
2077
3133
|
const bridgeUrl = this.config.bridgeUrl;
|
|
2078
3134
|
while (this.running) {
|
|
2079
3135
|
try {
|
|
2080
|
-
await new Promise((
|
|
3136
|
+
await new Promise((resolve6, reject) => {
|
|
2081
3137
|
const ws = new WebSocket(bridgeUrl);
|
|
2082
3138
|
this.ws = ws;
|
|
2083
3139
|
ws.on("open", () => {
|
|
@@ -2090,7 +3146,7 @@ var WhatsAppChannel = class extends BaseChannel {
|
|
|
2090
3146
|
ws.on("close", () => {
|
|
2091
3147
|
this.connected = false;
|
|
2092
3148
|
this.ws = null;
|
|
2093
|
-
|
|
3149
|
+
resolve6();
|
|
2094
3150
|
});
|
|
2095
3151
|
ws.on("error", (_err) => {
|
|
2096
3152
|
this.connected = false;
|
|
@@ -2173,17 +3229,18 @@ var WhatsAppChannel = class extends BaseChannel {
|
|
|
2173
3229
|
}
|
|
2174
3230
|
};
|
|
2175
3231
|
function sleep(ms) {
|
|
2176
|
-
return new Promise((
|
|
3232
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
2177
3233
|
}
|
|
2178
3234
|
|
|
2179
3235
|
// src/channels/discord.ts
|
|
2180
3236
|
import {
|
|
2181
3237
|
Client as Client2,
|
|
2182
3238
|
GatewayIntentBits,
|
|
2183
|
-
Partials
|
|
3239
|
+
Partials,
|
|
3240
|
+
MessageFlags
|
|
2184
3241
|
} from "discord.js";
|
|
2185
|
-
import { fetch as
|
|
2186
|
-
import { join as
|
|
3242
|
+
import { fetch as fetch4 } from "undici";
|
|
3243
|
+
import { join as join7 } from "path";
|
|
2187
3244
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2188
3245
|
var MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
|
|
2189
3246
|
var DiscordChannel = class extends BaseChannel {
|
|
@@ -2237,6 +3294,9 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
2237
3294
|
if (msg.replyTo) {
|
|
2238
3295
|
payload.reply = { messageReference: msg.replyTo };
|
|
2239
3296
|
}
|
|
3297
|
+
if (msg.metadata?.silent === true) {
|
|
3298
|
+
payload.flags = MessageFlags.SuppressNotifications;
|
|
3299
|
+
}
|
|
2240
3300
|
await textChannel.send(payload);
|
|
2241
3301
|
}
|
|
2242
3302
|
async handleIncoming(message) {
|
|
@@ -2254,7 +3314,7 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
2254
3314
|
contentParts.push(message.content);
|
|
2255
3315
|
}
|
|
2256
3316
|
if (message.attachments.size) {
|
|
2257
|
-
const mediaDir =
|
|
3317
|
+
const mediaDir = join7(getDataPath(), "media");
|
|
2258
3318
|
mkdirSync3(mediaDir, { recursive: true });
|
|
2259
3319
|
for (const attachment of message.attachments.values()) {
|
|
2260
3320
|
if (attachment.size && attachment.size > MAX_ATTACHMENT_BYTES) {
|
|
@@ -2262,14 +3322,14 @@ var DiscordChannel = class extends BaseChannel {
|
|
|
2262
3322
|
continue;
|
|
2263
3323
|
}
|
|
2264
3324
|
try {
|
|
2265
|
-
const res = await
|
|
3325
|
+
const res = await fetch4(attachment.url);
|
|
2266
3326
|
if (!res.ok) {
|
|
2267
3327
|
contentParts.push(`[attachment: ${attachment.name ?? "file"} - download failed]`);
|
|
2268
3328
|
continue;
|
|
2269
3329
|
}
|
|
2270
3330
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
2271
3331
|
const filename = `${attachment.id}_${(attachment.name ?? "file").replace(/\//g, "_")}`;
|
|
2272
|
-
const filePath =
|
|
3332
|
+
const filePath = join7(mediaDir, filename);
|
|
2273
3333
|
writeFileSync4(filePath, buffer);
|
|
2274
3334
|
mediaPaths.push(filePath);
|
|
2275
3335
|
contentParts.push(`[attachment: ${filePath}]`);
|
|
@@ -2515,9 +3575,9 @@ function parseMdTable(tableText) {
|
|
|
2515
3575
|
|
|
2516
3576
|
// src/channels/mochat.ts
|
|
2517
3577
|
import { io } from "socket.io-client";
|
|
2518
|
-
import { fetch as
|
|
2519
|
-
import { join as
|
|
2520
|
-
import { mkdirSync as mkdirSync4, existsSync as
|
|
3578
|
+
import { fetch as fetch5 } from "undici";
|
|
3579
|
+
import { join as join8 } from "path";
|
|
3580
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
2521
3581
|
var MAX_SEEN_MESSAGE_IDS = 2e3;
|
|
2522
3582
|
var CURSOR_SAVE_DEBOUNCE_MS = 500;
|
|
2523
3583
|
var AsyncLock = class {
|
|
@@ -2536,8 +3596,8 @@ var MochatChannel = class extends BaseChannel {
|
|
|
2536
3596
|
socket = null;
|
|
2537
3597
|
wsConnected = false;
|
|
2538
3598
|
wsReady = false;
|
|
2539
|
-
stateDir =
|
|
2540
|
-
cursorPath =
|
|
3599
|
+
stateDir = join8(getDataPath(), "mochat");
|
|
3600
|
+
cursorPath = join8(this.stateDir, "session_cursors.json");
|
|
2541
3601
|
sessionCursor = {};
|
|
2542
3602
|
cursorSaveTimer = null;
|
|
2543
3603
|
sessionSet = /* @__PURE__ */ new Set();
|
|
@@ -2723,15 +3783,15 @@ var MochatChannel = class extends BaseChannel {
|
|
|
2723
3783
|
socket.on(eventName, notifyHandler(eventName));
|
|
2724
3784
|
});
|
|
2725
3785
|
this.socket = socket;
|
|
2726
|
-
return new Promise((
|
|
2727
|
-
const timer = setTimeout(() =>
|
|
3786
|
+
return new Promise((resolve6) => {
|
|
3787
|
+
const timer = setTimeout(() => resolve6(false), timeout);
|
|
2728
3788
|
socket.once("connect", () => {
|
|
2729
3789
|
clearTimeout(timer);
|
|
2730
|
-
|
|
3790
|
+
resolve6(true);
|
|
2731
3791
|
});
|
|
2732
3792
|
socket.once("connect_error", () => {
|
|
2733
3793
|
clearTimeout(timer);
|
|
2734
|
-
|
|
3794
|
+
resolve6(false);
|
|
2735
3795
|
});
|
|
2736
3796
|
});
|
|
2737
3797
|
}
|
|
@@ -2793,17 +3853,17 @@ var MochatChannel = class extends BaseChannel {
|
|
|
2793
3853
|
if (!this.socket) {
|
|
2794
3854
|
return { result: false, message: "socket not connected" };
|
|
2795
3855
|
}
|
|
2796
|
-
return new Promise((
|
|
3856
|
+
return new Promise((resolve6) => {
|
|
2797
3857
|
this.socket?.timeout(1e4).emit(eventName, payload, (err, response) => {
|
|
2798
3858
|
if (err) {
|
|
2799
|
-
|
|
3859
|
+
resolve6({ result: false, message: String(err) });
|
|
2800
3860
|
return;
|
|
2801
3861
|
}
|
|
2802
3862
|
if (response && typeof response === "object") {
|
|
2803
|
-
|
|
3863
|
+
resolve6(response);
|
|
2804
3864
|
return;
|
|
2805
3865
|
}
|
|
2806
|
-
|
|
3866
|
+
resolve6({ result: true, data: response });
|
|
2807
3867
|
});
|
|
2808
3868
|
});
|
|
2809
3869
|
}
|
|
@@ -3226,11 +4286,11 @@ var MochatChannel = class extends BaseChannel {
|
|
|
3226
4286
|
}
|
|
3227
4287
|
}
|
|
3228
4288
|
async loadSessionCursors() {
|
|
3229
|
-
if (!
|
|
4289
|
+
if (!existsSync9(this.cursorPath)) {
|
|
3230
4290
|
return;
|
|
3231
4291
|
}
|
|
3232
4292
|
try {
|
|
3233
|
-
const raw =
|
|
4293
|
+
const raw = readFileSync7(this.cursorPath, "utf-8");
|
|
3234
4294
|
const data = JSON.parse(raw);
|
|
3235
4295
|
const cursors = data.cursors;
|
|
3236
4296
|
if (cursors && typeof cursors === "object") {
|
|
@@ -3259,7 +4319,7 @@ var MochatChannel = class extends BaseChannel {
|
|
|
3259
4319
|
}
|
|
3260
4320
|
async postJson(path, payload) {
|
|
3261
4321
|
const url = `${this.config.baseUrl.trim().replace(/\/$/, "")}${path}`;
|
|
3262
|
-
const response = await
|
|
4322
|
+
const response = await fetch5(url, {
|
|
3263
4323
|
method: "POST",
|
|
3264
4324
|
headers: {
|
|
3265
4325
|
"content-type": "application/json",
|
|
@@ -3459,12 +4519,12 @@ function readGroupId(metadata) {
|
|
|
3459
4519
|
return null;
|
|
3460
4520
|
}
|
|
3461
4521
|
function sleep2(ms) {
|
|
3462
|
-
return new Promise((
|
|
4522
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
3463
4523
|
}
|
|
3464
4524
|
|
|
3465
4525
|
// src/channels/dingtalk.ts
|
|
3466
4526
|
import { DWClient, EventAck, TOPIC_ROBOT } from "dingtalk-stream";
|
|
3467
|
-
import { fetch as
|
|
4527
|
+
import { fetch as fetch6 } from "undici";
|
|
3468
4528
|
var DingTalkChannel = class extends BaseChannel {
|
|
3469
4529
|
name = "dingtalk";
|
|
3470
4530
|
client = null;
|
|
@@ -3511,7 +4571,7 @@ var DingTalkChannel = class extends BaseChannel {
|
|
|
3511
4571
|
title: `${APP_TITLE} Reply`
|
|
3512
4572
|
})
|
|
3513
4573
|
};
|
|
3514
|
-
const response = await
|
|
4574
|
+
const response = await fetch6(url, {
|
|
3515
4575
|
method: "POST",
|
|
3516
4576
|
headers: {
|
|
3517
4577
|
"content-type": "application/json",
|
|
@@ -3565,7 +4625,7 @@ var DingTalkChannel = class extends BaseChannel {
|
|
|
3565
4625
|
appKey: this.config.clientId,
|
|
3566
4626
|
appSecret: this.config.clientSecret
|
|
3567
4627
|
};
|
|
3568
|
-
const response = await
|
|
4628
|
+
const response = await fetch6(url, {
|
|
3569
4629
|
method: "POST",
|
|
3570
4630
|
headers: { "content-type": "application/json" },
|
|
3571
4631
|
body: JSON.stringify(payload)
|
|
@@ -3589,7 +4649,7 @@ var DingTalkChannel = class extends BaseChannel {
|
|
|
3589
4649
|
import { ImapFlow } from "imapflow";
|
|
3590
4650
|
import { simpleParser } from "mailparser";
|
|
3591
4651
|
import nodemailer from "nodemailer";
|
|
3592
|
-
var sleep3 = (ms) => new Promise((
|
|
4652
|
+
var sleep3 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
3593
4653
|
var EmailChannel = class extends BaseChannel {
|
|
3594
4654
|
name = "email";
|
|
3595
4655
|
lastSubjectByChat = /* @__PURE__ */ new Map();
|
|
@@ -4186,8 +5246,8 @@ var ChannelManager = class {
|
|
|
4186
5246
|
};
|
|
4187
5247
|
|
|
4188
5248
|
// src/config/loader.ts
|
|
4189
|
-
import { readFileSync as
|
|
4190
|
-
import { resolve as
|
|
5249
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
5250
|
+
import { resolve as resolve5 } from "path";
|
|
4191
5251
|
import { z as z2 } from "zod";
|
|
4192
5252
|
|
|
4193
5253
|
// src/config/schema.ts
|
|
@@ -4257,7 +5317,10 @@ var PROVIDERS = [
|
|
|
4257
5317
|
detectByBaseKeyword: "",
|
|
4258
5318
|
defaultApiBase: "",
|
|
4259
5319
|
stripModelPrefix: false,
|
|
4260
|
-
modelOverrides: []
|
|
5320
|
+
modelOverrides: [],
|
|
5321
|
+
supportsWireApi: true,
|
|
5322
|
+
wireApiOptions: ["auto", "chat", "responses"],
|
|
5323
|
+
defaultWireApi: "auto"
|
|
4261
5324
|
},
|
|
4262
5325
|
{
|
|
4263
5326
|
name: "deepseek",
|
|
@@ -4549,13 +5612,39 @@ var AgentDefaultsSchema = z.object({
|
|
|
4549
5612
|
temperature: z.number().default(0.7),
|
|
4550
5613
|
maxToolIterations: z.number().int().default(20)
|
|
4551
5614
|
});
|
|
5615
|
+
var ContextBootstrapSchema = z.object({
|
|
5616
|
+
files: z.array(z.string()).default([
|
|
5617
|
+
"AGENTS.md",
|
|
5618
|
+
"SOUL.md",
|
|
5619
|
+
"USER.md",
|
|
5620
|
+
"IDENTITY.md",
|
|
5621
|
+
"TOOLS.md",
|
|
5622
|
+
"BOOT.md",
|
|
5623
|
+
"BOOTSTRAP.md",
|
|
5624
|
+
"HEARTBEAT.md"
|
|
5625
|
+
]),
|
|
5626
|
+
minimalFiles: z.array(z.string()).default(["AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"]),
|
|
5627
|
+
heartbeatFiles: z.array(z.string()).default(["HEARTBEAT.md"]),
|
|
5628
|
+
perFileChars: z.number().int().default(4e3),
|
|
5629
|
+
totalChars: z.number().int().default(12e3)
|
|
5630
|
+
});
|
|
5631
|
+
var ContextMemorySchema = z.object({
|
|
5632
|
+
enabled: z.boolean().default(true),
|
|
5633
|
+
maxChars: z.number().int().default(8e3)
|
|
5634
|
+
});
|
|
5635
|
+
var ContextConfigSchema = z.object({
|
|
5636
|
+
bootstrap: ContextBootstrapSchema.default({}),
|
|
5637
|
+
memory: ContextMemorySchema.default({})
|
|
5638
|
+
});
|
|
4552
5639
|
var AgentsConfigSchema = z.object({
|
|
4553
|
-
defaults: AgentDefaultsSchema.default({})
|
|
5640
|
+
defaults: AgentDefaultsSchema.default({}),
|
|
5641
|
+
context: ContextConfigSchema.default({})
|
|
4554
5642
|
});
|
|
4555
5643
|
var ProviderConfigSchema = z.object({
|
|
4556
5644
|
apiKey: z.string().default(""),
|
|
4557
5645
|
apiBase: z.string().nullable().default(null),
|
|
4558
|
-
extraHeaders: z.record(z.string()).nullable().default(null)
|
|
5646
|
+
extraHeaders: z.record(z.string()).nullable().default(null),
|
|
5647
|
+
wireApi: z.enum(["auto", "chat", "responses"]).default("auto")
|
|
4559
5648
|
});
|
|
4560
5649
|
var ProvidersConfigSchema = z.object({
|
|
4561
5650
|
anthropic: ProviderConfigSchema.default({}),
|
|
@@ -4649,16 +5738,16 @@ function getApiBase(config, model) {
|
|
|
4649
5738
|
|
|
4650
5739
|
// src/config/loader.ts
|
|
4651
5740
|
function getConfigPath() {
|
|
4652
|
-
return
|
|
5741
|
+
return resolve5(getDataPath(), "config.json");
|
|
4653
5742
|
}
|
|
4654
5743
|
function getDataDir() {
|
|
4655
5744
|
return getDataPath();
|
|
4656
5745
|
}
|
|
4657
5746
|
function loadConfig(configPath) {
|
|
4658
5747
|
const path = configPath ?? getConfigPath();
|
|
4659
|
-
if (
|
|
5748
|
+
if (existsSync10(path)) {
|
|
4660
5749
|
try {
|
|
4661
|
-
const raw =
|
|
5750
|
+
const raw = readFileSync8(path, "utf-8");
|
|
4662
5751
|
const data = JSON.parse(raw);
|
|
4663
5752
|
const migrated = migrateConfig(data);
|
|
4664
5753
|
return ConfigSchema.parse(migrated);
|
|
@@ -4671,7 +5760,7 @@ function loadConfig(configPath) {
|
|
|
4671
5760
|
}
|
|
4672
5761
|
function saveConfig(config, configPath) {
|
|
4673
5762
|
const path = configPath ?? getConfigPath();
|
|
4674
|
-
mkdirSync5(
|
|
5763
|
+
mkdirSync5(resolve5(path, ".."), { recursive: true });
|
|
4675
5764
|
writeFileSync6(path, JSON.stringify(config, null, 2));
|
|
4676
5765
|
}
|
|
4677
5766
|
function migrateConfig(data) {
|
|
@@ -4696,7 +5785,7 @@ var isPlainObject = (value) => {
|
|
|
4696
5785
|
};
|
|
4697
5786
|
var RELOAD_RULES = [
|
|
4698
5787
|
{ prefix: "channels", kind: "restart-channels" },
|
|
4699
|
-
{ prefix: "providers", kind: "
|
|
5788
|
+
{ prefix: "providers", kind: "reload-providers" },
|
|
4700
5789
|
{ prefix: "agents.defaults.model", kind: "restart-required" },
|
|
4701
5790
|
{ prefix: "agents.defaults.maxTokens", kind: "restart-required" },
|
|
4702
5791
|
{ prefix: "agents.defaults.temperature", kind: "restart-required" },
|
|
@@ -4745,6 +5834,7 @@ function buildReloadPlan(changedPaths) {
|
|
|
4745
5834
|
const plan = {
|
|
4746
5835
|
changedPaths,
|
|
4747
5836
|
restartChannels: false,
|
|
5837
|
+
reloadProviders: false,
|
|
4748
5838
|
restartRequired: [],
|
|
4749
5839
|
noopPaths: []
|
|
4750
5840
|
};
|
|
@@ -4758,6 +5848,10 @@ function buildReloadPlan(changedPaths) {
|
|
|
4758
5848
|
plan.restartChannels = true;
|
|
4759
5849
|
continue;
|
|
4760
5850
|
}
|
|
5851
|
+
if (rule.kind === "reload-providers") {
|
|
5852
|
+
plan.reloadProviders = true;
|
|
5853
|
+
continue;
|
|
5854
|
+
}
|
|
4761
5855
|
if (rule.kind === "restart-required") {
|
|
4762
5856
|
plan.restartRequired.push(path);
|
|
4763
5857
|
continue;
|
|
@@ -4768,7 +5862,7 @@ function buildReloadPlan(changedPaths) {
|
|
|
4768
5862
|
}
|
|
4769
5863
|
|
|
4770
5864
|
// src/cron/service.ts
|
|
4771
|
-
import { readFileSync as
|
|
5865
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
|
|
4772
5866
|
import { dirname as dirname2 } from "path";
|
|
4773
5867
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4774
5868
|
import cronParser from "cron-parser";
|
|
@@ -4806,9 +5900,9 @@ var CronService = class {
|
|
|
4806
5900
|
if (this.store) {
|
|
4807
5901
|
return this.store;
|
|
4808
5902
|
}
|
|
4809
|
-
if (
|
|
5903
|
+
if (existsSync11(this.storePath)) {
|
|
4810
5904
|
try {
|
|
4811
|
-
const data = JSON.parse(
|
|
5905
|
+
const data = JSON.parse(readFileSync9(this.storePath, "utf-8"));
|
|
4812
5906
|
const jobs = (data.jobs ?? []).map((job) => ({
|
|
4813
5907
|
id: String(job.id),
|
|
4814
5908
|
name: String(job.name),
|
|
@@ -5011,8 +6105,8 @@ var CronService = class {
|
|
|
5011
6105
|
};
|
|
5012
6106
|
|
|
5013
6107
|
// src/heartbeat/service.ts
|
|
5014
|
-
import { readFileSync as
|
|
5015
|
-
import { join as
|
|
6108
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
6109
|
+
import { join as join9 } from "path";
|
|
5016
6110
|
var DEFAULT_HEARTBEAT_INTERVAL_S = 30 * 60;
|
|
5017
6111
|
var HEARTBEAT_PROMPT = "Read HEARTBEAT.md in your workspace (if it exists).\nFollow any instructions or tasks listed there.\nIf nothing needs attention, reply with just: HEARTBEAT_OK";
|
|
5018
6112
|
var HEARTBEAT_OK_TOKEN = "HEARTBEAT_OK";
|
|
@@ -5040,12 +6134,12 @@ var HeartbeatService = class {
|
|
|
5040
6134
|
running = false;
|
|
5041
6135
|
timer = null;
|
|
5042
6136
|
get heartbeatFile() {
|
|
5043
|
-
return
|
|
6137
|
+
return join9(this.workspace, "HEARTBEAT.md");
|
|
5044
6138
|
}
|
|
5045
6139
|
readHeartbeatFile() {
|
|
5046
|
-
if (
|
|
6140
|
+
if (existsSync12(this.heartbeatFile)) {
|
|
5047
6141
|
try {
|
|
5048
|
-
return
|
|
6142
|
+
return readFileSync10(this.heartbeatFile, "utf-8");
|
|
5049
6143
|
} catch {
|
|
5050
6144
|
return null;
|
|
5051
6145
|
}
|
|
@@ -5107,10 +6201,12 @@ var OpenAICompatibleProvider = class extends LLMProvider {
|
|
|
5107
6201
|
client;
|
|
5108
6202
|
defaultModel;
|
|
5109
6203
|
extraHeaders;
|
|
6204
|
+
wireApi;
|
|
5110
6205
|
constructor(options) {
|
|
5111
6206
|
super(options.apiKey, options.apiBase);
|
|
5112
6207
|
this.defaultModel = options.defaultModel;
|
|
5113
6208
|
this.extraHeaders = options.extraHeaders ?? null;
|
|
6209
|
+
this.wireApi = options.wireApi ?? "auto";
|
|
5114
6210
|
this.client = new OpenAI({
|
|
5115
6211
|
apiKey: options.apiKey ?? void 0,
|
|
5116
6212
|
baseURL: options.apiBase ?? void 0,
|
|
@@ -5121,6 +6217,22 @@ var OpenAICompatibleProvider = class extends LLMProvider {
|
|
|
5121
6217
|
return this.defaultModel;
|
|
5122
6218
|
}
|
|
5123
6219
|
async chat(params) {
|
|
6220
|
+
if (this.wireApi === "chat") {
|
|
6221
|
+
return this.chatCompletions(params);
|
|
6222
|
+
}
|
|
6223
|
+
if (this.wireApi === "responses") {
|
|
6224
|
+
return this.chatResponses(params);
|
|
6225
|
+
}
|
|
6226
|
+
try {
|
|
6227
|
+
return await this.chatCompletions(params);
|
|
6228
|
+
} catch (error) {
|
|
6229
|
+
if (this.shouldFallbackToResponses(error)) {
|
|
6230
|
+
return await this.chatResponses(params);
|
|
6231
|
+
}
|
|
6232
|
+
throw error;
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
async chatCompletions(params) {
|
|
5124
6236
|
const model = params.model ?? this.defaultModel;
|
|
5125
6237
|
const temperature = params.temperature ?? 0.7;
|
|
5126
6238
|
const maxTokens = params.maxTokens ?? 4096;
|
|
@@ -5166,6 +6278,141 @@ var OpenAICompatibleProvider = class extends LLMProvider {
|
|
|
5166
6278
|
reasoningContent
|
|
5167
6279
|
};
|
|
5168
6280
|
}
|
|
6281
|
+
async chatResponses(params) {
|
|
6282
|
+
const model = params.model ?? this.defaultModel;
|
|
6283
|
+
const input = this.toResponsesInput(params.messages);
|
|
6284
|
+
const body = { model, input };
|
|
6285
|
+
if (params.tools && params.tools.length) {
|
|
6286
|
+
body.tools = params.tools;
|
|
6287
|
+
}
|
|
6288
|
+
const base = this.apiBase ?? "https://api.openai.com/v1";
|
|
6289
|
+
const responseUrl = new URL("responses", base.endsWith("/") ? base : `${base}/`);
|
|
6290
|
+
const response = await fetch(responseUrl.toString(), {
|
|
6291
|
+
method: "POST",
|
|
6292
|
+
headers: {
|
|
6293
|
+
"Authorization": this.apiKey ? `Bearer ${this.apiKey}` : "",
|
|
6294
|
+
"Content-Type": "application/json",
|
|
6295
|
+
...this.extraHeaders ?? {}
|
|
6296
|
+
},
|
|
6297
|
+
body: JSON.stringify(body)
|
|
6298
|
+
});
|
|
6299
|
+
if (!response.ok) {
|
|
6300
|
+
const text = await response.text();
|
|
6301
|
+
throw new Error(`Responses API failed (${response.status}): ${text.slice(0, 200)}`);
|
|
6302
|
+
}
|
|
6303
|
+
const responseAny = await response.json();
|
|
6304
|
+
const outputItems = responseAny.output ?? [];
|
|
6305
|
+
const toolCalls = [];
|
|
6306
|
+
const contentParts = [];
|
|
6307
|
+
let reasoningContent = null;
|
|
6308
|
+
for (const item of outputItems) {
|
|
6309
|
+
const itemAny = item;
|
|
6310
|
+
if (itemAny.type === "reasoning" && Array.isArray(itemAny.summary)) {
|
|
6311
|
+
const summaryText = itemAny.summary.map((entry) => typeof entry === "string" ? entry : String(entry.text ?? "")).filter(Boolean).join("\n");
|
|
6312
|
+
reasoningContent = summaryText || reasoningContent;
|
|
6313
|
+
}
|
|
6314
|
+
if (itemAny.type === "message" && Array.isArray(itemAny.content)) {
|
|
6315
|
+
for (const part of itemAny.content) {
|
|
6316
|
+
const partAny = part;
|
|
6317
|
+
if (partAny?.type === "output_text" || partAny?.type === "text") {
|
|
6318
|
+
const text = String(partAny?.text ?? "");
|
|
6319
|
+
if (text) {
|
|
6320
|
+
contentParts.push(text);
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
if (itemAny.type === "tool_call" || itemAny.type === "function_call") {
|
|
6326
|
+
const itemFunction = itemAny.function;
|
|
6327
|
+
const name = String(itemAny.name ?? itemFunction?.name ?? "");
|
|
6328
|
+
const rawArgs = itemAny.arguments ?? itemFunction?.arguments ?? itemAny.input ?? itemFunction?.input ?? "{}";
|
|
6329
|
+
let args = {};
|
|
6330
|
+
try {
|
|
6331
|
+
args = typeof rawArgs === "string" ? JSON.parse(rawArgs) : rawArgs;
|
|
6332
|
+
} catch {
|
|
6333
|
+
args = {};
|
|
6334
|
+
}
|
|
6335
|
+
toolCalls.push({
|
|
6336
|
+
id: String(itemAny.id ?? itemAny.call_id ?? `${name}-${toolCalls.length}`),
|
|
6337
|
+
name,
|
|
6338
|
+
arguments: args
|
|
6339
|
+
});
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
const usage = responseAny.usage ?? {};
|
|
6343
|
+
return {
|
|
6344
|
+
content: contentParts.join("") || null,
|
|
6345
|
+
toolCalls,
|
|
6346
|
+
finishReason: responseAny.status ?? "stop",
|
|
6347
|
+
usage: {
|
|
6348
|
+
prompt_tokens: usage.input_tokens ?? usage.prompt_tokens ?? 0,
|
|
6349
|
+
completion_tokens: usage.output_tokens ?? usage.completion_tokens ?? 0,
|
|
6350
|
+
total_tokens: usage.total_tokens ?? 0
|
|
6351
|
+
},
|
|
6352
|
+
reasoningContent
|
|
6353
|
+
};
|
|
6354
|
+
}
|
|
6355
|
+
shouldFallbackToResponses(error) {
|
|
6356
|
+
const err = error;
|
|
6357
|
+
const status = err?.status;
|
|
6358
|
+
const message = err?.message ?? "";
|
|
6359
|
+
if (status === 404) {
|
|
6360
|
+
return true;
|
|
6361
|
+
}
|
|
6362
|
+
if (message.includes("Cannot POST") && message.includes("chat/completions")) {
|
|
6363
|
+
return true;
|
|
6364
|
+
}
|
|
6365
|
+
if (message.includes("chat/completions") && message.includes("404")) {
|
|
6366
|
+
return true;
|
|
6367
|
+
}
|
|
6368
|
+
return false;
|
|
6369
|
+
}
|
|
6370
|
+
toResponsesInput(messages) {
|
|
6371
|
+
const input = [];
|
|
6372
|
+
for (const msg of messages) {
|
|
6373
|
+
const role = String(msg.role ?? "user");
|
|
6374
|
+
const content = msg.content;
|
|
6375
|
+
if (role === "tool") {
|
|
6376
|
+
const callId = typeof msg.tool_call_id === "string" ? msg.tool_call_id : "";
|
|
6377
|
+
const outputText = typeof content === "string" ? content : Array.isArray(content) ? JSON.stringify(content) : String(content ?? "");
|
|
6378
|
+
input.push({
|
|
6379
|
+
type: "function_call_output",
|
|
6380
|
+
call_id: callId,
|
|
6381
|
+
output: outputText
|
|
6382
|
+
});
|
|
6383
|
+
continue;
|
|
6384
|
+
}
|
|
6385
|
+
const output = { role };
|
|
6386
|
+
if (Array.isArray(content) || typeof content === "string") {
|
|
6387
|
+
output.content = content;
|
|
6388
|
+
} else {
|
|
6389
|
+
output.content = String(content ?? "");
|
|
6390
|
+
}
|
|
6391
|
+
if (typeof msg.reasoning_content === "string" && msg.reasoning_content) {
|
|
6392
|
+
output.reasoning = msg.reasoning_content;
|
|
6393
|
+
}
|
|
6394
|
+
input.push(output);
|
|
6395
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
6396
|
+
for (const call of msg.tool_calls) {
|
|
6397
|
+
const callAny = call;
|
|
6398
|
+
const functionAny = callAny.function ?? {};
|
|
6399
|
+
const callId = String(callAny.id ?? callAny.call_id ?? "");
|
|
6400
|
+
const name = String(functionAny.name ?? callAny.name ?? "");
|
|
6401
|
+
const args = String(functionAny.arguments ?? callAny.arguments ?? "{}");
|
|
6402
|
+
if (!callId || !name) {
|
|
6403
|
+
continue;
|
|
6404
|
+
}
|
|
6405
|
+
input.push({
|
|
6406
|
+
type: "function_call",
|
|
6407
|
+
name,
|
|
6408
|
+
arguments: args,
|
|
6409
|
+
call_id: callId
|
|
6410
|
+
});
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
return input;
|
|
6415
|
+
}
|
|
5169
6416
|
};
|
|
5170
6417
|
|
|
5171
6418
|
// src/providers/litellm_provider.ts
|
|
@@ -5181,11 +6428,14 @@ var LiteLLMProvider = class extends LLMProvider {
|
|
|
5181
6428
|
this.extraHeaders = options.extraHeaders ?? null;
|
|
5182
6429
|
this.providerName = options.providerName ?? null;
|
|
5183
6430
|
this.gatewaySpec = findGateway(this.providerName, options.apiKey ?? null, options.apiBase ?? null) ?? void 0;
|
|
6431
|
+
const providerSpec = this.providerName ? findProviderByName(this.providerName) : void 0;
|
|
6432
|
+
const wireApi = providerSpec?.supportsWireApi ? options.wireApi ?? providerSpec.defaultWireApi ?? "auto" : void 0;
|
|
5184
6433
|
this.client = new OpenAICompatibleProvider({
|
|
5185
6434
|
apiKey: options.apiKey ?? null,
|
|
5186
6435
|
apiBase: options.apiBase ?? null,
|
|
5187
6436
|
defaultModel: options.defaultModel,
|
|
5188
|
-
extraHeaders: options.extraHeaders ?? null
|
|
6437
|
+
extraHeaders: options.extraHeaders ?? null,
|
|
6438
|
+
wireApi
|
|
5189
6439
|
});
|
|
5190
6440
|
}
|
|
5191
6441
|
getDefaultModel() {
|
|
@@ -5263,6 +6513,20 @@ var LiteLLMProvider = class extends LLMProvider {
|
|
|
5263
6513
|
return findProviderByModel(model) ?? (this.providerName ? findProviderByName(this.providerName) : void 0);
|
|
5264
6514
|
}
|
|
5265
6515
|
};
|
|
6516
|
+
|
|
6517
|
+
// src/providers/provider_manager.ts
|
|
6518
|
+
var ProviderManager = class {
|
|
6519
|
+
provider;
|
|
6520
|
+
constructor(provider) {
|
|
6521
|
+
this.provider = provider;
|
|
6522
|
+
}
|
|
6523
|
+
get() {
|
|
6524
|
+
return this.provider;
|
|
6525
|
+
}
|
|
6526
|
+
set(next) {
|
|
6527
|
+
this.provider = next;
|
|
6528
|
+
}
|
|
6529
|
+
};
|
|
5266
6530
|
export {
|
|
5267
6531
|
APP_NAME,
|
|
5268
6532
|
APP_REPLY_SUBJECT,
|
|
@@ -5275,6 +6539,9 @@ export {
|
|
|
5275
6539
|
ChannelManager,
|
|
5276
6540
|
ChannelsConfigSchema,
|
|
5277
6541
|
ConfigSchema,
|
|
6542
|
+
ContextBootstrapSchema,
|
|
6543
|
+
ContextConfigSchema,
|
|
6544
|
+
ContextMemorySchema,
|
|
5278
6545
|
CronService,
|
|
5279
6546
|
DEFAULT_CONFIG_FILE,
|
|
5280
6547
|
DEFAULT_CONFIG_PATH,
|
|
@@ -5290,6 +6557,7 @@ export {
|
|
|
5290
6557
|
ExecToolConfigSchema,
|
|
5291
6558
|
FeishuConfigSchema,
|
|
5292
6559
|
GatewayConfigSchema,
|
|
6560
|
+
GatewayTool,
|
|
5293
6561
|
HEARTBEAT_OK_TOKEN,
|
|
5294
6562
|
HEARTBEAT_PROMPT,
|
|
5295
6563
|
HeartbeatService,
|
|
@@ -5301,6 +6569,7 @@ export {
|
|
|
5301
6569
|
MochatMentionSchema,
|
|
5302
6570
|
PROVIDERS,
|
|
5303
6571
|
ProviderConfigSchema,
|
|
6572
|
+
ProviderManager,
|
|
5304
6573
|
ProvidersConfigSchema,
|
|
5305
6574
|
QQConfigSchema,
|
|
5306
6575
|
SKILL_METADATA_KEY,
|