u-foo 1.0.6 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -4
- package/SKILLS/ufoo/SKILL.md +17 -2
- package/SKILLS/uinit/SKILL.md +8 -3
- package/bin/ucode-core.js +15 -0
- package/bin/ucode.js +125 -0
- package/bin/ufoo-assistant-agent.js +5 -0
- package/bin/ufoo-engine.js +25 -0
- package/bin/ufoo.js +4 -0
- package/modules/AGENTS.template.md +14 -4
- package/modules/bus/README.md +8 -5
- package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
- package/modules/context/SKILLS/uctx/SKILL.md +3 -1
- package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
- package/package.json +12 -3
- package/scripts/import-pi-mono.js +124 -0
- package/scripts/postinstall.js +20 -49
- package/scripts/sync-claude-skills.sh +21 -0
- package/src/agent/cliRunner.js +524 -31
- package/src/agent/internalRunner.js +76 -9
- package/src/agent/launcher.js +97 -45
- package/src/agent/normalizeOutput.js +1 -1
- package/src/agent/notifier.js +144 -4
- package/src/agent/ptyRunner.js +480 -10
- package/src/agent/ptyWrapper.js +28 -3
- package/src/agent/readyDetector.js +16 -0
- package/src/agent/ucode.js +443 -0
- package/src/agent/ucodeBootstrap.js +113 -0
- package/src/agent/ucodeBuild.js +67 -0
- package/src/agent/ucodeDoctor.js +184 -0
- package/src/agent/ucodeRuntimeConfig.js +129 -0
- package/src/agent/ufooAgent.js +11 -2
- package/src/assistant/agent.js +260 -0
- package/src/assistant/bridge.js +172 -0
- package/src/assistant/engine.js +252 -0
- package/src/assistant/stdio.js +58 -0
- package/src/assistant/ufooEngineCli.js +306 -0
- package/src/bus/activate.js +27 -11
- package/src/bus/daemon.js +133 -5
- package/src/bus/index.js +137 -80
- package/src/bus/inject.js +47 -17
- package/src/bus/message.js +145 -17
- package/src/bus/nickname.js +3 -1
- package/src/bus/queue.js +6 -1
- package/src/bus/store.js +189 -0
- package/src/bus/subscriber.js +20 -4
- package/src/bus/utils.js +9 -3
- package/src/chat/agentBar.js +117 -0
- package/src/chat/agentDirectory.js +88 -0
- package/src/chat/agentSockets.js +225 -0
- package/src/chat/agentViewController.js +298 -0
- package/src/chat/chatLogController.js +115 -0
- package/src/chat/commandExecutor.js +700 -0
- package/src/chat/commands.js +132 -0
- package/src/chat/completionController.js +414 -0
- package/src/chat/cronScheduler.js +160 -0
- package/src/chat/daemonConnection.js +166 -0
- package/src/chat/daemonCoordinator.js +64 -0
- package/src/chat/daemonMessageRouter.js +257 -0
- package/src/chat/daemonReconnect.js +41 -0
- package/src/chat/daemonTransport.js +36 -0
- package/src/chat/daemonTransportDefaults.js +10 -0
- package/src/chat/dashboardKeyController.js +480 -0
- package/src/chat/dashboardView.js +154 -0
- package/src/chat/index.js +935 -2909
- package/src/chat/inputHistoryController.js +105 -0
- package/src/chat/inputListenerController.js +304 -0
- package/src/chat/inputMath.js +104 -0
- package/src/chat/inputSubmitHandler.js +171 -0
- package/src/chat/layout.js +165 -0
- package/src/chat/pasteController.js +81 -0
- package/src/chat/rawKeyMap.js +42 -0
- package/src/chat/settingsController.js +132 -0
- package/src/chat/statusLineController.js +177 -0
- package/src/chat/streamTracker.js +138 -0
- package/src/chat/text.js +70 -0
- package/src/chat/transport.js +61 -0
- package/src/cli/busCoreCommands.js +59 -0
- package/src/cli/ctxCoreCommands.js +199 -0
- package/src/cli/onlineCoreCommands.js +379 -0
- package/src/cli.js +741 -238
- package/src/code/README.md +29 -0
- package/src/code/UCODE_PROMPT.md +32 -0
- package/src/code/agent.js +1651 -0
- package/src/code/cli.js +158 -0
- package/src/code/config +0 -0
- package/src/code/dispatch.js +42 -0
- package/src/code/index.js +70 -0
- package/src/code/nativeRunner.js +1213 -0
- package/src/code/runtime.js +154 -0
- package/src/code/sessionStore.js +162 -0
- package/src/code/taskDecomposer.js +269 -0
- package/src/code/tools/bash.js +53 -0
- package/src/code/tools/common.js +42 -0
- package/src/code/tools/edit.js +70 -0
- package/src/code/tools/read.js +44 -0
- package/src/code/tools/write.js +35 -0
- package/src/code/tui.js +1580 -0
- package/src/config.js +47 -1
- package/src/context/decisions.js +12 -2
- package/src/context/index.js +18 -1
- package/src/context/sync.js +127 -0
- package/src/daemon/agentProcessManager.js +74 -0
- package/src/daemon/cronOps.js +241 -0
- package/src/daemon/index.js +661 -488
- package/src/daemon/ipcServer.js +99 -0
- package/src/daemon/ops.js +417 -179
- package/src/daemon/promptLoop.js +319 -0
- package/src/daemon/promptRequest.js +101 -0
- package/src/daemon/providerSessions.js +32 -17
- package/src/daemon/reporting.js +90 -0
- package/src/daemon/run.js +2 -5
- package/src/daemon/status.js +24 -1
- package/src/init/index.js +68 -14
- package/src/online/bridge.js +663 -0
- package/src/online/client.js +245 -0
- package/src/online/runner.js +253 -0
- package/src/online/server.js +992 -0
- package/src/online/tokens.js +103 -0
- package/src/report/store.js +331 -0
- package/src/shared/eventContract.js +35 -0
- package/src/shared/ptySocketContract.js +21 -0
- package/src/status/index.js +50 -17
- package/src/terminal/adapterContract.js +87 -0
- package/src/terminal/adapterRouter.js +84 -0
- package/src/terminal/adapters/externalAdapter.js +14 -0
- package/src/terminal/adapters/internalAdapter.js +13 -0
- package/src/terminal/adapters/internalPtyAdapter.js +42 -0
- package/src/terminal/adapters/internalQueueAdapter.js +37 -0
- package/src/terminal/adapters/terminalAdapter.js +31 -0
- package/src/terminal/adapters/tmuxAdapter.js +30 -0
- package/src/ufoo/agentsStore.js +69 -3
- package/src/utils/banner.js +5 -2
- package/scripts/.archived/bash-to-js-migration/README.md +0 -46
- package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
- package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
- package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
- package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
- package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
- package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
- package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
- package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
- package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
- package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
- package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
- package/scripts/banner.sh +0 -2
- package/src/bus/API_DESIGN.md +0 -204
package/src/cli.js
CHANGED
|
@@ -3,6 +3,9 @@ const { spawnSync } = require("child_process");
|
|
|
3
3
|
const net = require("net");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const { socketPath, isRunning } = require("./daemon");
|
|
6
|
+
const { runBusCoreCommand } = require("./cli/busCoreCommands");
|
|
7
|
+
const { runCtxCommand } = require("./cli/ctxCoreCommands");
|
|
8
|
+
const { runOnlineCommand } = require("./cli/onlineCoreCommands");
|
|
6
9
|
|
|
7
10
|
function getPackageRoot() {
|
|
8
11
|
return path.resolve(__dirname, "..");
|
|
@@ -58,6 +61,7 @@ async function ensureDaemonRunning(projectRoot) {
|
|
|
58
61
|
// eslint-disable-next-line no-await-in-loop
|
|
59
62
|
await new Promise((r) => setTimeout(r, 200));
|
|
60
63
|
}
|
|
64
|
+
throw new Error("Failed to start ufoo daemon");
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
async function sendDaemonRequest(projectRoot, payload) {
|
|
@@ -125,6 +129,70 @@ function requireOptional(name) {
|
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
function collectOption(value, previous) {
|
|
133
|
+
const next = Array.isArray(previous) ? previous.slice() : [];
|
|
134
|
+
const parts = String(value || "")
|
|
135
|
+
.split(",")
|
|
136
|
+
.map((part) => part.trim())
|
|
137
|
+
.filter(Boolean);
|
|
138
|
+
return next.concat(parts);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function collectOptionValues(argv, name) {
|
|
142
|
+
const values = [];
|
|
143
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
144
|
+
if (argv[i] !== name) continue;
|
|
145
|
+
const value = argv[i + 1];
|
|
146
|
+
if (!value || value.startsWith("--")) continue;
|
|
147
|
+
values.push(value);
|
|
148
|
+
i += 1;
|
|
149
|
+
}
|
|
150
|
+
return values;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseJsonObject(text, fallback = {}) {
|
|
154
|
+
const raw = String(text || "").trim();
|
|
155
|
+
if (!raw) return fallback;
|
|
156
|
+
const parsed = JSON.parse(raw);
|
|
157
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
158
|
+
throw new Error("Expected JSON object");
|
|
159
|
+
}
|
|
160
|
+
return parsed;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizeReportPhase(action = "") {
|
|
164
|
+
const value = String(action || "").trim().toLowerCase();
|
|
165
|
+
if (value === "start") return "start";
|
|
166
|
+
if (value === "progress") return "progress";
|
|
167
|
+
if (value === "error" || value === "fail" || value === "failed") return "error";
|
|
168
|
+
if (value === "done" || value === "finish" || value === "finished") return "done";
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveOnlineAuthToken(opts) {
|
|
173
|
+
if (!opts) return "";
|
|
174
|
+
if (opts.authToken) return opts.authToken;
|
|
175
|
+
let tokens = null;
|
|
176
|
+
try {
|
|
177
|
+
// eslint-disable-next-line global-require
|
|
178
|
+
tokens = require("./online/tokens");
|
|
179
|
+
} catch {
|
|
180
|
+
return "";
|
|
181
|
+
}
|
|
182
|
+
const filePath = opts.tokenFile || tokens.defaultTokensPath();
|
|
183
|
+
let entry = null;
|
|
184
|
+
if (opts.subscriber) entry = tokens.getToken(filePath, opts.subscriber);
|
|
185
|
+
if (!entry && opts.nickname) entry = tokens.getTokenByNickname(filePath, opts.nickname);
|
|
186
|
+
if (!entry) return "";
|
|
187
|
+
return entry.token_hash || entry.token || "";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function onlineAuthHeaders(opts) {
|
|
191
|
+
const token = resolveOnlineAuthToken(opts);
|
|
192
|
+
if (!token) return {};
|
|
193
|
+
return { Authorization: `Bearer ${token}` };
|
|
194
|
+
}
|
|
195
|
+
|
|
128
196
|
async function runCli(argv) {
|
|
129
197
|
const pkg = require(path.resolve(getPackageRoot(), "package.json"));
|
|
130
198
|
|
|
@@ -204,6 +272,208 @@ async function runCli(argv) {
|
|
|
204
272
|
process.exitCode = 1;
|
|
205
273
|
}
|
|
206
274
|
});
|
|
275
|
+
program
|
|
276
|
+
.command("recover")
|
|
277
|
+
.description("List recoverable agents or recover a specific one")
|
|
278
|
+
.argument("[action]", "list|run", "list")
|
|
279
|
+
.argument("[target]", "Nickname or subscriber ID")
|
|
280
|
+
.option("--json", "Output recoverable list as JSON")
|
|
281
|
+
.action(async (action, target, opts) => {
|
|
282
|
+
try {
|
|
283
|
+
const projectRoot = process.cwd();
|
|
284
|
+
await ensureDaemonRunning(projectRoot);
|
|
285
|
+
const normalizedAction = (action || "list").toLowerCase();
|
|
286
|
+
|
|
287
|
+
if (normalizedAction === "list") {
|
|
288
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
289
|
+
type: "list_recoverable_agents",
|
|
290
|
+
target: target || "",
|
|
291
|
+
});
|
|
292
|
+
const result = resp?.data?.recoverable || { recoverable: [], skipped: [] };
|
|
293
|
+
if (opts.json) {
|
|
294
|
+
console.log(JSON.stringify(result, null, 2));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const recoverable = result.recoverable || [];
|
|
299
|
+
console.log(resp?.data?.reply || `Found ${recoverable.length} recoverable agent(s)`);
|
|
300
|
+
recoverable.forEach((item) => {
|
|
301
|
+
const nickname = item.nickname ? ` (${item.nickname})` : "";
|
|
302
|
+
const meta = item.launchMode ? ` [${item.agent}/${item.launchMode}]` : ` [${item.agent}]`;
|
|
303
|
+
console.log(` - ${item.id}${nickname}${meta}`);
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (normalizedAction === "run") {
|
|
309
|
+
if (!target) {
|
|
310
|
+
console.error("recover run requires <target>");
|
|
311
|
+
process.exitCode = 1;
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
315
|
+
type: "resume_agents",
|
|
316
|
+
target,
|
|
317
|
+
});
|
|
318
|
+
const reply = resp?.data?.reply || "Recover requested";
|
|
319
|
+
console.log(reply);
|
|
320
|
+
if (resp?.data?.resume?.resumed?.length) {
|
|
321
|
+
resp.data.resume.resumed.forEach((item) => {
|
|
322
|
+
const label = item.nickname ? ` (${item.nickname})` : "";
|
|
323
|
+
console.log(` - ${item.id}${label}`);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
console.error("recover action must be list|run");
|
|
330
|
+
process.exitCode = 1;
|
|
331
|
+
} catch (err) {
|
|
332
|
+
console.error(err.message || String(err));
|
|
333
|
+
process.exitCode = 1;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
program
|
|
338
|
+
.command("report")
|
|
339
|
+
.description("Report agent task status to daemon/ufoo-agent")
|
|
340
|
+
.argument("<action>", "start|progress|done|error|list")
|
|
341
|
+
.argument("[message...]", "Task message/summary")
|
|
342
|
+
.option("--task <id>", "Task ID (default: task-<timestamp>)")
|
|
343
|
+
.option("--agent <id>", "Agent ID (default: UFOO_SUBSCRIBER_ID)")
|
|
344
|
+
.option("--scope <scope>", "Report visibility: public|private", "public")
|
|
345
|
+
.option("--controller <id>", "Controller ID for private channel", "ufoo-agent")
|
|
346
|
+
.option("--summary <text>", "Summary text for done")
|
|
347
|
+
.option("--error <text>", "Error text for error")
|
|
348
|
+
.option("--meta <json>", "JSON metadata object")
|
|
349
|
+
.option("-n, --num <n>", "List count", "20")
|
|
350
|
+
.option("--json", "Output as JSON")
|
|
351
|
+
.action(async (action, messageParts, opts) => {
|
|
352
|
+
const normalized = normalizeReportPhase(action);
|
|
353
|
+
const text = Array.isArray(messageParts) ? messageParts.join(" ").trim() : String(messageParts || "").trim();
|
|
354
|
+
const projectRoot = process.cwd();
|
|
355
|
+
const { listReports } = require("./report/store");
|
|
356
|
+
|
|
357
|
+
if ((action || "").toLowerCase() === "list") {
|
|
358
|
+
try {
|
|
359
|
+
const rows = listReports(projectRoot, {
|
|
360
|
+
num: parseInt(opts.num, 10),
|
|
361
|
+
agent: opts.agent || "",
|
|
362
|
+
});
|
|
363
|
+
if (opts.json) {
|
|
364
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
console.log(`=== Reports (${rows.length} shown) ===`);
|
|
368
|
+
rows.forEach((row) => {
|
|
369
|
+
const detail = row.phase === "error"
|
|
370
|
+
? (row.error || row.summary || row.message || row.task_id)
|
|
371
|
+
: (row.summary || row.message || row.task_id);
|
|
372
|
+
console.log(`${row.ts || "-"} [${row.phase}] ${row.agent_id || "unknown-agent"} ${row.task_id || ""} ${detail}`);
|
|
373
|
+
});
|
|
374
|
+
if (rows.length === 0) console.log("No reports found.");
|
|
375
|
+
} catch (err) {
|
|
376
|
+
console.error(err.message || String(err));
|
|
377
|
+
process.exitCode = 1;
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!normalized) {
|
|
383
|
+
console.error("report action must be start|progress|done|error|list");
|
|
384
|
+
process.exitCode = 1;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let meta = {};
|
|
389
|
+
try {
|
|
390
|
+
meta = parseJsonObject(opts.meta, {});
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.error(`Invalid --meta: ${err.message}`);
|
|
393
|
+
process.exitCode = 1;
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const agentId = String(opts.agent || process.env.UFOO_SUBSCRIBER_ID || "unknown-agent").trim() || "unknown-agent";
|
|
398
|
+
const taskId = String(opts.task || `task-${Date.now()}`).trim();
|
|
399
|
+
const summary = String(opts.summary || (normalized === "done" ? text : "")).trim();
|
|
400
|
+
const error = String(opts.error || (normalized === "error" ? text : "")).trim();
|
|
401
|
+
const report = {
|
|
402
|
+
phase: normalized,
|
|
403
|
+
task_id: taskId,
|
|
404
|
+
agent_id: agentId,
|
|
405
|
+
message: text,
|
|
406
|
+
summary,
|
|
407
|
+
error,
|
|
408
|
+
ok: normalized !== "error",
|
|
409
|
+
source: "cli",
|
|
410
|
+
scope: opts.scope || "public",
|
|
411
|
+
controller_id: opts.controller || "ufoo-agent",
|
|
412
|
+
meta,
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
await ensureDaemonRunning(projectRoot);
|
|
417
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
418
|
+
type: "agent_report",
|
|
419
|
+
report,
|
|
420
|
+
});
|
|
421
|
+
const out = resp?.data?.report || report;
|
|
422
|
+
if (opts.json) {
|
|
423
|
+
console.log(JSON.stringify(out, null, 2));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const detail = out.phase === "error"
|
|
427
|
+
? (out.error || out.summary || out.message || out.task_id)
|
|
428
|
+
: (out.summary || out.message || out.task_id);
|
|
429
|
+
console.log(`[report] ${out.phase} ${out.agent_id} ${out.task_id} ${detail}`);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
console.error(err.message || String(err));
|
|
432
|
+
process.exitCode = 1;
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
program
|
|
437
|
+
.command("ucode")
|
|
438
|
+
.description("ucode core preparation helpers")
|
|
439
|
+
.argument("[action]", "doctor|prepare|build", "doctor")
|
|
440
|
+
.option("--skip-install", "Skip npm install even if node_modules is missing")
|
|
441
|
+
.action((action, opts) => {
|
|
442
|
+
const { inspectUcodeSetup, formatUcodeDoctor, prepareAndInspectUcode } = require("./agent/ucodeDoctor");
|
|
443
|
+
const { buildUcodeCore } = require("./agent/ucodeBuild");
|
|
444
|
+
const normalized = String(action || "doctor").trim().toLowerCase();
|
|
445
|
+
if (normalized !== "doctor" && normalized !== "prepare" && normalized !== "build") {
|
|
446
|
+
console.error("ucode action must be doctor|prepare|build");
|
|
447
|
+
process.exitCode = 1;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (normalized === "build") {
|
|
451
|
+
try {
|
|
452
|
+
const built = buildUcodeCore({
|
|
453
|
+
projectRoot: process.cwd(),
|
|
454
|
+
installIfMissing: !opts.skipInstall,
|
|
455
|
+
stdio: "inherit",
|
|
456
|
+
});
|
|
457
|
+
console.log("=== ucode build ===");
|
|
458
|
+
console.log(`workspace: ${built.workspaceRoot}`);
|
|
459
|
+
console.log(`core: ${built.coreRoot}`);
|
|
460
|
+
console.log(`dist: ${built.distCliPath}`);
|
|
461
|
+
console.log(`steps: ${built.steps.join(", ")}`);
|
|
462
|
+
return;
|
|
463
|
+
} catch (err) {
|
|
464
|
+
console.error(err.message || String(err));
|
|
465
|
+
process.exitCode = 1;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const result = normalized === "prepare"
|
|
470
|
+
? prepareAndInspectUcode({ projectRoot: process.cwd() })
|
|
471
|
+
: inspectUcodeSetup({ projectRoot: process.cwd() });
|
|
472
|
+
console.log(formatUcodeDoctor(result));
|
|
473
|
+
if (normalized === "prepare" && result.bootstrapPrepared && result.bootstrapPrepared.file) {
|
|
474
|
+
console.log(`prepared bootstrap: ${result.bootstrapPrepared.file}`);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
207
477
|
|
|
208
478
|
program
|
|
209
479
|
.command("init")
|
|
@@ -252,6 +522,168 @@ async function runCli(argv) {
|
|
|
252
522
|
}
|
|
253
523
|
});
|
|
254
524
|
|
|
525
|
+
const online = program.command("online").description("ufoo online helpers");
|
|
526
|
+
online
|
|
527
|
+
.command("server")
|
|
528
|
+
.description("Start ufoo-online relay server")
|
|
529
|
+
.option("--port <port>", "Listen port", "8787")
|
|
530
|
+
.option("--host <host>", "Listen host", "127.0.0.1")
|
|
531
|
+
.option("--token-file <path>", "Token file for auth validation")
|
|
532
|
+
.option("--idle-timeout <ms>", "Idle timeout in ms", "30000")
|
|
533
|
+
.option("--insecure", "Allow any token (dev only)")
|
|
534
|
+
.option("--tls-cert <path>", "TLS certificate file")
|
|
535
|
+
.option("--tls-key <path>", "TLS private key file")
|
|
536
|
+
.action(async (opts) => {
|
|
537
|
+
try {
|
|
538
|
+
await runOnlineCommand("server", { opts }, {
|
|
539
|
+
mode: "commander",
|
|
540
|
+
onlineAuthHeaders,
|
|
541
|
+
projectRoot: process.cwd(),
|
|
542
|
+
});
|
|
543
|
+
} catch (err) {
|
|
544
|
+
console.error(err.message || String(err));
|
|
545
|
+
process.exitCode = 1;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
online
|
|
549
|
+
.command("token")
|
|
550
|
+
.description("Generate and store a ufoo-online token")
|
|
551
|
+
.argument("<subscriber>", "Subscriber ID (e.g., claude-code:abc123)")
|
|
552
|
+
.option("--nickname <name>", "Nickname for this agent")
|
|
553
|
+
.option("--server <url>", "Online server URL")
|
|
554
|
+
.option("--file <path>", "Tokens file path")
|
|
555
|
+
.action(async (subscriber, opts) => {
|
|
556
|
+
try {
|
|
557
|
+
await runOnlineCommand("token", { subscriber, opts }, {
|
|
558
|
+
mode: "commander",
|
|
559
|
+
onlineAuthHeaders,
|
|
560
|
+
projectRoot: process.cwd(),
|
|
561
|
+
});
|
|
562
|
+
} catch (err) {
|
|
563
|
+
console.error(err.message || String(err));
|
|
564
|
+
process.exitCode = 1;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
online
|
|
569
|
+
.command("room")
|
|
570
|
+
.description("Manage online rooms (HTTP)")
|
|
571
|
+
.argument("<action>", "create|list")
|
|
572
|
+
.option("--server <url>", "Online server base URL (http://host:port)")
|
|
573
|
+
.option("--auth-token <token>", "Bearer token for HTTP auth (token or token_hash)")
|
|
574
|
+
.option("--token-file <path>", "Token file path for auth lookup")
|
|
575
|
+
.option("--subscriber <id>", "Subscriber ID to resolve token")
|
|
576
|
+
.option("--nickname <name>", "Nickname to resolve token")
|
|
577
|
+
.option("--name <room>", "Room name (optional)")
|
|
578
|
+
.option("--type <type>", "Room type (public|private)")
|
|
579
|
+
.option("--password <pwd>", "Room password (private only)")
|
|
580
|
+
.action(async (action, opts) => {
|
|
581
|
+
try {
|
|
582
|
+
await runOnlineCommand("room", { action, opts }, {
|
|
583
|
+
mode: "commander",
|
|
584
|
+
onlineAuthHeaders,
|
|
585
|
+
projectRoot: process.cwd(),
|
|
586
|
+
});
|
|
587
|
+
} catch (err) {
|
|
588
|
+
console.error(err.message || String(err));
|
|
589
|
+
process.exitCode = 1;
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
online
|
|
594
|
+
.command("channel")
|
|
595
|
+
.description("Manage online channels (HTTP)")
|
|
596
|
+
.argument("<action>", "create|list")
|
|
597
|
+
.option("--server <url>", "Online server base URL (http://host:port)")
|
|
598
|
+
.option("--auth-token <token>", "Bearer token for HTTP auth (token or token_hash)")
|
|
599
|
+
.option("--token-file <path>", "Token file path for auth lookup")
|
|
600
|
+
.option("--subscriber <id>", "Subscriber ID to resolve token")
|
|
601
|
+
.option("--nickname <name>", "Nickname to resolve token")
|
|
602
|
+
.option("--name <name>", "Channel name (unique)")
|
|
603
|
+
.option("--type <type>", "Channel type (world|public)")
|
|
604
|
+
.action(async (action, opts) => {
|
|
605
|
+
try {
|
|
606
|
+
await runOnlineCommand("channel", { action, opts }, {
|
|
607
|
+
mode: "commander",
|
|
608
|
+
onlineAuthHeaders,
|
|
609
|
+
projectRoot: process.cwd(),
|
|
610
|
+
});
|
|
611
|
+
} catch (err) {
|
|
612
|
+
console.error(err.message || String(err));
|
|
613
|
+
process.exitCode = 1;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
online
|
|
618
|
+
.command("connect")
|
|
619
|
+
.description("Connect to ufoo-online relay (long-running)")
|
|
620
|
+
.requiredOption("--nickname <name>", "Agent nickname")
|
|
621
|
+
.option("--url <url>", "WebSocket URL", "ws://127.0.0.1:8787/ufoo/online")
|
|
622
|
+
.option("--subscriber <id>", "Subscriber ID (auto-generated if omitted)")
|
|
623
|
+
.option("--token <tok>", "Auth token")
|
|
624
|
+
.option("--token-hash <hash>", "Auth token hash")
|
|
625
|
+
.option("--token-file <path>", "Token file path")
|
|
626
|
+
.option("--world <name>", "World name", "default")
|
|
627
|
+
.option("--ping-ms <ms>", "Keepalive ping interval (ms)")
|
|
628
|
+
.option("--join <channel>", "Join channel after connect")
|
|
629
|
+
.option("--room <room>", "Join private room (enables bus/decisions/wake sync)")
|
|
630
|
+
.option("--room-password <pwd>", "Room password")
|
|
631
|
+
.option("--interval <ms>", "Bus sync poll interval in ms", "1500")
|
|
632
|
+
.option("--allow-insecure-ws", "Allow ws:// to non-localhost (insecure)")
|
|
633
|
+
.option("--trust-remote", "Trust all private-room members for bus/decisions/wake sync")
|
|
634
|
+
.option("--allow-from <subscriberId>", "Allow bus/decisions/wake from subscriber ID (repeatable)", collectOption)
|
|
635
|
+
.action(async (opts) => {
|
|
636
|
+
try {
|
|
637
|
+
await runOnlineCommand("connect", { opts }, {
|
|
638
|
+
mode: "commander",
|
|
639
|
+
onlineAuthHeaders,
|
|
640
|
+
projectRoot: process.cwd(),
|
|
641
|
+
});
|
|
642
|
+
} catch (err) {
|
|
643
|
+
console.error(err.message || String(err));
|
|
644
|
+
process.exitCode = 1;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
online
|
|
649
|
+
.command("send")
|
|
650
|
+
.description("Send a message to a channel or room via outbox")
|
|
651
|
+
.requiredOption("--nickname <name>", "Agent nickname (must match a running connect)")
|
|
652
|
+
.requiredOption("--text <message>", "Message text")
|
|
653
|
+
.option("--channel <name>", "Target channel")
|
|
654
|
+
.option("--room <id>", "Target room")
|
|
655
|
+
.action(async (opts) => {
|
|
656
|
+
try {
|
|
657
|
+
await runOnlineCommand("send", { opts }, {
|
|
658
|
+
mode: "commander",
|
|
659
|
+
onlineAuthHeaders,
|
|
660
|
+
projectRoot: process.cwd(),
|
|
661
|
+
});
|
|
662
|
+
} catch (err) {
|
|
663
|
+
console.error(err.message || String(err));
|
|
664
|
+
process.exitCode = 1;
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
online
|
|
669
|
+
.command("inbox")
|
|
670
|
+
.description("View ufoo-online inbox for a nickname")
|
|
671
|
+
.argument("<nickname>", "Agent nickname")
|
|
672
|
+
.option("--clear", "Clear the inbox")
|
|
673
|
+
.option("--unread", "Show unread messages only")
|
|
674
|
+
.action(async (nickname, opts) => {
|
|
675
|
+
try {
|
|
676
|
+
await runOnlineCommand("inbox", { nickname, opts }, {
|
|
677
|
+
mode: "commander",
|
|
678
|
+
onlineAuthHeaders,
|
|
679
|
+
projectRoot: process.cwd(),
|
|
680
|
+
});
|
|
681
|
+
} catch (err) {
|
|
682
|
+
console.error(err.message || String(err));
|
|
683
|
+
process.exitCode = 1;
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
255
687
|
const bus = program.command("bus").description("Project bus commands");
|
|
256
688
|
bus
|
|
257
689
|
.command("alert")
|
|
@@ -330,7 +762,7 @@ async function runCli(argv) {
|
|
|
330
762
|
});
|
|
331
763
|
bus
|
|
332
764
|
.command("inject")
|
|
333
|
-
.description("Inject /
|
|
765
|
+
.description("Inject /bus into a Terminal.app tab by subscriber ID")
|
|
334
766
|
.argument("<subscriber>", "Subscriber ID to inject into")
|
|
335
767
|
.action((subscriber) => {
|
|
336
768
|
const EventBus = require("./bus");
|
|
@@ -344,6 +776,24 @@ async function runCli(argv) {
|
|
|
344
776
|
}
|
|
345
777
|
})();
|
|
346
778
|
});
|
|
779
|
+
bus
|
|
780
|
+
.command("wake")
|
|
781
|
+
.description("Wake an agent (inject /ubus into its terminal)")
|
|
782
|
+
.argument("<target>", "Subscriber ID or nickname")
|
|
783
|
+
.option("--reason <reason>", "Wake reason")
|
|
784
|
+
.option("--no-shake", "Disable window shake")
|
|
785
|
+
.action((target, opts) => {
|
|
786
|
+
const EventBus = require("./bus");
|
|
787
|
+
const eventBus = new EventBus(process.cwd());
|
|
788
|
+
(async () => {
|
|
789
|
+
try {
|
|
790
|
+
await eventBus.wake(target, { reason: opts.reason || "remote", shake: opts.shake !== false });
|
|
791
|
+
} catch (err) {
|
|
792
|
+
console.error(err.message || String(err));
|
|
793
|
+
process.exitCode = 1;
|
|
794
|
+
}
|
|
795
|
+
})();
|
|
796
|
+
});
|
|
347
797
|
bus
|
|
348
798
|
.command("activate")
|
|
349
799
|
.description("Activate (focus) the terminal/tmux window of an agent")
|
|
@@ -372,58 +822,8 @@ async function runCli(argv) {
|
|
|
372
822
|
const cmdArgs = args.slice(1);
|
|
373
823
|
|
|
374
824
|
try {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
await eventBus.init();
|
|
378
|
-
break;
|
|
379
|
-
case "join":
|
|
380
|
-
{
|
|
381
|
-
const subscriber = await eventBus.join(cmdArgs[0], cmdArgs[1], cmdArgs[2]);
|
|
382
|
-
if (subscriber) console.log(subscriber);
|
|
383
|
-
}
|
|
384
|
-
break;
|
|
385
|
-
case "leave":
|
|
386
|
-
await eventBus.leave(cmdArgs[0]);
|
|
387
|
-
break;
|
|
388
|
-
case "send":
|
|
389
|
-
{
|
|
390
|
-
// 自动 join(如果还没有 join)并获取 subscriber ID
|
|
391
|
-
const publisher = await eventBus.ensureJoined();
|
|
392
|
-
await eventBus.send(cmdArgs[0], cmdArgs[1], publisher);
|
|
393
|
-
}
|
|
394
|
-
break;
|
|
395
|
-
case "broadcast":
|
|
396
|
-
{
|
|
397
|
-
// 自动 join(如果还没有 join)并获取 subscriber ID
|
|
398
|
-
const publisher = await eventBus.ensureJoined();
|
|
399
|
-
await eventBus.broadcast(cmdArgs[0], publisher);
|
|
400
|
-
}
|
|
401
|
-
break;
|
|
402
|
-
case "check":
|
|
403
|
-
await eventBus.check(cmdArgs[0]);
|
|
404
|
-
break;
|
|
405
|
-
case "ack":
|
|
406
|
-
await eventBus.ack(cmdArgs[0]);
|
|
407
|
-
break;
|
|
408
|
-
case "consume":
|
|
409
|
-
await eventBus.consume(cmdArgs[0], cmdArgs.includes("--from-beginning"));
|
|
410
|
-
break;
|
|
411
|
-
case "status":
|
|
412
|
-
await eventBus.status();
|
|
413
|
-
break;
|
|
414
|
-
case "resolve":
|
|
415
|
-
await eventBus.resolve(cmdArgs[0], cmdArgs[1]);
|
|
416
|
-
break;
|
|
417
|
-
case "rename":
|
|
418
|
-
await eventBus.rename(cmdArgs[0], cmdArgs[1]);
|
|
419
|
-
break;
|
|
420
|
-
case "whoami":
|
|
421
|
-
await eventBus.whoami();
|
|
422
|
-
break;
|
|
423
|
-
default:
|
|
424
|
-
console.error(`Unknown bus subcommand: ${sub}`);
|
|
425
|
-
process.exitCode = 1;
|
|
426
|
-
}
|
|
825
|
+
const result = await runBusCoreCommand(eventBus, cmd, cmdArgs);
|
|
826
|
+
if (result && result.subscriber) console.log(result.subscriber);
|
|
427
827
|
} catch (err) {
|
|
428
828
|
console.error(err.message);
|
|
429
829
|
process.exitCode = 1;
|
|
@@ -432,98 +832,25 @@ async function runCli(argv) {
|
|
|
432
832
|
|
|
433
833
|
program
|
|
434
834
|
.command("ctx")
|
|
435
|
-
.description("Project context commands (doctor|lint|decisions)")
|
|
436
|
-
.argument("[subcmd]", "Subcommand (doctor|lint|decisions)", "doctor")
|
|
835
|
+
.description("Project context commands (doctor|lint|decisions|sync)")
|
|
836
|
+
.argument("[subcmd]", "Subcommand (doctor|lint|decisions|sync)", "doctor")
|
|
437
837
|
.allowUnknownOption(true)
|
|
438
838
|
.argument("[subargs...]", "Subcommand args")
|
|
439
839
|
.action(async (subcmd, subargs = []) => {
|
|
440
|
-
const DecisionsManager = require("./context/decisions");
|
|
441
|
-
const ContextDoctor = require("./context/doctor");
|
|
442
840
|
const cwd = process.cwd();
|
|
443
841
|
|
|
444
842
|
try {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
await doctor.run({ mode, projectPath });
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
case "lint": {
|
|
454
|
-
const doctor = new ContextDoctor(cwd);
|
|
455
|
-
const mode = subargs.includes("--project") ? "project" : "protocol";
|
|
456
|
-
const projectPath = mode === "project" ? subargs[subargs.indexOf("--project") + 1] : null;
|
|
457
|
-
if (mode === "project") {
|
|
458
|
-
doctor.lintProject(projectPath);
|
|
459
|
-
} else {
|
|
460
|
-
doctor.lintProtocol();
|
|
461
|
-
}
|
|
462
|
-
break;
|
|
463
|
-
}
|
|
464
|
-
case "decisions": {
|
|
465
|
-
const manager = new DecisionsManager(cwd);
|
|
466
|
-
const opts = {};
|
|
467
|
-
|
|
468
|
-
if (subargs[0] === "index" || subargs.includes("--index")) {
|
|
469
|
-
manager.writeIndex();
|
|
470
|
-
break;
|
|
471
|
-
}
|
|
472
|
-
if (subargs[0] === "new") {
|
|
473
|
-
const create = { title: "", author: "", status: "" };
|
|
474
|
-
for (let i = 1; i < subargs.length; i++) {
|
|
475
|
-
const arg = subargs[i];
|
|
476
|
-
if (arg === "--author") {
|
|
477
|
-
create.author = subargs[++i] || "";
|
|
478
|
-
continue;
|
|
479
|
-
}
|
|
480
|
-
if (arg === "--status") {
|
|
481
|
-
create.status = subargs[++i] || "";
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
if (!arg.startsWith("-")) {
|
|
485
|
-
create.title = create.title ? `${create.title} ${arg}` : arg;
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
manager.createDecision(create);
|
|
490
|
-
break;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Parse options
|
|
494
|
-
for (let i = 0; i < subargs.length; i++) {
|
|
495
|
-
if (subargs[i] === "-n") opts.num = parseInt(subargs[++i]);
|
|
496
|
-
if (subargs[i] === "-s") opts.status = subargs[++i];
|
|
497
|
-
if (subargs[i] === "-l") opts.listOnly = true;
|
|
498
|
-
if (subargs[i] === "-a") opts.all = true;
|
|
499
|
-
if (subargs[i] === "-d") {
|
|
500
|
-
manager.decisionsDir = subargs[++i];
|
|
501
|
-
manager.contextDir = path.dirname(manager.decisionsDir);
|
|
502
|
-
manager.indexFile = path.join(manager.contextDir, "decisions.jsonl");
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (opts.listOnly) {
|
|
507
|
-
manager.list({ status: opts.status || "open" });
|
|
508
|
-
} else {
|
|
509
|
-
manager.show({
|
|
510
|
-
status: opts.status || "open",
|
|
511
|
-
num: opts.num || 1,
|
|
512
|
-
all: opts.all || false,
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
default:
|
|
518
|
-
console.error(
|
|
519
|
-
chalk.red(
|
|
520
|
-
`Unknown ctx subcommand: ${subcmd}. Supported: doctor, lint, decisions`
|
|
521
|
-
)
|
|
522
|
-
);
|
|
523
|
-
process.exitCode = 1;
|
|
524
|
-
}
|
|
843
|
+
await runCtxCommand(subcmd, subargs, {
|
|
844
|
+
cwd,
|
|
845
|
+
allowIndexNew: true,
|
|
846
|
+
updateDecisionIndexPaths: true,
|
|
847
|
+
});
|
|
525
848
|
} catch (err) {
|
|
526
|
-
|
|
849
|
+
if (err && err.code === "UFOO_CTX_UNKNOWN") {
|
|
850
|
+
console.error(chalk.red(err.message));
|
|
851
|
+
} else {
|
|
852
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
853
|
+
}
|
|
527
854
|
process.exitCode = 1;
|
|
528
855
|
}
|
|
529
856
|
});
|
|
@@ -562,11 +889,24 @@ async function runCli(argv) {
|
|
|
562
889
|
console.log(" ufoo daemon --start|--stop|--status");
|
|
563
890
|
console.log(" ufoo chat");
|
|
564
891
|
console.log(" ufoo resume [nickname]");
|
|
892
|
+
console.log(" ufoo recover [list [target] | run <target>] [--json]");
|
|
893
|
+
console.log(" ufoo report <start|progress|done|error|list> [message] [--task <id>] [--agent <id>]");
|
|
894
|
+
console.log(" ufoo ucode [doctor|prepare|build] [--skip-install]");
|
|
565
895
|
console.log(" ufoo init [--modules <list>] [--project <dir>]");
|
|
566
896
|
console.log(" ufoo skills list");
|
|
567
897
|
console.log(" ufoo skills install <name|all> [--target <dir> | --codex | --agents]");
|
|
898
|
+
console.log(" ufoo online server [--port 8787] [--host 127.0.0.1] [--token-file <path>]");
|
|
899
|
+
console.log(" ufoo online token <subscriber> [--nickname <name>] [--server <url>] [--file <path>]");
|
|
900
|
+
console.log(" ufoo online room create [--name <room>] --type public|private [--password <pwd>] [--server <url>]");
|
|
901
|
+
console.log(" ufoo online room list [--server <url>]");
|
|
902
|
+
console.log(" ufoo online channel create --name <name> [--type world|public] [--server <url>]");
|
|
903
|
+
console.log(" ufoo online channel list [--server <url>]");
|
|
904
|
+
console.log(" ufoo online connect --nickname <name> [--join <ch>] [--room <id> --room-password <pwd>] [...]");
|
|
905
|
+
console.log(" ufoo online send --nickname <name> --text <msg> [--channel <ch>] [--room <id>]");
|
|
906
|
+
console.log(" ufoo online inbox <nickname> [--clear] [--unread]");
|
|
907
|
+
console.log(" ufoo bus wake <target> [--reason <reason>] [--no-shake]");
|
|
568
908
|
console.log(" ufoo bus <args...> (JS bus implementation)");
|
|
569
|
-
console.log(" ufoo ctx <subcmd> ... (doctor|lint|decisions)");
|
|
909
|
+
console.log(" ufoo ctx <subcmd> ... (doctor|lint|decisions|sync)");
|
|
570
910
|
console.log("");
|
|
571
911
|
console.log("Notes:");
|
|
572
912
|
console.log(" - For Codex notifications, use ufoo bus alert / ufoo bus listen");
|
|
@@ -626,6 +966,214 @@ async function runCli(argv) {
|
|
|
626
966
|
})();
|
|
627
967
|
return;
|
|
628
968
|
}
|
|
969
|
+
if (cmd === "recover") {
|
|
970
|
+
const first = rest[0] || "";
|
|
971
|
+
const action = first && !first.startsWith("--") ? first.toLowerCase() : "list";
|
|
972
|
+
const targetIdx = first && !first.startsWith("--") ? 1 : 0;
|
|
973
|
+
const target = rest[targetIdx] && !rest[targetIdx].startsWith("--") ? rest[targetIdx] : "";
|
|
974
|
+
const outputJson = rest.includes("--json");
|
|
975
|
+
(async () => {
|
|
976
|
+
try {
|
|
977
|
+
const projectRoot = process.cwd();
|
|
978
|
+
await ensureDaemonRunning(projectRoot);
|
|
979
|
+
|
|
980
|
+
if (action === "list") {
|
|
981
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
982
|
+
type: "list_recoverable_agents",
|
|
983
|
+
target,
|
|
984
|
+
});
|
|
985
|
+
const result = resp?.data?.recoverable || { recoverable: [], skipped: [] };
|
|
986
|
+
if (outputJson) {
|
|
987
|
+
console.log(JSON.stringify(result, null, 2));
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const recoverable = result.recoverable || [];
|
|
991
|
+
console.log(resp?.data?.reply || `Found ${recoverable.length} recoverable agent(s)`);
|
|
992
|
+
recoverable.forEach((item) => {
|
|
993
|
+
const nickname = item.nickname ? ` (${item.nickname})` : "";
|
|
994
|
+
const meta = item.launchMode ? ` [${item.agent}/${item.launchMode}]` : ` [${item.agent}]`;
|
|
995
|
+
console.log(` - ${item.id}${nickname}${meta}`);
|
|
996
|
+
});
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (action === "run") {
|
|
1001
|
+
if (!target) {
|
|
1002
|
+
console.error("recover run requires <target>");
|
|
1003
|
+
process.exitCode = 1;
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
1007
|
+
type: "resume_agents",
|
|
1008
|
+
target,
|
|
1009
|
+
});
|
|
1010
|
+
const reply = resp?.data?.reply || "Recover requested";
|
|
1011
|
+
console.log(reply);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
console.error("recover action must be list|run");
|
|
1016
|
+
process.exitCode = 1;
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
console.error(err.message || String(err));
|
|
1019
|
+
process.exitCode = 1;
|
|
1020
|
+
}
|
|
1021
|
+
})();
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (cmd === "report") {
|
|
1025
|
+
const action = String(rest[0] || "").toLowerCase();
|
|
1026
|
+
const normalized = normalizeReportPhase(action);
|
|
1027
|
+
const { listReports } = require("./report/store");
|
|
1028
|
+
|
|
1029
|
+
if (action === "list") {
|
|
1030
|
+
const agentIdx = rest.indexOf("--agent");
|
|
1031
|
+
const numIdx = rest.indexOf("--num");
|
|
1032
|
+
const nIdx = rest.indexOf("-n");
|
|
1033
|
+
const json = rest.includes("--json");
|
|
1034
|
+
const agent = agentIdx !== -1 ? (rest[agentIdx + 1] || "") : "";
|
|
1035
|
+
const numRaw = numIdx !== -1
|
|
1036
|
+
? rest[numIdx + 1]
|
|
1037
|
+
: (nIdx !== -1 ? rest[nIdx + 1] : "20");
|
|
1038
|
+
try {
|
|
1039
|
+
const rows = listReports(process.cwd(), { agent, num: parseInt(numRaw, 10) });
|
|
1040
|
+
if (json) {
|
|
1041
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
console.log(`=== Reports (${rows.length} shown) ===`);
|
|
1045
|
+
rows.forEach((row) => {
|
|
1046
|
+
const detail = row.phase === "error"
|
|
1047
|
+
? (row.error || row.summary || row.message || row.task_id)
|
|
1048
|
+
: (row.summary || row.message || row.task_id);
|
|
1049
|
+
console.log(`${row.ts || "-"} [${row.phase}] ${row.agent_id || "unknown-agent"} ${row.task_id || ""} ${detail}`);
|
|
1050
|
+
});
|
|
1051
|
+
if (rows.length === 0) console.log("No reports found.");
|
|
1052
|
+
} catch (err) {
|
|
1053
|
+
console.error(err.message || String(err));
|
|
1054
|
+
process.exitCode = 1;
|
|
1055
|
+
}
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (!normalized) {
|
|
1060
|
+
console.error("report action must be start|progress|done|error|list");
|
|
1061
|
+
process.exitCode = 1;
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const getOpt = (name, fallback = "") => {
|
|
1066
|
+
const idx = rest.indexOf(name);
|
|
1067
|
+
if (idx === -1 || idx + 1 >= rest.length) return fallback;
|
|
1068
|
+
const value = rest[idx + 1];
|
|
1069
|
+
if (!value || value.startsWith("--")) return fallback;
|
|
1070
|
+
return value;
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
const isValueToken = (token) =>
|
|
1074
|
+
token && !token.startsWith("--") && token !== action;
|
|
1075
|
+
const message = rest.slice(1).filter((token, idx, arr) => {
|
|
1076
|
+
if (!isValueToken(token)) return false;
|
|
1077
|
+
const prev = arr[idx - 1] || "";
|
|
1078
|
+
if (
|
|
1079
|
+
prev === "--task"
|
|
1080
|
+
|| prev === "--agent"
|
|
1081
|
+
|| prev === "--scope"
|
|
1082
|
+
|| prev === "--controller"
|
|
1083
|
+
|| prev === "--summary"
|
|
1084
|
+
|| prev === "--error"
|
|
1085
|
+
|| prev === "--meta"
|
|
1086
|
+
|| prev === "--num"
|
|
1087
|
+
|| prev === "-n"
|
|
1088
|
+
) {
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
return true;
|
|
1092
|
+
}).join(" ").trim();
|
|
1093
|
+
|
|
1094
|
+
let meta = {};
|
|
1095
|
+
try {
|
|
1096
|
+
meta = parseJsonObject(getOpt("--meta", ""), {});
|
|
1097
|
+
} catch (err) {
|
|
1098
|
+
console.error(`Invalid --meta: ${err.message}`);
|
|
1099
|
+
process.exitCode = 1;
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const report = {
|
|
1104
|
+
phase: normalized,
|
|
1105
|
+
task_id: getOpt("--task", `task-${Date.now()}`),
|
|
1106
|
+
agent_id: getOpt("--agent", process.env.UFOO_SUBSCRIBER_ID || "unknown-agent"),
|
|
1107
|
+
message,
|
|
1108
|
+
summary: getOpt("--summary", normalized === "done" ? message : ""),
|
|
1109
|
+
error: getOpt("--error", normalized === "error" ? message : ""),
|
|
1110
|
+
ok: normalized !== "error",
|
|
1111
|
+
source: "cli",
|
|
1112
|
+
scope: getOpt("--scope", "public"),
|
|
1113
|
+
controller_id: getOpt("--controller", "ufoo-agent"),
|
|
1114
|
+
meta,
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
(async () => {
|
|
1118
|
+
try {
|
|
1119
|
+
await ensureDaemonRunning(process.cwd());
|
|
1120
|
+
const resp = await sendDaemonRequest(process.cwd(), {
|
|
1121
|
+
type: "agent_report",
|
|
1122
|
+
report,
|
|
1123
|
+
});
|
|
1124
|
+
const out = resp?.data?.report || report;
|
|
1125
|
+
if (rest.includes("--json")) {
|
|
1126
|
+
console.log(JSON.stringify(out, null, 2));
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const detail = out.phase === "error"
|
|
1130
|
+
? (out.error || out.summary || out.message || out.task_id)
|
|
1131
|
+
: (out.summary || out.message || out.task_id);
|
|
1132
|
+
console.log(`[report] ${out.phase} ${out.agent_id} ${out.task_id} ${detail}`);
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
console.error(err.message || String(err));
|
|
1135
|
+
process.exitCode = 1;
|
|
1136
|
+
}
|
|
1137
|
+
})();
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (cmd === "ucode") {
|
|
1141
|
+
const action = String(rest[0] || "doctor").trim().toLowerCase();
|
|
1142
|
+
const { inspectUcodeSetup, formatUcodeDoctor, prepareAndInspectUcode } = require("./agent/ucodeDoctor");
|
|
1143
|
+
const { buildUcodeCore } = require("./agent/ucodeBuild");
|
|
1144
|
+
const skipInstall = rest.includes("--skip-install");
|
|
1145
|
+
if (action !== "doctor" && action !== "prepare" && action !== "build") {
|
|
1146
|
+
console.error("ucode action must be doctor|prepare|build");
|
|
1147
|
+
process.exitCode = 1;
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (action === "build") {
|
|
1151
|
+
try {
|
|
1152
|
+
const built = buildUcodeCore({
|
|
1153
|
+
projectRoot: process.cwd(),
|
|
1154
|
+
installIfMissing: !skipInstall,
|
|
1155
|
+
stdio: "inherit",
|
|
1156
|
+
});
|
|
1157
|
+
console.log("=== ucode build ===");
|
|
1158
|
+
console.log(`workspace: ${built.workspaceRoot}`);
|
|
1159
|
+
console.log(`core: ${built.coreRoot}`);
|
|
1160
|
+
console.log(`dist: ${built.distCliPath}`);
|
|
1161
|
+
console.log(`steps: ${built.steps.join(", ")}`);
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
console.error(err.message || String(err));
|
|
1164
|
+
process.exitCode = 1;
|
|
1165
|
+
}
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const result = action === "prepare"
|
|
1169
|
+
? prepareAndInspectUcode({ projectRoot: process.cwd() })
|
|
1170
|
+
: inspectUcodeSetup({ projectRoot: process.cwd() });
|
|
1171
|
+
console.log(formatUcodeDoctor(result));
|
|
1172
|
+
if (action === "prepare" && result.bootstrapPrepared && result.bootstrapPrepared.file) {
|
|
1173
|
+
console.log(`prepared bootstrap: ${result.bootstrapPrepared.file}`);
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
629
1177
|
if (cmd === "init") {
|
|
630
1178
|
const UfooInit = require("./init");
|
|
631
1179
|
const init = new UfooInit(repoRoot);
|
|
@@ -684,6 +1232,35 @@ async function runCli(argv) {
|
|
|
684
1232
|
process.exitCode = 1;
|
|
685
1233
|
return;
|
|
686
1234
|
}
|
|
1235
|
+
if (cmd === "online") {
|
|
1236
|
+
const sub = rest[0] || "";
|
|
1237
|
+
if (!sub) {
|
|
1238
|
+
help();
|
|
1239
|
+
process.exitCode = 1;
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
(async () => {
|
|
1244
|
+
try {
|
|
1245
|
+
await runOnlineCommand(sub, { argv: rest }, {
|
|
1246
|
+
mode: "fallback",
|
|
1247
|
+
onlineAuthHeaders,
|
|
1248
|
+
projectRoot: process.cwd(),
|
|
1249
|
+
collectOptionValues,
|
|
1250
|
+
collectOption,
|
|
1251
|
+
defaultChannelType: "public",
|
|
1252
|
+
});
|
|
1253
|
+
} catch (err) {
|
|
1254
|
+
if (err && err.code === "UFOO_ONLINE_UNKNOWN") {
|
|
1255
|
+
help();
|
|
1256
|
+
} else {
|
|
1257
|
+
console.error(err.message || String(err));
|
|
1258
|
+
}
|
|
1259
|
+
process.exitCode = 1;
|
|
1260
|
+
}
|
|
1261
|
+
})();
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
687
1264
|
if (cmd === "bus") {
|
|
688
1265
|
const sub = rest[0] || "";
|
|
689
1266
|
if (sub === "alert") {
|
|
@@ -776,6 +1353,24 @@ async function runCli(argv) {
|
|
|
776
1353
|
})();
|
|
777
1354
|
return;
|
|
778
1355
|
}
|
|
1356
|
+
if (sub === "wake") {
|
|
1357
|
+
const EventBus = require("./bus");
|
|
1358
|
+
const eventBus = new EventBus(process.cwd());
|
|
1359
|
+
(async () => {
|
|
1360
|
+
try {
|
|
1361
|
+
const target = rest[1];
|
|
1362
|
+
if (!target) throw new Error("wake requires <subscriber-id|nickname>");
|
|
1363
|
+
const reasonIdx = rest.indexOf("--reason");
|
|
1364
|
+
const reason = reasonIdx !== -1 ? rest[reasonIdx + 1] : "remote";
|
|
1365
|
+
const shake = !rest.includes("--no-shake");
|
|
1366
|
+
await eventBus.wake(target, { reason, shake });
|
|
1367
|
+
} catch (err) {
|
|
1368
|
+
console.error(err.message || String(err));
|
|
1369
|
+
process.exitCode = 1;
|
|
1370
|
+
}
|
|
1371
|
+
})();
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
779
1374
|
|
|
780
1375
|
// Use JavaScript EventBus module for core commands
|
|
781
1376
|
const EventBus = require("./bus");
|
|
@@ -784,58 +1379,8 @@ async function runCli(argv) {
|
|
|
784
1379
|
(async () => {
|
|
785
1380
|
try {
|
|
786
1381
|
const cmdArgs = rest.slice(1);
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
await eventBus.init();
|
|
790
|
-
break;
|
|
791
|
-
case "join":
|
|
792
|
-
{
|
|
793
|
-
const subscriber = await eventBus.join(cmdArgs[0], cmdArgs[1], cmdArgs[2]);
|
|
794
|
-
if (subscriber) console.log(subscriber);
|
|
795
|
-
}
|
|
796
|
-
break;
|
|
797
|
-
case "leave":
|
|
798
|
-
await eventBus.leave(cmdArgs[0]);
|
|
799
|
-
break;
|
|
800
|
-
case "send":
|
|
801
|
-
{
|
|
802
|
-
// 自动 join(如果还没有 join)并获取 subscriber ID
|
|
803
|
-
const publisher = await eventBus.ensureJoined();
|
|
804
|
-
await eventBus.send(cmdArgs[0], cmdArgs[1], publisher);
|
|
805
|
-
}
|
|
806
|
-
break;
|
|
807
|
-
case "broadcast":
|
|
808
|
-
{
|
|
809
|
-
// 自动 join(如果还没有 join)并获取 subscriber ID
|
|
810
|
-
const publisher = await eventBus.ensureJoined();
|
|
811
|
-
await eventBus.broadcast(cmdArgs[0], publisher);
|
|
812
|
-
}
|
|
813
|
-
break;
|
|
814
|
-
case "check":
|
|
815
|
-
await eventBus.check(cmdArgs[0]);
|
|
816
|
-
break;
|
|
817
|
-
case "ack":
|
|
818
|
-
await eventBus.ack(cmdArgs[0]);
|
|
819
|
-
break;
|
|
820
|
-
case "consume":
|
|
821
|
-
await eventBus.consume(cmdArgs[0], cmdArgs.includes("--from-beginning"));
|
|
822
|
-
break;
|
|
823
|
-
case "status":
|
|
824
|
-
await eventBus.status();
|
|
825
|
-
break;
|
|
826
|
-
case "resolve":
|
|
827
|
-
await eventBus.resolve(cmdArgs[0], cmdArgs[1]);
|
|
828
|
-
break;
|
|
829
|
-
case "rename":
|
|
830
|
-
await eventBus.rename(cmdArgs[0], cmdArgs[1]);
|
|
831
|
-
break;
|
|
832
|
-
case "whoami":
|
|
833
|
-
await eventBus.whoami();
|
|
834
|
-
break;
|
|
835
|
-
default:
|
|
836
|
-
console.error(`Unknown bus subcommand: ${sub}`);
|
|
837
|
-
process.exitCode = 1;
|
|
838
|
-
}
|
|
1382
|
+
const result = await runBusCoreCommand(eventBus, sub, cmdArgs);
|
|
1383
|
+
if (result && result.subscriber) console.log(result.subscriber);
|
|
839
1384
|
} catch (err) {
|
|
840
1385
|
console.error(err.message);
|
|
841
1386
|
process.exitCode = 1;
|
|
@@ -846,57 +1391,15 @@ async function runCli(argv) {
|
|
|
846
1391
|
if (cmd === "ctx") {
|
|
847
1392
|
const sub = rest[0] || "doctor";
|
|
848
1393
|
const subargs = rest.slice(1);
|
|
849
|
-
const DecisionsManager = require("./context/decisions");
|
|
850
|
-
const ContextDoctor = require("./context/doctor");
|
|
851
1394
|
const cwd = process.cwd();
|
|
852
1395
|
|
|
853
1396
|
(async () => {
|
|
854
1397
|
try {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
await doctor.run({ mode, projectPath });
|
|
861
|
-
break;
|
|
862
|
-
}
|
|
863
|
-
case "lint": {
|
|
864
|
-
const doctor = new ContextDoctor(cwd);
|
|
865
|
-
const mode = subargs.includes("--project") ? "project" : "protocol";
|
|
866
|
-
const projectPath = mode === "project" ? subargs[subargs.indexOf("--project") + 1] : null;
|
|
867
|
-
if (mode === "project") {
|
|
868
|
-
doctor.lintProject(projectPath);
|
|
869
|
-
} else {
|
|
870
|
-
doctor.lintProtocol();
|
|
871
|
-
}
|
|
872
|
-
break;
|
|
873
|
-
}
|
|
874
|
-
case "decisions": {
|
|
875
|
-
const manager = new DecisionsManager(cwd);
|
|
876
|
-
const opts = {};
|
|
877
|
-
|
|
878
|
-
for (let i = 0; i < subargs.length; i++) {
|
|
879
|
-
if (subargs[i] === "-n") opts.num = parseInt(subargs[++i]);
|
|
880
|
-
if (subargs[i] === "-s") opts.status = subargs[++i];
|
|
881
|
-
if (subargs[i] === "-l") opts.listOnly = true;
|
|
882
|
-
if (subargs[i] === "-a") opts.all = true;
|
|
883
|
-
if (subargs[i] === "-d") manager.decisionsDir = subargs[++i];
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
if (opts.listOnly) {
|
|
887
|
-
manager.list({ status: opts.status || "open" });
|
|
888
|
-
} else {
|
|
889
|
-
manager.show({
|
|
890
|
-
status: opts.status || "open",
|
|
891
|
-
num: opts.num || 1,
|
|
892
|
-
all: opts.all || false,
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
break;
|
|
896
|
-
}
|
|
897
|
-
default:
|
|
898
|
-
throw new Error(`Unknown ctx subcommand: ${sub}`);
|
|
899
|
-
}
|
|
1398
|
+
await runCtxCommand(sub, subargs, {
|
|
1399
|
+
cwd,
|
|
1400
|
+
allowIndexNew: false,
|
|
1401
|
+
updateDecisionIndexPaths: false,
|
|
1402
|
+
});
|
|
900
1403
|
} catch (err) {
|
|
901
1404
|
console.error(`Error: ${err.message}`);
|
|
902
1405
|
process.exit(1);
|