@offerpilot/axiomruntime 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -0
- package/dist/cli/commands/add.js +29 -0
- package/dist/cli/commands/context.js +29 -0
- package/dist/cli/commands/doctor.js +62 -0
- package/dist/cli/commands/edit.js +85 -0
- package/dist/cli/commands/help.js +16 -0
- package/dist/cli/commands/list.js +25 -0
- package/dist/cli/commands/log.js +63 -0
- package/dist/cli/commands/memory.js +241 -0
- package/dist/cli/commands/report.js +35 -0
- package/dist/cli/commands/session.js +74 -0
- package/dist/cli/commands/setup.js +86 -0
- package/dist/cli/commands/status.js +50 -0
- package/dist/cli/commands/telegram.js +701 -0
- package/dist/cli/commands/use.js +108 -0
- package/dist/cli/commands/version.js +12 -0
- package/dist/cli/index.js +276 -0
- package/dist/cli/output/table.js +22 -0
- package/dist/cli/prompts/prompt.js +37 -0
- package/dist/cli/registry.js +16 -0
- package/dist/core/config/cache-store.js +193 -0
- package/dist/core/config/json-store.js +114 -0
- package/dist/core/config/paths.js +85 -0
- package/dist/core/config/providers-store.js +89 -0
- package/dist/core/config/schema.js +60 -0
- package/dist/core/config/session-store.js +30 -0
- package/dist/core/config/usage-store.js +18 -0
- package/dist/core/context/context-service.js +186 -0
- package/dist/core/integrations/integration-state.js +105 -0
- package/dist/core/logs/log-service.js +56 -0
- package/dist/core/memory/embedding-check.js +121 -0
- package/dist/core/memory/memory-config.js +122 -0
- package/dist/core/models/model-discovery.js +430 -0
- package/dist/core/models/model-filter.js +13 -0
- package/dist/core/providers/provider-service.js +212 -0
- package/dist/core/reports/report-service.js +166 -0
- package/dist/core/runner/command-resolver.js +60 -0
- package/dist/core/runner/engine-registry.js +93 -0
- package/dist/core/runner/fallback.js +114 -0
- package/dist/core/runner/openai-usage-http.js +82 -0
- package/dist/core/runner/openai-usage-proxy.js +1 -0
- package/dist/core/runner/openai-usage-recording.js +172 -0
- package/dist/core/runner/openai-usage-responses.js +469 -0
- package/dist/core/runner/openai-usage-server.js +319 -0
- package/dist/core/runner/openai-usage-types.js +1 -0
- package/dist/core/runner/tool-runner.js +138 -0
- package/dist/core/sessions/session-service.js +47 -0
- package/dist/core/status/doctor-service.js +391 -0
- package/dist/core/status/status-service.js +60 -0
- package/dist/core/types.js +1 -0
- package/dist/core/usage/pricing.js +113 -0
- package/dist/core/usage/usage-service.js +30 -0
- package/dist/core/utils/is-record.js +3 -0
- package/dist/server/index.js +28 -0
- package/dist/server/runtime-server.js +430 -0
- package/dist/telegram/bot-registry.js +80 -0
- package/dist/telegram/bot.js +128 -0
- package/dist/telegram/config.js +235 -0
- package/dist/telegram/engine/claude-engine.js +240 -0
- package/dist/telegram/engine/codex-engine.js +437 -0
- package/dist/telegram/engine/engine-utils.js +67 -0
- package/dist/telegram/engine/process-utils.js +132 -0
- package/dist/telegram/engine/registry.js +31 -0
- package/dist/telegram/engine/types.js +1 -0
- package/dist/telegram/handler-registry.js +28 -0
- package/dist/telegram/handlers/callback.js +311 -0
- package/dist/telegram/handlers/command.js +272 -0
- package/dist/telegram/handlers/document.js +108 -0
- package/dist/telegram/handlers/memory.js +305 -0
- package/dist/telegram/handlers/message.js +701 -0
- package/dist/telegram/handlers/provider.js +332 -0
- package/dist/telegram/handlers/setup.js +527 -0
- package/dist/telegram/handlers/usage.js +124 -0
- package/dist/telegram/index.js +93 -0
- package/dist/telegram/interaction/approval.js +108 -0
- package/dist/telegram/interaction/command-menu.js +253 -0
- package/dist/telegram/interaction/formatter.js +487 -0
- package/dist/telegram/interaction/keyboards.js +145 -0
- package/dist/telegram/interaction/progress-reporter.js +160 -0
- package/dist/telegram/interaction/prompt-middleware.js +168 -0
- package/dist/telegram/interaction/result-store.js +41 -0
- package/dist/telegram/interaction/token-budget.js +21 -0
- package/dist/telegram/interaction/tool-name.js +41 -0
- package/dist/telegram/lifecycle-registry.js +47 -0
- package/dist/telegram/log.js +46 -0
- package/dist/telegram/memory/memory-inject.js +52 -0
- package/dist/telegram/memory/memory-service.js +413 -0
- package/dist/telegram/memory/memory-store.js +216 -0
- package/dist/telegram/memory/types.js +1 -0
- package/dist/telegram/network-retry.js +22 -0
- package/dist/telegram/network.js +53 -0
- package/dist/telegram/session/manager.js +229 -0
- package/dist/telegram/session/store.js +363 -0
- package/dist/telegram/session/types.js +1 -0
- package/dist/telegram/supervisor.js +57 -0
- package/dist/telegram/templates/messages.js +1 -0
- package/docs/README.md +98 -0
- package/docs/USAGE.html +853 -0
- package/package.json +57 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { escapeHtml, truncateText } from "../interaction/formatter.js";
|
|
5
|
+
import { completeAgentProfile, showReadyCard } from "./setup.js";
|
|
6
|
+
export const MAX_TEXT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
7
|
+
export const MAX_IMAGE_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
8
|
+
export const MAX_ATTACHMENTS_PER_PROMPT = 8;
|
|
9
|
+
export function registerDocumentHandler(bot, deps) {
|
|
10
|
+
bot.on("message:document", async (ctx) => {
|
|
11
|
+
const document = ctx.message.document;
|
|
12
|
+
if (!document.file_name) {
|
|
13
|
+
await ctx.reply("文件必须包含文件名。");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const isImage = document.mime_type?.startsWith("image/") ?? false;
|
|
17
|
+
await receiveAttachment(ctx, deps, {
|
|
18
|
+
name: document.file_name,
|
|
19
|
+
kind: isImage ? "image" : "text",
|
|
20
|
+
mimeType: document.mime_type,
|
|
21
|
+
declaredSize: document.file_size
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
bot.on("message:photo", async (ctx) => {
|
|
25
|
+
const photo = ctx.message.photo.at(-1);
|
|
26
|
+
if (!photo)
|
|
27
|
+
return;
|
|
28
|
+
await receiveAttachment(ctx, deps, {
|
|
29
|
+
name: `telegram-photo-${ctx.message.message_id}.jpg`,
|
|
30
|
+
kind: "image",
|
|
31
|
+
mimeType: "image/jpeg",
|
|
32
|
+
declaredSize: photo.file_size
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function receiveAttachment(ctx, deps, incoming) {
|
|
37
|
+
const chatId = ctx.chat?.id;
|
|
38
|
+
if (!chatId)
|
|
39
|
+
return;
|
|
40
|
+
const current = deps.sessionManager.getOrCreate(chatId);
|
|
41
|
+
if (current.attachments.length >= MAX_ATTACHMENTS_PER_PROMPT) {
|
|
42
|
+
await ctx.reply(`一次最多附加 ${MAX_ATTACHMENTS_PER_PROMPT} 个文件;请先发送问题使用现有附件。`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const maxBytes = incoming.kind === "image" ? MAX_IMAGE_ATTACHMENT_BYTES : MAX_TEXT_ATTACHMENT_BYTES;
|
|
46
|
+
if (incoming.declaredSize && incoming.declaredSize > maxBytes) {
|
|
47
|
+
await ctx.reply(formatSizeLimit(incoming.kind, maxBytes));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const buffer = await downloadTelegramFile(ctx, deps.config.token, maxBytes);
|
|
52
|
+
let text = "";
|
|
53
|
+
if (incoming.kind === "text") {
|
|
54
|
+
try {
|
|
55
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
await ctx.reply("该文件不是有效的 UTF-8 文本;二进制文件暂不能作为文本上下文。图片请直接以照片或 image/* 文件发送。");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (current.setupStep === "agent_profile") {
|
|
63
|
+
if (incoming.kind === "image") {
|
|
64
|
+
await ctx.reply("画像设置阶段请发送 UTF-8 文本文档;完成设置后即可发送图片。");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const next = await completeAgentProfile(ctx, deps, text, { showReady: false });
|
|
68
|
+
if (next)
|
|
69
|
+
await showReadyCard(ctx, deps, next);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "ai-gateway-telegram-"));
|
|
73
|
+
const filePath = path.join(dir, path.basename(incoming.name));
|
|
74
|
+
await fs.writeFile(filePath, buffer);
|
|
75
|
+
const attachment = {
|
|
76
|
+
name: incoming.name,
|
|
77
|
+
path: filePath,
|
|
78
|
+
text: incoming.kind === "text" ? truncateText(text, 24000) : "",
|
|
79
|
+
kind: incoming.kind,
|
|
80
|
+
mimeType: incoming.mimeType,
|
|
81
|
+
sizeBytes: buffer.length,
|
|
82
|
+
createdAt: new Date().toISOString()
|
|
83
|
+
};
|
|
84
|
+
const session = deps.sessionManager.addAttachment(chatId, attachment);
|
|
85
|
+
const label = incoming.kind === "image" ? "图片" : "文件";
|
|
86
|
+
await ctx.reply(`已附加${label} <code>${escapeHtml(incoming.name)}</code>,将在下一条请求中使用(${session.attachments.length}/${MAX_ATTACHMENTS_PER_PROMPT})。`, { parse_mode: "HTML" });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
await ctx.reply(`附件处理失败:<code>${escapeHtml(message)}</code>`, { parse_mode: "HTML" });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function downloadTelegramFile(ctx, token, maxBytes) {
|
|
94
|
+
const file = await ctx.getFile();
|
|
95
|
+
if (!file.file_path)
|
|
96
|
+
throw new Error("Telegram 未返回文件路径。");
|
|
97
|
+
const response = await fetch(`https://api.telegram.org/file/bot${token}/${file.file_path}`);
|
|
98
|
+
if (!response.ok)
|
|
99
|
+
throw new Error(`下载失败(HTTP ${response.status})。`);
|
|
100
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
101
|
+
if (buffer.length > maxBytes)
|
|
102
|
+
throw new Error(formatSizeLimit("file", maxBytes));
|
|
103
|
+
return buffer;
|
|
104
|
+
}
|
|
105
|
+
function formatSizeLimit(kind, maxBytes) {
|
|
106
|
+
const label = kind === "image" ? "图片" : kind === "text" ? "文本文件" : "文件";
|
|
107
|
+
return `${label}过大,单个附件上限为 ${Math.round(maxBytes / 1024 / 1024)} MB。`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { escapeHtml } from "../interaction/formatter.js";
|
|
2
|
+
import { buildMemoryClearConfirmKeyboard, buildMemoryManageKeyboard, buildRememberScopeKeyboard } from "../interaction/keyboards.js";
|
|
3
|
+
export async function handleRememberCommand(ctx, deps) {
|
|
4
|
+
const memory = deps.memoryService;
|
|
5
|
+
if (!memory) {
|
|
6
|
+
await ctx.reply("Bot Memory 未启用。");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const text = String(ctx.match ?? "").trim();
|
|
10
|
+
if (!text) {
|
|
11
|
+
await ctx.reply("🤖 这条记忆保存到哪里?", {
|
|
12
|
+
reply_markup: buildRememberScopeKeyboard()
|
|
13
|
+
});
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const parsed = parseRememberInput(text);
|
|
18
|
+
if (parsed.kind === "pin") {
|
|
19
|
+
await ctx.reply("加密记忆尚未启用,凭证类内容不会明文保存。");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (parsed.kind === "profile") {
|
|
23
|
+
const entry = memory.saveProfile(ctx.chat.id, parsed.content);
|
|
24
|
+
await replySaved(ctx, entry, "用户画像已更新");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (parsed.kind === "persona") {
|
|
28
|
+
const entry = memory.savePersona(ctx.chat.id, parsed.content);
|
|
29
|
+
await replySaved(ctx, entry, "Bot 人设已更新");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const entry = memory.remember(ctx.chat.id, parsed);
|
|
33
|
+
await replySaved(ctx, entry, "记住了");
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
await ctx.reply(`记忆失败:<code>${escapeHtml(error instanceof Error ? error.message : String(error))}</code>`, {
|
|
37
|
+
parse_mode: "HTML"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function handleRememberScopeCallback(ctx, deps, mode) {
|
|
42
|
+
const chatId = ctx.chat?.id;
|
|
43
|
+
if (!chatId)
|
|
44
|
+
return;
|
|
45
|
+
if (!deps.memoryService) {
|
|
46
|
+
await editOrReply(ctx, "Bot Memory 未启用。", undefined, { edit: true });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const session = deps.sessionManager.getOrCreate(chatId);
|
|
50
|
+
deps.sessionManager.update(session.id, { setupStep: rememberModeToStep(mode) });
|
|
51
|
+
await editOrReply(ctx, "🤖 输入要记录的记忆吧", undefined, { edit: true });
|
|
52
|
+
}
|
|
53
|
+
export async function handleRememberInput(ctx, deps, text, session) {
|
|
54
|
+
const mode = rememberStepToMode(session.setupStep);
|
|
55
|
+
if (!mode)
|
|
56
|
+
return false;
|
|
57
|
+
const chatId = ctx.chat?.id;
|
|
58
|
+
if (!chatId)
|
|
59
|
+
return false;
|
|
60
|
+
const memory = deps.memoryService;
|
|
61
|
+
if (!memory) {
|
|
62
|
+
deps.sessionManager.update(session.id, { setupStep: "ready" });
|
|
63
|
+
await ctx.reply("Bot Memory 未启用。");
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const content = text.trim();
|
|
67
|
+
if (!content) {
|
|
68
|
+
await ctx.reply("🤖 输入要记录的记忆吧");
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
deps.sessionManager.update(session.id, { setupStep: "ready" });
|
|
72
|
+
try {
|
|
73
|
+
const entry = memory.remember(chatId, {
|
|
74
|
+
content,
|
|
75
|
+
scope: mode === "auto" ? undefined : mode
|
|
76
|
+
});
|
|
77
|
+
await replySaved(ctx, entry, "记住了");
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
await ctx.reply(`记忆失败:<code>${escapeHtml(error instanceof Error ? error.message : String(error))}</code>`, {
|
|
81
|
+
parse_mode: "HTML"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
export function isRememberSetupStep(step) {
|
|
87
|
+
return rememberStepToMode(step) !== null;
|
|
88
|
+
}
|
|
89
|
+
export async function handleMemoryManageInput(ctx, deps, text, session) {
|
|
90
|
+
if (session.setupStep !== "memory_promote")
|
|
91
|
+
return false;
|
|
92
|
+
const chatId = ctx.chat?.id;
|
|
93
|
+
if (!chatId)
|
|
94
|
+
return false;
|
|
95
|
+
const memory = deps.memoryService;
|
|
96
|
+
if (!memory) {
|
|
97
|
+
deps.sessionManager.update(session.id, { setupStep: "ready" });
|
|
98
|
+
await ctx.reply("Bot Memory 未启用。");
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
const id = Number(text.trim().replace(/^#/, ""));
|
|
102
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
103
|
+
await ctx.reply("🤖 请输入要升级的记忆编号,例如 12");
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
deps.sessionManager.update(session.id, { setupStep: "ready" });
|
|
107
|
+
try {
|
|
108
|
+
const entry = memory.promote(chatId, id);
|
|
109
|
+
await ctx.reply(entry ? `🤖 已升级为 global:<code>#${entry.id}</code>` : "🤖 只能升级当前 bot 的记忆。", {
|
|
110
|
+
parse_mode: "HTML"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
await ctx.reply(`升级失败:<code>${escapeHtml(error instanceof Error ? error.message : String(error))}</code>`, {
|
|
115
|
+
parse_mode: "HTML"
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
export async function handleForgetCommand(ctx, deps) {
|
|
121
|
+
const memory = deps.memoryService;
|
|
122
|
+
if (!memory) {
|
|
123
|
+
await ctx.reply("Bot Memory 未启用。");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const selector = String(ctx.match ?? "").trim();
|
|
127
|
+
if (!selector) {
|
|
128
|
+
await ctx.reply("用法:/forget <编号或关键词>");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const count = memory.delete(ctx.chat.id, selector);
|
|
132
|
+
await ctx.reply(count > 0 ? `🤖 已删除 ${count} 条记忆。` : "🤖 没有找到匹配的记忆。");
|
|
133
|
+
}
|
|
134
|
+
export async function handleMemoriesCommand(ctx, deps) {
|
|
135
|
+
const memory = deps.memoryService;
|
|
136
|
+
if (!memory) {
|
|
137
|
+
await ctx.reply("Bot Memory 未启用。");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const text = String(ctx.match ?? "").trim();
|
|
141
|
+
const query = text.startsWith("search ") ? text.slice("search ".length).trim() : text;
|
|
142
|
+
await ctx.reply(memory.formatList(ctx.chat.id, query || undefined), { parse_mode: "HTML" });
|
|
143
|
+
}
|
|
144
|
+
export async function handleMemoryCommand(ctx, deps) {
|
|
145
|
+
const memory = deps.memoryService;
|
|
146
|
+
if (!memory) {
|
|
147
|
+
await ctx.reply("Bot Memory 未启用。");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const text = String(ctx.match ?? "").trim();
|
|
151
|
+
if (!text) {
|
|
152
|
+
await showMemoryPanel(ctx);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const [command, first, second] = text.split(/\s+/);
|
|
156
|
+
if (command === "promote" && /^\d+$/.test(first ?? "")) {
|
|
157
|
+
try {
|
|
158
|
+
const entry = memory.promote(ctx.chat.id, Number(first));
|
|
159
|
+
await ctx.reply(entry ? `🤖 已升级为 global:<code>#${entry.id}</code>` : "🤖 只能升级当前 bot 的记忆。", {
|
|
160
|
+
parse_mode: "HTML"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
await ctx.reply(`升级失败:<code>${escapeHtml(error instanceof Error ? error.message : String(error))}</code>`, {
|
|
165
|
+
parse_mode: "HTML"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (command === "clear" && (first === "bot" || first === "global")) {
|
|
171
|
+
if (second !== "confirm") {
|
|
172
|
+
await askMemoryClearConfirm(ctx, first);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const count = memory.clearScope(ctx.chat.id, first);
|
|
176
|
+
await ctx.reply(`🤖 已清空 ${first} 记忆 ${count} 条。`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (command === "pin" || command === "unlock" || command === "lock") {
|
|
180
|
+
await ctx.reply("加密记忆尚未启用。");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (command === "export" || command === "import") {
|
|
184
|
+
await ctx.reply("Memory export/import 将在后续阶段启用。");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await showMemoryPanel(ctx);
|
|
188
|
+
}
|
|
189
|
+
export async function handleMemoryManageCallback(ctx, deps, action, scope) {
|
|
190
|
+
const chatId = ctx.chat?.id;
|
|
191
|
+
if (!chatId)
|
|
192
|
+
return;
|
|
193
|
+
const memory = deps.memoryService;
|
|
194
|
+
if (!memory) {
|
|
195
|
+
await editOrReply(ctx, "Bot Memory 未启用。", undefined, { edit: true });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (action === "panel") {
|
|
199
|
+
await showMemoryPanel(ctx, { edit: true });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (action === "promote") {
|
|
203
|
+
const session = deps.sessionManager.getOrCreate(chatId);
|
|
204
|
+
deps.sessionManager.update(session.id, { setupStep: "memory_promote" });
|
|
205
|
+
await editOrReply(ctx, "🤖 输入要升级为 global 的记忆编号吧", undefined, { edit: true });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (action === "clear" && isMemoryScope(scope)) {
|
|
209
|
+
await askMemoryClearConfirm(ctx, scope, { edit: true });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (action === "clear_confirm" && isMemoryScope(scope)) {
|
|
213
|
+
const session = deps.sessionManager.getOrCreate(chatId);
|
|
214
|
+
deps.sessionManager.update(session.id, { setupStep: "ready" });
|
|
215
|
+
const count = memory.clearScope(chatId, scope);
|
|
216
|
+
await editOrReply(ctx, `🤖 已清空 ${scope} 记忆 ${count} 条。`, {
|
|
217
|
+
parse_mode: "HTML",
|
|
218
|
+
reply_markup: buildMemoryManageKeyboard()
|
|
219
|
+
}, { edit: true });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
await showMemoryPanel(ctx, { edit: true });
|
|
223
|
+
}
|
|
224
|
+
function parseRememberInput(text) {
|
|
225
|
+
const [command, ...rest] = text.split(/\s+/);
|
|
226
|
+
const content = rest.join(" ").trim();
|
|
227
|
+
if (command === "profile" && content)
|
|
228
|
+
return { kind: "profile", content };
|
|
229
|
+
if (command === "persona" && content)
|
|
230
|
+
return { kind: "persona", content };
|
|
231
|
+
if (command === "pin" && content)
|
|
232
|
+
return { kind: "pin", content };
|
|
233
|
+
if (command === "global" && content)
|
|
234
|
+
return { scope: "global", content };
|
|
235
|
+
if (command === "bot" && content)
|
|
236
|
+
return { scope: "bot", content };
|
|
237
|
+
if (command === "temp") {
|
|
238
|
+
const parsed = parseTempMemory(rest.join(" "));
|
|
239
|
+
if (parsed)
|
|
240
|
+
return parsed;
|
|
241
|
+
}
|
|
242
|
+
return { content: text };
|
|
243
|
+
}
|
|
244
|
+
function parseTempMemory(value) {
|
|
245
|
+
const text = value.trim();
|
|
246
|
+
const match = text.match(/^(\d+)([hd])\s+([\s\S]+)$/);
|
|
247
|
+
if (!match)
|
|
248
|
+
return text ? { content: text, scope: "bot", duration: "short_term" } : null;
|
|
249
|
+
const amount = Number(match[1]);
|
|
250
|
+
const unit = match[2];
|
|
251
|
+
const content = match[3]?.trim();
|
|
252
|
+
if (!content || !Number.isFinite(amount) || amount <= 0)
|
|
253
|
+
return null;
|
|
254
|
+
const ttlMs = amount * (unit === "h" ? 60 * 60 * 1000 : 24 * 60 * 60 * 1000);
|
|
255
|
+
return { content, scope: "bot", duration: "short_term", ttlMs };
|
|
256
|
+
}
|
|
257
|
+
function rememberModeToStep(mode) {
|
|
258
|
+
if (mode === "global")
|
|
259
|
+
return "remember_global";
|
|
260
|
+
if (mode === "bot")
|
|
261
|
+
return "remember_bot";
|
|
262
|
+
return "remember_auto";
|
|
263
|
+
}
|
|
264
|
+
function rememberStepToMode(step) {
|
|
265
|
+
if (step === "remember_global")
|
|
266
|
+
return "global";
|
|
267
|
+
if (step === "remember_bot")
|
|
268
|
+
return "bot";
|
|
269
|
+
if (step === "remember_auto")
|
|
270
|
+
return "auto";
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
function isMemoryScope(value) {
|
|
274
|
+
return value === "bot" || value === "global";
|
|
275
|
+
}
|
|
276
|
+
async function showMemoryPanel(ctx, options = {}) {
|
|
277
|
+
await editOrReply(ctx, "🤖 选择你要管理的记忆操作吧", {
|
|
278
|
+
reply_markup: buildMemoryManageKeyboard()
|
|
279
|
+
}, options);
|
|
280
|
+
}
|
|
281
|
+
async function askMemoryClearConfirm(ctx, scope, options = {}) {
|
|
282
|
+
await editOrReply(ctx, `🤖 确认清空 ${scope} 记忆吗?`, {
|
|
283
|
+
reply_markup: buildMemoryClearConfirmKeyboard(scope)
|
|
284
|
+
}, options);
|
|
285
|
+
}
|
|
286
|
+
async function editOrReply(ctx, text, extra, options = {}) {
|
|
287
|
+
if (!options.edit) {
|
|
288
|
+
await ctx.reply(text, extra);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
await ctx.editMessageText(text, extra);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// The source message may not be editable; replying keeps the flow usable.
|
|
297
|
+
}
|
|
298
|
+
await ctx.reply(text, extra);
|
|
299
|
+
}
|
|
300
|
+
async function replySaved(ctx, entry, message) {
|
|
301
|
+
await ctx.reply([
|
|
302
|
+
`🤖 ${escapeHtml(message)}:<code>#${entry.id}</code>`,
|
|
303
|
+
`Scope:<code>${escapeHtml(entry.scope)}</code> · Category:<code>${escapeHtml(entry.category)}</code>`
|
|
304
|
+
].join("\n"), { parse_mode: "HTML" });
|
|
305
|
+
}
|