u-foo 1.2.16 → 1.4.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.
- package/modules/online/README.md +18 -0
- package/package.json +2 -1
- package/src/agent/cliRunner.js +1 -1
- package/src/agent/launcher.js +23 -4
- package/src/agent/ptyRunner.js +39 -16
- package/src/agent/ufooAgent.js +2 -1
- package/src/assistant/agent.js +2 -1
- package/src/assistant/bridge.js +9 -3
- package/src/assistant/constants.js +15 -0
- package/src/assistant/engine.js +7 -2
- package/src/assistant/ufooEngineCli.js +9 -3
- package/src/chat/commandExecutor.js +188 -13
- package/src/chat/commands.js +11 -0
- package/src/chat/daemonMessageRouter.js +107 -0
- package/src/cli/groupCoreCommands.js +246 -0
- package/src/cli/onlineCoreCommands.js +8 -0
- package/src/cli.js +325 -2
- package/src/daemon/groupOrchestrator.js +557 -0
- package/src/daemon/index.js +319 -1
- package/src/daemon/status.js +48 -0
- package/src/group/diagram.js +222 -0
- package/src/group/templates.js +280 -0
- package/src/group/validateTemplate.js +234 -0
- package/src/online/server.js +320 -28
- package/src/shared/eventContract.js +5 -0
- package/src/ufoo/paths.js +2 -0
- package/templates/groups/dev-basic.json +78 -0
- package/templates/groups/research-quick.json +49 -0
package/src/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ const { socketPath, isRunning } = require("./daemon");
|
|
|
6
6
|
const { runBusCoreCommand } = require("./cli/busCoreCommands");
|
|
7
7
|
const { runCtxCommand } = require("./cli/ctxCoreCommands");
|
|
8
8
|
const { runOnlineCommand } = require("./cli/onlineCoreCommands");
|
|
9
|
+
const { runGroupCoreCommand } = require("./cli/groupCoreCommands");
|
|
9
10
|
|
|
10
11
|
function getPackageRoot() {
|
|
11
12
|
return path.resolve(__dirname, "..");
|
|
@@ -561,6 +562,189 @@ async function runCli(argv) {
|
|
|
561
562
|
}
|
|
562
563
|
});
|
|
563
564
|
|
|
565
|
+
const group = program.command("group").description("Agent group template commands");
|
|
566
|
+
group
|
|
567
|
+
.command("templates")
|
|
568
|
+
.description("List available group templates")
|
|
569
|
+
.argument("[action]", "list", "list")
|
|
570
|
+
.option("--json", "Output as JSON")
|
|
571
|
+
.action(async (action, opts) => {
|
|
572
|
+
const normalizedAction = String(action || "list").trim().toLowerCase();
|
|
573
|
+
if (normalizedAction !== "list" && normalizedAction !== "ls") {
|
|
574
|
+
console.error(`Unknown group templates action: ${normalizedAction}`);
|
|
575
|
+
process.exitCode = 1;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
await runGroupCoreCommand("templates", [normalizedAction], {
|
|
580
|
+
cwd: process.cwd(),
|
|
581
|
+
json: opts.json,
|
|
582
|
+
});
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.error(err.message || String(err));
|
|
585
|
+
process.exitCode = 1;
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
group
|
|
590
|
+
.command("template")
|
|
591
|
+
.description("Group template operations")
|
|
592
|
+
.argument("<action>", "list|show|validate|new")
|
|
593
|
+
.argument("[target]", "Template alias (or path for validate)")
|
|
594
|
+
.option("--from <alias>", "Builtin template alias (for template new)")
|
|
595
|
+
.option("--global", "Create template in ~/.ufoo/templates/groups")
|
|
596
|
+
.option("--project", "Create template in .ufoo/templates/groups (default)")
|
|
597
|
+
.option("--force", "Overwrite existing file (for template new)")
|
|
598
|
+
.option("--json", "Output as JSON")
|
|
599
|
+
.action(async (action, target, opts) => {
|
|
600
|
+
const args = [action];
|
|
601
|
+
if (target) args.push(target);
|
|
602
|
+
if (opts.from) args.push("--from", opts.from);
|
|
603
|
+
if (opts.global) args.push("--global");
|
|
604
|
+
if (opts.project) args.push("--project");
|
|
605
|
+
if (opts.force) args.push("--force");
|
|
606
|
+
if (opts.json) args.push("--json");
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
await runGroupCoreCommand("template", args, {
|
|
610
|
+
cwd: process.cwd(),
|
|
611
|
+
json: opts.json,
|
|
612
|
+
});
|
|
613
|
+
} catch (err) {
|
|
614
|
+
console.error(err.message || String(err));
|
|
615
|
+
process.exitCode = 1;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
group
|
|
620
|
+
.command("run")
|
|
621
|
+
.description("Launch an agent group from template")
|
|
622
|
+
.argument("<alias>", "Template alias")
|
|
623
|
+
.option("--instance <name>", "Group instance ID")
|
|
624
|
+
.option("--dry-run", "Validate and compile launch plan without starting agents")
|
|
625
|
+
.option("--json", "Output daemon response as JSON")
|
|
626
|
+
.action(async (alias, opts) => {
|
|
627
|
+
try {
|
|
628
|
+
const projectRoot = process.cwd();
|
|
629
|
+
await ensureDaemonRunning(projectRoot);
|
|
630
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
631
|
+
type: "launch_group",
|
|
632
|
+
alias,
|
|
633
|
+
instance: opts.instance || "",
|
|
634
|
+
dry_run: opts.dryRun === true,
|
|
635
|
+
});
|
|
636
|
+
if (opts.json) {
|
|
637
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const reply = resp?.data?.reply || "Group run requested";
|
|
641
|
+
console.log(reply);
|
|
642
|
+
if (resp?.data?.group?.ok === false) {
|
|
643
|
+
process.exitCode = 1;
|
|
644
|
+
}
|
|
645
|
+
} catch (err) {
|
|
646
|
+
console.error(err.message || String(err));
|
|
647
|
+
process.exitCode = 1;
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
group
|
|
652
|
+
.command("status")
|
|
653
|
+
.description("Show group runtime status (single group or list)")
|
|
654
|
+
.argument("[groupId]", "Group ID (optional)")
|
|
655
|
+
.option("--json", "Output daemon response as JSON")
|
|
656
|
+
.action(async (groupId, opts) => {
|
|
657
|
+
try {
|
|
658
|
+
const projectRoot = process.cwd();
|
|
659
|
+
await ensureDaemonRunning(projectRoot);
|
|
660
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
661
|
+
type: "group_status",
|
|
662
|
+
group_id: groupId || "",
|
|
663
|
+
});
|
|
664
|
+
if (opts.json) {
|
|
665
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const reply = resp?.data?.reply || "Group status requested";
|
|
669
|
+
console.log(reply);
|
|
670
|
+
const group = resp?.data?.group || {};
|
|
671
|
+
if (groupId && group?.group) {
|
|
672
|
+
console.log(JSON.stringify(group.group, null, 2));
|
|
673
|
+
} else if (!groupId && Array.isArray(group?.groups)) {
|
|
674
|
+
group.groups.forEach((item) => {
|
|
675
|
+
console.log(`- ${item.group_id} [${item.status}] ${item.template_alias} active=${item.members_active}/${item.members_total}`);
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
} catch (err) {
|
|
679
|
+
console.error(err.message || String(err));
|
|
680
|
+
process.exitCode = 1;
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
group
|
|
685
|
+
.command("diagram")
|
|
686
|
+
.description("Render group diagram from template alias or group runtime ID")
|
|
687
|
+
.argument("<target>", "Template alias or group ID")
|
|
688
|
+
.option("--ascii", "Render ASCII diagram (default)")
|
|
689
|
+
.option("--mermaid", "Render Mermaid flowchart")
|
|
690
|
+
.option("--json", "Output daemon response as JSON")
|
|
691
|
+
.action(async (target, opts) => {
|
|
692
|
+
try {
|
|
693
|
+
const projectRoot = process.cwd();
|
|
694
|
+
await ensureDaemonRunning(projectRoot);
|
|
695
|
+
const format = opts.mermaid ? "mermaid" : "ascii";
|
|
696
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
697
|
+
type: "group_diagram",
|
|
698
|
+
alias: target,
|
|
699
|
+
group_id: target,
|
|
700
|
+
format,
|
|
701
|
+
});
|
|
702
|
+
if (opts.json) {
|
|
703
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const reply = resp?.data?.reply || "Group diagram requested";
|
|
707
|
+
console.log(reply);
|
|
708
|
+
if (resp?.data?.group?.diagram) {
|
|
709
|
+
console.log(resp.data.group.diagram);
|
|
710
|
+
}
|
|
711
|
+
if (resp?.data?.group?.ok === false) {
|
|
712
|
+
process.exitCode = 1;
|
|
713
|
+
}
|
|
714
|
+
} catch (err) {
|
|
715
|
+
console.error(err.message || String(err));
|
|
716
|
+
process.exitCode = 1;
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
group
|
|
721
|
+
.command("stop")
|
|
722
|
+
.description("Stop a running group instance")
|
|
723
|
+
.argument("<groupId>", "Group ID")
|
|
724
|
+
.option("--json", "Output daemon response as JSON")
|
|
725
|
+
.action(async (groupId, opts) => {
|
|
726
|
+
try {
|
|
727
|
+
const projectRoot = process.cwd();
|
|
728
|
+
await ensureDaemonRunning(projectRoot);
|
|
729
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
730
|
+
type: "stop_group",
|
|
731
|
+
group_id: groupId,
|
|
732
|
+
});
|
|
733
|
+
if (opts.json) {
|
|
734
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const reply = resp?.data?.reply || "Group stop requested";
|
|
738
|
+
console.log(reply);
|
|
739
|
+
if (resp?.data?.group?.ok === false) {
|
|
740
|
+
process.exitCode = 1;
|
|
741
|
+
}
|
|
742
|
+
} catch (err) {
|
|
743
|
+
console.error(err.message || String(err));
|
|
744
|
+
process.exitCode = 1;
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
564
748
|
const online = program.command("online").description("ufoo online helpers");
|
|
565
749
|
online
|
|
566
750
|
.command("server")
|
|
@@ -616,6 +800,7 @@ async function runCli(argv) {
|
|
|
616
800
|
.option("--name <room>", "Room name (optional)")
|
|
617
801
|
.option("--type <type>", "Room type (public|private)")
|
|
618
802
|
.option("--password <pwd>", "Room password (private only)")
|
|
803
|
+
.option("--created-by <name>", "Room creator nickname metadata")
|
|
619
804
|
.action(async (action, opts) => {
|
|
620
805
|
try {
|
|
621
806
|
await runOnlineCommand("room", { action, opts }, {
|
|
@@ -640,6 +825,7 @@ async function runCli(argv) {
|
|
|
640
825
|
.option("--nickname <name>", "Nickname to resolve token")
|
|
641
826
|
.option("--name <name>", "Channel name (unique)")
|
|
642
827
|
.option("--type <type>", "Channel type (world|public)")
|
|
828
|
+
.option("--created-by <name>", "Channel creator nickname metadata")
|
|
643
829
|
.action(async (action, opts) => {
|
|
644
830
|
try {
|
|
645
831
|
await runOnlineCommand("channel", { action, opts }, {
|
|
@@ -934,11 +1120,17 @@ async function runCli(argv) {
|
|
|
934
1120
|
console.log(" ufoo init [--modules <list>] [--project <dir>]");
|
|
935
1121
|
console.log(" ufoo skills list");
|
|
936
1122
|
console.log(" ufoo skills install <name|all> [--target <dir> | --codex | --agents]");
|
|
1123
|
+
console.log(" ufoo group templates [list|ls] [--json]");
|
|
1124
|
+
console.log(" ufoo group template <list|show|validate|new> [target] [--from <builtin>] [--global] [--force] [--json]");
|
|
1125
|
+
console.log(" ufoo group run <alias> [--instance <name>] [--dry-run] [--json]");
|
|
1126
|
+
console.log(" ufoo group status [groupId] [--json]");
|
|
1127
|
+
console.log(" ufoo group diagram <alias|groupId> [--ascii|--mermaid] [--json]");
|
|
1128
|
+
console.log(" ufoo group stop <groupId> [--json]");
|
|
937
1129
|
console.log(" ufoo online server [--port 8787] [--host 127.0.0.1] [--token-file <path>]");
|
|
938
1130
|
console.log(" ufoo online token <subscriber> [--nickname <name>] [--server <url>] [--file <path>]");
|
|
939
|
-
console.log(" ufoo online room create [--name <room>] --type public|private [--password <pwd>] [--server <url>]");
|
|
1131
|
+
console.log(" ufoo online room create [--name <room>] --type public|private [--password <pwd>] [--created-by <name>] [--server <url>]");
|
|
940
1132
|
console.log(" ufoo online room list [--server <url>]");
|
|
941
|
-
console.log(" ufoo online channel create --name <name> [--type world|public] [--server <url>]");
|
|
1133
|
+
console.log(" ufoo online channel create --name <name> [--type world|public] [--created-by <name>] [--server <url>]");
|
|
942
1134
|
console.log(" ufoo online channel list [--server <url>]");
|
|
943
1135
|
console.log(" ufoo online connect --nickname <name> [--join <ch>] [--room <id> --room-password <pwd>] [...]");
|
|
944
1136
|
console.log(" ufoo online send --nickname <name> --text <msg> [--channel <ch>] [--room <id>]");
|
|
@@ -1271,6 +1463,137 @@ async function runCli(argv) {
|
|
|
1271
1463
|
process.exitCode = 1;
|
|
1272
1464
|
return;
|
|
1273
1465
|
}
|
|
1466
|
+
if (cmd === "group") {
|
|
1467
|
+
const sub = String(rest[0] || "").trim().toLowerCase();
|
|
1468
|
+
|
|
1469
|
+
(async () => {
|
|
1470
|
+
try {
|
|
1471
|
+
if (sub === "templates") {
|
|
1472
|
+
const action = String(rest[1] || "list").trim().toLowerCase();
|
|
1473
|
+
if (action !== "list" && action !== "ls") {
|
|
1474
|
+
throw new Error(`Unknown group templates action: ${action}`);
|
|
1475
|
+
}
|
|
1476
|
+
const json = rest.includes("--json");
|
|
1477
|
+
await runGroupCoreCommand("templates", [action], {
|
|
1478
|
+
cwd: process.cwd(),
|
|
1479
|
+
json,
|
|
1480
|
+
});
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if (sub === "template") {
|
|
1485
|
+
const action = String(rest[1] || "list").trim().toLowerCase();
|
|
1486
|
+
const args = [action, ...rest.slice(2)];
|
|
1487
|
+
const json = rest.includes("--json");
|
|
1488
|
+
await runGroupCoreCommand("template", args, {
|
|
1489
|
+
cwd: process.cwd(),
|
|
1490
|
+
json,
|
|
1491
|
+
});
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (sub === "run") {
|
|
1496
|
+
const alias = String(rest[1] || "").trim();
|
|
1497
|
+
if (!alias) throw new Error("group run requires <alias>");
|
|
1498
|
+
const instanceIdx = rest.indexOf("--instance");
|
|
1499
|
+
const instance = instanceIdx !== -1 ? String(rest[instanceIdx + 1] || "").trim() : "";
|
|
1500
|
+
const dryRun = rest.includes("--dry-run");
|
|
1501
|
+
const outputJson = rest.includes("--json");
|
|
1502
|
+
|
|
1503
|
+
const projectRoot = process.cwd();
|
|
1504
|
+
await ensureDaemonRunning(projectRoot);
|
|
1505
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
1506
|
+
type: "launch_group",
|
|
1507
|
+
alias,
|
|
1508
|
+
instance,
|
|
1509
|
+
dry_run: dryRun,
|
|
1510
|
+
});
|
|
1511
|
+
if (outputJson) {
|
|
1512
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
console.log(resp?.data?.reply || "Group run requested");
|
|
1516
|
+
if (resp?.data?.group?.ok === false) process.exitCode = 1;
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
if (sub === "status") {
|
|
1521
|
+
const groupId = rest[1] && !rest[1].startsWith("--") ? rest[1] : "";
|
|
1522
|
+
const outputJson = rest.includes("--json");
|
|
1523
|
+
const projectRoot = process.cwd();
|
|
1524
|
+
await ensureDaemonRunning(projectRoot);
|
|
1525
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
1526
|
+
type: "group_status",
|
|
1527
|
+
group_id: groupId,
|
|
1528
|
+
});
|
|
1529
|
+
if (outputJson) {
|
|
1530
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
console.log(resp?.data?.reply || "Group status requested");
|
|
1534
|
+
const group = resp?.data?.group || {};
|
|
1535
|
+
if (groupId && group?.group) {
|
|
1536
|
+
console.log(JSON.stringify(group.group, null, 2));
|
|
1537
|
+
} else if (!groupId && Array.isArray(group?.groups)) {
|
|
1538
|
+
group.groups.forEach((item) => {
|
|
1539
|
+
console.log(`- ${item.group_id} [${item.status}] ${item.template_alias} active=${item.members_active}/${item.members_total}`);
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
if (sub === "stop") {
|
|
1546
|
+
const groupId = String(rest[1] || "").trim();
|
|
1547
|
+
if (!groupId) throw new Error("group stop requires <groupId>");
|
|
1548
|
+
const outputJson = rest.includes("--json");
|
|
1549
|
+
const projectRoot = process.cwd();
|
|
1550
|
+
await ensureDaemonRunning(projectRoot);
|
|
1551
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
1552
|
+
type: "stop_group",
|
|
1553
|
+
group_id: groupId,
|
|
1554
|
+
});
|
|
1555
|
+
if (outputJson) {
|
|
1556
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
console.log(resp?.data?.reply || "Group stop requested");
|
|
1560
|
+
if (resp?.data?.group?.ok === false) process.exitCode = 1;
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
if (sub === "diagram") {
|
|
1565
|
+
const target = String(rest[1] || "").trim();
|
|
1566
|
+
if (!target) throw new Error("group diagram requires <alias|groupId>");
|
|
1567
|
+
const outputJson = rest.includes("--json");
|
|
1568
|
+
const format = rest.includes("--mermaid") ? "mermaid" : "ascii";
|
|
1569
|
+
const projectRoot = process.cwd();
|
|
1570
|
+
await ensureDaemonRunning(projectRoot);
|
|
1571
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
1572
|
+
type: "group_diagram",
|
|
1573
|
+
alias: target,
|
|
1574
|
+
group_id: target,
|
|
1575
|
+
format,
|
|
1576
|
+
});
|
|
1577
|
+
if (outputJson) {
|
|
1578
|
+
console.log(JSON.stringify(resp?.data || {}, null, 2));
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
console.log(resp?.data?.reply || "Group diagram requested");
|
|
1582
|
+
if (resp?.data?.group?.diagram) {
|
|
1583
|
+
console.log(resp.data.group.diagram);
|
|
1584
|
+
}
|
|
1585
|
+
if (resp?.data?.group?.ok === false) process.exitCode = 1;
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
throw new Error("group requires templates|template|run|status|diagram|stop subcommand");
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
console.error(err.message || String(err));
|
|
1592
|
+
process.exitCode = 1;
|
|
1593
|
+
}
|
|
1594
|
+
})();
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1274
1597
|
if (cmd === "online") {
|
|
1275
1598
|
const sub = rest[0] || "";
|
|
1276
1599
|
if (!sub) {
|