u-foo 2.2.0 → 2.2.2
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/package.json +1 -1
- package/src/bus/store.js +1 -47
- package/src/bus/subscriber.js +26 -0
- package/src/chat/commandExecutor.js +98 -5
- package/src/chat/commands.js +47 -24
- package/src/chat/completionController.js +30 -13
- package/src/config.js +6 -3
- package/src/controller/flags.js +13 -6
- package/src/controller/gateRouter.js +4 -3
- package/src/daemon/index.js +26 -11
package/package.json
CHANGED
package/src/bus/store.js
CHANGED
|
@@ -15,19 +15,6 @@ function readQueueTty(queueDir) {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function nicknamePrefixForType(agentType = "") {
|
|
19
|
-
return agentType === "claude-code" ? "claude"
|
|
20
|
-
: agentType === "ufoo-code" ? "ucode"
|
|
21
|
-
: String(agentType || "agent");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isRecoverableSessionId(sessionId = "") {
|
|
25
|
-
const text = String(sessionId || "").trim();
|
|
26
|
-
if (!text) return false;
|
|
27
|
-
if (text.includes(":") || text.includes("_")) return false;
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
18
|
function buildUsedNicknameSet(agents = {}) {
|
|
32
19
|
const set = new Set();
|
|
33
20
|
for (const meta of Object.values(agents || {})) {
|
|
@@ -38,15 +25,6 @@ function buildUsedNicknameSet(agents = {}) {
|
|
|
38
25
|
return set;
|
|
39
26
|
}
|
|
40
27
|
|
|
41
|
-
function allocateRecoveredNickname(agentType, used) {
|
|
42
|
-
const prefix = nicknamePrefixForType(agentType);
|
|
43
|
-
let idx = 1;
|
|
44
|
-
while (used.has(`${prefix}-${idx}`)) idx += 1;
|
|
45
|
-
const nick = `${prefix}-${idx}`;
|
|
46
|
-
used.add(nick);
|
|
47
|
-
return nick;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
28
|
function recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now) {
|
|
51
29
|
if (!subscriber || data.agents[subscriber]) return false;
|
|
52
30
|
|
|
@@ -67,31 +45,7 @@ function recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now) {
|
|
|
67
45
|
};
|
|
68
46
|
return true;
|
|
69
47
|
}
|
|
70
|
-
|
|
71
|
-
const parts = subscriber.split(":");
|
|
72
|
-
if (parts.length !== 2) return false;
|
|
73
|
-
const [agentType, sessionId] = parts;
|
|
74
|
-
if (!agentType || !sessionId) return false;
|
|
75
|
-
if (!isRecoverableSessionId(sessionId)) return false;
|
|
76
|
-
|
|
77
|
-
const tty = readQueueTty(queueDir);
|
|
78
|
-
const ttyInfo = tty ? getTtyProcessInfo(tty) : null;
|
|
79
|
-
const activeByTty = Boolean(ttyInfo && ttyInfo.alive && ttyInfo.hasAgent);
|
|
80
|
-
const nickname = activeByTty ? allocateRecoveredNickname(agentType, usedNicknames) : "";
|
|
81
|
-
|
|
82
|
-
data.agents[subscriber] = {
|
|
83
|
-
agent_type: agentType,
|
|
84
|
-
nickname,
|
|
85
|
-
status: activeByTty ? "active" : "inactive",
|
|
86
|
-
joined_at: now,
|
|
87
|
-
last_seen: now,
|
|
88
|
-
pid: 0,
|
|
89
|
-
tty,
|
|
90
|
-
tty_shell_pid: ttyInfo && ttyInfo.shellPid ? ttyInfo.shellPid : 0,
|
|
91
|
-
tmux_pane: "",
|
|
92
|
-
launch_mode: "",
|
|
93
|
-
};
|
|
94
|
-
return true;
|
|
48
|
+
return false;
|
|
95
49
|
}
|
|
96
50
|
|
|
97
51
|
function reconcileReservedControllerAliases(data, now) {
|
package/src/bus/subscriber.js
CHANGED
|
@@ -98,6 +98,30 @@ class SubscriberManager {
|
|
|
98
98
|
this.queueManager = queueManager;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
cleanupSubscriberArtifacts(subscriber) {
|
|
102
|
+
if (!subscriber || !this.queueManager) return;
|
|
103
|
+
try {
|
|
104
|
+
const queueDir = this.queueManager.getQueueDir
|
|
105
|
+
? this.queueManager.getQueueDir(subscriber)
|
|
106
|
+
: "";
|
|
107
|
+
if (queueDir) {
|
|
108
|
+
fs.rmSync(queueDir, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// ignore cleanup errors
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const offsetPath = this.queueManager.getOffsetPath
|
|
115
|
+
? this.queueManager.getOffsetPath(subscriber)
|
|
116
|
+
: "";
|
|
117
|
+
if (offsetPath) {
|
|
118
|
+
fs.rmSync(offsetPath, { force: true });
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// ignore cleanup errors
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
async cleanupDuplicateTty(currentSubscriber, ttyPath) {
|
|
102
126
|
if (!ttyPath) return null;
|
|
103
127
|
if (!this.busData.agents) return null;
|
|
@@ -301,6 +325,7 @@ class SubscriberManager {
|
|
|
301
325
|
this.busData.agents[subscriber].status = "inactive";
|
|
302
326
|
this.busData.agents[subscriber].activity_state = "";
|
|
303
327
|
this.busData.agents[subscriber].last_seen = getTimestamp();
|
|
328
|
+
this.cleanupSubscriberArtifacts(subscriber);
|
|
304
329
|
|
|
305
330
|
return true;
|
|
306
331
|
}
|
|
@@ -364,6 +389,7 @@ class SubscriberManager {
|
|
|
364
389
|
meta.status = "inactive";
|
|
365
390
|
meta.activity_state = "";
|
|
366
391
|
meta.last_seen = getTimestamp();
|
|
392
|
+
this.cleanupSubscriberArtifacts(id);
|
|
367
393
|
}
|
|
368
394
|
}
|
|
369
395
|
}
|
|
@@ -3,7 +3,13 @@ const EventBus = require("../bus");
|
|
|
3
3
|
const { IPC_REQUEST_TYPES } = require("../shared/eventContract");
|
|
4
4
|
const UfooInit = require("../init");
|
|
5
5
|
const { runGroupCoreCommand } = require("../cli/groupCoreCommands");
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
loadConfig: loadProjectConfig,
|
|
8
|
+
saveConfig: saveProjectConfig,
|
|
9
|
+
loadGlobalUcodeConfig,
|
|
10
|
+
saveGlobalUcodeConfig,
|
|
11
|
+
normalizeControllerMode,
|
|
12
|
+
} = require("../config");
|
|
7
13
|
const { resolveTransport } = require("../code/nativeRunner");
|
|
8
14
|
const { parseIntervalMs, formatIntervalMs } = require("./cronScheduler");
|
|
9
15
|
const { isGlobalControllerProjectRoot, resolveGlobalControllerUfooDir } = require("../projects");
|
|
@@ -1104,10 +1110,10 @@ function createCommandExecutor(options = {}) {
|
|
|
1104
1110
|
kv.group_id ||
|
|
1105
1111
|
kv.group ||
|
|
1106
1112
|
kv.alias ||
|
|
1107
|
-
(diagramArgs[0] && !String(diagramArgs[0]).includes("=") ? diagramArgs[0] : "")
|
|
1113
|
+
(diagramArgs[0] && !String(diagramArgs[0]).includes("=") ? diagramArgs[0] : "current")
|
|
1108
1114
|
).trim();
|
|
1109
1115
|
if (!target) {
|
|
1110
|
-
logMessage("error", "{white-fg}✗{/white-fg} Usage: /group diagram
|
|
1116
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /group diagram [current|alias|groupId] [format=ascii|mermaid]");
|
|
1111
1117
|
return;
|
|
1112
1118
|
}
|
|
1113
1119
|
const format = diagramArgs.includes("--mermaid")
|
|
@@ -1133,7 +1139,13 @@ function createCommandExecutor(options = {}) {
|
|
|
1133
1139
|
async function handleSettingsCommand(args = []) {
|
|
1134
1140
|
const section = String(args[0] || "").trim().toLowerCase();
|
|
1135
1141
|
if (!section) {
|
|
1136
|
-
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings ucode
|
|
1142
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings <router|ucode> ...");
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (section === "router") {
|
|
1147
|
+
const subArgs = args.slice(1);
|
|
1148
|
+
await handleRouterSettingsCommand(subArgs);
|
|
1137
1149
|
return;
|
|
1138
1150
|
}
|
|
1139
1151
|
|
|
@@ -1147,7 +1159,88 @@ function createCommandExecutor(options = {}) {
|
|
|
1147
1159
|
return;
|
|
1148
1160
|
}
|
|
1149
1161
|
|
|
1150
|
-
logMessage("error", "{white-fg}✗{/white-fg} Unknown settings section. Use: ucode");
|
|
1162
|
+
logMessage("error", "{white-fg}✗{/white-fg} Unknown settings section. Use: router, ucode");
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
async function handleRouterSettingsCommand(args = []) {
|
|
1166
|
+
const first = String(args[0] || "").trim().toLowerCase();
|
|
1167
|
+
const hasInlineKv = args.some((item) => String(item || "").includes("="));
|
|
1168
|
+
const action = !first ? "show" : (hasInlineKv ? "set" : first);
|
|
1169
|
+
|
|
1170
|
+
if (action === "show" || action === "status") {
|
|
1171
|
+
const config = loadConfig(projectRoot) || {};
|
|
1172
|
+
const mode = normalizeControllerMode(config.controllerMode);
|
|
1173
|
+
logMessage("system", "{cyan-fg}router config:{/cyan-fg}");
|
|
1174
|
+
logMessage("system", ` • controllerMode: ${mode}`);
|
|
1175
|
+
logMessage("system", ` • provider: ${String(config.routerProvider || "").trim() || "(unset)"}`);
|
|
1176
|
+
logMessage("system", ` • model: ${String(config.routerModel || "").trim() || "(unset)"}`);
|
|
1177
|
+
logMessage("system", " • allowed modes: main | loop | legacy | shadow");
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
if (action === "set") {
|
|
1182
|
+
const kvArgs = hasInlineKv ? args : args.slice(1);
|
|
1183
|
+
const kv = parseKeyValueArgs(kvArgs);
|
|
1184
|
+
const updates = {};
|
|
1185
|
+
|
|
1186
|
+
if (Object.prototype.hasOwnProperty.call(kv, "mode")) {
|
|
1187
|
+
const nextMode = normalizeControllerMode(kv.mode);
|
|
1188
|
+
if (!["main", "loop", "legacy", "shadow"].includes(nextMode) || nextMode !== String(kv.mode || "").trim().toLowerCase()) {
|
|
1189
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings router set mode=<main|loop|legacy|shadow> provider=<id> model=<id>");
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
updates.controllerMode = nextMode;
|
|
1193
|
+
}
|
|
1194
|
+
if (Object.prototype.hasOwnProperty.call(kv, "provider")) updates.routerProvider = String(kv.provider || "").trim();
|
|
1195
|
+
if (Object.prototype.hasOwnProperty.call(kv, "model")) updates.routerModel = String(kv.model || "").trim();
|
|
1196
|
+
|
|
1197
|
+
if (Object.keys(updates).length === 0) {
|
|
1198
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings router set mode=<main|loop|legacy|shadow> provider=<id> model=<id>");
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
saveConfig(projectRoot, updates);
|
|
1203
|
+
logMessage("system", "{white-fg}✓{/white-fg} router config updated");
|
|
1204
|
+
if (Object.prototype.hasOwnProperty.call(updates, "controllerMode")) {
|
|
1205
|
+
logMessage("system", ` • controllerMode: ${updates.controllerMode}`);
|
|
1206
|
+
}
|
|
1207
|
+
if (Object.prototype.hasOwnProperty.call(updates, "routerProvider")) {
|
|
1208
|
+
logMessage("system", ` • provider: ${updates.routerProvider || "(unset)"}`);
|
|
1209
|
+
}
|
|
1210
|
+
if (Object.prototype.hasOwnProperty.call(updates, "routerModel")) {
|
|
1211
|
+
logMessage("system", ` • model: ${updates.routerModel || "(unset)"}`);
|
|
1212
|
+
}
|
|
1213
|
+
await restartDaemon(projectRoot);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (action === "clear") {
|
|
1218
|
+
const fieldsRaw = args.slice(1).map((item) => String(item || "").trim().toLowerCase()).filter(Boolean);
|
|
1219
|
+
const fields = fieldsRaw.length === 0 ? ["all"] : fieldsRaw;
|
|
1220
|
+
const updates = {};
|
|
1221
|
+
const clearAll = fields.includes("all");
|
|
1222
|
+
if (clearAll || fields.includes("mode")) updates.controllerMode = "main";
|
|
1223
|
+
if (clearAll || fields.includes("provider")) updates.routerProvider = "";
|
|
1224
|
+
if (clearAll || fields.includes("model")) updates.routerModel = "";
|
|
1225
|
+
if (Object.keys(updates).length === 0) {
|
|
1226
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings router clear [mode|provider|model|all]");
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
saveConfig(projectRoot, updates);
|
|
1230
|
+
logMessage("system", "{white-fg}✓{/white-fg} router config cleared");
|
|
1231
|
+
await restartDaemon(projectRoot);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const nextMode = normalizeControllerMode(action);
|
|
1236
|
+
if (nextMode !== action || !["main", "loop", "legacy", "shadow"].includes(nextMode)) {
|
|
1237
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings router <main|loop|legacy|shadow>");
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
saveConfig(projectRoot, { controllerMode: nextMode });
|
|
1242
|
+
logMessage("system", `{white-fg}✓{/white-fg} router mode set to ${nextMode}`);
|
|
1243
|
+
await restartDaemon(projectRoot);
|
|
1151
1244
|
}
|
|
1152
1245
|
|
|
1153
1246
|
function parseUcodeConfigKv(args = []) {
|
package/src/chat/commands.js
CHANGED
|
@@ -38,10 +38,10 @@ const COMMAND_TREE = {
|
|
|
38
38
|
"/group": {
|
|
39
39
|
desc: "Agent group orchestration",
|
|
40
40
|
children: {
|
|
41
|
-
run: { desc: "Launch a group template" },
|
|
41
|
+
run: { desc: "Launch a group template", order: 1 },
|
|
42
|
+
diagram: { desc: "Render group diagram (ascii|mermaid)", order: 2 },
|
|
42
43
|
stop: { desc: "Stop a running group" },
|
|
43
44
|
status: { desc: "Show group runtime status" },
|
|
44
|
-
diagram: { desc: "Render group diagram (ascii|mermaid)" },
|
|
45
45
|
template: { desc: "Template ops (list/show/validate/new)" },
|
|
46
46
|
templates: { desc: "List available templates" },
|
|
47
47
|
},
|
|
@@ -87,7 +87,26 @@ const COMMAND_TREE = {
|
|
|
87
87
|
"/settings": {
|
|
88
88
|
desc: "Settings operations",
|
|
89
89
|
children: {
|
|
90
|
-
|
|
90
|
+
router: {
|
|
91
|
+
desc: "Manage controller routing mode/provider/model",
|
|
92
|
+
children: {
|
|
93
|
+
show: { desc: "Show router mode/provider/model", order: 1 },
|
|
94
|
+
set: { desc: "Set router mode/provider/model", order: 2 },
|
|
95
|
+
clear: { desc: "Clear router provider/model or reset mode", order: 3 },
|
|
96
|
+
main: { desc: "Set router mode to main", order: 4 },
|
|
97
|
+
loop: { desc: "Set router mode to loop", order: 5 },
|
|
98
|
+
legacy: { desc: "Set router mode to legacy", order: 6 },
|
|
99
|
+
shadow: { desc: "Set router mode to shadow", order: 7 },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
ucode: {
|
|
103
|
+
desc: "Manage ucode model provider config",
|
|
104
|
+
children: {
|
|
105
|
+
show: { desc: "Show ucode provider/model/url/key", order: 1 },
|
|
106
|
+
set: { desc: "Set ucode provider/model/url/key", order: 2 },
|
|
107
|
+
clear: { desc: "Clear ucode provider/model/url/key", order: 3 },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
91
110
|
},
|
|
92
111
|
},
|
|
93
112
|
"/skills": {
|
|
@@ -101,7 +120,7 @@ const COMMAND_TREE = {
|
|
|
101
120
|
"/ufoo": { desc: "ufoo protocol (session marker)" },
|
|
102
121
|
};
|
|
103
122
|
|
|
104
|
-
const COMMAND_ORDER = ["/launch", "/bus", "/ctx"];
|
|
123
|
+
const COMMAND_ORDER = ["/launch", "/group", "/bus", "/ctx"];
|
|
105
124
|
const COMMAND_ORDER_MAP = new Map(COMMAND_ORDER.map((cmd, idx) => [cmd, idx]));
|
|
106
125
|
|
|
107
126
|
function sortCommands(a, b) {
|
|
@@ -112,30 +131,34 @@ function sortCommands(a, b) {
|
|
|
112
131
|
}
|
|
113
132
|
|
|
114
133
|
function buildCommandRegistry(tree) {
|
|
134
|
+
function mapNode(node = {}) {
|
|
135
|
+
const entry = {
|
|
136
|
+
desc: node.desc || "",
|
|
137
|
+
order: Number.isFinite(node.order) ? node.order : undefined,
|
|
138
|
+
};
|
|
139
|
+
if (node.children) {
|
|
140
|
+
entry.subcommands = Object.keys(node.children)
|
|
141
|
+
.sort((a, b) => {
|
|
142
|
+
const aNode = node.children[a] || {};
|
|
143
|
+
const bNode = node.children[b] || {};
|
|
144
|
+
const aOrder = Number.isFinite(aNode.order) ? aNode.order : Number.POSITIVE_INFINITY;
|
|
145
|
+
const bOrder = Number.isFinite(bNode.order) ? bNode.order : Number.POSITIVE_INFINITY;
|
|
146
|
+
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
147
|
+
return a.localeCompare(b, "en", { sensitivity: "base" });
|
|
148
|
+
})
|
|
149
|
+
.map((sub) => ({
|
|
150
|
+
cmd: sub,
|
|
151
|
+
...mapNode(node.children[sub]),
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
return entry;
|
|
155
|
+
}
|
|
156
|
+
|
|
115
157
|
return Object.keys(tree)
|
|
116
158
|
.sort(sortCommands)
|
|
117
159
|
.map((cmd) => {
|
|
118
160
|
const node = tree[cmd] || {};
|
|
119
|
-
|
|
120
|
-
if (node.children) {
|
|
121
|
-
entry.subcommands = Object.keys(node.children)
|
|
122
|
-
.sort((a, b) => {
|
|
123
|
-
const aNode = node.children[a] || {};
|
|
124
|
-
const bNode = node.children[b] || {};
|
|
125
|
-
const aOrder = Number.isFinite(aNode.order) ? aNode.order : Number.POSITIVE_INFINITY;
|
|
126
|
-
const bOrder = Number.isFinite(bNode.order) ? bNode.order : Number.POSITIVE_INFINITY;
|
|
127
|
-
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
128
|
-
return a.localeCompare(b, "en", { sensitivity: "base" });
|
|
129
|
-
})
|
|
130
|
-
.map((sub) => ({
|
|
131
|
-
cmd: sub,
|
|
132
|
-
desc: (node.children[sub] && node.children[sub].desc) || "",
|
|
133
|
-
order: Number.isFinite(node.children[sub] && node.children[sub].order)
|
|
134
|
-
? node.children[sub].order
|
|
135
|
-
: undefined,
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
return entry;
|
|
161
|
+
return { cmd, ...mapNode(node) };
|
|
139
162
|
});
|
|
140
163
|
}
|
|
141
164
|
|
|
@@ -11,6 +11,19 @@ function sortSubcommandEntries(a, b) {
|
|
|
11
11
|
return String(a && a.cmd ? a.cmd : "").localeCompare(String(b && b.cmd ? b.cmd : ""), "en", { sensitivity: "base" });
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function mapSubcommandSuggestions(subs = [], parentCmd, tokenIndex, filterText = "") {
|
|
15
|
+
const normalizedFilter = String(filterText || "").toLowerCase();
|
|
16
|
+
return subs
|
|
17
|
+
.filter((sub) => String(sub && sub.cmd ? sub.cmd : "").toLowerCase().startsWith(normalizedFilter))
|
|
18
|
+
.map((sub) => ({
|
|
19
|
+
...sub,
|
|
20
|
+
isSubcommand: true,
|
|
21
|
+
parentCmd,
|
|
22
|
+
tokenIndex,
|
|
23
|
+
}))
|
|
24
|
+
.sort(sortSubcommandEntries);
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
function createCompletionController(options = {}) {
|
|
15
28
|
const {
|
|
16
29
|
input,
|
|
@@ -217,14 +230,14 @@ function createCompletionController(options = {}) {
|
|
|
217
230
|
subs = Array.from(merged.values());
|
|
218
231
|
}
|
|
219
232
|
if (isLaunch) {
|
|
220
|
-
return subs
|
|
221
|
-
|
|
222
|
-
|
|
233
|
+
return mapSubcommandSuggestions(subs, mainCmd, 1, "");
|
|
234
|
+
}
|
|
235
|
+
const selectedSub = subs.find((sub) => String(sub && sub.cmd ? sub.cmd : "").toLowerCase() === subFilter.toLowerCase());
|
|
236
|
+
if (selectedSub && selectedSub.subcommands && (parts.length > 2 || endsWithSpace)) {
|
|
237
|
+
const nestedFilter = parts[2] || "";
|
|
238
|
+
return mapSubcommandSuggestions(selectedSub.subcommands, mainCmd, 2, nestedFilter);
|
|
223
239
|
}
|
|
224
|
-
return subs
|
|
225
|
-
.filter((sub) => sub.cmd.toLowerCase().startsWith(subFilter.toLowerCase()))
|
|
226
|
-
.map((sub) => ({ ...sub, isSubcommand: true, parentCmd: mainCmd }))
|
|
227
|
-
.sort(sortSubcommandEntries);
|
|
240
|
+
return mapSubcommandSuggestions(subs, mainCmd, 1, subFilter);
|
|
228
241
|
}
|
|
229
242
|
return [];
|
|
230
243
|
}
|
|
@@ -312,8 +325,10 @@ function createCompletionController(options = {}) {
|
|
|
312
325
|
|
|
313
326
|
if (selected.isSubcommand) {
|
|
314
327
|
const parts = trimmed.split(/\s+/);
|
|
315
|
-
const
|
|
316
|
-
|
|
328
|
+
const tokenIndex = Number.isInteger(selected.tokenIndex) ? selected.tokenIndex : 1;
|
|
329
|
+
while (parts.length <= tokenIndex) parts.push("");
|
|
330
|
+
parts[tokenIndex] = selected.cmd;
|
|
331
|
+
const completedCore = parts.slice(0, tokenIndex + 1).join(" ").trim();
|
|
317
332
|
const isComplete = trimmed === completedCore || trimmed.startsWith(`${completedCore} `);
|
|
318
333
|
return { text: `${completedCore} `, isComplete };
|
|
319
334
|
}
|
|
@@ -353,8 +368,10 @@ function createCompletionController(options = {}) {
|
|
|
353
368
|
input.value = `@${mentionTarget} `;
|
|
354
369
|
} else if (selected.isSubcommand) {
|
|
355
370
|
const parts = input.value.split(/\s+/);
|
|
356
|
-
|
|
357
|
-
|
|
371
|
+
const tokenIndex = Number.isInteger(selected.tokenIndex) ? selected.tokenIndex : 1;
|
|
372
|
+
while (parts.length <= tokenIndex) parts.push("");
|
|
373
|
+
parts[tokenIndex] = selected.cmd;
|
|
374
|
+
input.value = `${parts.slice(0, tokenIndex + 1).join(" ")} `;
|
|
358
375
|
} else if (selected.isArgumentSuggestion) {
|
|
359
376
|
const prefix = String(selected.argumentPrefix || "").trim();
|
|
360
377
|
input.value = prefix ? `${prefix} ${selected.cmd} ` : `${selected.cmd} `;
|
|
@@ -369,7 +386,7 @@ function createCompletionController(options = {}) {
|
|
|
369
386
|
}
|
|
370
387
|
updateDraftFromInput();
|
|
371
388
|
|
|
372
|
-
if (
|
|
389
|
+
if (selected.subcommands && selected.subcommands.length > 0) {
|
|
373
390
|
show(input.value);
|
|
374
391
|
} else if (
|
|
375
392
|
selected.isSubcommand
|
|
@@ -417,7 +434,7 @@ function createCompletionController(options = {}) {
|
|
|
417
434
|
const nextPreview = preview(selected);
|
|
418
435
|
if (!nextPreview.isComplete) {
|
|
419
436
|
applyPreview(nextPreview);
|
|
420
|
-
if (
|
|
437
|
+
if (selected.subcommands && selected.subcommands.length > 0) {
|
|
421
438
|
show(input.value);
|
|
422
439
|
} else if (
|
|
423
440
|
selected.isSubcommand
|
package/src/config.js
CHANGED
|
@@ -7,7 +7,7 @@ const UCODE_FIELDS = ["ucodeProvider", "ucodeModel", "ucodeBaseUrl", "ucodeApiKe
|
|
|
7
7
|
const DEFAULT_CONFIG = {
|
|
8
8
|
launchMode: "auto",
|
|
9
9
|
agentProvider: "codex-cli",
|
|
10
|
-
controllerMode: "
|
|
10
|
+
controllerMode: "main",
|
|
11
11
|
codexInternalThreadMode: "legacy",
|
|
12
12
|
codexAuthPath: "",
|
|
13
13
|
claudeOauthProfile: "",
|
|
@@ -42,7 +42,8 @@ function normalizeAgentProvider(value) {
|
|
|
42
42
|
function normalizeControllerMode(value) {
|
|
43
43
|
const raw = String(value || "").trim().toLowerCase();
|
|
44
44
|
if (raw === "shadow") return "shadow";
|
|
45
|
-
if (raw === "router-api") return "
|
|
45
|
+
if (raw === "router-api") return "main";
|
|
46
|
+
if (raw === "main") return "main";
|
|
46
47
|
if (raw === "loop") return "loop";
|
|
47
48
|
return "legacy";
|
|
48
49
|
}
|
|
@@ -95,7 +96,9 @@ function loadConfig(projectRoot) {
|
|
|
95
96
|
...raw,
|
|
96
97
|
launchMode: normalizeLaunchMode(raw.launchMode),
|
|
97
98
|
agentProvider: normalizeAgentProvider(raw.agentProvider),
|
|
98
|
-
controllerMode:
|
|
99
|
+
controllerMode: Object.prototype.hasOwnProperty.call(raw, "controllerMode")
|
|
100
|
+
? normalizeControllerMode(raw.controllerMode)
|
|
101
|
+
: DEFAULT_CONFIG.controllerMode,
|
|
99
102
|
codexInternalThreadMode: normalizeCodexInternalThreadMode(raw.codexInternalThreadMode),
|
|
100
103
|
codexAuthPath: normalizeCodexAuthPath(raw.codexAuthPath),
|
|
101
104
|
claudeOauthProfile: normalizeClaudeOauthProfile(raw.claudeOauthProfile),
|
package/src/controller/flags.js
CHANGED
|
@@ -5,7 +5,7 @@ const { loadConfig, normalizeControllerMode } = require("../config");
|
|
|
5
5
|
const CONTROLLER_MODES = Object.freeze({
|
|
6
6
|
LEGACY: "legacy",
|
|
7
7
|
SHADOW: "shadow",
|
|
8
|
-
|
|
8
|
+
MAIN: "main",
|
|
9
9
|
LOOP: "loop",
|
|
10
10
|
});
|
|
11
11
|
|
|
@@ -15,7 +15,11 @@ const appliedControllerModes = new Map();
|
|
|
15
15
|
const appliedControllerModeHistory = new Map();
|
|
16
16
|
|
|
17
17
|
function readProcessControllerMode(env = process.env) {
|
|
18
|
-
|
|
18
|
+
const normalized = normalizeControllerMode(env.UFOO_CONTROLLER_MODE);
|
|
19
|
+
if (normalized !== CONTROLLER_MODES.LEGACY || String(env.UFOO_CONTROLLER_MODE || "").trim().toLowerCase() === CONTROLLER_MODES.LEGACY) {
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
return CONTROLLER_MODES.MAIN;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
function resolveControllerMode({
|
|
@@ -37,7 +41,10 @@ function resolveControllerMode({
|
|
|
37
41
|
: null;
|
|
38
42
|
if (loadedConfig) {
|
|
39
43
|
const projectMode = normalizeControllerMode(loadedConfig.controllerMode);
|
|
40
|
-
|
|
44
|
+
if (projectMode !== CONTROLLER_MODES.LEGACY || String(loadedConfig.controllerMode || "").trim().toLowerCase() === CONTROLLER_MODES.LEGACY) {
|
|
45
|
+
return projectMode;
|
|
46
|
+
}
|
|
47
|
+
return CONTROLLER_MODES.MAIN;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
return readProcessControllerMode(env);
|
|
@@ -62,7 +69,7 @@ function pushModeHistory(key, previousMode, normalizedMode, messageId) {
|
|
|
62
69
|
|
|
63
70
|
function applyControllerModeForMessage({
|
|
64
71
|
projectRoot,
|
|
65
|
-
nextMode = CONTROLLER_MODES.
|
|
72
|
+
nextMode = CONTROLLER_MODES.MAIN,
|
|
66
73
|
messageId = "",
|
|
67
74
|
} = {}) {
|
|
68
75
|
const key = getControllerModeStateKey(projectRoot);
|
|
@@ -96,7 +103,7 @@ function rollbackControllerModeForMessage({
|
|
|
96
103
|
const key = getControllerModeStateKey(projectRoot);
|
|
97
104
|
const list = appliedControllerModeHistory.get(key) || [];
|
|
98
105
|
const lastTransition = list.length > 0 ? list[list.length - 1] : null;
|
|
99
|
-
const currentMode = appliedControllerModes.get(key) || CONTROLLER_MODES.
|
|
106
|
+
const currentMode = appliedControllerModes.get(key) || CONTROLLER_MODES.MAIN;
|
|
100
107
|
|
|
101
108
|
if (!lastTransition) {
|
|
102
109
|
return {
|
|
@@ -135,7 +142,7 @@ function rollbackControllerModeForMessage({
|
|
|
135
142
|
|
|
136
143
|
function getAppliedControllerMode(projectRoot) {
|
|
137
144
|
const key = getControllerModeStateKey(projectRoot);
|
|
138
|
-
return appliedControllerModes.get(key) || CONTROLLER_MODES.
|
|
145
|
+
return appliedControllerModes.get(key) || CONTROLLER_MODES.MAIN;
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
function getControllerModeHistoryForTests(projectRoot) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { loadConfig } = require("../config");
|
|
4
4
|
|
|
5
|
-
const DEFAULT_EXECUTION_PATH = "
|
|
5
|
+
const DEFAULT_EXECUTION_PATH = "main";
|
|
6
6
|
const DEFAULT_CONFIDENCE_THRESHOLD = 0.6;
|
|
7
7
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
8
8
|
|
|
@@ -55,7 +55,8 @@ const NON_ROUTING_PATTERNS = [
|
|
|
55
55
|
|
|
56
56
|
function normalizeExecutionPath(value = "") {
|
|
57
57
|
const text = String(value || "").trim().toLowerCase();
|
|
58
|
-
if (text === "router-api") return "
|
|
58
|
+
if (text === "router-api") return "main";
|
|
59
|
+
if (text === "main") return "main";
|
|
59
60
|
if (text === "shadow") return "shadow";
|
|
60
61
|
if (text === "loop") return "loop";
|
|
61
62
|
return DEFAULT_EXECUTION_PATH;
|
|
@@ -110,7 +111,7 @@ function shouldUseGateRouter({
|
|
|
110
111
|
return {
|
|
111
112
|
executionPath,
|
|
112
113
|
intent,
|
|
113
|
-
enabled: executionPath === "
|
|
114
|
+
enabled: executionPath === "main" || executionPath === "loop",
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
117
|
|
package/src/daemon/index.js
CHANGED
|
@@ -1770,19 +1770,34 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
|
|
|
1770
1770
|
);
|
|
1771
1771
|
return;
|
|
1772
1772
|
}
|
|
1773
|
-
|
|
1774
|
-
if (!target) {
|
|
1775
|
-
socket.write(
|
|
1776
|
-
`${JSON.stringify({
|
|
1777
|
-
type: IPC_RESPONSE_TYPES.ERROR,
|
|
1778
|
-
error: "group diagram requires alias|group_id",
|
|
1779
|
-
})}
|
|
1780
|
-
`,
|
|
1781
|
-
);
|
|
1782
|
-
return;
|
|
1783
|
-
}
|
|
1773
|
+
let target = req.group_id || req.groupId || req.instance || req.alias || req.target || "";
|
|
1784
1774
|
const format = normalizeFormat(req.format || (req.mermaid ? "mermaid" : "ascii"));
|
|
1785
1775
|
try {
|
|
1776
|
+
if (!target || target === "current") {
|
|
1777
|
+
const latest = daemonGroupOrchestrator.getStatus({});
|
|
1778
|
+
if (latest && latest.ok && Array.isArray(latest.groups) && latest.groups.length > 0) {
|
|
1779
|
+
target = latest.groups[0].group_id || "";
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
if (!target) {
|
|
1783
|
+
socket.write(
|
|
1784
|
+
`${JSON.stringify({
|
|
1785
|
+
type: IPC_RESPONSE_TYPES.RESPONSE,
|
|
1786
|
+
data: {
|
|
1787
|
+
reply: "Group diagram failed: no running groups and no template alias provided",
|
|
1788
|
+
group: {
|
|
1789
|
+
ok: false,
|
|
1790
|
+
mode: "runtime",
|
|
1791
|
+
target: "",
|
|
1792
|
+
format,
|
|
1793
|
+
error: "no running groups",
|
|
1794
|
+
},
|
|
1795
|
+
},
|
|
1796
|
+
})}
|
|
1797
|
+
`,
|
|
1798
|
+
);
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1786
1801
|
const runtimeState = daemonGroupOrchestrator.getStatus({ group_id: target });
|
|
1787
1802
|
if (runtimeState && runtimeState.ok === false && runtimeState.error === "invalid group_id") {
|
|
1788
1803
|
socket.write(
|