@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,135 @@
1
+ /**
2
+ * Scheduled tasks — at-most-once cron-style triggers with run history.
3
+ *
4
+ * Methods attach via `Object.assign`. The scheduler module (./scheduler.ts)
5
+ * pulls `getDueScheduledTasks` each tick and calls `markScheduledTaskRun`
6
+ * after the run completes. `next_run_at` is the integer-ms epoch field the
7
+ * scheduler indexes on; `markScheduledTaskRun` preserves `last_issue_id`
8
+ * when `issueId=null` is passed so the agent mode can re-bind to the same
9
+ * issue across repeat iterations.
10
+ */
11
+ import { buildUpdate } from "./build-update.js";
12
+ export const scheduleMethods = {
13
+ listScheduledTasks(filter) {
14
+ let sql = "SELECT * FROM scheduled_tasks WHERE 1=1";
15
+ const params = [];
16
+ if (filter?.groupId) {
17
+ sql += " AND group_id = ?";
18
+ params.push(filter.groupId);
19
+ }
20
+ sql += " ORDER BY id DESC";
21
+ return this.db.prepare(sql).all(...params);
22
+ },
23
+ getScheduledTask(id) {
24
+ return this.db.prepare("SELECT * FROM scheduled_tasks WHERE id = ?")
25
+ .get(id);
26
+ },
27
+ /** 返回所有已启用、且 next_run_at <= now 的任务。调度器串行消费。 */
28
+ getDueScheduledTasks(now) {
29
+ return this.db.prepare("SELECT * FROM scheduled_tasks WHERE enabled = 1 AND next_run_at <= ? ORDER BY next_run_at ASC").all(now);
30
+ },
31
+ /** 新建任务;返回插入后的完整 row(包含自动算出的首个 next_run_at)。 */
32
+ createScheduledTask(input) {
33
+ const now = Date.now();
34
+ const enabled = input.enabled === false ? 0 : 1;
35
+ const nextRunAt = input.scheduleKind === "once"
36
+ ? (input.runAt ?? now)
37
+ : now + (input.intervalSec ?? 0) * 1000;
38
+ const info = this.db.prepare(`
39
+ INSERT INTO scheduled_tasks (
40
+ name, group_id, mode, agent_name, schedule_kind, interval_sec, run_at,
41
+ prompt, enabled, next_run_at, repeat_times, repeat_count, created_at, updated_at,
42
+ handler_key, handler_payload
43
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)
44
+ `).run(input.name, input.groupId, input.mode, input.agentName ?? null, input.scheduleKind, input.intervalSec ?? null, input.runAt ?? null, input.prompt, enabled, nextRunAt, input.repeatTimes ?? null, now, now, input.handlerKey ?? null, input.handlerPayload ?? null);
45
+ return this.getScheduledTask(Number(info.lastInsertRowid));
46
+ },
47
+ /** 部分更新;改 schedule 字段会触发 next_run_at 重算。 */
48
+ updateScheduledTask(id, patch) {
49
+ const task = this.getScheduledTask(id);
50
+ if (!task)
51
+ return undefined;
52
+ // schedule_kind / interval_sec / run_at 任一变化都重算 next_run_at
53
+ const scheduleChanged = patch.scheduleKind !== undefined ||
54
+ patch.intervalSec !== undefined ||
55
+ patch.runAt !== undefined;
56
+ const extraSets = [];
57
+ if (scheduleChanged) {
58
+ const kind = patch.scheduleKind ?? task.schedule_kind;
59
+ const intervalSec = patch.intervalSec !== undefined ? patch.intervalSec : task.interval_sec;
60
+ const runAt = patch.runAt !== undefined ? patch.runAt : task.run_at;
61
+ const now = Date.now();
62
+ const nextRunAt = kind === "once"
63
+ ? (runAt ?? now)
64
+ : now + (intervalSec ?? 0) * 1000;
65
+ extraSets.push({ column: "schedule_kind", value: kind }, { column: "interval_sec", value: intervalSec }, { column: "run_at", value: runAt }, { column: "next_run_at", value: nextRunAt });
66
+ }
67
+ const built = buildUpdate({
68
+ table: "scheduled_tasks",
69
+ sets: {
70
+ name: patch.name,
71
+ mode: patch.mode,
72
+ agent_name: patch.agentName,
73
+ prompt: patch.prompt,
74
+ enabled: patch.enabled !== undefined ? (patch.enabled ? 1 : 0) : undefined,
75
+ repeat_times: patch.repeatTimes,
76
+ handler_key: patch.handlerKey,
77
+ handler_payload: patch.handlerPayload,
78
+ },
79
+ where: "id = ?",
80
+ whereParams: [id],
81
+ updatedAt: "epoch",
82
+ extraSets,
83
+ });
84
+ if (!built)
85
+ return task;
86
+ this.db.prepare(built.sql).run(...built.params);
87
+ return this.getScheduledTask(id);
88
+ },
89
+ deleteScheduledTask(id) {
90
+ const result = this.db.prepare("DELETE FROM scheduled_tasks WHERE id = ?").run(id);
91
+ return result.changes > 0;
92
+ },
93
+ /** at-most-once 推进:先把 next_run_at 推到下一个未来时间点,再执行。 */
94
+ rescheduleTask(id, nextRunAt) {
95
+ this.db.prepare("UPDATE scheduled_tasks SET next_run_at = ?, updated_at = ? WHERE id = ?").run(nextRunAt, Date.now(), id);
96
+ },
97
+ /** 立刻触发:把 next_run_at 设为 now,下一个 tick 就跑。 */
98
+ triggerScheduledTask(id, now = Date.now()) {
99
+ const result = this.db.prepare("UPDATE scheduled_tasks SET next_run_at = ?, updated_at = ? WHERE id = ? AND enabled = 1").run(now, Date.now(), id);
100
+ return result.changes > 0;
101
+ },
102
+ /** 记录本次执行结果。repeat_count 由调度器计算后传入,这里直接覆写。
103
+ * issueId=null 保留上一次 last_issue_id(用于 agent skip 的防堆积场景 —— 下一轮继续反查同一个 prev issue);
104
+ * 传字符串才覆写。
105
+ * 没有显式 clear 路径 —— last_issue_id 一旦被 agent 模式写入就持续追踪到 disable 为止。 */
106
+ markScheduledTaskRun(id, runAt, status, error, issueId, repeatCount) {
107
+ if (issueId !== null) {
108
+ this.db.prepare(`
109
+ UPDATE scheduled_tasks SET
110
+ last_run_at = ?,
111
+ last_status = ?,
112
+ last_error = ?,
113
+ last_issue_id = ?,
114
+ repeat_count = ?,
115
+ updated_at = ?
116
+ WHERE id = ?
117
+ `).run(runAt, status, error, issueId, repeatCount, Date.now(), id);
118
+ }
119
+ else {
120
+ this.db.prepare(`
121
+ UPDATE scheduled_tasks SET
122
+ last_run_at = ?,
123
+ last_status = ?,
124
+ last_error = ?,
125
+ repeat_count = ?,
126
+ updated_at = ?
127
+ WHERE id = ?
128
+ `).run(runAt, status, error, repeatCount, Date.now(), id);
129
+ }
130
+ },
131
+ /** 一次性任务跑完,或 repeat_times 用尽,自动 enabled=0。 */
132
+ disableScheduledTask(id) {
133
+ this.db.prepare("UPDATE scheduled_tasks SET enabled = 0, updated_at = ? WHERE id = ?").run(Date.now(), id);
134
+ },
135
+ };
@@ -0,0 +1,144 @@
1
+ import { nowBeijing } from "../../shared/time.js";
2
+ import { buildUpdate } from "./build-update.js";
3
+ // ─── Methods ──────────────────────────────────────────────────────────────
4
+ export const skillMethods = {
5
+ /** 全局 skill 索引(不含 content)。默认只返回 active。 */
6
+ listSkills(filter = {}) {
7
+ const { category, activeOnly = true } = filter;
8
+ const where = [];
9
+ const params = [];
10
+ if (activeOnly) {
11
+ where.push("active = 1");
12
+ }
13
+ if (category) {
14
+ where.push("category = ?");
15
+ params.push(category);
16
+ }
17
+ const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
18
+ return this.db.prepare(`SELECT id, name, description, category, source_type, created_by, created_at, view_count
19
+ FROM agent_skills ${whereClause} ORDER BY name ASC`).all(...params);
20
+ },
21
+ /** 关键词搜索(LIKE 匹配 name/description/category/content)。只搜 active。 */
22
+ searchSkills(keyword) {
23
+ const kw = `%${keyword}%`;
24
+ return this.db.prepare(`SELECT id, name, description, category, source_type, created_by, created_at, view_count
25
+ FROM agent_skills
26
+ WHERE active = 1 AND (name LIKE ? OR description LIKE ? OR category LIKE ? OR content LIKE ?)
27
+ ORDER BY view_count DESC, name ASC`).all(kw, kw, kw, kw);
28
+ },
29
+ /** 详情(content 全文)。view_count += 1。 */
30
+ getSkill(id) {
31
+ const row = this.db.prepare("SELECT * FROM agent_skills WHERE id = ?").get(id);
32
+ if (!row)
33
+ return undefined;
34
+ if (row.active === 1) {
35
+ this.db.prepare("UPDATE agent_skills SET view_count = view_count + 1, last_viewed_at = datetime('now') WHERE id = ?").run(id);
36
+ }
37
+ return row;
38
+ },
39
+ getSkillByName(name) {
40
+ return this.db.prepare("SELECT * FROM agent_skills WHERE name = ?").get(name);
41
+ },
42
+ createSkill(input) {
43
+ const now = nowBeijing();
44
+ this.db.prepare(`
45
+ INSERT INTO agent_skills (id, name, description, content, category, source_type, source_ref, created_by, created_at, updated_at, active, view_count, last_viewed_at)
46
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, NULL)
47
+ `).run(input.id, input.name, input.description, input.content, input.category ?? null, input.sourceType ?? "manual", input.sourceRef ?? null, input.createdBy, now, now);
48
+ },
49
+ updateSkill(id, fields) {
50
+ const built = buildUpdate({
51
+ table: "agent_skills",
52
+ sets: {
53
+ name: fields.name,
54
+ description: fields.description,
55
+ content: fields.content,
56
+ category: fields.category,
57
+ },
58
+ where: "id = ?",
59
+ whereParams: [id],
60
+ updatedAt: "datetime-now",
61
+ });
62
+ if (!built)
63
+ return false;
64
+ const result = this.db.prepare(built.sql).run(...built.params);
65
+ return result.changes > 0;
66
+ },
67
+ deactivateSkill(id) {
68
+ const result = this.db.prepare("UPDATE agent_skills SET active = 0, updated_at = datetime('now') WHERE id = ?").run(id);
69
+ return result.changes > 0;
70
+ },
71
+ // ─── 绑定关系 ───────────────────────────────────────────────────────────
72
+ /** 绑定 (group, agent, skill)。UNIQUE 冲突时 ignore。 */
73
+ bindSkill(input) {
74
+ const now = nowBeijing();
75
+ const result = this.db.prepare(`INSERT OR IGNORE INTO agent_skill_bindings (group_id, agent_name, skill_id, created_by, created_at)
76
+ VALUES (?, ?, ?, ?, ?)`).run(input.groupId, input.agentName, input.skillId, input.createdBy, now);
77
+ return result.changes > 0;
78
+ },
79
+ unbindSkill(input) {
80
+ const result = this.db.prepare("DELETE FROM agent_skill_bindings WHERE group_id = ? AND agent_name = ? AND skill_id = ?").run(input.groupId, input.agentName, input.skillId);
81
+ return result.changes > 0;
82
+ },
83
+ /** 绑定关系查询(群设置/工具箱管理用)。 */
84
+ listBindings(filter = {}) {
85
+ const where = [];
86
+ const params = [];
87
+ if (filter.groupId) {
88
+ where.push("group_id = ?");
89
+ params.push(filter.groupId);
90
+ }
91
+ if (filter.agentName) {
92
+ where.push("agent_name = ?");
93
+ params.push(filter.agentName);
94
+ }
95
+ if (filter.skillId) {
96
+ where.push("skill_id = ?");
97
+ params.push(filter.skillId);
98
+ }
99
+ const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
100
+ return this.db.prepare(`SELECT * FROM agent_skill_bindings ${whereClause} ORDER BY created_at DESC`).all(...params);
101
+ },
102
+ /** 该 agent 在该群绑定的 skill 数(供 prompt 极简指针)。只算 active skill。 */
103
+ countSkillsForAgent(groupId, agentName) {
104
+ const row = this.db.prepare(`SELECT COUNT(*) as n
105
+ FROM agent_skill_bindings b
106
+ JOIN agent_skills s ON s.id = b.skill_id
107
+ WHERE b.group_id = ? AND b.agent_name = ? AND s.active = 1`).get(groupId, agentName);
108
+ return row?.n ?? 0;
109
+ },
110
+ /** 该 agent 在该群绑定的 skill 索引(供 agent `rotom skill mine` 拉取)。 */
111
+ listSkillsForAgent(groupId, agentName) {
112
+ return this.db.prepare(`SELECT s.id, s.name, s.description, s.category, s.source_type, s.created_by, s.created_at, s.view_count
113
+ FROM agent_skill_bindings b
114
+ JOIN agent_skills s ON s.id = b.skill_id
115
+ WHERE b.group_id = ? AND b.agent_name = ? AND s.active = 1
116
+ ORDER BY s.name ASC`).all(groupId, agentName);
117
+ },
118
+ // ─── playbook memory → skill 沉淀 ───────────────────────────────────────
119
+ /** 把一条 playbook memory 升级成 skill。
120
+ * memory.value → skill.content,memory.key → skill.name(或显式),
121
+ * memory.summary → skill.description(或显式)。
122
+ * memory 保留 active=1,skill.source_ref 指向 memory_id。 */
123
+ promoteMemoryToSkill(memoryId, opts) {
124
+ const mem = this.db.prepare("SELECT * FROM agent_memory WHERE id = ?").get(memoryId);
125
+ if (!mem)
126
+ throw new Error(`memory ${memoryId} not found`);
127
+ const name = (opts.name ?? mem.key).trim();
128
+ if (!name)
129
+ throw new Error("skill name 不能为空(memory.key 为空且未传 --name)");
130
+ const description = (opts.description ?? mem.summary ?? mem.value.slice(0, 120)).trim();
131
+ const skillId = randomUUIDLike();
132
+ const now = nowBeijing();
133
+ this.db.prepare(`
134
+ INSERT INTO agent_skills (id, name, description, content, category, source_type, source_ref, created_by, created_at, updated_at, active, view_count, last_viewed_at)
135
+ VALUES (?, ?, ?, ?, 'playbook', 'promoted', ?, ?, ?, ?, 1, 0, NULL)
136
+ `).run(skillId, name, description, mem.value, memoryId, opts.createdBy, now, now);
137
+ return { skillId, name };
138
+ },
139
+ };
140
+ /** 生成 UUID-like ID(避免引入 crypto 依赖到 db 层,用 Date+random 兜底)。
141
+ * 调用方一般用 randomUUID;promote 路径在 db 层内自调,用此兜底。 */
142
+ function randomUUIDLike() {
143
+ return `sk_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
144
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * team / team_peers / human_membership CRUD。
3
+ *
4
+ * - team 表存"本机 master 加入了哪些团队"(本机视角,每行一个团队)
5
+ * - team_peers 存"团队里有哪些其他 master"(协调侧权威 + member 侧缓存)
6
+ * - human_membership 存"本机真人加入了哪些团队"
7
+ *
8
+ * agent_visibility 在 ./agent-visibility.ts(单独模块,方法较多)。
9
+ *
10
+ * 历史命名:Phase 2 叫 department,migration 058 改名 team。所有 department_id
11
+ * 列同步改为 team_id。底层 federation 协议字段 departmentId → teamId。
12
+ */
13
+ import { buildUpdate } from "./build-update.js";
14
+ export const teamMethods = {
15
+ // ─── team ──────────────────────────────────────────────────────────────
16
+ listTeams() {
17
+ return this.db.prepare("SELECT * FROM team ORDER BY name").all();
18
+ },
19
+ getTeam(id) {
20
+ return this.db.prepare("SELECT * FROM team WHERE id = ?").get(id);
21
+ },
22
+ insertTeam(input) {
23
+ this.db.prepare("INSERT INTO team (id, name, description, my_role, coord_endpoints) VALUES (?, ?, ?, ?, ?)").run(input.id, input.name, input.description ?? null, input.my_role, input.coord_endpoints);
24
+ },
25
+ updateTeamMeta(id, meta) {
26
+ const built = buildUpdate({
27
+ table: "team",
28
+ sets: { name: meta.name, description: meta.description },
29
+ where: "id = ?",
30
+ whereParams: [id],
31
+ updatedAt: false,
32
+ });
33
+ if (built)
34
+ this.db.prepare(built.sql).run(...built.params);
35
+ },
36
+ deleteTeam(id) {
37
+ this.db.prepare("DELETE FROM team WHERE id = ?").run(id);
38
+ },
39
+ // ─── team_peers ────────────────────────────────────────────────────────
40
+ listPeers(teamId) {
41
+ return this.db.prepare("SELECT * FROM team_peers WHERE team_id = ? ORDER BY hostname").all(teamId);
42
+ },
43
+ getPeer(teamId, masterId) {
44
+ return this.db.prepare("SELECT * FROM team_peers WHERE team_id = ? AND master_id = ?").get(teamId, masterId);
45
+ },
46
+ /** hostname 冲突检测:团队内同 hostname 是否已有 peer */
47
+ findPeerByHostname(teamId, hostname) {
48
+ return this.db.prepare("SELECT * FROM team_peers WHERE team_id = ? AND hostname = ?").get(teamId, hostname);
49
+ },
50
+ upsertPeer(input) {
51
+ const existing = this.db.prepare("SELECT 1 FROM team_peers WHERE team_id = ? AND master_id = ?").get(input.team_id, input.master_id);
52
+ if (existing) {
53
+ this.db.prepare(`
54
+ UPDATE team_peers SET
55
+ hostname = ?,
56
+ endpoint = COALESCE(?, endpoint),
57
+ role = ?,
58
+ last_seen_at = datetime('now')
59
+ WHERE team_id = ? AND master_id = ?
60
+ `).run(input.hostname, input.endpoint ?? null, input.role, input.team_id, input.master_id);
61
+ return;
62
+ }
63
+ this.db.prepare(`
64
+ INSERT INTO team_peers (team_id, master_id, hostname, endpoint, role, last_seen_at)
65
+ VALUES (?, ?, ?, ?, ?, datetime('now'))
66
+ `).run(input.team_id, input.master_id, input.hostname, input.endpoint ?? null, input.role);
67
+ },
68
+ touchPeerHeartbeat(teamId, masterId) {
69
+ this.db.prepare("UPDATE team_peers SET last_seen_at = datetime('now') WHERE team_id = ? AND master_id = ?").run(teamId, masterId);
70
+ },
71
+ deletePeer(teamId, masterId) {
72
+ this.db.prepare("DELETE FROM team_peers WHERE team_id = ? AND master_id = ?").run(teamId, masterId);
73
+ },
74
+ /** member 离开团队时清掉所有本地缓存的 peer */
75
+ clearPeers(teamId) {
76
+ this.db.prepare("DELETE FROM team_peers WHERE team_id = ?").run(teamId);
77
+ },
78
+ // ─── human_membership ──────────────────────────────────────────────────
79
+ listHumanMemberships(agentId) {
80
+ return this.db.prepare("SELECT * FROM human_membership WHERE agent_id = ? ORDER BY joined_at").all(agentId);
81
+ },
82
+ addHumanMembership(agentId, teamId) {
83
+ this.db.prepare("INSERT OR IGNORE INTO human_membership (agent_id, team_id) VALUES (?, ?)").run(agentId, teamId);
84
+ },
85
+ removeHumanMembership(agentId, teamId) {
86
+ this.db.prepare("DELETE FROM human_membership WHERE agent_id = ? AND team_id = ?").run(agentId, teamId);
87
+ },
88
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Row type definitions for the mesh SQLite database.
3
+ *
4
+ * Each interface mirrors the SQLite column names exactly — the runtime
5
+ * layer in `db/internal.ts` maps SELECT results onto these types.
6
+ *
7
+ * Kept in its own module so consumers (api/, executor/worker.ts, tests,
8
+ * dashboard) can import row types without dragging in better-sqlite3.
9
+ */
10
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Backwards-compatible facade for the database layer.
3
+ *
4
+ * The implementation has moved into `./db/`:
5
+ * • `./db/types.ts` — Row interfaces (no better-sqlite3 import)
6
+ * • `./db/internal.ts` — MeshDb runtime class (currently still monolithic;
7
+ * method-by-method extraction lands in follow-up PRs)
8
+ * • `./db/index.ts` — Public surface
9
+ *
10
+ * Existing callers (`import { MeshDb } from "../db.js"`) continue to work.
11
+ */
12
+ export * from "./db/index.js";
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Digital Employee Mesh — Embedded Master
3
+ *
4
+ * Starts Master HTTP+WS server programmatically (non-standalone).
5
+ * Used by startEmbeddedMaster() for in-process Master startup.
6
+ *
7
+ * Unlike server.ts (standalone entry), this does NOT call process.exit()
8
+ * or listen for SIGINT/SIGTERM — the host process manages lifecycle.
9
+ */
10
+ import express from "express";
11
+ import fs from "node:fs";
12
+ import http from "node:http";
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { MeshDb } from "./db.js";
16
+ import { AuthService } from "./auth.js";
17
+ import { WSHub } from "./ws-hub.js";
18
+ import { Router } from "./router.js";
19
+ import { OfflineQueue } from "./offline-queue.js";
20
+ import { createApi } from "./api/index.js";
21
+ import { createLogger, enableFileLogging, closeFileLogging } from "../shared/logger.js";
22
+ import { ShareTokenStore } from "./share-tokens.js";
23
+ import { getMasterIdentity } from "./federation/identity.js";
24
+ import { runOpcBootstrap, ensureLocalExecutor } from "./opc-bootstrap.js";
25
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
+ // ---------------------------------------------------------------------------
27
+ // Start
28
+ // ---------------------------------------------------------------------------
29
+ export async function startEmbeddedMaster(config, logger = createLogger("mesh-master")) {
30
+ const host = config.host ?? "0.0.0.0";
31
+ // Ensure data directory exists
32
+ const dataDir = path.resolve(config.dataDir);
33
+ if (!fs.existsSync(dataDir)) {
34
+ fs.mkdirSync(dataDir, { recursive: true });
35
+ }
36
+ // Enable daily-rotated file logging under data directory
37
+ enableFileLogging(path.join(dataDir, "logs"));
38
+ // Database
39
+ const db = new MeshDb(path.join(dataDir, "mesh.db"));
40
+ // OPC bootstrap — 与命令行入口一致:解析 master 身份 + 首次启动建默认 agent/group。
41
+ // 嵌入式场景失败(hostname 校验等)也直接抛错 —— 身份不能没有。
42
+ const identity = getMasterIdentity({ rotomHome: dataDir });
43
+ const opcResult = runOpcBootstrap(db, identity);
44
+ // Reset stale online status from previous run
45
+ const resetCount = db.resetAllOnline();
46
+ if (resetCount > 0) {
47
+ logger.info(`[mesh-master] Reset ${resetCount} stale online agent(s) to offline`);
48
+ }
49
+ // Services — single AuthService shared between WSHub and API
50
+ const auth = new AuthService(db);
51
+ const offlineQueue = new OfflineQueue(db);
52
+ const router = new Router(db, logger);
53
+ const shareTokens = new ShareTokenStore();
54
+ // HTTP + Express
55
+ const app = express();
56
+ app.use(express.json({ limit: "1mb" }));
57
+ // Create HTTP server first — needed by WSHub
58
+ const httpServer = http.createServer(app);
59
+ // WebSocket Hub — shares auth service with API
60
+ const hub = new WSHub(httpServer, db, auth, router, offlineQueue, logger);
61
+ hub.start();
62
+ // REST API — shares auth service and hub with WSHub
63
+ app.use("/api", createApi(db, auth, hub, router, config.port, shareTokens));
64
+ // Dashboard static files
65
+ // Prod (running from dist/master): build:master copies React dashboard
66
+ // build output to dist/master/dashboard.
67
+ // Dev (running src/master via tsx): fall back to packages/dashboard build
68
+ // output — run `pnpm dashboard:build` first.
69
+ let dashboardDir = path.resolve(__dirname, "dashboard");
70
+ if (!fs.existsSync(dashboardDir)) {
71
+ dashboardDir = path.resolve(__dirname, "../../packages/dashboard/dist/src/master/dashboard");
72
+ }
73
+ if (fs.existsSync(dashboardDir)) {
74
+ app.use("/dashboard", express.static(dashboardDir));
75
+ // SPA fallback — serve index.html for all /dashboard/* routes (client-side routing)
76
+ app.get("/dashboard/*", (_req, res) => {
77
+ res.sendFile(path.join(dashboardDir, "index.html"));
78
+ });
79
+ }
80
+ // Root redirect
81
+ app.get("/", (_req, res) => res.redirect("/dashboard"));
82
+ // Health check (unauthenticated — for load balancer probes)
83
+ app.get("/health", (_req, res) => {
84
+ res.json({ status: "ok", ...db.stats() });
85
+ });
86
+ // Listen
87
+ await new Promise((resolve, reject) => {
88
+ httpServer.on("error", (err) => {
89
+ if (err.code === "EADDRINUSE") {
90
+ reject(new Error(`Port ${config.port} already in use`));
91
+ }
92
+ else {
93
+ reject(err);
94
+ }
95
+ });
96
+ httpServer.listen(config.port, host, () => {
97
+ logger.info(`[mesh-master] Embedded master running on http://${host}:${config.port}`);
98
+ logger.info(`[mesh-master] Dashboard: http://localhost:${config.port}/dashboard`);
99
+ resolve();
100
+ });
101
+ });
102
+ // 自动 spawn 本机 executor(可选,默认关闭)。嵌入式场景通常 host 自己管 executor。
103
+ let localExecutor = null;
104
+ if (config.autoSpawnExecutor && opcResult.defaultAgent) {
105
+ localExecutor = ensureLocalExecutor({
106
+ rotomHome: dataDir,
107
+ masterPort: config.port,
108
+ defaultAgentName: opcResult.defaultAgent.name,
109
+ });
110
+ }
111
+ // Read or ensure API key for return value
112
+ const apiKey = db.getConfig("api_key") || "";
113
+ // Return handle for lifecycle management
114
+ return {
115
+ port: config.port,
116
+ db,
117
+ hub,
118
+ apiKey,
119
+ stop: async () => {
120
+ logger.info("[mesh-master] Embedded master stopping...");
121
+ localExecutor?.stop();
122
+ hub.stop();
123
+ router.stop();
124
+ db.close();
125
+ await new Promise((resolve) => {
126
+ httpServer.close(() => resolve());
127
+ setTimeout(resolve, 3000); // Force after 3s
128
+ });
129
+ closeFileLogging();
130
+ logger.info("[mesh-master] Embedded master stopped.");
131
+ },
132
+ };
133
+ }