@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,321 @@
1
+ /**
2
+ * rotom — shared types, config management, HTTP client, output helpers, CLI arg parsing.
3
+ */
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import * as os from "node:os";
7
+ import { fileURLToPath } from "node:url";
8
+ import { spawn } from "node:child_process";
9
+ import { createLogger } from "../shared/logger.js";
10
+ const log = createLogger("mesh-cli", { stream: "stderr" });
11
+ // ── Constants ─────────────────────────────────────────────────────────────
12
+ export const ROTOM_HOME = process.env.ROTOM_HOME || path.join(os.homedir(), ".rotom");
13
+ export const ROTOM_CONFIG = path.join(ROTOM_HOME, "config.json");
14
+ export const DEFAULT_EXECUTOR_CONFIG = path.join(ROTOM_HOME, "executor.config.json");
15
+ // ── Path helpers ──────────────────────────────────────────────────────────
16
+ export function expandHome(p) {
17
+ if (!p)
18
+ return p;
19
+ if (p.startsWith("~/"))
20
+ return path.join(os.homedir(), p.slice(2));
21
+ if (p === "~")
22
+ return os.homedir();
23
+ return p;
24
+ }
25
+ export function installRoot() {
26
+ const here = path.dirname(fileURLToPath(import.meta.url));
27
+ return path.resolve(here, "..", "..");
28
+ }
29
+ export function runShellScript(scriptRel, args) {
30
+ const scriptPath = path.join(installRoot(), scriptRel);
31
+ if (!fs.existsSync(scriptPath)) {
32
+ fail(`shell script not found: ${scriptPath}`);
33
+ }
34
+ return new Promise((resolve, reject) => {
35
+ const child = spawn("bash", [scriptPath, ...args], { stdio: "inherit" });
36
+ child.on("error", (err) => reject(err));
37
+ child.on("exit", (code) => resolve(code ?? 1));
38
+ });
39
+ }
40
+ // ── Config load / save ────────────────────────────────────────────────────
41
+ export function loadRotomConfig() {
42
+ if (!fs.existsSync(ROTOM_CONFIG))
43
+ return {};
44
+ try {
45
+ return JSON.parse(fs.readFileSync(ROTOM_CONFIG, "utf-8"));
46
+ }
47
+ catch (e) {
48
+ fail(`failed to parse ${ROTOM_CONFIG}: ${e.message}`);
49
+ }
50
+ }
51
+ export function saveRotomConfig(cfg) {
52
+ if (!fs.existsSync(ROTOM_HOME))
53
+ fs.mkdirSync(ROTOM_HOME, { recursive: true });
54
+ fs.writeFileSync(ROTOM_CONFIG, JSON.stringify(cfg, null, 2));
55
+ }
56
+ export function resolveFromExecutorConfig(name, configPath) {
57
+ if (!fs.existsSync(configPath))
58
+ return null;
59
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
60
+ const workers = Array.isArray(raw?.workers)
61
+ ? raw.workers
62
+ : (raw?.name ? [raw] : []);
63
+ const w = workers.find((x) => x?.name === name);
64
+ if (!w)
65
+ return null;
66
+ const master = w.master || raw?.master;
67
+ if (!master || !w.token) {
68
+ fail(`executor config ${configPath} missing master/token for "${name}"`);
69
+ }
70
+ return {
71
+ name, master, token: w.token, kind: "executor", configPath,
72
+ ...(w.cliTool ? { cliTool: w.cliTool } : {}),
73
+ ...(w.workingDir ? { workingDir: w.workingDir } : {}),
74
+ ...(w.profile ? { profile: w.profile } : {}),
75
+ };
76
+ }
77
+ export function listExecutorWorkers(configPath) {
78
+ if (!fs.existsSync(configPath))
79
+ return [];
80
+ try {
81
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
82
+ const workers = Array.isArray(raw?.workers)
83
+ ? raw.workers
84
+ : (raw?.name ? [raw] : []);
85
+ return workers.map((w) => w?.name).filter(Boolean);
86
+ }
87
+ catch {
88
+ return [];
89
+ }
90
+ }
91
+ export function resolveAgentFromEntry(name, entry) {
92
+ const p = expandHome(entry.configPath);
93
+ if (!fs.existsSync(p))
94
+ fail(`config not found for agent "${name}": ${p}`);
95
+ const raw = JSON.parse(fs.readFileSync(p, "utf-8"));
96
+ if (entry.kind === "openclaw") {
97
+ const ch = raw?.channels?.["a2a-gateway"];
98
+ if (!ch?.token || !ch?.master || !ch?.name) {
99
+ fail(`openclaw config ${p} missing channels['a2a-gateway'].{master,token,name}`);
100
+ }
101
+ if (ch.name !== name) {
102
+ fail(`agent name mismatch: rotom expects "${name}" but ${p} declares "${ch.name}"`);
103
+ }
104
+ return { name, master: ch.master, token: ch.token, kind: "openclaw", configPath: p };
105
+ }
106
+ if (entry.kind === "local") {
107
+ // rotom join 产物:扁平结构,对齐 executor.config.json workers[] 单条 entry + master 字段。
108
+ // { master, name, token, cliTool?, workingDir?, profile? }
109
+ if (!raw?.token || !raw?.master || !raw?.name) {
110
+ fail(`local agent config ${p} missing {master,token,name}`);
111
+ }
112
+ if (raw.name !== name) {
113
+ fail(`agent name mismatch: rotom expects "${name}" but ${p} declares "${raw.name}"`);
114
+ }
115
+ return {
116
+ name, master: raw.master, token: raw.token, kind: "local", configPath: p,
117
+ ...(raw.cliTool ? { cliTool: raw.cliTool } : {}),
118
+ ...(raw.workingDir ? { workingDir: raw.workingDir } : {}),
119
+ ...(raw.profile ? { profile: raw.profile } : {}),
120
+ };
121
+ }
122
+ const resolved = resolveFromExecutorConfig(name, p);
123
+ if (!resolved)
124
+ fail(`executor config ${p} has no worker named "${name}"`);
125
+ return resolved;
126
+ }
127
+ export function resolveAgent(asFlag) {
128
+ const cfg = loadRotomConfig();
129
+ const chosen = process.env.ROTOM_AGENT || asFlag || cfg.defaultAgent;
130
+ log.info(`Resolving agent with --as=${asFlag} ROTOM_AGENT=${process.env.ROTOM_AGENT} defaultAgent=${cfg.defaultAgent}`);
131
+ if (!chosen) {
132
+ const known = cfg.agents ? Object.keys(cfg.agents) : [];
133
+ const executorWorkers = listExecutorWorkers(DEFAULT_EXECUTOR_CONFIG);
134
+ const lines = [];
135
+ if (known.length)
136
+ lines.push(`Registered agents: ${known.join(", ")}`);
137
+ if (executorWorkers.length)
138
+ lines.push(`Executor workers (${DEFAULT_EXECUTOR_CONFIG}): ${executorWorkers.join(", ")}`);
139
+ if (lines.length === 0) {
140
+ lines.push(`No agents registered yet. Either:`, ` - create ${DEFAULT_EXECUTOR_CONFIG} (auto-discovered), or`, ` - rotom config add-openclaw <name> <path-to-openclaw.json>`, ` - rotom config add-executor <name> <path-to-executor.config.json>`);
141
+ }
142
+ else {
143
+ lines.push(`Use: rotom --as <name> ... or rotom config use <name>`);
144
+ }
145
+ fail(`no agent selected (--as / ROTOM_AGENT / defaultAgent all unset)\n${lines.join("\n")}`);
146
+ }
147
+ const entry = cfg.agents?.[chosen];
148
+ if (entry)
149
+ return resolveAgentFromEntry(chosen, entry);
150
+ const fromExecutor = resolveFromExecutorConfig(chosen, DEFAULT_EXECUTOR_CONFIG);
151
+ if (fromExecutor)
152
+ return fromExecutor;
153
+ const master = process.env.ROTOM_MASTER;
154
+ const token = process.env.ROTOM_TOKEN;
155
+ if (master && token) {
156
+ return { name: chosen, master, token, kind: "executor", configPath: "(env)" };
157
+ }
158
+ const known = cfg.agents ? Object.keys(cfg.agents).join(", ") : "(none)";
159
+ const executorWorkers = listExecutorWorkers(DEFAULT_EXECUTOR_CONFIG);
160
+ const hint = executorWorkers.length
161
+ ? `\nAvailable in ${DEFAULT_EXECUTOR_CONFIG}: ${executorWorkers.join(", ")}`
162
+ : "";
163
+ fail(`agent "${chosen}" not registered in ${ROTOM_CONFIG}\nKnown: ${known}${hint}`);
164
+ }
165
+ // ── HTTP client ───────────────────────────────────────────────────────────
166
+ export function masterHttpUrl(masterWsOrHttp) {
167
+ return masterWsOrHttp
168
+ .replace(/^ws:\/\//, "http://")
169
+ .replace(/^wss:\/\//, "https://")
170
+ .replace(/\/+$/, "");
171
+ }
172
+ export async function api(agent, method, route, body) {
173
+ const url = `${masterHttpUrl(agent.master)}/api${route}`;
174
+ const init = {
175
+ method,
176
+ headers: {
177
+ "Authorization": `Bearer ${agent.token}`,
178
+ "Content-Type": "application/json",
179
+ },
180
+ };
181
+ if (body !== undefined)
182
+ init.body = JSON.stringify(body);
183
+ const idempotent = method === "GET" || method === "PUT" || method === "DELETE";
184
+ const maxAttempts = idempotent ? 2 : 1;
185
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
186
+ let resp;
187
+ try {
188
+ resp = await fetch(url, init);
189
+ const text = await resp.text();
190
+ let data;
191
+ try {
192
+ data = text ? JSON.parse(text) : null;
193
+ }
194
+ catch {
195
+ data = text;
196
+ }
197
+ if (!resp.ok) {
198
+ const detail = typeof data === "object" && data?.error ? data.error : text;
199
+ failKind("http", url, resp.status, method, route, detail);
200
+ }
201
+ return data;
202
+ }
203
+ catch (e) {
204
+ const reason = e.message;
205
+ const partial = resp !== undefined;
206
+ if (attempt < maxAttempts && !partial) {
207
+ await new Promise((r) => setTimeout(r, 800));
208
+ continue;
209
+ }
210
+ failKind(partial ? "partial-response" : "network", url, reason, partial ? resp.status : 0);
211
+ }
212
+ }
213
+ throw new Error("rotom: api() loop fell through");
214
+ }
215
+ // ── Output helpers ────────────────────────────────────────────────────────
216
+ export let pretty = false;
217
+ export function setPretty(v) { pretty = v; }
218
+ export function isPretty() { return pretty; }
219
+ export function printJson(data) {
220
+ process.stdout.write(JSON.stringify(data, null, pretty ? 2 : 0) + "\n");
221
+ }
222
+ export function printTable(rows, columns) {
223
+ if (!pretty || !rows?.length) {
224
+ printJson(rows);
225
+ return;
226
+ }
227
+ const cols = columns || Object.keys(rows[0]);
228
+ const widths = cols.map((c) => Math.max(c.length, ...rows.map((r) => String(r[c] ?? "").length)));
229
+ const sep = cols.map((_, i) => "─".repeat(widths[i])).join("─┼─");
230
+ const header = cols.map((c, i) => c.padEnd(widths[i])).join(" │ ");
231
+ process.stdout.write(header + "\n" + sep + "\n");
232
+ for (const r of rows) {
233
+ process.stdout.write(cols.map((c, i) => String(r[c] ?? "").padEnd(widths[i])).join(" │ ") + "\n");
234
+ }
235
+ }
236
+ export function fail(msg) {
237
+ process.stderr.write(`rotom: ${msg}\n`);
238
+ process.exit(1);
239
+ }
240
+ export function failKind(kind, ...args) {
241
+ const prefix = kind === "network"
242
+ ? "rotom network"
243
+ : kind === "partial-response"
244
+ ? "rotom partial-response"
245
+ : kind === "http"
246
+ ? "rotom http"
247
+ : "rotom";
248
+ process.stderr.write(`${prefix}: ${args.map((a) => String(a)).join(" ")}\n`);
249
+ const code = kind === "network" || kind === "partial-response" ? 75 :
250
+ kind === "http" ? 69 : 1;
251
+ process.exit(code);
252
+ }
253
+ export function parseArgs(argv) {
254
+ const positional = [];
255
+ const flags = {};
256
+ for (const a of argv) {
257
+ if (a.startsWith("--")) {
258
+ const eq = a.indexOf("=");
259
+ if (eq !== -1) {
260
+ const k = a.slice(2, eq);
261
+ const v = a.slice(eq + 1);
262
+ flags[k] = v === "" ? true : v;
263
+ }
264
+ else {
265
+ if (a.startsWith("--no-")) {
266
+ flags[a.slice(5)] = false;
267
+ continue;
268
+ }
269
+ flags[a.slice(2)] = true;
270
+ }
271
+ }
272
+ else if (a.startsWith("-") && a.length === 2) {
273
+ flags[a[1]] = true;
274
+ }
275
+ else {
276
+ positional.push(a);
277
+ }
278
+ }
279
+ for (const key of Object.keys(flags)) {
280
+ if (flags[key] !== true)
281
+ continue;
282
+ const idx = argv.indexOf(`--${key}`);
283
+ if (idx === -1 && key.length === 1)
284
+ continue;
285
+ if (idx === -1)
286
+ continue;
287
+ const valIdx = idx + 1;
288
+ if (valIdx < argv.length && !argv[valIdx].startsWith("-")) {
289
+ const m = argv[valIdx].match(/^-?\d+(\.\d+)?$/);
290
+ if (m) {
291
+ flags[key] = m[0];
292
+ }
293
+ else {
294
+ flags[key] = argv[valIdx];
295
+ }
296
+ }
297
+ }
298
+ return { positional, flags };
299
+ }
300
+ export function requireFlag(flags, name) {
301
+ const v = flags[name];
302
+ if (v === undefined || v === true || v === false) {
303
+ fail(`--${name} is required`);
304
+ }
305
+ return v;
306
+ }
307
+ export function flagStr(flags, name) {
308
+ const v = flags[name];
309
+ if (v === undefined || v === true || v === false)
310
+ return undefined;
311
+ return v;
312
+ }
313
+ export function flagInt(flags, name) {
314
+ const v = flagStr(flags, name);
315
+ if (!v)
316
+ return undefined;
317
+ const n = Number(v);
318
+ if (!Number.isFinite(n))
319
+ fail(`--${name} must be a number`);
320
+ return n;
321
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * rotom config — agent configuration management.
3
+ */
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { ROTOM_CONFIG, loadRotomConfig, saveRotomConfig, resolveAgentFromEntry, expandHome, printJson, fail, } from "./common.js";
7
+ import { usage, unknownSubcommand } from "./routes.js";
8
+ export async function cmdConfig(rest, _flags) {
9
+ const sub = rest[0];
10
+ const cfg = loadRotomConfig();
11
+ cfg.agents = cfg.agents || {};
12
+ if (sub === "show") {
13
+ printJson({ configPath: ROTOM_CONFIG, ...cfg });
14
+ return;
15
+ }
16
+ if (sub === "init") {
17
+ if (fs.existsSync(ROTOM_CONFIG))
18
+ fail(`${ROTOM_CONFIG} already exists`);
19
+ saveRotomConfig({ agents: {} });
20
+ process.stdout.write(`Created ${ROTOM_CONFIG}\n`);
21
+ return;
22
+ }
23
+ if (sub === "use") {
24
+ const name = rest[1];
25
+ if (!name)
26
+ usage("config use", "<name>");
27
+ if (!cfg.agents?.[name])
28
+ fail(`agent "${name}" not registered`);
29
+ cfg.defaultAgent = name;
30
+ saveRotomConfig(cfg);
31
+ process.stdout.write(`defaultAgent = ${name}\n`);
32
+ return;
33
+ }
34
+ if (sub === "add-openclaw" || sub === "add-executor") {
35
+ const name = rest[1];
36
+ const cfgPath = rest[2];
37
+ if (!name || !cfgPath)
38
+ usage(`config ${sub}`, "<name> <path>");
39
+ const abs = path.resolve(expandHome(cfgPath));
40
+ if (!fs.existsSync(abs))
41
+ fail(`config file not found: ${abs}`);
42
+ const kind = sub === "add-openclaw" ? "openclaw" : "executor";
43
+ cfg.agents[name] = { configPath: abs, kind };
44
+ if (!cfg.defaultAgent)
45
+ cfg.defaultAgent = name;
46
+ saveRotomConfig(cfg);
47
+ const resolved = resolveAgentFromEntry(name, cfg.agents[name]);
48
+ process.stdout.write(`Registered ${name} (${kind}) → master=${resolved.master}\n`);
49
+ return;
50
+ }
51
+ if (sub === "remove") {
52
+ const name = rest[1];
53
+ if (!name)
54
+ usage("config remove", "<name>");
55
+ if (!cfg.agents[name])
56
+ fail(`agent "${name}" not registered`);
57
+ delete cfg.agents[name];
58
+ if (cfg.defaultAgent === name)
59
+ delete cfg.defaultAgent;
60
+ saveRotomConfig(cfg);
61
+ process.stdout.write(`Removed ${name}\n`);
62
+ return;
63
+ }
64
+ unknownSubcommand("config", sub);
65
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * rotom directory — list agents.
3
+ */
4
+ import { api, flagStr, printTable } from "./common.js";
5
+ export async function cmdDirectory(agent, flags) {
6
+ const route = flags.online === true ? "/agents/online" : "/agents";
7
+ let data = await api(agent, "GET", route);
8
+ const domain = flagStr(flags, "domain");
9
+ if (domain)
10
+ data = data.filter((a) => a.domain === domain);
11
+ printTable(data.map((a) => ({
12
+ name: a.name,
13
+ domain: a.domain || "-",
14
+ status: a.status,
15
+ description: (a.description || "").slice(0, 60),
16
+ })), ["name", "domain", "status", "description"]);
17
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * rotom executor — start executor workers.
3
+ */
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import * as os from "node:os";
7
+ import { spawn } from "node:child_process";
8
+ import { installRoot, fail, flagStr } from "./common.js";
9
+ export async function cmdExecutor(rest, flags) {
10
+ const root = installRoot();
11
+ const distJs = path.join(root, "dist", "executor", "index.js");
12
+ const srcTs = path.join(root, "src", "executor", "index.ts");
13
+ const fwd = [];
14
+ const cfg = flagStr(flags, "config");
15
+ if (cfg)
16
+ fwd.push("--config", cfg);
17
+ for (const a of rest)
18
+ fwd.push(a);
19
+ let useTsx = false;
20
+ let entry;
21
+ if (fs.existsSync(distJs)) {
22
+ entry = distJs;
23
+ }
24
+ else if (fs.existsSync(srcTs)) {
25
+ const tsxBin = path.join(root, "node_modules", ".bin", "tsx");
26
+ if (!fs.existsSync(tsxBin)) {
27
+ fail(`tsx not found at ${tsxBin} — required for dev mode. Run \`pnpm install\` first.`);
28
+ }
29
+ entry = tsxBin;
30
+ useTsx = true;
31
+ }
32
+ else {
33
+ fail(`cannot find executor entry: tried ${distJs} and ${srcTs}`);
34
+ }
35
+ const cmdline = useTsx
36
+ ? [entry, srcTs, ...fwd]
37
+ : [entry, ...fwd];
38
+ const bin = useTsx ? entry : process.execPath;
39
+ await new Promise((resolve) => {
40
+ const child = spawn(bin, cmdline, { stdio: "inherit" });
41
+ const forward = (sig) => { try {
42
+ child.kill(sig);
43
+ }
44
+ catch { /* already gone */ } };
45
+ process.on("SIGINT", () => forward("SIGINT"));
46
+ process.on("SIGTERM", () => forward("SIGTERM"));
47
+ child.on("exit", (code, signal) => {
48
+ if (code !== null && code !== undefined) {
49
+ process.exit(code);
50
+ return;
51
+ }
52
+ const sigNum = signal ? os.constants.signals[signal] : 0;
53
+ process.exit(typeof sigNum === "number" && sigNum > 0 ? 128 + sigNum : 1);
54
+ resolve();
55
+ });
56
+ child.on("error", (err) => fail(`failed to spawn executor: ${err.message}`));
57
+ });
58
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * rotom fed — 跨机对话 CLI(走本机 rotom-link daemon)。
3
+ *
4
+ * rotom fed members 列出可见 agent(从协调 master 同步来的目录)
5
+ * rotom fed ask <ref> "<question>" 阻塞等回复(--timeout 5m,对齐 src/cli/ask.ts)
6
+ *
7
+ * `<ref>` 形如 "alice@hostB" 或 "alice"(后者依赖团队内 name 唯一)。
8
+ * 走本机 link daemon 的 http://127.0.0.1:28900。
9
+ */
10
+ import { fail, flagStr, printJson } from "./common.js";
11
+ import { masterFetch } from "./routes.js";
12
+ import { parseAgentRef } from "../shared/protocol/federation.js";
13
+ const DEFAULT_LINK_PORT = 28900;
14
+ const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
15
+ function linkHttpBase(flags) {
16
+ const port = flagStr(flags, "port") ?? String(DEFAULT_LINK_PORT);
17
+ return `http://127.0.0.1:${port}`;
18
+ }
19
+ async function probeLink(httpBase) {
20
+ const probe = await masterFetch(`${httpBase}/health`, { method: "GET" }).catch(() => null);
21
+ if (!probe || probe.status === 0) {
22
+ fail(`rotom-link daemon unreachable at ${httpBase}. ` +
23
+ `Start it first: \`rotom link start\` (after \`rotom link join <coordEndpoint> --hostname <name>\`).`);
24
+ }
25
+ }
26
+ export async function cmdFed(rest, flags) {
27
+ const sub = rest[0];
28
+ if (!sub) {
29
+ fail("usage: rotom fed <members|ask> [args]\n" +
30
+ " members 列出可见 agent (协调 master 同步目录)\n" +
31
+ " ask <ref> \"<question>\" [--timeout 5m] 阻塞等回复 (ref 形如 alice@hostB 或 alice)");
32
+ }
33
+ const httpBase = linkHttpBase(flags);
34
+ await probeLink(httpBase);
35
+ const args = rest.slice(1);
36
+ switch (sub) {
37
+ case "members": return cmdFedMembers(httpBase);
38
+ case "ask": return cmdFedAsk(httpBase, args, flags);
39
+ default: fail(`unknown fed subcommand: ${sub}\nRun 'rotom fed' for usage.`);
40
+ }
41
+ }
42
+ async function cmdFedMembers(httpBase) {
43
+ const resp = await masterFetch(`${httpBase}/fed/directory`, { method: "GET" });
44
+ if (resp.status < 200 || resp.status >= 300) {
45
+ const err = resp.data?.error ?? JSON.stringify(resp.data);
46
+ fail(`fed members failed (HTTP ${resp.status}): ${err}`);
47
+ }
48
+ printJson(resp.data);
49
+ }
50
+ async function cmdFedAsk(httpBase, args, flags) {
51
+ const ref = args[0];
52
+ const message = args.slice(1).join(" ").trim();
53
+ if (!ref) {
54
+ fail("usage: rotom fed ask <ref> \"<question>\"\n ref e.g. alice@hostB 或 alice(团队内 name 唯一)");
55
+ }
56
+ if (!message) {
57
+ fail(`question is empty (got: ${args.slice(1).join(" ")})`);
58
+ }
59
+ // 校验 ref 形式:不带 @ 的裸 name 也接受,link daemon 会依赖团队内唯一性
60
+ const parsed = parseAgentRef(ref);
61
+ if (!parsed.hostname) {
62
+ process.stderr.write(`[rotom-fed] warning: "${ref}" 不带 @hostname,link daemon 会按团队内 name 唯一性路由\n`);
63
+ }
64
+ const timeoutStr = flagStr(flags, "timeout");
65
+ const timeoutMs = timeoutStr ? parseDurationMs(timeoutStr) : DEFAULT_TIMEOUT_MS;
66
+ const controller = new AbortController();
67
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
68
+ try {
69
+ const resp = await masterFetch(`${httpBase}/fed/ask`, {
70
+ method: "POST",
71
+ body: JSON.stringify({ to: ref, message }),
72
+ signal: controller.signal,
73
+ });
74
+ if (resp.status < 200 || resp.status >= 300) {
75
+ const err = resp.data?.error ?? JSON.stringify(resp.data);
76
+ fail(`fed ask failed (HTTP ${resp.status}): ${err}`);
77
+ }
78
+ printJson(resp.data);
79
+ }
80
+ finally {
81
+ clearTimeout(timer);
82
+ }
83
+ }
84
+ function parseDurationMs(s) {
85
+ const m = s.match(/^(\d+)(s|m|h)$/);
86
+ if (!m)
87
+ fail(`invalid --timeout: ${s} (e.g. 30s / 5m / 1h)`);
88
+ const n = parseInt(m[1], 10);
89
+ const unit = m[2];
90
+ return n * (unit === "s" ? 1000 : unit === "m" ? 60_000 : 3_600_000);
91
+ }