@konglx/rotom 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +417 -0
- package/bin/mesh-master.sh +439 -0
- package/bin/rotom +29 -0
- package/bin/rotom-link.sh +136 -0
- package/bin/rotom-send-with-status +57 -0
- package/bin/rotom-up.sh +428 -0
- package/dist/cli/ask.js +62 -0
- package/dist/cli/common.js +321 -0
- package/dist/cli/config.js +65 -0
- package/dist/cli/directory.js +17 -0
- package/dist/cli/executor.js +58 -0
- package/dist/cli/fed.js +91 -0
- package/dist/cli/group.js +273 -0
- package/dist/cli/identity.js +62 -0
- package/dist/cli/init.js +268 -0
- package/dist/cli/issue.js +202 -0
- package/dist/cli/join.js +170 -0
- package/dist/cli/link.js +47 -0
- package/dist/cli/master.js +51 -0
- package/dist/cli/memory.js +307 -0
- package/dist/cli/note.js +68 -0
- package/dist/cli/repo.js +77 -0
- package/dist/cli/rotom.js +277 -0
- package/dist/cli/routes.js +118 -0
- package/dist/cli/run.js +45 -0
- package/dist/cli/schedule.js +237 -0
- package/dist/cli/skill.js +173 -0
- package/dist/cli/team.js +106 -0
- package/dist/executor/claude-code-hook.cjs +80 -0
- package/dist/executor/cli-executor.js +8 -0
- package/dist/executor/executors/claude-code.js +780 -0
- package/dist/executor/executors/codex.js +719 -0
- package/dist/executor/executors/hermes-cli.js +855 -0
- package/dist/executor/executors/openclaw.js +467 -0
- package/dist/executor/executors/pi.js +514 -0
- package/dist/executor/index.js +269 -0
- package/dist/executor/jsonrpc-transport.js +125 -0
- package/dist/executor/process-runner.js +101 -0
- package/dist/executor/reasoning-status.js +83 -0
- package/dist/executor/repo-cache.js +502 -0
- package/dist/executor/session-store.js +188 -0
- package/dist/executor/worker-chat.js +257 -0
- package/dist/executor/worker-connection.js +89 -0
- package/dist/executor/worker-issue.js +264 -0
- package/dist/executor/worker.js +877 -0
- package/dist/link/pending-requests.js +72 -0
- package/dist/link/server.js +233 -0
- package/dist/link/visibility-store.js +58 -0
- package/dist/master/api/agents.js +333 -0
- package/dist/master/api/artifacts.js +271 -0
- package/dist/master/api/domains.js +64 -0
- package/dist/master/api/groups.js +635 -0
- package/dist/master/api/guidance-templates.js +147 -0
- package/dist/master/api/index.js +89 -0
- package/dist/master/api/issues-patrol.js +172 -0
- package/dist/master/api/issues.js +663 -0
- package/dist/master/api/links-patrol.js +168 -0
- package/dist/master/api/links.js +114 -0
- package/dist/master/api/memory.js +259 -0
- package/dist/master/api/messages.js +157 -0
- package/dist/master/api/notes.js +77 -0
- package/dist/master/api/schedule-patterns.js +133 -0
- package/dist/master/api/schedules.js +272 -0
- package/dist/master/api/sessions.js +158 -0
- package/dist/master/api/share.js +269 -0
- package/dist/master/api/skills.js +190 -0
- package/dist/master/api/teams.js +122 -0
- package/dist/master/api/uploads.js +245 -0
- package/dist/master/auth.js +134 -0
- package/dist/master/dashboard/animations/calico-dozing.apng +0 -0
- package/dist/master/dashboard/animations/calico-error.apng +0 -0
- package/dist/master/dashboard/animations/calico-happy.apng +0 -0
- package/dist/master/dashboard/animations/calico-notification.apng +0 -0
- package/dist/master/dashboard/animations/calico-sleeping.apng +0 -0
- package/dist/master/dashboard/animations/calico-thinking.apng +0 -0
- package/dist/master/dashboard/animations/calico-waking.apng +0 -0
- package/dist/master/dashboard/assets/ApprovalCard-C38VV6ko.css +1 -0
- package/dist/master/dashboard/assets/ApprovalCard-CHPh2dmE.js +17 -0
- package/dist/master/dashboard/assets/ArtifactPanel-P_2gAP7v.js +1 -0
- package/dist/master/dashboard/assets/ArtifactPanel-aGHySny5.css +1 -0
- package/dist/master/dashboard/assets/css.worker-DaIe3gwK.js +84 -0
- package/dist/master/dashboard/assets/editor.worker-BCzxt1at.js +12 -0
- package/dist/master/dashboard/assets/html.worker-CKrFyw_2.js +461 -0
- package/dist/master/dashboard/assets/index-CChrTn81.css +32 -0
- package/dist/master/dashboard/assets/index-Dhu4SN1z.js +181 -0
- package/dist/master/dashboard/assets/json.worker-B7c_PmGb.js +49 -0
- package/dist/master/dashboard/assets/markdown-CeN5IgdF.js +29 -0
- package/dist/master/dashboard/assets/monaco-core-DyX1CsEw.css +1 -0
- package/dist/master/dashboard/assets/monaco-core-oQiQUisy.js +833 -0
- package/dist/master/dashboard/assets/monaco-setup-CiOPQdmo.js +1 -0
- package/dist/master/dashboard/assets/react-vendor-C8IxlyCR.js +67 -0
- package/dist/master/dashboard/assets/ts.worker-BhkL8olL.js +51334 -0
- package/dist/master/dashboard/assets/useMonaco-ILb4vyPh.js +12 -0
- package/dist/master/dashboard/assets/vite-preload-CxJPbCTl.js +1 -0
- package/dist/master/dashboard/debug-auth.html +197 -0
- package/dist/master/dashboard/favicon.ico +0 -0
- package/dist/master/dashboard/index.html +20 -0
- package/dist/master/dashboard/rotom-avatar.png +0 -0
- package/dist/master/db/agent-sessions.js +60 -0
- package/dist/master/db/agent-visibility.js +64 -0
- package/dist/master/db/agents.js +119 -0
- package/dist/master/db/ask-bridges.js +157 -0
- package/dist/master/db/build-update.js +59 -0
- package/dist/master/db/core.js +82 -0
- package/dist/master/db/domains.js +80 -0
- package/dist/master/db/groups.js +316 -0
- package/dist/master/db/guidance-templates.js +58 -0
- package/dist/master/db/index.js +12 -0
- package/dist/master/db/internal.js +45 -0
- package/dist/master/db/issues-patrol.js +81 -0
- package/dist/master/db/issues.js +373 -0
- package/dist/master/db/links.js +221 -0
- package/dist/master/db/master-node.js +43 -0
- package/dist/master/db/memory.js +272 -0
- package/dist/master/db/messages.js +210 -0
- package/dist/master/db/notes.js +55 -0
- package/dist/master/db/schedule-patterns.js +56 -0
- package/dist/master/db/schedules.js +135 -0
- package/dist/master/db/skills.js +144 -0
- package/dist/master/db/team.js +88 -0
- package/dist/master/db/types.js +10 -0
- package/dist/master/db.js +12 -0
- package/dist/master/embedded.js +133 -0
- package/dist/master/federation/client.js +283 -0
- package/dist/master/federation/identity.js +133 -0
- package/dist/master/federation/manager.js +267 -0
- package/dist/master/federation/publisher.js +87 -0
- package/dist/master/federation/self-publisher.js +69 -0
- package/dist/master/federation/server.js +487 -0
- package/dist/master/group-paths.js +208 -0
- package/dist/master/offline-queue.js +38 -0
- package/dist/master/opc-bootstrap.js +245 -0
- package/dist/master/patrol-terminal.js +275 -0
- package/dist/master/repo-scan.js +188 -0
- package/dist/master/router.js +214 -0
- package/dist/master/scheduler-handlers.js +510 -0
- package/dist/master/scheduler.js +201 -0
- package/dist/master/server.js +203 -0
- package/dist/master/services/link-collector.js +82 -0
- package/dist/master/services/link-patrol-bootstrap.js +50 -0
- package/dist/master/services/memory-extract-prompt.js +34 -0
- package/dist/master/services/patrol-bootstrap.js +63 -0
- package/dist/master/share-tokens.js +56 -0
- package/dist/master/terminal-hub.js +300 -0
- package/dist/master/uploads.js +108 -0
- package/dist/master/util/fs.js +100 -0
- package/dist/master/util/paths.js +50 -0
- package/dist/master/util/persona.js +10 -0
- package/dist/master/ws-hub/connection.js +928 -0
- package/dist/master/ws-hub/conversation.js +290 -0
- package/dist/master/ws-hub/directory.js +70 -0
- package/dist/master/ws-hub/dispatch-enrich.js +34 -0
- package/dist/master/ws-hub/hub.js +136 -0
- package/dist/master/ws-hub/index.js +9 -0
- package/dist/master/ws-hub/internal.js +35 -0
- package/dist/master/ws-hub/routing.js +295 -0
- package/dist/master/ws-hub/sessions.js +130 -0
- package/dist/master/ws-hub.js +11 -0
- package/dist/shared/agent-profile.js +44 -0
- package/dist/shared/constants.js +55 -0
- package/dist/shared/dedup.js +33 -0
- package/dist/shared/group-context.js +62 -0
- package/dist/shared/json-codec.js +33 -0
- package/dist/shared/logger.js +136 -0
- package/dist/shared/mention.js +22 -0
- package/dist/shared/network.js +40 -0
- package/dist/shared/parse.js +18 -0
- package/dist/shared/prompt-composer.js +171 -0
- package/dist/shared/protocol/client-messages.js +8 -0
- package/dist/shared/protocol/enums.js +6 -0
- package/dist/shared/protocol/federation.js +62 -0
- package/dist/shared/protocol/guards.js +87 -0
- package/dist/shared/protocol/server-messages.js +8 -0
- package/dist/shared/protocol/types.js +8 -0
- package/dist/shared/protocol.js +19 -0
- package/dist/shared/readonly-allowlist.js +122 -0
- package/dist/shared/rotom-cli-prompt.js +23 -0
- package/dist/shared/skill-context.js +19 -0
- package/dist/shared/skill-md.js +43 -0
- package/dist/shared/slash-commands.js +50 -0
- package/dist/shared/time.js +80 -0
- package/dist/shared/title.js +46 -0
- package/dist/shared/url-extractor.js +99 -0
- package/migrations/001-schema.sql +942 -0
- package/package.json +68 -0
- package/scripts/fix-node-pty-perms.mjs +46 -0
- package/skill/rotom-a2a-communicate/SKILL.md +257 -0
- package/skill/rotom-bus-host/SKILL.md +78 -0
- package/skill/rotom-bus-host/scripts/poll-replies.sh +148 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { nowBeijing } from "../../shared/time.js";
|
|
2
|
+
import { buildUpdate } from "./build-update.js";
|
|
3
|
+
// ─── Methods ──────────────────────────────────────────────────────────────
|
|
4
|
+
export const memoryMethods = {
|
|
5
|
+
/** 列表过滤。返回轻量索引(无 value 全文)。 */
|
|
6
|
+
listMemory(filter = {}) {
|
|
7
|
+
const { scope, groupId, category, key, tags, includePending = false, agentVisible, limit = 50, } = filter;
|
|
8
|
+
const where = ["active = 1"];
|
|
9
|
+
const params = [];
|
|
10
|
+
if (scope) {
|
|
11
|
+
where.push("scope = ?");
|
|
12
|
+
params.push(scope);
|
|
13
|
+
}
|
|
14
|
+
if (groupId) {
|
|
15
|
+
where.push("group_id = ?");
|
|
16
|
+
params.push(groupId);
|
|
17
|
+
}
|
|
18
|
+
if (category) {
|
|
19
|
+
where.push("category = ?");
|
|
20
|
+
params.push(category);
|
|
21
|
+
}
|
|
22
|
+
if (key) {
|
|
23
|
+
where.push("key = ?");
|
|
24
|
+
params.push(key);
|
|
25
|
+
}
|
|
26
|
+
if (agentVisible !== undefined) {
|
|
27
|
+
where.push("agent_visible = ?");
|
|
28
|
+
params.push(agentVisible);
|
|
29
|
+
}
|
|
30
|
+
if (!includePending) {
|
|
31
|
+
where.push("pending_review = 0");
|
|
32
|
+
}
|
|
33
|
+
if (tags && tags.length > 0) {
|
|
34
|
+
// JSON array LIKE 匹配
|
|
35
|
+
const ors = tags.map(() => "tags LIKE ?");
|
|
36
|
+
where.push(`(${ors.join(" OR ")})`);
|
|
37
|
+
for (const t of tags)
|
|
38
|
+
params.push(`%"${t.replace(/"/g, '\\"')}%"`);
|
|
39
|
+
}
|
|
40
|
+
params.push(limit);
|
|
41
|
+
const rows = this.db.prepare(`SELECT id, key, summary, tags, category, scope, group_id, agent_visible,
|
|
42
|
+
created_by, created_at
|
|
43
|
+
FROM agent_memory
|
|
44
|
+
WHERE ${where.join(" AND ")}
|
|
45
|
+
ORDER BY created_at DESC
|
|
46
|
+
LIMIT ?`).all(...params);
|
|
47
|
+
return rows;
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* 关键词搜索(SQL LIKE)。强制 agent_visible=1 AND pending_review=0 AND active=1
|
|
51
|
+
* —— note(agent_visible=0)永远搜不到。
|
|
52
|
+
* 命中时 injected_count += 1(计入"被检索到"统计)。
|
|
53
|
+
*/
|
|
54
|
+
searchMemory(keyword, filter = {}) {
|
|
55
|
+
const { scope, groupId, category, limit = 20 } = filter;
|
|
56
|
+
const where = [
|
|
57
|
+
"active = 1",
|
|
58
|
+
"agent_visible = 1",
|
|
59
|
+
"pending_review = 0",
|
|
60
|
+
];
|
|
61
|
+
const params = [];
|
|
62
|
+
const kw = `%${keyword}%`;
|
|
63
|
+
// 在 key/value/summary/tags 上 LIKE
|
|
64
|
+
where.push("(key LIKE ? OR value LIKE ? OR summary LIKE ? OR tags LIKE ?)");
|
|
65
|
+
params.push(kw, kw, kw, kw);
|
|
66
|
+
if (scope) {
|
|
67
|
+
where.push("scope = ?");
|
|
68
|
+
params.push(scope);
|
|
69
|
+
}
|
|
70
|
+
if (groupId) {
|
|
71
|
+
where.push("group_id = ?");
|
|
72
|
+
params.push(groupId);
|
|
73
|
+
}
|
|
74
|
+
if (category) {
|
|
75
|
+
where.push("category = ?");
|
|
76
|
+
params.push(category);
|
|
77
|
+
}
|
|
78
|
+
params.push(limit);
|
|
79
|
+
const rows = this.db.prepare(`SELECT id, key, summary, tags, category, scope, group_id, agent_visible,
|
|
80
|
+
created_by, created_at
|
|
81
|
+
FROM agent_memory
|
|
82
|
+
WHERE ${where.join(" AND ")}
|
|
83
|
+
ORDER BY view_count DESC, created_at DESC
|
|
84
|
+
LIMIT ?`).all(...params);
|
|
85
|
+
// 批量 injected_count += 1
|
|
86
|
+
if (rows.length > 0) {
|
|
87
|
+
const ids = rows.map(r => r.id);
|
|
88
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
89
|
+
this.db.prepare(`UPDATE agent_memory SET injected_count = injected_count + 1 WHERE id IN (${placeholders})`).run(...ids);
|
|
90
|
+
}
|
|
91
|
+
return rows;
|
|
92
|
+
},
|
|
93
|
+
/** 详情。memory(agent_visible=1) 读取时 view_count += 1;note 不计数。 */
|
|
94
|
+
getMemory(id) {
|
|
95
|
+
const row = this.db.prepare("SELECT * FROM agent_memory WHERE id = ?").get(id);
|
|
96
|
+
if (!row)
|
|
97
|
+
return undefined;
|
|
98
|
+
if (row.agent_visible === 1 && row.active === 1 && row.pending_review === 0) {
|
|
99
|
+
this.db.prepare(`UPDATE agent_memory SET view_count = view_count + 1, last_viewed_at = datetime('now') WHERE id = ?`).run(id);
|
|
100
|
+
}
|
|
101
|
+
return row;
|
|
102
|
+
},
|
|
103
|
+
/** 计数 agent_visible=1 的 memory(供 prompt 极简指针注入)。 */
|
|
104
|
+
countMemory(scope, groupId) {
|
|
105
|
+
const where = ["active = 1", "agent_visible = 1", "pending_review = 0", "scope = ?"];
|
|
106
|
+
const params = [scope];
|
|
107
|
+
if (scope === "group" && groupId) {
|
|
108
|
+
where.push("group_id = ?");
|
|
109
|
+
params.push(groupId);
|
|
110
|
+
}
|
|
111
|
+
const row = this.db.prepare(`SELECT COUNT(*) as n FROM agent_memory WHERE ${where.join(" AND ")}`).get(...params);
|
|
112
|
+
return row?.n ?? 0;
|
|
113
|
+
},
|
|
114
|
+
addMemory(input) {
|
|
115
|
+
// 硬约束:visibility=global + agent_visible=1 必须走审核(pending_review=1)。
|
|
116
|
+
// 全局 memory 不允许直接对 agent 可见,必须先 create(pending) → 人工 approve 才生效。
|
|
117
|
+
// 唯一合法直接 visible 的全局 memory 路径是 approveMemory(已审核)和 promoteMemoryVisibility(从 group 升 global 时内部强制 agent_visible=0)。
|
|
118
|
+
// 这个 guard 防止"cli 直接 addMemory(scope=global, visibility=global, agent_visible=true) 绕过审核"。
|
|
119
|
+
if ((input.scope === "global" || input.visibility === "global") &&
|
|
120
|
+
input.agentVisible === true &&
|
|
121
|
+
input.pendingReview !== true) {
|
|
122
|
+
throw new Error(`addMemory blocked: scope=global + visibility=global + agent_visible=true must go through pending review. ` +
|
|
123
|
+
`Use addMemory({ ..., scope: 'global', visibility: 'global', agentVisible: false, pendingReview: true }) ` +
|
|
124
|
+
`or addMemory({ ..., scope: 'group', visibility: 'group' }) + later promoteMemoryVisibility(id, 'global').`);
|
|
125
|
+
}
|
|
126
|
+
const now = nowBeijing();
|
|
127
|
+
const summary = input.summary ?? input.value.slice(0, 80);
|
|
128
|
+
const scope = input.scope ?? "group";
|
|
129
|
+
const groupId = scope === "global" ? null : (input.groupId ?? null);
|
|
130
|
+
this.db.prepare(`
|
|
131
|
+
INSERT INTO agent_memory (
|
|
132
|
+
id, group_id, scope, category, source_type, source_ref,
|
|
133
|
+
key, value, summary, tags, visibility, agent_visible,
|
|
134
|
+
created_by, created_at, updated_at, expires_at,
|
|
135
|
+
active, pending_review, injected_count, view_count, last_viewed_at
|
|
136
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, 0, 0, NULL)
|
|
137
|
+
`).run(input.id, groupId, scope, input.category, input.sourceType ?? "manual", input.sourceRef ?? null, input.key, input.value, summary, JSON.stringify(input.tags ?? []), input.visibility ?? "group", input.agentVisible ? 1 : 0, input.createdBy, now, now, input.expiresAt ?? null, input.pendingReview ? 1 : 0);
|
|
138
|
+
},
|
|
139
|
+
updateMemory(id, fields) {
|
|
140
|
+
const extraSets = [];
|
|
141
|
+
// value 改了但 summary 没显式给 → 自动重算
|
|
142
|
+
if (fields.value !== undefined && fields.summary === undefined) {
|
|
143
|
+
extraSets.push({ column: "summary", value: fields.value.slice(0, 80) });
|
|
144
|
+
}
|
|
145
|
+
const built = buildUpdate({
|
|
146
|
+
table: "agent_memory",
|
|
147
|
+
sets: {
|
|
148
|
+
value: fields.value,
|
|
149
|
+
summary: fields.summary,
|
|
150
|
+
tags: fields.tags !== undefined ? JSON.stringify(fields.tags) : undefined,
|
|
151
|
+
category: fields.category,
|
|
152
|
+
visibility: fields.visibility,
|
|
153
|
+
agent_visible: fields.agentVisible !== undefined ? (fields.agentVisible ? 1 : 0) : undefined,
|
|
154
|
+
expires_at: fields.expiresAt,
|
|
155
|
+
},
|
|
156
|
+
where: "id = ?",
|
|
157
|
+
whereParams: [id],
|
|
158
|
+
updatedAt: "datetime-now",
|
|
159
|
+
extraSets,
|
|
160
|
+
});
|
|
161
|
+
if (!built)
|
|
162
|
+
return false;
|
|
163
|
+
const result = this.db.prepare(built.sql).run(...built.params);
|
|
164
|
+
return result.changes > 0;
|
|
165
|
+
},
|
|
166
|
+
/** 软删除:active=0。 */
|
|
167
|
+
deactivateMemory(id) {
|
|
168
|
+
const result = this.db.prepare("UPDATE agent_memory SET active = 0, updated_at = datetime('now') WHERE id = ?").run(id);
|
|
169
|
+
return result.changes > 0;
|
|
170
|
+
},
|
|
171
|
+
/** 标记过期(仍 active=1,但 expires_at 设为过去时间;读取时由调用方判断)。 */
|
|
172
|
+
expireMemory(id) {
|
|
173
|
+
const result = this.db.prepare("UPDATE agent_memory SET expires_at = datetime('now'), updated_at = datetime('now') WHERE id = ?").run(id);
|
|
174
|
+
return result.changes > 0;
|
|
175
|
+
},
|
|
176
|
+
/** group → global(scope 随之变 global, group_id 置 NULL)。 */
|
|
177
|
+
promoteMemoryVisibility(id, newVisibility) {
|
|
178
|
+
const built = buildUpdate({
|
|
179
|
+
table: "agent_memory",
|
|
180
|
+
sets: { visibility: newVisibility },
|
|
181
|
+
where: "id = ?",
|
|
182
|
+
whereParams: [id],
|
|
183
|
+
updatedAt: "datetime-now",
|
|
184
|
+
extraSets: newVisibility === "global" ? [{ column: "scope", value: "global" }, { sql: "group_id = NULL" }] : [],
|
|
185
|
+
});
|
|
186
|
+
if (!built)
|
|
187
|
+
return false;
|
|
188
|
+
const result = this.db.prepare(built.sql).run(...built.params);
|
|
189
|
+
return result.changes > 0;
|
|
190
|
+
},
|
|
191
|
+
/** 审核队列:pending_review=1。 */
|
|
192
|
+
listPendingMemory(scope, groupId) {
|
|
193
|
+
const where = ["active = 1", "pending_review = 1"];
|
|
194
|
+
const params = [];
|
|
195
|
+
if (scope) {
|
|
196
|
+
where.push("scope = ?");
|
|
197
|
+
params.push(scope);
|
|
198
|
+
}
|
|
199
|
+
if (groupId) {
|
|
200
|
+
where.push("group_id = ?");
|
|
201
|
+
params.push(groupId);
|
|
202
|
+
}
|
|
203
|
+
return this.db.prepare(`SELECT id, key, summary, tags, category, scope, group_id, agent_visible,
|
|
204
|
+
created_by, created_at
|
|
205
|
+
FROM agent_memory
|
|
206
|
+
WHERE ${where.join(" AND ")}
|
|
207
|
+
ORDER BY created_at DESC`).all(...params);
|
|
208
|
+
},
|
|
209
|
+
/** 审核通过:pending_review=0,隐含 agent_visible=1(审核通过即对 agent 可见)。 */
|
|
210
|
+
approveMemory(id) {
|
|
211
|
+
const result = this.db.prepare(`UPDATE agent_memory SET pending_review = 0, agent_visible = 1, updated_at = datetime('now') WHERE id = ?`).run(id);
|
|
212
|
+
return result.changes > 0;
|
|
213
|
+
},
|
|
214
|
+
/** 审核拒绝:active=0。 */
|
|
215
|
+
rejectMemory(id) {
|
|
216
|
+
const result = this.db.prepare("UPDATE agent_memory SET active = 0, updated_at = datetime('now') WHERE id = ?").run(id);
|
|
217
|
+
return result.changes > 0;
|
|
218
|
+
},
|
|
219
|
+
/** 死记忆:view_count=0 && age > minAgeDays(只看 agent_visible=1)。 */
|
|
220
|
+
listStaleMemory(filter = {}) {
|
|
221
|
+
const { scope, groupId, minAgeDays = 30 } = filter;
|
|
222
|
+
const where = [
|
|
223
|
+
"active = 1", "agent_visible = 1", "pending_review = 0", "view_count = 0",
|
|
224
|
+
`created_at < datetime('now', '-${minAgeDays} days')`,
|
|
225
|
+
];
|
|
226
|
+
const params = [];
|
|
227
|
+
if (scope) {
|
|
228
|
+
where.push("scope = ?");
|
|
229
|
+
params.push(scope);
|
|
230
|
+
}
|
|
231
|
+
if (groupId) {
|
|
232
|
+
where.push("group_id = ?");
|
|
233
|
+
params.push(groupId);
|
|
234
|
+
}
|
|
235
|
+
return this.db.prepare(`SELECT id, key, summary, tags, category, scope, group_id, agent_visible,
|
|
236
|
+
created_by, created_at
|
|
237
|
+
FROM agent_memory
|
|
238
|
+
WHERE ${where.join(" AND ")}
|
|
239
|
+
ORDER BY created_at ASC`).all(...params);
|
|
240
|
+
},
|
|
241
|
+
/** 聚合统计。 */
|
|
242
|
+
memoryStats(scope, groupId) {
|
|
243
|
+
const where = ["1=1"];
|
|
244
|
+
const params = [];
|
|
245
|
+
if (scope) {
|
|
246
|
+
where.push("scope = ?");
|
|
247
|
+
params.push(scope);
|
|
248
|
+
}
|
|
249
|
+
if (groupId) {
|
|
250
|
+
where.push("group_id = ?");
|
|
251
|
+
params.push(groupId);
|
|
252
|
+
}
|
|
253
|
+
const base = `FROM agent_memory WHERE ${where.join(" AND ")}`;
|
|
254
|
+
const total = this.db.prepare(`SELECT COUNT(*) as n ${base}`).get(...params).n;
|
|
255
|
+
const active = this.db.prepare(`SELECT COUNT(*) as n ${base} AND active = 1`).get(...params).n;
|
|
256
|
+
const pending = this.db.prepare(`SELECT COUNT(*) as n ${base} AND active = 1 AND pending_review = 1`).get(...params).n;
|
|
257
|
+
const byCatRows = this.db.prepare(`SELECT category, COUNT(*) as n ${base} AND active = 1 GROUP BY category`).all(...params);
|
|
258
|
+
const byCategory = {};
|
|
259
|
+
for (const r of byCatRows)
|
|
260
|
+
byCategory[r.category] = r.n;
|
|
261
|
+
const noteN = this.db.prepare(`SELECT COUNT(*) as n ${base} AND active = 1 AND agent_visible = 0`).get(...params).n;
|
|
262
|
+
const memN = this.db.prepare(`SELECT COUNT(*) as n ${base} AND active = 1 AND agent_visible = 1`).get(...params).n;
|
|
263
|
+
const topViewed = this.db.prepare(`SELECT id, key, summary, tags, category, scope, group_id, agent_visible, created_by, created_at
|
|
264
|
+
${base} AND active = 1 AND agent_visible = 1
|
|
265
|
+
ORDER BY view_count DESC LIMIT 5`).all(...params);
|
|
266
|
+
return {
|
|
267
|
+
total, active, pending, byCategory,
|
|
268
|
+
byAgentVisible: { note: noteN, memory: memN },
|
|
269
|
+
topViewed,
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages — offline queue, audit log, message log (dashboard conversations),
|
|
3
|
+
* retention cleanup, and aggregate stats.
|
|
4
|
+
*
|
|
5
|
+
* Methods attach to a `MeshDb` instance via `Object.assign`. Cross-module
|
|
6
|
+
* calls: `enqueueOffline` → `getAgentById` (agents); `stats` → `listAgents`.
|
|
7
|
+
*/
|
|
8
|
+
import { LOG_RETENTION_DAYS } from "../../shared/constants.js";
|
|
9
|
+
export const messageMethods = {
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// Offline messages
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
13
|
+
enqueueOffline(targetAgent, fromName, fromDomain, payload, routeType) {
|
|
14
|
+
// Purge expired
|
|
15
|
+
this.db.prepare("DELETE FROM offline_messages WHERE expires_at < datetime('now')").run();
|
|
16
|
+
// Check per-agent limit
|
|
17
|
+
const row = this.db.prepare("SELECT COUNT(*) as c FROM offline_messages WHERE target_agent = ?").get(targetAgent);
|
|
18
|
+
if (row.c >= 100)
|
|
19
|
+
return false;
|
|
20
|
+
// Verify target agent exists
|
|
21
|
+
const agent = this.getAgentById(targetAgent);
|
|
22
|
+
if (!agent)
|
|
23
|
+
return false;
|
|
24
|
+
this.db.prepare(`
|
|
25
|
+
INSERT INTO offline_messages (target_agent, from_name, from_domain, payload, route_type, expires_at)
|
|
26
|
+
VALUES (?, ?, ?, ?, ?, datetime('now', '+1 day'))
|
|
27
|
+
`).run(targetAgent, fromName, fromDomain || "", payload, routeType);
|
|
28
|
+
return true;
|
|
29
|
+
},
|
|
30
|
+
popOffline(targetAgent) {
|
|
31
|
+
const msgs = this.db.prepare("SELECT * FROM offline_messages WHERE target_agent = ? AND expires_at > datetime('now') ORDER BY created_at").all(targetAgent);
|
|
32
|
+
if (msgs.length > 0) {
|
|
33
|
+
this.db.prepare("DELETE FROM offline_messages WHERE target_agent = ?").run(targetAgent);
|
|
34
|
+
}
|
|
35
|
+
return msgs;
|
|
36
|
+
},
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// Federation offline messages (跨 master 暂存重投)
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* 暂存一条跨 master FedDeliver(target member 离线时由协调 master 调用)。
|
|
42
|
+
*
|
|
43
|
+
* 复用 offline_messages 表(migration 057/059 扩的 3 列),按 target_master_id 主键查重投。
|
|
44
|
+
* 不做"target agent 是否存在"的校验——member 重连后如果该 agent 还在 agent_visibility
|
|
45
|
+
* 就投,不在就由 member 端 FedClient.handleDeliver 返回 false,coord 记 route_failed。
|
|
46
|
+
*
|
|
47
|
+
* per-member 100 条上限(对齐本地 agent 离线队列)。
|
|
48
|
+
*/
|
|
49
|
+
enqueueFedOffline(input) {
|
|
50
|
+
// Purge expired(同 enqueueOffline 的清理时机)
|
|
51
|
+
this.db.prepare("DELETE FROM offline_messages WHERE expires_at < datetime('now')").run();
|
|
52
|
+
const row = this.db.prepare("SELECT COUNT(*) as c FROM offline_messages WHERE target_master_id = ?").get(input.target_master_id);
|
|
53
|
+
if (row.c >= 100)
|
|
54
|
+
return false;
|
|
55
|
+
this.db.prepare(`
|
|
56
|
+
INSERT INTO offline_messages
|
|
57
|
+
(target_agent, from_name, from_domain, payload, route_type, expires_at,
|
|
58
|
+
target_hostname, source_master_id, target_master_id)
|
|
59
|
+
VALUES (?, ?, ?, ?, 'federated', datetime('now', '+1 day'), ?, ?, ?)
|
|
60
|
+
`).run(input.target_agent, input.source_agent, input.source_hostname, input.payload, input.target_hostname, input.source_master_id, input.target_master_id);
|
|
61
|
+
return true;
|
|
62
|
+
},
|
|
63
|
+
/** 取出并删除某 member 的所有 fed 暂存消息(原子,member 重连时调用) */
|
|
64
|
+
popFedOfflineByMaster(targetMasterId) {
|
|
65
|
+
const msgs = this.db.prepare(`SELECT * FROM offline_messages
|
|
66
|
+
WHERE target_master_id = ? AND expires_at > datetime('now')
|
|
67
|
+
ORDER BY created_at`).all(targetMasterId);
|
|
68
|
+
if (msgs.length > 0) {
|
|
69
|
+
this.db.prepare("DELETE FROM offline_messages WHERE target_master_id = ?").run(targetMasterId);
|
|
70
|
+
}
|
|
71
|
+
return msgs;
|
|
72
|
+
},
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
74
|
+
// Audit
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
76
|
+
audit(entry) {
|
|
77
|
+
this.db.prepare(`
|
|
78
|
+
INSERT INTO audit_log (from_name, from_domain, to_name, to_domain, route_type, result, message_summary)
|
|
79
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
80
|
+
`).run(entry.fromName || null, entry.fromDomain || null, entry.toName || null, entry.toDomain || null, entry.routeType || null, entry.result, entry.messageSummary?.slice(0, 100) || null);
|
|
81
|
+
},
|
|
82
|
+
listAudit(limit = 50) {
|
|
83
|
+
return this.db.prepare("SELECT * FROM audit_log ORDER BY timestamp DESC LIMIT ?").all(limit);
|
|
84
|
+
},
|
|
85
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
86
|
+
// Config (general key/value)
|
|
87
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
88
|
+
getConfig(key) {
|
|
89
|
+
const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get(key);
|
|
90
|
+
return row?.value;
|
|
91
|
+
},
|
|
92
|
+
setConfig(key, value) {
|
|
93
|
+
this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run(key, value);
|
|
94
|
+
},
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// Message log (for dashboard conversations)
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
98
|
+
logMessage(entry) {
|
|
99
|
+
this.db.prepare(`
|
|
100
|
+
INSERT INTO message_log (request_id, from_name, from_domain, to_name, to_domain, route_type, direction, payload, status, latency_ms, group_id, source)
|
|
101
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
102
|
+
`).run(entry.requestId, entry.fromName, entry.fromDomain || null, entry.toName || null, entry.toDomain || null, entry.routeType || null, entry.direction, entry.payload, entry.status, entry.latencyMs ?? null, entry.groupId || null, entry.source || null);
|
|
103
|
+
},
|
|
104
|
+
listMessages(opts) {
|
|
105
|
+
let sql = "SELECT * FROM message_log WHERE 1=1";
|
|
106
|
+
const params = [];
|
|
107
|
+
if (opts?.agent) {
|
|
108
|
+
sql += " AND (from_name = ? OR to_name = ?)";
|
|
109
|
+
params.push(opts.agent, opts.agent);
|
|
110
|
+
}
|
|
111
|
+
if (opts?.from) {
|
|
112
|
+
sql += " AND from_name = ?";
|
|
113
|
+
params.push(opts.from);
|
|
114
|
+
}
|
|
115
|
+
if (opts?.to) {
|
|
116
|
+
sql += " AND to_name = ?";
|
|
117
|
+
params.push(opts.to);
|
|
118
|
+
}
|
|
119
|
+
if (opts?.status) {
|
|
120
|
+
sql += " AND status = ?";
|
|
121
|
+
params.push(opts.status);
|
|
122
|
+
}
|
|
123
|
+
if (opts?.groupId) {
|
|
124
|
+
sql += " AND group_id = ?";
|
|
125
|
+
params.push(opts.groupId);
|
|
126
|
+
}
|
|
127
|
+
if (opts?.keyword) {
|
|
128
|
+
sql += " AND payload LIKE ?";
|
|
129
|
+
params.push(`%${opts.keyword}%`);
|
|
130
|
+
}
|
|
131
|
+
if (opts?.before) {
|
|
132
|
+
sql += " AND timestamp < ?";
|
|
133
|
+
params.push(opts.before);
|
|
134
|
+
}
|
|
135
|
+
sql += " ORDER BY timestamp DESC LIMIT ? OFFSET ?";
|
|
136
|
+
params.push(Math.min(opts?.limit || 100, 500));
|
|
137
|
+
params.push(Math.max(opts?.offset || 0, 0));
|
|
138
|
+
return this.db.prepare(sql).all(...params);
|
|
139
|
+
},
|
|
140
|
+
countMessages(opts) {
|
|
141
|
+
let sql = "SELECT COUNT(*) as total FROM message_log WHERE 1=1";
|
|
142
|
+
const params = [];
|
|
143
|
+
if (opts?.agent) {
|
|
144
|
+
sql += " AND (from_name = ? OR to_name = ?)";
|
|
145
|
+
params.push(opts.agent, opts.agent);
|
|
146
|
+
}
|
|
147
|
+
if (opts?.from) {
|
|
148
|
+
sql += " AND from_name = ?";
|
|
149
|
+
params.push(opts.from);
|
|
150
|
+
}
|
|
151
|
+
if (opts?.to) {
|
|
152
|
+
sql += " AND to_name = ?";
|
|
153
|
+
params.push(opts.to);
|
|
154
|
+
}
|
|
155
|
+
if (opts?.status) {
|
|
156
|
+
sql += " AND status = ?";
|
|
157
|
+
params.push(opts.status);
|
|
158
|
+
}
|
|
159
|
+
if (opts?.groupId) {
|
|
160
|
+
sql += " AND group_id = ?";
|
|
161
|
+
params.push(opts.groupId);
|
|
162
|
+
}
|
|
163
|
+
if (opts?.keyword) {
|
|
164
|
+
sql += " AND payload LIKE ?";
|
|
165
|
+
params.push(`%${opts.keyword}%`);
|
|
166
|
+
}
|
|
167
|
+
if (opts?.before) {
|
|
168
|
+
sql += " AND timestamp < ?";
|
|
169
|
+
params.push(opts.before);
|
|
170
|
+
}
|
|
171
|
+
return this.db.prepare(sql).get(...params).total;
|
|
172
|
+
},
|
|
173
|
+
/** Per-agent message stats */
|
|
174
|
+
agentMessageStats() {
|
|
175
|
+
return this.db.prepare(`
|
|
176
|
+
SELECT
|
|
177
|
+
name,
|
|
178
|
+
(SELECT COUNT(*) FROM message_log WHERE to_name = agents.name AND direction = 'send') as received,
|
|
179
|
+
(SELECT COUNT(*) FROM message_log WHERE from_name = agents.name AND direction = 'send') as sent,
|
|
180
|
+
(SELECT COUNT(*) FROM message_log WHERE to_name = agents.name AND direction = 'reply') as replied,
|
|
181
|
+
(SELECT COUNT(*) FROM message_log WHERE (from_name = agents.name OR to_name = agents.name) AND status = 'failed') as failed,
|
|
182
|
+
(SELECT AVG(latency_ms) FROM message_log WHERE to_name = agents.name AND direction = 'reply' AND latency_ms IS NOT NULL) as avg_latency_ms
|
|
183
|
+
FROM agents
|
|
184
|
+
ORDER BY name
|
|
185
|
+
`).all();
|
|
186
|
+
},
|
|
187
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
188
|
+
// Log cleanup — prevents unbounded table growth
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
190
|
+
/** Delete audit_log and message_log entries older than retention period. */
|
|
191
|
+
cleanupOldLogs(retentionDays = LOG_RETENTION_DAYS) {
|
|
192
|
+
const auditResult = this.db.prepare("DELETE FROM audit_log WHERE timestamp < datetime('now', ? || ' days')").run(`-${retentionDays}`);
|
|
193
|
+
const messageResult = this.db.prepare("DELETE FROM message_log WHERE timestamp < datetime('now', ? || ' days')").run(`-${retentionDays}`);
|
|
194
|
+
return {
|
|
195
|
+
auditDeleted: auditResult.changes,
|
|
196
|
+
messageDeleted: messageResult.changes,
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
200
|
+
// Aggregate stats
|
|
201
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
202
|
+
stats() {
|
|
203
|
+
const agents = this.listAgents();
|
|
204
|
+
return {
|
|
205
|
+
total: agents.length,
|
|
206
|
+
online: agents.filter(a => a.status === "online").length,
|
|
207
|
+
domains: this.db.prepare("SELECT COUNT(DISTINCT domain) as c FROM agents WHERE domain IS NOT NULL AND domain != ''").get().c,
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { nowBeijing } from "../../shared/time.js";
|
|
2
|
+
import { buildUpdate } from "./build-update.js";
|
|
3
|
+
/** 把 agent_memory 行映射回旧 NoteRow 形状(title/description)。 */
|
|
4
|
+
function rowToNote(row) {
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
group_id: row.group_id,
|
|
8
|
+
title: row.key,
|
|
9
|
+
description: row.value,
|
|
10
|
+
created_by: row.created_by ?? "",
|
|
11
|
+
created_at: row.created_at,
|
|
12
|
+
updated_at: row.updated_at,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export const noteMethods = {
|
|
16
|
+
createNote(note) {
|
|
17
|
+
const now = nowBeijing();
|
|
18
|
+
this.db.prepare(`
|
|
19
|
+
INSERT INTO agent_memory (
|
|
20
|
+
id, group_id, scope, category, source_type, source_ref,
|
|
21
|
+
key, value, summary, tags, visibility, agent_visible,
|
|
22
|
+
created_by, created_at, updated_at, expires_at,
|
|
23
|
+
active, pending_review, injected_count, view_count, last_viewed_at
|
|
24
|
+
) VALUES (?, ?, 'group', 'note', 'manual', NULL, ?, ?, ?, '[]', 'group', 0, ?, ?, ?, NULL, 1, 0, 0, 0, NULL)
|
|
25
|
+
`).run(note.id, note.groupId, note.title, note.description || "", (note.description || "").slice(0, 80), note.createdBy, now, now);
|
|
26
|
+
},
|
|
27
|
+
getNoteById(id) {
|
|
28
|
+
const row = this.db.prepare("SELECT * FROM agent_memory WHERE id = ?").get(id);
|
|
29
|
+
return row ? rowToNote(row) : undefined;
|
|
30
|
+
},
|
|
31
|
+
listNotesByGroup(groupId) {
|
|
32
|
+
const rows = this.db.prepare("SELECT * FROM agent_memory WHERE group_id = ? AND active = 1 ORDER BY created_at DESC").all(groupId);
|
|
33
|
+
return rows.map(rowToNote);
|
|
34
|
+
},
|
|
35
|
+
updateNote(id, fields) {
|
|
36
|
+
const built = buildUpdate({
|
|
37
|
+
table: "agent_memory",
|
|
38
|
+
sets: {
|
|
39
|
+
key: fields.title,
|
|
40
|
+
value: fields.description,
|
|
41
|
+
summary: fields.description !== undefined ? fields.description.slice(0, 80) : undefined,
|
|
42
|
+
},
|
|
43
|
+
where: "id = ?",
|
|
44
|
+
whereParams: [id],
|
|
45
|
+
updatedAt: "datetime-now",
|
|
46
|
+
});
|
|
47
|
+
if (!built)
|
|
48
|
+
return false;
|
|
49
|
+
const result = this.db.prepare(built.sql).run(...built.params);
|
|
50
|
+
return result.changes > 0;
|
|
51
|
+
},
|
|
52
|
+
deleteNote(id) {
|
|
53
|
+
this.db.prepare("UPDATE agent_memory SET active = 0, updated_at = datetime('now') WHERE id = ?").run(id);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schedule patterns — 调度模式参考库。
|
|
3
|
+
*
|
|
4
|
+
* 常见定时任务模式的样板,供用户在配置群指导模板时学习引用。不直接
|
|
5
|
+
* 管理 scheduled_tasks 实例。schedule_config 是 JSON 字符串,形如
|
|
6
|
+
* GuidanceScheduleConfig。
|
|
7
|
+
*
|
|
8
|
+
* 种子模式 is_default=1,deleteSchedulePattern 拒绝删除,API 也兜底返回 400。
|
|
9
|
+
*/
|
|
10
|
+
import { buildUpdate } from "./build-update.js";
|
|
11
|
+
export const schedulePatternMethods = {
|
|
12
|
+
listSchedulePatterns() {
|
|
13
|
+
return this.db.prepare("SELECT * FROM schedule_patterns ORDER BY sort_order ASC, id ASC").all();
|
|
14
|
+
},
|
|
15
|
+
getSchedulePattern(id) {
|
|
16
|
+
return this.db.prepare("SELECT * FROM schedule_patterns WHERE id = ?")
|
|
17
|
+
.get(id);
|
|
18
|
+
},
|
|
19
|
+
createSchedulePattern(input) {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const info = this.db.prepare(`
|
|
22
|
+
INSERT INTO schedule_patterns (
|
|
23
|
+
name, description, schedule_config, sort_order, is_default, created_at, updated_at
|
|
24
|
+
) VALUES (?, ?, ?, ?, 0, ?, ?)
|
|
25
|
+
`).run(input.name, input.description ?? "", input.schedule_config ?? null, input.sort_order ?? 0, now, now);
|
|
26
|
+
return this.getSchedulePattern(Number(info.lastInsertRowid));
|
|
27
|
+
},
|
|
28
|
+
updateSchedulePattern(id, patch) {
|
|
29
|
+
const built = buildUpdate({
|
|
30
|
+
table: "schedule_patterns",
|
|
31
|
+
sets: {
|
|
32
|
+
name: patch.name,
|
|
33
|
+
description: patch.description,
|
|
34
|
+
schedule_config: patch.schedule_config,
|
|
35
|
+
sort_order: patch.sort_order,
|
|
36
|
+
},
|
|
37
|
+
where: "id = ?",
|
|
38
|
+
whereParams: [id],
|
|
39
|
+
updatedAt: "epoch",
|
|
40
|
+
});
|
|
41
|
+
if (!built)
|
|
42
|
+
return this.getSchedulePattern(id);
|
|
43
|
+
this.db.prepare(built.sql).run(...built.params);
|
|
44
|
+
return this.getSchedulePattern(id);
|
|
45
|
+
},
|
|
46
|
+
/** 删除模式;种子模式(is_default=1)拒绝删除,返回 false。 */
|
|
47
|
+
deleteSchedulePattern(id) {
|
|
48
|
+
const row = this.getSchedulePattern(id);
|
|
49
|
+
if (!row)
|
|
50
|
+
return false;
|
|
51
|
+
if (row.is_default === 1)
|
|
52
|
+
return false;
|
|
53
|
+
this.db.prepare("DELETE FROM schedule_patterns WHERE id = ?").run(id);
|
|
54
|
+
return true;
|
|
55
|
+
},
|
|
56
|
+
};
|