@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.
Files changed (189) hide show
  1. package/README.md +417 -0
  2. package/bin/mesh-master.sh +439 -0
  3. package/bin/rotom +29 -0
  4. package/bin/rotom-link.sh +136 -0
  5. package/bin/rotom-send-with-status +57 -0
  6. package/bin/rotom-up.sh +428 -0
  7. package/dist/cli/ask.js +62 -0
  8. package/dist/cli/common.js +321 -0
  9. package/dist/cli/config.js +65 -0
  10. package/dist/cli/directory.js +17 -0
  11. package/dist/cli/executor.js +58 -0
  12. package/dist/cli/fed.js +91 -0
  13. package/dist/cli/group.js +273 -0
  14. package/dist/cli/identity.js +62 -0
  15. package/dist/cli/init.js +268 -0
  16. package/dist/cli/issue.js +202 -0
  17. package/dist/cli/join.js +170 -0
  18. package/dist/cli/link.js +47 -0
  19. package/dist/cli/master.js +51 -0
  20. package/dist/cli/memory.js +307 -0
  21. package/dist/cli/note.js +68 -0
  22. package/dist/cli/repo.js +77 -0
  23. package/dist/cli/rotom.js +277 -0
  24. package/dist/cli/routes.js +118 -0
  25. package/dist/cli/run.js +45 -0
  26. package/dist/cli/schedule.js +237 -0
  27. package/dist/cli/skill.js +173 -0
  28. package/dist/cli/team.js +106 -0
  29. package/dist/executor/claude-code-hook.cjs +80 -0
  30. package/dist/executor/cli-executor.js +8 -0
  31. package/dist/executor/executors/claude-code.js +780 -0
  32. package/dist/executor/executors/codex.js +719 -0
  33. package/dist/executor/executors/hermes-cli.js +855 -0
  34. package/dist/executor/executors/openclaw.js +467 -0
  35. package/dist/executor/executors/pi.js +514 -0
  36. package/dist/executor/index.js +269 -0
  37. package/dist/executor/jsonrpc-transport.js +125 -0
  38. package/dist/executor/process-runner.js +101 -0
  39. package/dist/executor/reasoning-status.js +83 -0
  40. package/dist/executor/repo-cache.js +502 -0
  41. package/dist/executor/session-store.js +188 -0
  42. package/dist/executor/worker-chat.js +257 -0
  43. package/dist/executor/worker-connection.js +89 -0
  44. package/dist/executor/worker-issue.js +264 -0
  45. package/dist/executor/worker.js +877 -0
  46. package/dist/link/pending-requests.js +72 -0
  47. package/dist/link/server.js +233 -0
  48. package/dist/link/visibility-store.js +58 -0
  49. package/dist/master/api/agents.js +333 -0
  50. package/dist/master/api/artifacts.js +271 -0
  51. package/dist/master/api/domains.js +64 -0
  52. package/dist/master/api/groups.js +635 -0
  53. package/dist/master/api/guidance-templates.js +147 -0
  54. package/dist/master/api/index.js +89 -0
  55. package/dist/master/api/issues-patrol.js +172 -0
  56. package/dist/master/api/issues.js +663 -0
  57. package/dist/master/api/links-patrol.js +168 -0
  58. package/dist/master/api/links.js +114 -0
  59. package/dist/master/api/memory.js +259 -0
  60. package/dist/master/api/messages.js +157 -0
  61. package/dist/master/api/notes.js +77 -0
  62. package/dist/master/api/schedule-patterns.js +133 -0
  63. package/dist/master/api/schedules.js +272 -0
  64. package/dist/master/api/sessions.js +158 -0
  65. package/dist/master/api/share.js +269 -0
  66. package/dist/master/api/skills.js +190 -0
  67. package/dist/master/api/teams.js +122 -0
  68. package/dist/master/api/uploads.js +245 -0
  69. package/dist/master/auth.js +134 -0
  70. package/dist/master/dashboard/animations/calico-dozing.apng +0 -0
  71. package/dist/master/dashboard/animations/calico-error.apng +0 -0
  72. package/dist/master/dashboard/animations/calico-happy.apng +0 -0
  73. package/dist/master/dashboard/animations/calico-notification.apng +0 -0
  74. package/dist/master/dashboard/animations/calico-sleeping.apng +0 -0
  75. package/dist/master/dashboard/animations/calico-thinking.apng +0 -0
  76. package/dist/master/dashboard/animations/calico-waking.apng +0 -0
  77. package/dist/master/dashboard/assets/ApprovalCard-C38VV6ko.css +1 -0
  78. package/dist/master/dashboard/assets/ApprovalCard-CHPh2dmE.js +17 -0
  79. package/dist/master/dashboard/assets/ArtifactPanel-P_2gAP7v.js +1 -0
  80. package/dist/master/dashboard/assets/ArtifactPanel-aGHySny5.css +1 -0
  81. package/dist/master/dashboard/assets/css.worker-DaIe3gwK.js +84 -0
  82. package/dist/master/dashboard/assets/editor.worker-BCzxt1at.js +12 -0
  83. package/dist/master/dashboard/assets/html.worker-CKrFyw_2.js +461 -0
  84. package/dist/master/dashboard/assets/index-CChrTn81.css +32 -0
  85. package/dist/master/dashboard/assets/index-Dhu4SN1z.js +181 -0
  86. package/dist/master/dashboard/assets/json.worker-B7c_PmGb.js +49 -0
  87. package/dist/master/dashboard/assets/markdown-CeN5IgdF.js +29 -0
  88. package/dist/master/dashboard/assets/monaco-core-DyX1CsEw.css +1 -0
  89. package/dist/master/dashboard/assets/monaco-core-oQiQUisy.js +833 -0
  90. package/dist/master/dashboard/assets/monaco-setup-CiOPQdmo.js +1 -0
  91. package/dist/master/dashboard/assets/react-vendor-C8IxlyCR.js +67 -0
  92. package/dist/master/dashboard/assets/ts.worker-BhkL8olL.js +51334 -0
  93. package/dist/master/dashboard/assets/useMonaco-ILb4vyPh.js +12 -0
  94. package/dist/master/dashboard/assets/vite-preload-CxJPbCTl.js +1 -0
  95. package/dist/master/dashboard/debug-auth.html +197 -0
  96. package/dist/master/dashboard/favicon.ico +0 -0
  97. package/dist/master/dashboard/index.html +20 -0
  98. package/dist/master/dashboard/rotom-avatar.png +0 -0
  99. package/dist/master/db/agent-sessions.js +60 -0
  100. package/dist/master/db/agent-visibility.js +64 -0
  101. package/dist/master/db/agents.js +119 -0
  102. package/dist/master/db/ask-bridges.js +157 -0
  103. package/dist/master/db/build-update.js +59 -0
  104. package/dist/master/db/core.js +82 -0
  105. package/dist/master/db/domains.js +80 -0
  106. package/dist/master/db/groups.js +316 -0
  107. package/dist/master/db/guidance-templates.js +58 -0
  108. package/dist/master/db/index.js +12 -0
  109. package/dist/master/db/internal.js +45 -0
  110. package/dist/master/db/issues-patrol.js +81 -0
  111. package/dist/master/db/issues.js +373 -0
  112. package/dist/master/db/links.js +221 -0
  113. package/dist/master/db/master-node.js +43 -0
  114. package/dist/master/db/memory.js +272 -0
  115. package/dist/master/db/messages.js +210 -0
  116. package/dist/master/db/notes.js +55 -0
  117. package/dist/master/db/schedule-patterns.js +56 -0
  118. package/dist/master/db/schedules.js +135 -0
  119. package/dist/master/db/skills.js +144 -0
  120. package/dist/master/db/team.js +88 -0
  121. package/dist/master/db/types.js +10 -0
  122. package/dist/master/db.js +12 -0
  123. package/dist/master/embedded.js +133 -0
  124. package/dist/master/federation/client.js +283 -0
  125. package/dist/master/federation/identity.js +133 -0
  126. package/dist/master/federation/manager.js +267 -0
  127. package/dist/master/federation/publisher.js +87 -0
  128. package/dist/master/federation/self-publisher.js +69 -0
  129. package/dist/master/federation/server.js +487 -0
  130. package/dist/master/group-paths.js +208 -0
  131. package/dist/master/offline-queue.js +38 -0
  132. package/dist/master/opc-bootstrap.js +245 -0
  133. package/dist/master/patrol-terminal.js +275 -0
  134. package/dist/master/repo-scan.js +188 -0
  135. package/dist/master/router.js +214 -0
  136. package/dist/master/scheduler-handlers.js +510 -0
  137. package/dist/master/scheduler.js +201 -0
  138. package/dist/master/server.js +203 -0
  139. package/dist/master/services/link-collector.js +82 -0
  140. package/dist/master/services/link-patrol-bootstrap.js +50 -0
  141. package/dist/master/services/memory-extract-prompt.js +34 -0
  142. package/dist/master/services/patrol-bootstrap.js +63 -0
  143. package/dist/master/share-tokens.js +56 -0
  144. package/dist/master/terminal-hub.js +300 -0
  145. package/dist/master/uploads.js +108 -0
  146. package/dist/master/util/fs.js +100 -0
  147. package/dist/master/util/paths.js +50 -0
  148. package/dist/master/util/persona.js +10 -0
  149. package/dist/master/ws-hub/connection.js +928 -0
  150. package/dist/master/ws-hub/conversation.js +290 -0
  151. package/dist/master/ws-hub/directory.js +70 -0
  152. package/dist/master/ws-hub/dispatch-enrich.js +34 -0
  153. package/dist/master/ws-hub/hub.js +136 -0
  154. package/dist/master/ws-hub/index.js +9 -0
  155. package/dist/master/ws-hub/internal.js +35 -0
  156. package/dist/master/ws-hub/routing.js +295 -0
  157. package/dist/master/ws-hub/sessions.js +130 -0
  158. package/dist/master/ws-hub.js +11 -0
  159. package/dist/shared/agent-profile.js +44 -0
  160. package/dist/shared/constants.js +55 -0
  161. package/dist/shared/dedup.js +33 -0
  162. package/dist/shared/group-context.js +62 -0
  163. package/dist/shared/json-codec.js +33 -0
  164. package/dist/shared/logger.js +136 -0
  165. package/dist/shared/mention.js +22 -0
  166. package/dist/shared/network.js +40 -0
  167. package/dist/shared/parse.js +18 -0
  168. package/dist/shared/prompt-composer.js +171 -0
  169. package/dist/shared/protocol/client-messages.js +8 -0
  170. package/dist/shared/protocol/enums.js +6 -0
  171. package/dist/shared/protocol/federation.js +62 -0
  172. package/dist/shared/protocol/guards.js +87 -0
  173. package/dist/shared/protocol/server-messages.js +8 -0
  174. package/dist/shared/protocol/types.js +8 -0
  175. package/dist/shared/protocol.js +19 -0
  176. package/dist/shared/readonly-allowlist.js +122 -0
  177. package/dist/shared/rotom-cli-prompt.js +23 -0
  178. package/dist/shared/skill-context.js +19 -0
  179. package/dist/shared/skill-md.js +43 -0
  180. package/dist/shared/slash-commands.js +50 -0
  181. package/dist/shared/time.js +80 -0
  182. package/dist/shared/title.js +46 -0
  183. package/dist/shared/url-extractor.js +99 -0
  184. package/migrations/001-schema.sql +942 -0
  185. package/package.json +68 -0
  186. package/scripts/fix-node-pty-perms.mjs +46 -0
  187. package/skill/rotom-a2a-communicate/SKILL.md +257 -0
  188. package/skill/rotom-bus-host/SKILL.md +78 -0
  189. package/skill/rotom-bus-host/scripts/poll-replies.sh +148 -0
