@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,439 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Digital Employee Mesh — Master 管理脚本
4
+ #
5
+ # 用法: mesh-master <command> [options]
6
+ # 命令: start | stop | restart | status | install-service | uninstall-service
7
+ # 选项: --port/-p <port> --host <addr> --data/-d <dir> --daemon
8
+ #
9
+
10
+ set -euo pipefail
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
13
+ SERVER_JS="$SCRIPT_DIR/dist/master/server.js"
14
+ OPENCLAW_DIR="$HOME/.openclaw"
15
+ PID_FILE="$OPENCLAW_DIR/mesh-master.pid"
16
+ SVC="com.openclaw.mesh-master"
17
+
18
+ PORT="${MESH_MASTER_PORT:-28800}"
19
+ HOST="${MESH_MASTER_HOST:-0.0.0.0}"
20
+ DATA="${MESH_MASTER_DATA:-$HOME/.rotom}"
21
+ DAEMON=false
22
+ DEV=false
23
+
24
+ # ── 工具函数 ──────────────────────────────────────────────────────────────────
25
+
26
+ detect_os() { [[ "$OSTYPE" == darwin* ]] && echo macos || echo linux; }
27
+
28
+ # 查找占用端口的 PID(lsof 在 macOS SIP 下可能看不到 launchd 子进程)
29
+ find_port_pid() {
30
+ if command -v lsof &>/dev/null; then
31
+ lsof -ti :"$PORT" -sTCP:LISTEN 2>/dev/null || true
32
+ elif command -v ss &>/dev/null; then
33
+ ss -tlnp "sport = :$PORT" 2>/dev/null | grep -oP 'pid=\K[0-9]+' | head -1 || true
34
+ elif command -v fuser &>/dev/null; then
35
+ fuser "$PORT/tcp" 2>/dev/null | tr -d ' ' || true
36
+ else
37
+ echo ""
38
+ fi
39
+ }
40
+
41
+ # 通过 launchctl 获取服务 PID(macOS)
42
+ find_service_pid() {
43
+ if [ "$(detect_os)" = "macos" ]; then
44
+ launchctl list "$SVC" 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]+$' || true
45
+ fi
46
+ }
47
+
48
+ # 检测端口是否被占用:lsof → TCP 探测 → curl health
49
+ is_port_in_use() {
50
+ # 1. lsof(快,但 macOS SIP 下可能看不到 launchd 子进程)
51
+ [ -n "$(find_port_pid)" ] && return 0
52
+ # 2. TCP 连接探测(不受 SIP 限制)
53
+ python3 -c "
54
+ import socket, sys
55
+ s = socket.socket()
56
+ s.settimeout(1)
57
+ try:
58
+ s.connect(('127.0.0.1', $PORT))
59
+ s.close()
60
+ except:
61
+ sys.exit(1)
62
+ " 2>/dev/null && return 0
63
+ # 3. curl health 端点
64
+ curl -sf --connect-timeout 2 "http://127.0.0.1:$PORT/health" >/dev/null 2>&1 && return 0
65
+ return 1
66
+ }
67
+
68
+ # 轮询等待端口就绪(默认最多 10 秒)
69
+ wait_for_port() {
70
+ local max_wait=${1:-10} i=0
71
+ while [ $i -lt $max_wait ]; do
72
+ is_port_in_use && return 0
73
+ sleep 1
74
+ i=$((i+1))
75
+ done
76
+ return 1
77
+ }
78
+
79
+ # 轮询等待端口释放(默认最多 5 秒)
80
+ wait_for_port_free() {
81
+ local max_wait=${1:-5} i=0
82
+ while [ $i -lt $max_wait ]; do
83
+ is_port_in_use || return 0
84
+ sleep 1
85
+ i=$((i+1))
86
+ done
87
+ return 1
88
+ }
89
+
90
+ # 检查系统服务是否已安装(plist/service 文件存在)
91
+ is_service_installed() {
92
+ if [ "$(detect_os)" = "macos" ]; then
93
+ [ -f "$HOME/Library/LaunchAgents/$SVC.plist" ]
94
+ else
95
+ [ -f "$HOME/.config/systemd/user/mesh-master.service" ]
96
+ fi
97
+ }
98
+
99
+ # 停止系统服务(不删除 plist 文件)
100
+ # macOS: bootout 替代废弃的 stop+unload,彻底停止并阻止 KeepAlive 重启
101
+ stop_service() {
102
+ if [ "$(detect_os)" = "macos" ]; then
103
+ local domain="gui/$(id -u)"
104
+ launchctl bootout "$domain/$SVC" 2>/dev/null || true
105
+ else
106
+ systemctl --user stop mesh-master 2>/dev/null || true
107
+ fi
108
+ }
109
+
110
+ # 启动系统服务
111
+ # macOS: bootstrap + kickstart 替代废弃的 load,确保进程实际启动
112
+ start_service() {
113
+ if [ "$(detect_os)" = "macos" ]; then
114
+ local domain="gui/$(id -u)"
115
+ local plist="$HOME/Library/LaunchAgents/$SVC.plist"
116
+ # bootstrap 注册服务(如果已注册则忽略错误)
117
+ launchctl bootstrap "$domain" "$plist" 2>/dev/null || true
118
+ # kickstart -k 强制(重新)启动,解决 bootstrap 不触发 RunAtLoad 的已知问题
119
+ launchctl kickstart -k "$domain/$SVC" 2>/dev/null || true
120
+ else
121
+ systemctl --user start mesh-master 2>/dev/null || true
122
+ fi
123
+ }
124
+
125
+ fix_path() {
126
+ [ -d "/opt/homebrew/bin" ] && export PATH="/opt/homebrew/bin:$PATH"
127
+ [ -d "/usr/local/bin" ] && export PATH="/usr/local/bin:$PATH"
128
+ [ -d "$HOME/.nodejs/bin" ] && export PATH="$HOME/.nodejs/bin:$PATH"
129
+ [ -s "$HOME/.nvm/nvm.sh" ] && { export NVM_DIR="$HOME/.nvm"; . "$NVM_DIR/nvm.sh"; }
130
+ [ -d "$HOME/.fnm" ] && { export PATH="$HOME/.fnm:$PATH"; eval "$(fnm env 2>/dev/null)" || true; }
131
+ [ -d "$HOME/.volta" ] && export PATH="$HOME/.volta/bin:$PATH"
132
+ }
133
+
134
+ detect_pkg() {
135
+ command -v pnpm &>/dev/null && echo pnpm && return
136
+ command -v npm &>/dev/null && echo npm && return
137
+ command -v yarn &>/dev/null && echo yarn && return
138
+ echo ""
139
+ }
140
+
141
+ ensure_built() {
142
+ local need=false
143
+ [ ! -f "$SERVER_JS" ] && need=true
144
+ # 选择哪些源文件参与“是否需要重建”的判断
145
+ # dev: 只看 src/ 下的后端 .ts;prod: 同时看 packages/ 下的前端
146
+ local watch_dirs="$SCRIPT_DIR/src"
147
+ [ "$DEV" = false ] && watch_dirs="$watch_dirs $SCRIPT_DIR/packages"
148
+ [ "$need" = false ] && {
149
+ local newer
150
+ # shellcheck disable=SC2086
151
+ newer=$(find $watch_dirs \( -name '*.ts' -o -name '*.tsx' -o -name '*.html' -o -name '*.css' \) -newer "$SERVER_JS" -print -quit 2>/dev/null)
152
+ [ -n "$newer" ] && need=true
153
+ }
154
+ if [ "$need" = true ]; then
155
+ local target="build:master"
156
+ [ "$DEV" = true ] && target="build"
157
+ echo "[mesh-master] 构建中 ($target)..."
158
+ local pkg; pkg=$(detect_pkg)
159
+ case "$pkg" in
160
+ pnpm) (cd "$SCRIPT_DIR" && pnpm "$target") ;;
161
+ npm) (cd "$SCRIPT_DIR" && npm run "$target") ;;
162
+ yarn) (cd "$SCRIPT_DIR" && yarn "$target") ;;
163
+ *) echo "[mesh-master] 未找到包管理器"; return 1 ;;
164
+ esac
165
+ fi
166
+ }
167
+
168
+ # 杀掉进程(PID 文件 → launchctl 查询 → 端口查找)
169
+ kill_process() {
170
+ local pid
171
+ # 1. PID 文件
172
+ pid=$(cat "$PID_FILE" 2>/dev/null || true)
173
+ # 2. launchctl list(macOS,SIP 下 lsof 看不到 launchd 子进程)
174
+ if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
175
+ pid=$(find_service_pid)
176
+ fi
177
+ # 3. 端口查找
178
+ if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
179
+ pid=$(find_port_pid)
180
+ fi
181
+ if [ -z "$pid" ]; then
182
+ return 1
183
+ fi
184
+ echo "[mesh-master] 停止中 (PID $pid)..."
185
+ kill "$pid" 2>/dev/null || true
186
+ local i=0; while kill -0 "$pid" 2>/dev/null && [ $i -lt 50 ]; do sleep 0.1; i=$((i+1)); done
187
+ kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
188
+ rm -f "$PID_FILE"
189
+ return 0
190
+ }
191
+
192
+ # ── 命令 ──────────────────────────────────────────────────────────────────────
193
+
194
+ do_run() {
195
+ fix_path || true
196
+ ensure_built || true
197
+ mkdir -p "$OPENCLAW_DIR" "$DATA"
198
+ exec node "$SERVER_JS" --port "$PORT" --host "$HOST" --data "$DATA"
199
+ }
200
+
201
+ do_start() {
202
+ if is_port_in_use; then
203
+ echo "[mesh-master] 端口 $PORT 已被占用"; return 1
204
+ fi
205
+ ensure_built
206
+ mkdir -p "$OPENCLAW_DIR" "$DATA"
207
+ if [ "$DAEMON" = true ]; then
208
+ nohup node "$SERVER_JS" --port "$PORT" --host "$HOST" --data "$DATA" >/dev/null 2>&1 &
209
+ echo "$!" > "$PID_FILE"
210
+ sleep 1
211
+ if kill -0 "$!" 2>/dev/null; then
212
+ echo "[mesh-master] 启动成功 (PID $!, port $PORT)"
213
+ else
214
+ rm -f "$PID_FILE"; echo "[mesh-master] 启动失败,查看 ~/.rotom/logs/ 下的日志文件"; return 1
215
+ fi
216
+ else
217
+ echo "$$" > "$PID_FILE"
218
+ trap 'rm -f "$PID_FILE"' EXIT INT TERM
219
+ exec node "$SERVER_JS" --port "$PORT" --host "$HOST" --data "$DATA"
220
+ fi
221
+ }
222
+
223
+ do_stop() {
224
+ local was_running=false
225
+
226
+ # 如果有系统服务,先 bootout(彻底停止 + 阻止 KeepAlive 重启)
227
+ if is_service_installed; then
228
+ echo "[mesh-master] 检测到系统服务,停止服务..."
229
+ stop_service
230
+ was_running=true
231
+ sleep 1
232
+ fi
233
+
234
+ # 再杀残留进程(手动启动的、或 bootout 未杀干净的)
235
+ if kill_process 2>/dev/null; then
236
+ was_running=true
237
+ fi
238
+
239
+ if [ "$was_running" = true ]; then
240
+ wait_for_port_free 5 || true
241
+ echo "[mesh-master] 已停止"
242
+ elif is_port_in_use; then
243
+ echo "[mesh-master] 警告: 端口 $PORT 被未知进程占用"
244
+ return 1
245
+ else
246
+ echo "[mesh-master] 未在运行"
247
+ fi
248
+ }
249
+
250
+ do_restart() {
251
+ do_stop
252
+ sleep 1
253
+ # 如果有系统服务,用系统服务启动
254
+ if is_service_installed; then
255
+ echo "[mesh-master] 通过系统服务启动..."
256
+ start_service
257
+ if wait_for_port 10; then
258
+ echo "[mesh-master] 重启成功"
259
+ else
260
+ echo "[mesh-master] 重启失败,查看 ~/.rotom/logs/ 下的日志文件"; return 1
261
+ fi
262
+ else
263
+ do_start
264
+ fi
265
+ }
266
+
267
+ do_status() {
268
+ if is_port_in_use; then
269
+ echo "[mesh-master] 运行中 (port $PORT)"
270
+ is_service_installed && echo " 模式: 系统服务" || echo " 模式: 手动"
271
+ curl -sf "http://127.0.0.1:$PORT/health" 2>/dev/null | python3 -m json.tool 2>/dev/null || true
272
+ else
273
+ echo "[mesh-master] 未运行"; return 1
274
+ fi
275
+ }
276
+
277
+ # ── 系统服务 ──────────────────────────────────────────────────────────────────
278
+
279
+ do_install_service() {
280
+ # 先停掉现有进程和旧服务
281
+ if is_service_installed || is_port_in_use; then
282
+ echo "[mesh-master] 停止现有服务/进程..."
283
+ do_stop
284
+ sleep 1
285
+ fi
286
+
287
+ ensure_built
288
+ mkdir -p "$OPENCLAW_DIR" "$DATA"
289
+ local self="$SCRIPT_DIR/bin/mesh-master.sh"
290
+
291
+ if [ "$(detect_os)" = "macos" ]; then
292
+ local plist="$HOME/Library/LaunchAgents/$SVC.plist"
293
+ local domain="gui/$(id -u)"
294
+ mkdir -p "$HOME/Library/LaunchAgents"
295
+
296
+ local data_args=""
297
+ if [ -n "$DATA" ]; then
298
+ data_args=" <string>--data</string><string>${DATA}</string>"
299
+ fi
300
+
301
+ cat > "$plist" << PLIST
302
+ <?xml version="1.0" encoding="UTF-8"?>
303
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
304
+ <plist version="1.0">
305
+ <dict>
306
+ <key>Label</key><string>${SVC}</string>
307
+ <key>ProgramArguments</key>
308
+ <array>
309
+ <string>${self}</string><string>_run</string>
310
+ <string>--port</string><string>${PORT}</string>
311
+ <string>--host</string><string>${HOST}</string>
312
+ ${data_args}
313
+ </array>
314
+ <key>EnvironmentVariables</key>
315
+ <dict>
316
+ <key>PATH</key><string>${PATH}</string>
317
+ <key>HOME</key><string>${HOME}</string>
318
+ <key>LANG</key><string>${LANG:-en_US.UTF-8}</string>
319
+ </dict>
320
+ <key>RunAtLoad</key><true/>
321
+ <key>KeepAlive</key><true/>
322
+ <key>StandardOutPath</key><string>/dev/null</string>
323
+ <key>StandardErrorPath</key><string>/dev/null</string>
324
+ <key>WorkingDirectory</key><string>${SCRIPT_DIR}</string>
325
+ </dict>
326
+ </plist>
327
+ PLIST
328
+ # 现代 API: bootstrap 注册 + kickstart 强制启动
329
+ if ! launchctl bootstrap "$domain" "$plist" 2>&1; then
330
+ echo "[mesh-master] bootstrap 失败"; return 1
331
+ fi
332
+ launchctl kickstart -k "$domain/$SVC" 2>/dev/null || true
333
+ else
334
+ local svc_file="$HOME/.config/systemd/user/mesh-master.service"
335
+ mkdir -p "$(dirname "$svc_file")"
336
+
337
+ local data_flag=""
338
+ [ -n "$DATA" ] && data_flag=" --data ${DATA}"
339
+
340
+ cat > "$svc_file" << SERVICE
341
+ [Unit]
342
+ Description=Digital Employee Mesh Master
343
+ After=network.target
344
+ [Service]
345
+ Type=simple
346
+ ExecStart=${self} _run --port ${PORT} --host ${HOST}${data_flag}
347
+ Restart=always
348
+ RestartSec=5
349
+ Environment=PATH=${PATH}
350
+ Environment=HOME=${HOME}
351
+ Environment=LANG=${LANG:-en_US.UTF-8}
352
+ StandardOutput=null
353
+ StandardError=null
354
+ WorkingDirectory=${SCRIPT_DIR}
355
+ [Install]
356
+ WantedBy=default.target
357
+ SERVICE
358
+ systemctl --user daemon-reload
359
+ systemctl --user enable mesh-master
360
+ systemctl --user start mesh-master
361
+ fi
362
+
363
+ if wait_for_port 10; then
364
+ echo "[mesh-master] 系统服务已安装并启动 ✅"
365
+ echo " 升级: git pull → $0 restart"
366
+ echo " 卸载: $0 uninstall-service"
367
+ echo " 日志: JS logger → ~/.rotom/logs/mesh-master-YYYY-MM-DD.log"
368
+ else
369
+ echo "[mesh-master] 启动失败,查看 ~/.rotom/logs/ 下的日志文件"; return 1
370
+ fi
371
+ }
372
+
373
+ do_uninstall_service() {
374
+ if [ "$(detect_os)" = "macos" ]; then
375
+ local domain="gui/$(id -u)"
376
+ # bootout 停止并注销服务
377
+ launchctl bootout "$domain/$SVC" 2>/dev/null || true
378
+ rm -f "$HOME/Library/LaunchAgents/$SVC.plist"
379
+ else
380
+ systemctl --user stop mesh-master 2>/dev/null || true
381
+ systemctl --user disable mesh-master 2>/dev/null || true
382
+ rm -f "$HOME/.config/systemd/user/mesh-master.service"
383
+ systemctl --user daemon-reload
384
+ fi
385
+ echo "[mesh-master] 系统服务已卸载 ✅"
386
+ }
387
+
388
+ # ── 帮助 ──────────────────────────────────────────────────────────────────────
389
+
390
+ do_help() {
391
+ cat <<EOF
392
+ Digital Employee Mesh — Master 管理脚本
393
+
394
+ 用法: mesh-master <command> [options]
395
+
396
+ 命令:
397
+ start 启动 (默认前台, --daemon 后台)
398
+ stop 停止(自动识别手动/系统服务)
399
+ restart 重启(自动识别手动/系统服务)
400
+ status 查看状态
401
+ install-service 安装系统服务(开机自启 + 守护进程 + 升级自动编译)
402
+ uninstall-service 卸载系统服务
403
+
404
+ 选项:
405
+ --port, -p <port> 端口 (默认: ${PORT})
406
+ --host <addr> 地址 (默认: ${HOST})
407
+ --data, -d <dir> 数据目录 (默认: ~/Library/Application Support/a2a-gateway/mesh-data)
408
+ --daemon 后台运行
409
+ --dev 开发模式:只编译后端,跳过 vite 前端构建
410
+ 前端用 \`pnpm dashboard:dev\` 单独跑(vite dev :3000)
411
+
412
+ 示例:
413
+ mesh-master start --daemon # 后台启动
414
+ mesh-master start --dev # 开发模式(不 build vite)
415
+ mesh-master install-service # 安装为系统服务
416
+ mesh-master restart # 重启(升级后用)
417
+ mesh-master status # 查看状态
418
+ EOF
419
+ }
420
+
421
+ # ── 入口 ──────────────────────────────────────────────────────────────────────
422
+
423
+ CMD="${1:-help}"; shift 2>/dev/null || true
424
+
425
+ while [[ $# -gt 0 ]]; do
426
+ case "$1" in
427
+ --port|-p) PORT="$2"; shift 2 ;; --host) HOST="$2"; shift 2 ;;
428
+ --data|-d) DATA="$2"; shift 2 ;; --daemon) DAEMON=true; shift ;;
429
+ --dev) DEV=true; shift ;;
430
+ --help) do_help; exit 0 ;; *) echo "未知选项: $1"; do_help; exit 1 ;;
431
+ esac
432
+ done
433
+
434
+ case "$CMD" in
435
+ start) do_start ;; stop) do_stop ;; restart) do_restart ;;
436
+ status) do_status ;; _run) do_run ;;
437
+ install-service) do_install_service ;; uninstall-service) do_uninstall_service ;;
438
+ help|-h|--help) do_help ;; *) echo "未知命令: $CMD"; do_help; exit 1 ;;
439
+ esac
package/bin/rotom ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # rotom CLI launcher.
3
+ # Prefers the compiled JS; falls back to running the TS source through tsx
4
+ # (so contributors don't need to rebuild on every change).
5
+ set -e
6
+
7
+ # Resolve the real path of this script, following symlinks (so global installs
8
+ # via `ln -s ... /usr/local/bin/rotom` still locate the repo).
9
+ SOURCE="${BASH_SOURCE[0]}"
10
+ while [ -L "$SOURCE" ]; do
11
+ TARGET="$(readlink "$SOURCE")"
12
+ case "$TARGET" in
13
+ /*) SOURCE="$TARGET" ;;
14
+ *) SOURCE="$(cd "$(dirname "$SOURCE")" && pwd)/$TARGET" ;;
15
+ esac
16
+ done
17
+ HERE="$(cd "$(dirname "$SOURCE")" && pwd)"
18
+ ROOT="$(cd "$HERE/.." && pwd)"
19
+ DIST="$ROOT/dist/cli/rotom.js"
20
+ SRC="$ROOT/src/cli/rotom.ts"
21
+
22
+ if [ -f "$DIST" ]; then
23
+ exec node "$DIST" "$@"
24
+ elif [ -f "$SRC" ]; then
25
+ exec node --import tsx "$SRC" "$@"
26
+ else
27
+ echo "rotom: cannot find dist/cli/rotom.js or src/cli/rotom.ts (looked in $ROOT)" >&2
28
+ exit 1
29
+ fi
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # rotom-link —— 轻量 federation link daemon 启停脚本。
4
+ #
5
+ # 用法: rotom-link <command> [options]
6
+ # 命令: start | stop | restart | status | logs
7
+ # 选项: --port/-p <port> 默认 28900
8
+ #
9
+ # 运行时目录: ~/.rotom/{run,logs}
10
+ #
11
+
12
+ set -euo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
15
+ LINK_JS="$SCRIPT_DIR/dist/link/server.js"
16
+
17
+ ROTOM_HOME="${ROTOM_HOME:-$HOME/.rotom}"
18
+ RUN_DIR="$ROTOM_HOME/run"
19
+ LOG_DIR="$ROTOM_HOME/logs"
20
+ LINK_PID="$RUN_DIR/link.pid"
21
+ LINK_LOG="$LOG_DIR/link.log"
22
+ LINK_CONFIG="$ROTOM_HOME/link.json"
23
+
24
+ PORT="${ROTOM_LINK_PORT:-28900}"
25
+
26
+ mkdir -p "$RUN_DIR" "$LOG_DIR"
27
+
28
+ is_pid_alive() {
29
+ local pid_file="$1"
30
+ [ -f "$pid_file" ] || return 1
31
+ local pid; pid=$(cat "$pid_file" 2>/dev/null || true)
32
+ [ -z "$pid" ] && return 1
33
+ kill -0 "$pid" 2>/dev/null
34
+ }
35
+
36
+ stop_pid_file() {
37
+ local pid_file="$1" label="$2"
38
+ if ! is_pid_alive "$pid_file"; then
39
+ rm -f "$pid_file"
40
+ return 1
41
+ fi
42
+ local pid; pid=$(cat "$pid_file")
43
+ echo "[rotom-link] 停止 $label (PID $pid)..."
44
+ kill "$pid" 2>/dev/null || true
45
+ local i=0
46
+ while kill -0 "$pid" 2>/dev/null && [ $i -lt 50 ]; do
47
+ sleep 0.1; i=$((i+1))
48
+ done
49
+ kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
50
+ rm -f "$pid_file"
51
+ return 0
52
+ }
53
+
54
+ wait_link_ready() {
55
+ local max_wait=${1:-10} i=0
56
+ while [ $i -lt $max_wait ]; do
57
+ if curl -sf --connect-timeout 1 "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
58
+ return 0
59
+ fi
60
+ sleep 1; i=$((i+1))
61
+ done
62
+ return 1
63
+ }
64
+
65
+ do_start() {
66
+ if is_pid_alive "$LINK_PID"; then
67
+ echo "[rotom-link] 已在运行 (PID $(cat "$LINK_PID"))"
68
+ return 0
69
+ fi
70
+ if [ ! -f "$LINK_JS" ]; then
71
+ echo "[rotom-link] ❌ $LINK_JS 不存在,请先 \`pnpm build\`"
72
+ return 1
73
+ fi
74
+ if [ ! -f "$LINK_CONFIG" ]; then
75
+ echo "[rotom-link] ❌ $LINK_CONFIG 不存在,请先 \`rotom link join <coordEndpoint> --hostname <name>\`"
76
+ return 1
77
+ fi
78
+ # 端口预检
79
+ if command -v lsof >/dev/null 2>&1; then
80
+ local occupier
81
+ occupier=$(lsof -nP -iTCP:"$PORT" -sTCP:LISTEN -t 2>/dev/null | head -n1 || true)
82
+ if [ -n "$occupier" ]; then
83
+ local occupier_cmd
84
+ occupier_cmd=$(ps -p "$occupier" -o command= 2>/dev/null | head -c 120 || echo "(unknown)")
85
+ echo "[rotom-link] ❌ 端口 $PORT 已被占用 (PID $occupier: $occupier_cmd)"
86
+ return 1
87
+ fi
88
+ fi
89
+ echo "[rotom-link] 启动 link daemon (port=$PORT)..."
90
+ nohup node "$LINK_JS" --port "$PORT" >> "$LINK_LOG" 2>&1 &
91
+ echo "$!" > "$LINK_PID"
92
+ if ! wait_link_ready 10; then
93
+ echo "[rotom-link] ❌ 启动失败,查看 $LINK_LOG"
94
+ stop_pid_file "$LINK_PID" link || true
95
+ return 1
96
+ fi
97
+ echo "[rotom-link] ✅ 就绪 (PID $(cat "$LINK_PID"), http://127.0.0.1:$PORT)"
98
+ }
99
+
100
+ do_stop() {
101
+ stop_pid_file "$LINK_PID" link || echo "[rotom-link] 未在运行"
102
+ }
103
+
104
+ do_status() {
105
+ if is_pid_alive "$LINK_PID"; then
106
+ local pid; pid=$(cat "$LINK_PID")
107
+ echo "[rotom-link] running (PID $pid, port=$PORT)"
108
+ if curl -sf --connect-timeout 1 "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
109
+ curl -s "http://127.0.0.1:$PORT/health"
110
+ echo
111
+ fi
112
+ else
113
+ echo "[rotom-link] stopped"
114
+ fi
115
+ }
116
+
117
+ do_logs() {
118
+ if [ -f "$LINK_LOG" ]; then
119
+ tail -n 200 "$LINK_LOG"
120
+ else
121
+ echo "[rotom-link] no log at $LINK_LOG"
122
+ fi
123
+ }
124
+
125
+ cmd="${1:-}"; shift || true
126
+ case "$cmd" in
127
+ start) do_start ;;
128
+ stop) do_stop ;;
129
+ restart) do_stop; do_start ;;
130
+ status) do_status ;;
131
+ logs) do_logs ;;
132
+ *)
133
+ echo "usage: rotom-link <start|stop|restart|status|logs> [--port N]"
134
+ exit 1
135
+ ;;
136
+ esac
@@ -0,0 +1,57 @@
1
+ #!/bin/bash
2
+ # rotom-send-with-status - 发送消息并解释投递状态
3
+ #
4
+ # 用法:rotom-send-with-status <groupId> <target> <message>
5
+ # 示例:rotom-send-with-status cda34ffc-c8e9-428b-b9da-2bec7c6039d1 小寿 "@小寿 帮我看看这个问题"
6
+
7
+ GROUP_ID=$1
8
+ TARGET=$2
9
+ MESSAGE=$3
10
+
11
+ if [ -z "$GROUP_ID" ] || [ -z "$TARGET" ] || [ -z "$MESSAGE" ]; then
12
+ echo "用法: rotom-send-with-status <groupId> <target> <message>" >&2
13
+ exit 1
14
+ fi
15
+
16
+ # 调用 rotom 并捕获输出
17
+ RESULT=$(rotom group send "$GROUP_ID" "$TARGET" "$MESSAGE" 2>&1)
18
+ EXIT_CODE=$?
19
+
20
+ if [ $EXIT_CODE -ne 0 ]; then
21
+ echo "❌ 命令执行失败: $RESULT" >&2
22
+ exit $EXIT_CODE
23
+ fi
24
+
25
+ # 检查是否有 jq 命令
26
+ if ! command -v jq >/dev/null 2>&1; then
27
+ # 没有 jq,直接返回原始 JSON
28
+ echo "$RESULT"
29
+ exit 0
30
+ fi
31
+
32
+ # 解析 JSON
33
+ DELIVERED=$(echo "$RESULT" | jq -r '.delivered')
34
+ QUEUED=$(echo "$RESULT" | jq -r '.queued')
35
+ ERROR=$(echo "$RESULT" | jq -r '.error // empty')
36
+ REQUEST_ID=$(echo "$RESULT" | jq -r '.requestId // empty')
37
+
38
+ # 生成人类可读的输出
39
+ echo "$RESULT" # 原始 JSON(供程序处理)
40
+ echo "" # 空行分隔
41
+
42
+ # 解释性输出(供人类阅读)
43
+ echo "📤 → $TARGET: $MESSAGE"
44
+
45
+ if [ "$DELIVERED" = "true" ]; then
46
+ echo "✅ 消息已实时送达(对方在线,会立即处理)"
47
+ elif [ "$QUEUED" = "true" ]; then
48
+ echo "⏳ $TARGET 当前离线,消息已缓存,上线后会收到"
49
+ else
50
+ if [ -n "$ERROR" ]; then
51
+ echo "❌ 消息投递失败:$ERROR"
52
+ else
53
+ echo "❓ 未知投递状态(请联系管理员检查)"
54
+ fi
55
+ fi
56
+
57
+ exit 0