@@ -0,0 +1,316 @@
1
+ import { nowBeijing, shiftBeijing } from "../../shared/time.js";
2
+ // 内部 helper:执行 SELECT 群消息 + LEFT JOIN chat_message_prompts,
3
+ // 拼装成 GroupMessageRow[](剥掉 layers/final/generated_at/prompt_version
4
+ // 四个字符串列,组装成 composed_prompt 对象)。whereClause 由调用方提供,
5
+ // 绑定参数顺序为 groupId,然后再是 LIMIT 值(如果有的话)。
6
+ //
7
+ // module-level 而非 groupMethods 上的方法,因为只有 getGroupMessages 在用,
8
+ // 放在 self 上反而需要扩展 MeshDbSelf interface。
9
+ function fetchGroupMessageRows(db, whereClause, groupId, ...bindParams) {
10
+ const sql = `SELECT m.id, m.sender, m.content, m.mentions, m.created_at, m.cancelled_at,
11
+ p.layers, p.final, p.generated_at, p.prompt_version
12
+ FROM group_messages m
13
+ LEFT JOIN chat_message_prompts p ON p.group_message_id = m.id
14
+ ${whereClause}`;
15
+ const rows = db.prepare(sql).all(groupId, ...bindParams);
16
+ return rows.map((r) => {
17
+ let composed_prompt = null;
18
+ if (r.layers && r.final && r.generated_at && r.prompt_version) {
19
+ composed_prompt = {
20
+ layers: JSON.parse(r.layers),
21
+ final: r.final,
22
+ generated_at: r.generated_at,
23
+ prompt_version: r.prompt_version,
24
+ };
25
+ }
26
+ return {
27
+ id: r.id, sender: r.sender, content: r.content, mentions: r.mentions, created_at: r.created_at,
28
+ cancelled_at: r.cancelled_at,
29
+ composed_prompt,
30
+ };
31
+ });
32
+ }
33
+ export const groupMethods = {
34
+ createGroup(id, name, createdBy, workingDir) {
35
+ this.db.prepare("INSERT INTO groups (id, name, created_by, working_dir) VALUES (?, ?, ?, ?)").run(id, name, createdBy || null, workingDir || null);
36
+ },
37
+ updateGroupWorkingDir(id, workingDir) {
38
+ this.db.prepare("UPDATE groups SET working_dir = ? WHERE id = ?")
39
+ .run(workingDir, id);
40
+ },
41
+ updateGroupName(id, name) {
42
+ this.db.prepare("UPDATE groups SET name = ? WHERE id = ?")
43
+ .run(name, id);
44
+ },
45
+ /**
46
+ * Toggle (or set explicitly) the per-group pinned_at timestamp.
47
+ * Passing `null` unpins; passing a value pins to "now" (UTC string).
48
+ */
49
+ updateGroupPinned(id, pinned) {
50
+ const next = pinned ? nowBeijing() : null;
51
+ this.db.prepare("UPDATE groups SET pinned_at = ? WHERE id = ?")
52
+ .run(next, id);
53
+ return next;
54
+ },
55
+ /**
56
+ * Archive or unarchive a group. Archived groups are read-only: no new messages,
57
+ * issues. Passing `true` sets archived_at to "now";
58
+ * passing `false` clears it.
59
+ */
60
+ updateGroupArchived(id, archived) {
61
+ const next = archived ? nowBeijing() : null;
62
+ this.db.prepare("UPDATE groups SET archived_at = ? WHERE id = ?")
63
+ .run(next, id);
64
+ return next;
65
+ },
66
+ /**
67
+ * Returns the archived_at value of a group, or null if not archived.
68
+ */
69
+ isGroupArchived(id) {
70
+ const row = this.db.prepare("SELECT archived_at FROM groups WHERE id = ?").get(id);
71
+ return row?.archived_at ?? null;
72
+ },
73
+ /**
74
+ * Toggle (or set explicitly) the per-group starred_at timestamp.
75
+ * Starred groups are "重要少用":可读可写,只是侧栏分层展示。
76
+ * Passing `null` unstars; passing a value stars to "now" (北京时间字符串)。
77
+ */
78
+ updateGroupStarred(id, starred) {
79
+ const next = starred ? nowBeijing() : null;
80
+ this.db.prepare("UPDATE groups SET starred_at = ? WHERE id = ?")
81
+ .run(next, id);
82
+ return next;
83
+ },
84
+ /**
85
+ * Returns the starred_at value of a group, or null if not starred.
86
+ */
87
+ isGroupStarred(id) {
88
+ const row = this.db.prepare("SELECT starred_at FROM groups WHERE id = ?").get(id);
89
+ return row?.starred_at ?? null;
90
+ },
91
+ /**
92
+ * Backfill working_dir on legacy groups (NULL or empty). Caller supplies the
93
+ * `compute` fn — kept out of db.ts so this module stays free of filesystem
94
+ * conventions (homedir, ARTIFACTS_ROOT, etc.). Returns the list of (id, path)
95
+ * pairs that were written, so the caller can mkdir each one.
96
+ */
97
+ backfillGroupDefaultWorkingDir(compute) {
98
+ const rows = this.db.prepare("SELECT id FROM groups WHERE working_dir IS NULL OR working_dir = ''").all();
99
+ if (rows.length === 0)
100
+ return [];
101
+ const update = this.db.prepare("UPDATE groups SET working_dir = ? WHERE id = ?");
102
+ const filled = [];
103
+ const tx = this.db.transaction((items) => {
104
+ for (const { id } of items) {
105
+ const wd = compute(id);
106
+ update.run(wd, id);
107
+ filled.push({ id, workingDir: wd });
108
+ }
109
+ });
110
+ tx(rows);
111
+ return filled;
112
+ },
113
+ listGroups() {
114
+ return this.db.prepare(`
115
+ SELECT g.*, COUNT(gm.agent_name) as member_count,
116
+ (SELECT MAX(m.created_at) FROM group_messages m WHERE m.group_id = g.id) AS last_message_at
117
+ FROM groups g
118
+ LEFT JOIN group_members gm ON g.id = gm.group_id
119
+ GROUP BY g.id
120
+ ORDER BY g.created_at DESC
121
+ `).all();
122
+ },
123
+ listGroupsWithMembers() {
124
+ const groups = this.listGroups();
125
+ const rows = this.db.prepare(`
126
+ SELECT gm.group_id, gm.agent_name, gm.joined_at, gms.working_dir, gms.profile
127
+ FROM group_members gm
128
+ LEFT JOIN group_member_settings gms
129
+ ON gms.group_id = gm.group_id AND gms.agent_name = gm.agent_name
130
+ ORDER BY gm.joined_at
131
+ `).all();
132
+ const byGroup = new Map();
133
+ for (const r of rows) {
134
+ let list = byGroup.get(r.group_id);
135
+ if (!list) {
136
+ list = [];
137
+ byGroup.set(r.group_id, list);
138
+ }
139
+ list.push({ agent_name: r.agent_name, joined_at: r.joined_at, working_dir: r.working_dir, profile: r.profile });
140
+ }
141
+ return groups.map((g) => ({ ...g, members: byGroup.get(g.id) ?? [] }));
142
+ },
143
+ getGroupById(id) {
144
+ return this.db.prepare("SELECT * FROM groups WHERE id = ?").get(id);
145
+ },
146
+ deleteGroup(id) {
147
+ this.db.prepare("DELETE FROM group_messages WHERE group_id = ?").run(id);
148
+ this.db.prepare("DELETE FROM group_member_settings WHERE group_id = ?").run(id);
149
+ this.db.prepare("DELETE FROM group_members WHERE group_id = ?").run(id);
150
+ this.db.prepare("DELETE FROM groups WHERE id = ?").run(id);
151
+ },
152
+ /** Create group with type and metadata (for e2ed integration) */
153
+ createGroupTyped(opts) {
154
+ this.db.prepare("INSERT INTO groups (id, name, created_by, working_dir, type, metadata) VALUES (?, ?, ?, ?, ?, ?)").run(opts.id, opts.name, opts.createdBy || null, opts.workingDir || null, opts.type, opts.metadata || '{}');
155
+ },
156
+ /** Get group by id including type and metadata columns */
157
+ getGroupByIdFull(id) {
158
+ return this.db.prepare("SELECT * FROM groups WHERE id = ?").get(id);
159
+ },
160
+ /** List groups filtered by type */
161
+ listGroupsByType(type) {
162
+ return this.db.prepare("SELECT id, name, created_by, created_at, working_dir, type, metadata FROM groups WHERE type = ? ORDER BY created_at DESC").all(type);
163
+ },
164
+ /** Update group metadata JSON */
165
+ updateGroupMetadata(id, metadata) {
166
+ this.db.prepare("UPDATE groups SET metadata = ? WHERE id = ?").run(metadata, id);
167
+ },
168
+ /** 群级别指导 prompt;传 null/空串清空。 */
169
+ updateGroupGuidancePrompt(id, prompt) {
170
+ const v = prompt && prompt.trim() ? prompt : null;
171
+ this.db.prepare("UPDATE groups SET guidance_prompt = ? WHERE id = ?").run(v, id);
172
+ },
173
+ /**
174
+ * 更新群的内置 repo 配置(migration 051)。
175
+ *
176
+ * - repoUrl 为空串/null:清空 repo_url,该 group 回退现状(无 worktree)
177
+ * - extraReposJson 为空串/null:清空 extra_repos
178
+ * - 三列独立更新,避免一次只想改 defaultBranch 时把 url 也清掉
179
+ */
180
+ updateGroupRepo(id, repoUrl, repoDefaultBranch, extraReposJson, worktreeMode) {
181
+ const mode = worktreeMode === "issue" ? "issue" : "group";
182
+ this.db.prepare("UPDATE groups SET repo_url = ?, repo_default_branch = ?, extra_repos = ?, worktree_mode = ? WHERE id = ?").run(repoUrl || null, repoDefaultBranch || null, extraReposJson || null, mode, id);
183
+ },
184
+ addGroupMembers(groupId, agentNames) {
185
+ const stmt = this.db.prepare("INSERT OR IGNORE INTO group_members (group_id, agent_name) VALUES (?, ?)");
186
+ for (const name of agentNames) {
187
+ stmt.run(groupId, name);
188
+ }
189
+ },
190
+ removeGroupMembers(groupId, agentNames) {
191
+ const tx = this.db.transaction((names) => {
192
+ const m = this.db.prepare("DELETE FROM group_members WHERE group_id = ? AND agent_name = ?");
193
+ const s = this.db.prepare("DELETE FROM group_member_settings WHERE group_id = ? AND agent_name = ?");
194
+ for (const name of names) {
195
+ m.run(groupId, name);
196
+ s.run(groupId, name);
197
+ }
198
+ });
199
+ tx(agentNames);
200
+ },
201
+ getGroupMembers(groupId) {
202
+ return this.db.prepare(`
203
+ SELECT gm.agent_name, gm.joined_at, gms.working_dir, gms.profile
204
+ FROM group_members gm
205
+ LEFT JOIN group_member_settings gms
206
+ ON gms.group_id = gm.group_id AND gms.agent_name = gm.agent_name
207
+ WHERE gm.group_id = ?
208
+ ORDER BY gm.joined_at
209
+ `).all(groupId);
210
+ },
211
+ getGroupMemberSetting(groupId, agentName) {
212
+ const row = this.db.prepare("SELECT working_dir FROM group_member_settings WHERE group_id = ? AND agent_name = ?").get(groupId, agentName);
213
+ // working_dir 列是 NOT NULL(migration 020);空串表示"只设了 profile,没有
214
+ // working_dir 覆盖",归一化为 null 让上层 falsy 判断正常工作。
215
+ const v = row?.working_dir;
216
+ return v && v.length > 0 ? v : null;
217
+ },
218
+ listGroupMemberSettings(groupId) {
219
+ return this.db.prepare("SELECT agent_name, working_dir, updated_at, profile FROM group_member_settings WHERE group_id = ? ORDER BY agent_name").all(groupId);
220
+ },
221
+ getGroupMemberProfile(groupId, agentName) {
222
+ const row = this.db.prepare("SELECT profile FROM group_member_settings WHERE group_id = ? AND agent_name = ?").get(groupId, agentName);
223
+ return row?.profile ?? null;
224
+ },
225
+ upsertGroupMemberProfile(groupId, agentName, profileJson) {
226
+ // 群级别 profile 覆盖单独存一列。若该 (group, agent) 还没有 settings 行,
227
+ // INSERT 一行 working_dir=''(NOT NULL 兜底,getGroupMemberSetting 归一化为
228
+ // null 表示无覆盖)再写 profile; 若已有行, UPDATE profile 列(不动 working_dir)。
229
+ this.db.prepare(`
230
+ INSERT INTO group_member_settings (group_id, agent_name, working_dir, profile, updated_at)
231
+ VALUES (?, ?, '', ?, ?)
232
+ ON CONFLICT(group_id, agent_name) DO UPDATE SET
233
+ profile = excluded.profile,
234
+ updated_at = excluded.updated_at
235
+ `).run(groupId, agentName, profileJson, nowBeijing());
236
+ },
237
+ upsertGroupMemberSetting(groupId, agentName, workingDir) {
238
+ this.db.prepare(`
239
+ INSERT INTO group_member_settings (group_id, agent_name, working_dir, updated_at)
240
+ VALUES (?, ?, ?, ?)
241
+ ON CONFLICT(group_id, agent_name) DO UPDATE SET
242
+ working_dir = excluded.working_dir,
243
+ updated_at = excluded.updated_at
244
+ `).run(groupId, agentName, workingDir, nowBeijing());
245
+ },
246
+ clearGroupMemberSetting(groupId, agentName) {
247
+ const result = this.db.prepare("DELETE FROM group_member_settings WHERE group_id = ? AND agent_name = ?").run(groupId, agentName);
248
+ return result.changes > 0;
249
+ },
250
+ addGroupMessage(groupId, sender, content, mentions = [], options) {
251
+ const result = this.db.prepare("INSERT INTO group_messages (group_id, sender, content, mentions, cancelled_at) VALUES (?, ?, ?, ?, ?)").run(groupId, sender, content, JSON.stringify(mentions), options?.cancelledAt ?? null);
252
+ return Number(result.lastInsertRowid);
253
+ },
254
+ /**
255
+ * 把 worker 算出的 ComposedPrompt 持久化到 chat_message_prompts,keyed by
256
+ * group_messages.id。Dashboard 点击消息时读这张表渲染分层组成。
257
+ */
258
+ addChatMessagePrompt(groupMessageId, layersJson, final, generatedAt, promptVersion) {
259
+ this.db.prepare(`INSERT INTO chat_message_prompts (group_message_id, layers, final, generated_at, prompt_version)
260
+ VALUES (?, ?, ?, ?, ?)`).run(groupMessageId, layersJson, final, generatedAt, promptVersion);
261
+ },
262
+ getChatMessagePrompt(groupMessageId) {
263
+ const row = this.db.prepare(`SELECT layers, final, generated_at, prompt_version
264
+ FROM chat_message_prompts WHERE group_message_id = ?`).get(groupMessageId);
265
+ if (!row)
266
+ return null;
267
+ return {
268
+ layers: JSON.parse(row.layers),
269
+ final: row.final,
270
+ generated_at: row.generated_at,
271
+ prompt_version: row.prompt_version,
272
+ };
273
+ },
274
+ // 内部 helper:执行 SELECT 群消息 + LEFT JOIN chat_message_prompts,
275
+ // 拼装成 GroupMessageRow[](剥掉 layers/final/generated_at/prompt_version
276
+ // 四个字符串列,组装成 composed_prompt 对象)。whereClause 由调用方提供,
277
+ // 绑定参数顺序为 groupId,然后再是 LIMIT 值(如果有的话)。
278
+ // 拉取群消息。和 getIssueEvents 同款 head+tail+marker 思路:
279
+ // - 总数 <= headKeep+tailKeep:全拿(ASC)。
280
+ // - 总数超过:head(最早 headKeep 条) + tail(最新 tailKeep 条) + 中间合成一条
281
+ // truncated marker,sender='__truncated',前端渲染成「已省略 N 条」chip。
282
+ // 旧实现 ORDER BY created_at ASC LIMIT N 会把最新消息截掉,群里消息累积
283
+ // 超过 N 条后新消息永远拉不到——参见群 f080e51e 的复现。
284
+ getGroupMessages(groupId, headKeep = 5, tailKeep = 295) {
285
+ const totalCount = this.db.prepare("SELECT COUNT(*) AS c FROM group_messages WHERE group_id = ?").get(groupId);
286
+ const total = totalCount.c;
287
+ if (total <= headKeep + tailKeep) {
288
+ return fetchGroupMessageRows(this.db, `WHERE m.group_id = ? ORDER BY m.created_at ASC, m.id ASC`, groupId);
289
+ }
290
+ const head = fetchGroupMessageRows(this.db, `WHERE m.group_id = ? ORDER BY m.created_at ASC, m.id ASC LIMIT ?`, groupId, headKeep);
291
+ const tail = fetchGroupMessageRows(this.db, `WHERE m.group_id = ? ORDER BY m.created_at DESC, m.id DESC LIMIT ?`, groupId, tailKeep);
292
+ tail.reverse();
293
+ const omitted = total - head.length - tail.length;
294
+ // marker 的 created_at 取 head 末尾 +1ms,落在 head 和 tail 之间,
295
+ // 前端按 timestamp 排序时位置正确。
296
+ const markerTime = head.length > 0
297
+ ? shiftBeijing(head[head.length - 1].created_at, 1)
298
+ : (tail[0]?.created_at ?? nowBeijing());
299
+ const marker = {
300
+ id: -1,
301
+ sender: "__truncated",
302
+ content: "",
303
+ mentions: "[]",
304
+ created_at: markerTime,
305
+ cancelled_at: null,
306
+ composed_prompt: null,
307
+ truncated: { omitted },
308
+ };
309
+ return [...head, marker, ...tail];
310
+ },
311
+ /** 按 since 时间过滤群消息(UTC ISO 或北京时间字符串都行,字符串字典序比较)。
312
+ * 不走 head/tail 截断——轮询用,只看新增量。返回 ASC 排序。 */
313
+ getGroupMessagesSince(groupId, sinceIso) {
314
+ return fetchGroupMessageRows(this.db, `WHERE m.group_id = ? AND m.created_at > ? ORDER BY m.created_at ASC, m.id ASC`, groupId, sinceIso);
315
+ },
316
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Guidance templates — 群指导 prompt 模板库。
3
+ *
4
+ * 每条模板包含 prompt_text 和可选的 schedule_config(JSON 字符串)。选模板时
5
+ * 前端把 prompt_text 填进 groups.guidance_prompt;若 schedule_config 存在,
6
+ * 同时调 schedules.createScheduledTask 建定时任务。prompt_text 和
7
+ * schedule_config 都支持 {{teacher}}/{{student}}/{{topic}} 占位符,在前端解析。
8
+ *
9
+ * 种子模板 is_default=1,deleteGuidanceTemplate 拒绝删除,API 也兜底返回 400。
10
+ */
11
+ import { buildUpdate } from "./build-update.js";
12
+ export const guidanceTemplateMethods = {
13
+ listGuidanceTemplates() {
14
+ return this.db.prepare("SELECT * FROM guidance_templates ORDER BY sort_order ASC, id ASC").all();
15
+ },
16
+ getGuidanceTemplate(id) {
17
+ return this.db.prepare("SELECT * FROM guidance_templates WHERE id = ?")
18
+ .get(id);
19
+ },
20
+ createGuidanceTemplate(input) {
21
+ const now = Date.now();
22
+ const info = this.db.prepare(`
23
+ INSERT INTO guidance_templates (
24
+ name, description, prompt_text, schedule_config, sort_order, is_default, created_at, updated_at
25
+ ) VALUES (?, ?, ?, ?, ?, 0, ?, ?)
26
+ `).run(input.name, input.description ?? "", input.prompt_text, input.schedule_config ?? null, input.sort_order ?? 0, now, now);
27
+ return this.getGuidanceTemplate(Number(info.lastInsertRowid));
28
+ },
29
+ updateGuidanceTemplate(id, patch) {
30
+ const built = buildUpdate({
31
+ table: "guidance_templates",
32
+ sets: {
33
+ name: patch.name,
34
+ description: patch.description,
35
+ prompt_text: patch.prompt_text,
36
+ schedule_config: patch.schedule_config,
37
+ sort_order: patch.sort_order,
38
+ },
39
+ where: "id = ?",
40
+ whereParams: [id],
41
+ updatedAt: "epoch",
42
+ });
43
+ if (!built)
44
+ return this.getGuidanceTemplate(id);
45
+ this.db.prepare(built.sql).run(...built.params);
46
+ return this.getGuidanceTemplate(id);
47
+ },
48
+ /** 删除模板;种子模板(is_default=1)拒绝删除,返回 false。 */
49
+ deleteGuidanceTemplate(id) {
50
+ const row = this.getGuidanceTemplate(id);
51
+ if (!row)
52
+ return false;
53
+ if (row.is_default === 1)
54
+ return false;
55
+ this.db.prepare("DELETE FROM guidance_templates WHERE id = ?").run(id);
56
+ return true;
57
+ },
58
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MeshDb facade.
3
+ *
4
+ * Public re-export so existing callers (`from "../db.js"`) keep working
5
+ * without modification. The actual `MeshDb` class still lives in
6
+ * `./internal.ts` while method-by-method extraction lands in follow-up PRs.
7
+ *
8
+ * Once extraction is complete this file can become the composition root —
9
+ * each domain module (agents / groups / issues / ...) contributes its
10
+ * methods to a single `MeshDb` instance.
11
+ */
12
+ export { MeshDb } from "./internal.js";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * MeshDb — composition root that wires each domain module's methods onto a
3
+ * single instance. Public API surface (the method names) is unchanged from
4
+ * the pre-split monolithic version, so the 27 call sites across
5
+ * `src/master/*.ts` and `tests/*.ts` need no edits.
6
+ *
7
+ * Each domain module exports a method bag whose `this` is typed as
8
+ * `MeshDbSelf` (see ./core.ts) — a structural shape with the `db` handle
9
+ * plus the cross-module method signatures they reference (e.g.
10
+ * messages.enqueueOffline → agents.getAgentById).
11
+ *
12
+ * Type-wise, the MeshDb interface merges `MeshDbCore` with the intersection
13
+ * of every domain method bag — declaration merging puts the bag methods on
14
+ * the class instance type without 200+ lines of `declare` boilerplate. The
15
+ * runtime instance is built via `Object.assign(this, …bags)` in the
16
+ * constructor; implementations live in the domain modules.
17
+ */
18
+ import { MeshDbCore } from "./core.js";
19
+ import { agentMethods } from "./agents.js";
20
+ import { messageMethods } from "./messages.js";
21
+ import { domainMethods } from "./domains.js";
22
+ import { groupMethods } from "./groups.js";
23
+ import { issueMethods } from "./issues.js";
24
+ import { noteMethods } from "./notes.js";
25
+ import { memoryMethods } from "./memory.js";
26
+ import { skillMethods } from "./skills.js";
27
+ import { scheduleMethods } from "./schedules.js";
28
+ import { askBridgeMethods } from "./ask-bridges.js";
29
+ import { guidanceTemplateMethods } from "./guidance-templates.js";
30
+ import { schedulePatternMethods } from "./schedule-patterns.js";
31
+ import { agentSessionMethods } from "./agent-sessions.js";
32
+ import { issuePatrolMethods } from "./issues-patrol.js";
33
+ import { linkMethods } from "./links.js";
34
+ import { masterNodeMethods } from "./master-node.js";
35
+ import { teamMethods } from "./team.js";
36
+ import { agentVisibilityMethods } from "./agent-visibility.js";
37
+ export class MeshDb extends MeshDbCore {
38
+ constructor(dbPath) {
39
+ super(dbPath);
40
+ // Each method bag's `this` resolves to this instance at call time.
41
+ // The `MethodBags` intersection on the interface above ensures
42
+ // TypeScript sees the assigned methods as members of MeshDb.
43
+ Object.assign(this, agentMethods, messageMethods, domainMethods, groupMethods, issueMethods, noteMethods, memoryMethods, skillMethods, scheduleMethods, askBridgeMethods, guidanceTemplateMethods, schedulePatternMethods, agentSessionMethods, issuePatrolMethods, linkMethods, masterNodeMethods, teamMethods, agentVisibilityMethods);
44
+ }
45
+ }
@@ -0,0 +1,81 @@
1
+ import { nowBeijing } from "../../shared/time.js";
2
+ import { buildUpdate } from "./build-update.js";
3
+ export const issuePatrolMethods = {
4
+ createPatrolRun(input) {
5
+ const now = input.startedAt ?? nowBeijing();
6
+ this.db.prepare(`
7
+ INSERT INTO issue_patrol_runs
8
+ (run_id, patrol_group_id, patrol_issue_id, started_at, in_progress_count, candidates_scanned, candidates_ready, status, note)
9
+ VALUES (?, ?, ?, ?, ?, 0, 0, ?, NULL)
10
+ `).run(input.runId, input.patrolGroupId, input.patrolIssueId ?? null, now, input.inProgressCount, input.status ?? "dispatched");
11
+ },
12
+ /** 标记本轮结束。scanned/ready/note 不传则保留原值。 */
13
+ finishPatrolRun(runId, status, opts) {
14
+ const built = buildUpdate({
15
+ table: "issue_patrol_runs",
16
+ sets: {
17
+ candidates_scanned: opts?.scanned,
18
+ candidates_ready: opts?.ready,
19
+ note: opts?.note,
20
+ },
21
+ where: "run_id = ?",
22
+ whereParams: [runId],
23
+ updatedAt: false,
24
+ extraSets: [
25
+ { column: "finished_at", value: nowBeijing() },
26
+ { column: "status", value: status },
27
+ ],
28
+ });
29
+ if (built)
30
+ this.db.prepare(built.sql).run(...built.params);
31
+ },
32
+ getPatrolRunByIssueId(patrolIssueId) {
33
+ return this.db.prepare("SELECT * FROM issue_patrol_runs WHERE patrol_issue_id = ? ORDER BY started_at DESC LIMIT 1").get(patrolIssueId);
34
+ },
35
+ getPatrolRun(runId) {
36
+ return this.db.prepare("SELECT * FROM issue_patrol_runs WHERE run_id = ?").get(runId);
37
+ },
38
+ listPatrolRuns(opts) {
39
+ const limit = Math.min(opts?.limit ?? 50, 500);
40
+ const where = [];
41
+ const params = [];
42
+ if (opts?.patrolGroupId) {
43
+ where.push("patrol_group_id = ?");
44
+ params.push(opts.patrolGroupId);
45
+ }
46
+ const whereClause = where.length ? `WHERE ${where.join(" AND ")}` : "";
47
+ params.push(limit);
48
+ return this.db.prepare(`SELECT * FROM issue_patrol_runs ${whereClause} ORDER BY started_at DESC LIMIT ?`).all(...params);
49
+ },
50
+ insertPatrolLog(input) {
51
+ const now = nowBeijing();
52
+ this.db.prepare(`
53
+ INSERT INTO issue_patrol_logs
54
+ (id, run_id, patrol_group_id, issue_id, candidate_group_id, verdict, rule_matched, rationale, raw, created_at)
55
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
56
+ `).run(input.id, input.runId, input.patrolGroupId, input.issueId ?? null, input.candidateGroupId ?? null, input.verdict, input.ruleMatched ?? null, input.rationale ?? null, input.raw ?? null, now);
57
+ },
58
+ listPatrolLogsForRun(runId) {
59
+ return this.db.prepare("SELECT * FROM issue_patrol_logs WHERE run_id = ? ORDER BY created_at ASC").all(runId);
60
+ },
61
+ listPatrolLogs(opts) {
62
+ const limit = Math.min(opts?.limit ?? 200, 1000);
63
+ const where = [];
64
+ const params = [];
65
+ if (opts?.patrolGroupId) {
66
+ where.push("patrol_group_id = ?");
67
+ params.push(opts.patrolGroupId);
68
+ }
69
+ if (opts?.verdict) {
70
+ where.push("verdict = ?");
71
+ params.push(opts.verdict);
72
+ }
73
+ if (opts?.candidateGroupId) {
74
+ where.push("candidate_group_id = ?");
75
+ params.push(opts.candidateGroupId);
76
+ }
77
+ const whereClause = where.length ? `WHERE ${where.join(" AND ")}` : "";
78
+ params.push(limit);
79
+ return this.db.prepare(`SELECT * FROM issue_patrol_logs ${whereClause} ORDER BY created_at DESC LIMIT ?`).all(...params);
80
+ },
81
+ };