u-foo 2.2.3 → 2.3.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/SKILLS/ufoo/SKILL.md +56 -12
- package/SKILLS/uinit/SKILL.md +3 -2
- package/modules/AGENTS.template.md +2 -1
- package/modules/bus/README.md +1 -1
- package/modules/context/SKILLS/uctx/SKILL.md +6 -4
- package/package.json +1 -1
- package/src/agent/codexThreadProvider.js +2 -2
- package/src/agent/controllerToolExecutor.js +24 -1
- package/src/agent/credentials/claude.js +85 -16
- package/src/agent/credentials/codex.js +251 -23
- package/src/agent/defaultBootstrap.js +3 -1
- package/src/agent/directAuthStatus.js +264 -0
- package/src/agent/internalRunner.js +18 -12
- package/src/agent/loopObservability.js +10 -0
- package/src/agent/loopRuntime.js +19 -0
- package/src/agent/ufooAgent.js +43 -13
- package/src/agent/upstreamTransport.js +23 -8
- package/src/bus/index.js +13 -5
- package/src/bus/message.js +157 -9
- package/src/bus/nickname.js +14 -3
- package/src/bus/subscriber.js +30 -10
- package/src/chat/agentDirectory.js +2 -2
- package/src/chat/commandExecutor.js +190 -8
- package/src/chat/commands.js +23 -4
- package/src/chat/completionController.js +30 -7
- package/src/chat/daemonMessageRouter.js +2 -1
- package/src/chat/index.js +9 -8
- package/src/cli/groupCoreCommands.js +5 -0
- package/src/cli.js +309 -0
- package/src/code/UCODE_PROMPT.md +3 -2
- package/src/code/nativeRunner.js +2 -1
- package/src/code/prompts/ufoo.js +3 -2
- package/src/config.js +16 -3
- package/src/context/doctor.js +1 -1
- package/src/daemon/groupOrchestrator.js +13 -9
- package/src/daemon/index.js +35 -18
- package/src/daemon/nicknameScope.js +37 -0
- package/src/daemon/ops.js +22 -10
- package/src/daemon/promptRequest.js +11 -2
- package/src/daemon/reporting.js +2 -3
- package/src/daemon/soloBootstrap.js +15 -3
- package/src/daemon/status.js +5 -1
- package/src/group/bootstrap.js +1 -1
- package/src/group/promptProfiles.js +106 -22
- package/src/group/templates.js +1 -0
- package/src/init/index.js +4 -0
- package/src/memory/historySearch.js +308 -0
- package/src/memory/index.js +653 -8
- package/src/providerapi/redactor.js +4 -1
- package/src/status/index.js +26 -2
- package/src/tools/handlers/memory.js +168 -0
- package/src/tools/index.js +12 -0
- package/src/tools/registry.js +12 -0
- package/src/tools/schemaFixtures.js +213 -0
- package/src/tools/tier1/editMemory.js +14 -0
- package/src/tools/tier1/forget.js +14 -0
- package/src/tools/tier1/recall.js +14 -0
- package/src/tools/tier1/remember.js +14 -0
- package/src/tools/tier1/searchHistory.js +14 -0
- package/src/tools/tier1/searchMemory.js +14 -0
- package/templates/groups/build-lane.json +44 -6
- package/templates/groups/build-ultra.json +6 -5
- package/templates/groups/design-system.json +84 -0
- package/templates/groups/product-discovery.json +9 -4
- package/templates/groups/ui-plan-review.json +84 -0
- package/templates/groups/ui-polish.json +6 -2
- package/templates/groups/verify-ship.json +9 -4
package/src/bus/subscriber.js
CHANGED
|
@@ -175,20 +175,29 @@ class SubscriberManager {
|
|
|
175
175
|
// 检查是否是重新加入(rejoin)
|
|
176
176
|
const existingMeta = this.busData.agents[subscriber];
|
|
177
177
|
let finalNickname = nickname;
|
|
178
|
+
let finalScopedNickname = typeof options.scopedNickname === "string"
|
|
179
|
+
? options.scopedNickname.trim()
|
|
180
|
+
: (typeof process.env.UFOO_SCOPED_NICKNAME === "string" ? process.env.UFOO_SCOPED_NICKNAME.trim() : "");
|
|
178
181
|
|
|
179
|
-
if (
|
|
180
|
-
// 重新加入,保留原昵称
|
|
181
|
-
finalNickname = existingMeta.nickname;
|
|
182
|
-
} else if (nickname) {
|
|
182
|
+
if (nickname) {
|
|
183
183
|
// 新昵称,检查冲突
|
|
184
|
-
|
|
184
|
+
const conflictTarget = finalScopedNickname || nickname;
|
|
185
|
+
if (nicknameManager.nicknameExists(conflictTarget, subscriber)) {
|
|
185
186
|
throw new Error(`Nickname "${nickname}" already exists`);
|
|
186
187
|
}
|
|
187
188
|
finalNickname = nickname;
|
|
189
|
+
} else if (existingMeta && existingMeta.nickname) {
|
|
190
|
+
// 重新加入,保留原昵称
|
|
191
|
+
finalNickname = existingMeta.nickname;
|
|
192
|
+
finalScopedNickname = existingMeta.scoped_nickname || finalScopedNickname || finalNickname;
|
|
188
193
|
} else {
|
|
189
194
|
// 自动生成昵称(并标记占用,避免并发重复)
|
|
190
195
|
finalNickname = nicknameManager.generateAutoNickname(agentType);
|
|
191
|
-
nicknameManager.setNickname(subscriber, finalNickname);
|
|
196
|
+
nicknameManager.setNickname(subscriber, finalNickname, finalScopedNickname);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!finalScopedNickname) {
|
|
200
|
+
finalScopedNickname = existingMeta?.scoped_nickname || finalNickname;
|
|
192
201
|
}
|
|
193
202
|
|
|
194
203
|
const explicitLaunchMode = typeof options.launchMode === "string"
|
|
@@ -221,6 +230,7 @@ class SubscriberManager {
|
|
|
221
230
|
const inheritedNickname = await this.cleanupDuplicateTty(subscriber, finalTty);
|
|
222
231
|
if (inheritedNickname && !nickname && !existingMeta) {
|
|
223
232
|
finalNickname = inheritedNickname;
|
|
233
|
+
if (!finalScopedNickname) finalScopedNickname = inheritedNickname;
|
|
224
234
|
}
|
|
225
235
|
|
|
226
236
|
// 更新订阅者信息(保留已有字段,如 provider_session_*)
|
|
@@ -252,6 +262,7 @@ class SubscriberManager {
|
|
|
252
262
|
...preserved,
|
|
253
263
|
agent_type: agentType,
|
|
254
264
|
nickname: finalNickname,
|
|
265
|
+
scoped_nickname: finalScopedNickname || finalNickname,
|
|
255
266
|
status: "active",
|
|
256
267
|
activity_state: "starting",
|
|
257
268
|
activity_since: getTimestamp(),
|
|
@@ -311,7 +322,11 @@ class SubscriberManager {
|
|
|
311
322
|
// 创建队列目录
|
|
312
323
|
this.queueManager.ensureQueueDir(subscriber);
|
|
313
324
|
|
|
314
|
-
return {
|
|
325
|
+
return {
|
|
326
|
+
subscriber,
|
|
327
|
+
nickname: finalNickname,
|
|
328
|
+
scopedNickname: this.busData.agents[subscriber].scoped_nickname || finalNickname,
|
|
329
|
+
};
|
|
315
330
|
}
|
|
316
331
|
|
|
317
332
|
/**
|
|
@@ -333,22 +348,27 @@ class SubscriberManager {
|
|
|
333
348
|
/**
|
|
334
349
|
* 重命名订阅者
|
|
335
350
|
*/
|
|
336
|
-
async rename(subscriber, newNickname) {
|
|
351
|
+
async rename(subscriber, newNickname, options = {}) {
|
|
337
352
|
if (!this.busData.agents || !this.busData.agents[subscriber]) {
|
|
338
353
|
throw new Error(`Subscriber "${subscriber}" not found`);
|
|
339
354
|
}
|
|
340
355
|
|
|
341
356
|
const nicknameManager = new NicknameManager(this.busData);
|
|
357
|
+
const scopedNickname = typeof options.scopedNickname === "string" && options.scopedNickname.trim()
|
|
358
|
+
? options.scopedNickname.trim()
|
|
359
|
+
: newNickname;
|
|
342
360
|
|
|
343
361
|
// 检查昵称冲突
|
|
344
|
-
if (nicknameManager.nicknameExists(
|
|
362
|
+
if (nicknameManager.nicknameExists(scopedNickname, subscriber)) {
|
|
345
363
|
throw new Error(`Nickname "${newNickname}" already exists`);
|
|
346
364
|
}
|
|
347
365
|
|
|
348
366
|
const oldNickname = this.busData.agents[subscriber].nickname;
|
|
367
|
+
const oldScopedNickname = this.busData.agents[subscriber].scoped_nickname || oldNickname;
|
|
349
368
|
this.busData.agents[subscriber].nickname = newNickname;
|
|
369
|
+
this.busData.agents[subscriber].scoped_nickname = scopedNickname;
|
|
350
370
|
|
|
351
|
-
return { subscriber, oldNickname, newNickname };
|
|
371
|
+
return { subscriber, oldNickname, newNickname, oldScopedNickname, newScopedNickname: scopedNickname };
|
|
352
372
|
}
|
|
353
373
|
|
|
354
374
|
/**
|
|
@@ -10,8 +10,8 @@ function buildAgentMaps(activeAgents = [], metaList = [], fallbackMap = null) {
|
|
|
10
10
|
|
|
11
11
|
for (const id of activeAgents) {
|
|
12
12
|
const meta = metaById.get(id);
|
|
13
|
-
const label = meta && meta.nickname
|
|
14
|
-
? meta.nickname
|
|
13
|
+
const label = meta && (meta.display_nickname || meta.nickname)
|
|
14
|
+
? (meta.display_nickname || meta.nickname)
|
|
15
15
|
: (fallbackMap && fallbackMap.get(id)) || id;
|
|
16
16
|
labelMap.set(id, label);
|
|
17
17
|
if (meta) {
|
|
@@ -11,10 +11,15 @@ const {
|
|
|
11
11
|
normalizeControllerMode,
|
|
12
12
|
} = require("../config");
|
|
13
13
|
const { resolveTransport } = require("../code/nativeRunner");
|
|
14
|
+
const { resolveDisplayNickname } = require("../daemon/nicknameScope");
|
|
14
15
|
const { parseIntervalMs, formatIntervalMs } = require("./cronScheduler");
|
|
15
16
|
const { isGlobalControllerProjectRoot, resolveGlobalControllerUfooDir } = require("../projects");
|
|
16
17
|
const { loadPromptProfileRegistry } = require("../group/promptProfiles");
|
|
17
18
|
const { resolveSoloAgentType } = require("../solo/commands");
|
|
19
|
+
const {
|
|
20
|
+
inspectDirectAuthStatus,
|
|
21
|
+
formatDirectAuthStatus,
|
|
22
|
+
} = require("../agent/directAuthStatus");
|
|
18
23
|
|
|
19
24
|
function defaultCreateDoctor(projectRoot) {
|
|
20
25
|
const UfooDoctor = require("../doctor");
|
|
@@ -38,6 +43,41 @@ function defaultResolveTerminalApp() {
|
|
|
38
43
|
return "";
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
const SETTINGS_MODEL_DEFAULTS = Object.freeze({
|
|
47
|
+
agent: Object.freeze({
|
|
48
|
+
codex: "gpt-5.5",
|
|
49
|
+
claude: "opus-4.7",
|
|
50
|
+
}),
|
|
51
|
+
router: Object.freeze({
|
|
52
|
+
codex: "gpt-5.4-mini",
|
|
53
|
+
claude: "sonnet-4.7",
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function normalizeSettingsProvider(value = "", fallback = "codex-cli") {
|
|
58
|
+
const text = String(value || "").trim().toLowerCase();
|
|
59
|
+
if (text === "claude" || text === "claude-cli" || text === "claude-code" || text === "anthropic") {
|
|
60
|
+
return "claude-cli";
|
|
61
|
+
}
|
|
62
|
+
if (text === "codex" || text === "codex-cli" || text === "codex-code" || text === "openai") {
|
|
63
|
+
return "codex-cli";
|
|
64
|
+
}
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function agentProviderKey(value = "") {
|
|
69
|
+
return normalizeSettingsProvider(value) === "claude-cli" ? "claude" : "codex";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function defaultAgentModelForProvider(value = "") {
|
|
73
|
+
return SETTINGS_MODEL_DEFAULTS.agent[agentProviderKey(value)] || SETTINGS_MODEL_DEFAULTS.agent.codex;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function defaultGateModelForProvider(value = "") {
|
|
77
|
+
const key = agentProviderKey(value);
|
|
78
|
+
return SETTINGS_MODEL_DEFAULTS.router[key] || SETTINGS_MODEL_DEFAULTS.router.codex;
|
|
79
|
+
}
|
|
80
|
+
|
|
41
81
|
function collectHostLaunchRequestContext(env = process.env) {
|
|
42
82
|
const hostInjectSock = String(env.UFOO_HOST_INJECT_SOCK || env.HORIZON_INJECT_SOCK || "").trim();
|
|
43
83
|
const hostDaemonSock = String(env.UFOO_HOST_DAEMON_SOCK || "").trim();
|
|
@@ -101,6 +141,8 @@ function createCommandExecutor(options = {}) {
|
|
|
101
141
|
listCronTasks = () => [],
|
|
102
142
|
stopCronTask = () => false,
|
|
103
143
|
runGroupCore = runGroupCoreCommand,
|
|
144
|
+
inspectDirectAuth = inspectDirectAuthStatus,
|
|
145
|
+
formatDirectAuth = formatDirectAuthStatus,
|
|
104
146
|
requestCron = null,
|
|
105
147
|
globalMode = false,
|
|
106
148
|
listProjects = () => [],
|
|
@@ -163,6 +205,14 @@ function createCommandExecutor(options = {}) {
|
|
|
163
205
|
} else {
|
|
164
206
|
logMessage("system", "{white-fg}✗{/white-fg} Daemon is not running");
|
|
165
207
|
}
|
|
208
|
+
|
|
209
|
+
const authStatus = await inspectDirectAuth({
|
|
210
|
+
projectRoot: getActiveProjectRoot(),
|
|
211
|
+
autoRefresh: false,
|
|
212
|
+
});
|
|
213
|
+
for (const line of formatDirectAuth(authStatus, { compact: true })) {
|
|
214
|
+
logMessage("system", escapeBlessed(line));
|
|
215
|
+
}
|
|
166
216
|
}
|
|
167
217
|
|
|
168
218
|
async function handleDaemonCommand(args = []) {
|
|
@@ -319,7 +369,8 @@ function createCommandExecutor(options = {}) {
|
|
|
319
369
|
} else {
|
|
320
370
|
logMessage("system", "{cyan-fg}Active agents:{/cyan-fg}");
|
|
321
371
|
for (const [id, meta] of subscribers) {
|
|
322
|
-
const
|
|
372
|
+
const displayNickname = meta ? resolveDisplayNickname(projectRoot, meta) : "";
|
|
373
|
+
const nickname = displayNickname ? ` (${displayNickname})` : "";
|
|
323
374
|
const status = meta && meta.status ? meta.status : "unknown";
|
|
324
375
|
logMessage("system", ` • ${id}${nickname} {white-fg}[${status}]{/white-fg}`);
|
|
325
376
|
}
|
|
@@ -1138,8 +1189,14 @@ function createCommandExecutor(options = {}) {
|
|
|
1138
1189
|
|
|
1139
1190
|
async function handleSettingsCommand(args = []) {
|
|
1140
1191
|
const section = String(args[0] || "").trim().toLowerCase();
|
|
1141
|
-
if (!section) {
|
|
1142
|
-
|
|
1192
|
+
if (!section || section === "show" || section === "status") {
|
|
1193
|
+
await handleSettingsOverviewCommand();
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (section === "agent" || section === "ufoo") {
|
|
1198
|
+
const subArgs = args.slice(1);
|
|
1199
|
+
await handleAgentSettingsCommand(subArgs);
|
|
1143
1200
|
return;
|
|
1144
1201
|
}
|
|
1145
1202
|
|
|
@@ -1159,7 +1216,116 @@ function createCommandExecutor(options = {}) {
|
|
|
1159
1216
|
return;
|
|
1160
1217
|
}
|
|
1161
1218
|
|
|
1162
|
-
logMessage("error", "{white-fg}✗{/white-fg} Unknown settings section. Use: router, ucode");
|
|
1219
|
+
logMessage("error", "{white-fg}✗{/white-fg} Unknown settings section. Use: show, agent, router, ucode");
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
async function handleSettingsOverviewCommand() {
|
|
1223
|
+
const config = loadConfig(projectRoot) || {};
|
|
1224
|
+
const agentProvider = normalizeSettingsProvider(config.agentProvider);
|
|
1225
|
+
const agentKey = agentProviderKey(agentProvider);
|
|
1226
|
+
const agentModel = String(config.agentModel || "").trim();
|
|
1227
|
+
const routerMode = normalizeControllerMode(config.controllerMode);
|
|
1228
|
+
const routerProvider = String(config.routerProvider || "").trim();
|
|
1229
|
+
const routerModel = String(config.routerModel || "").trim();
|
|
1230
|
+
const ucodeConfig = loadUcodeConfig() || {};
|
|
1231
|
+
const ucodeProvider = String(ucodeConfig.ucodeProvider || "").trim();
|
|
1232
|
+
const ucodeModel = String(ucodeConfig.ucodeModel || "").trim();
|
|
1233
|
+
|
|
1234
|
+
logMessage("system", "{cyan-fg}settings:{/cyan-fg}");
|
|
1235
|
+
logMessage("system", ` • agent: ${agentKey} · model ${agentModel || `(unset, recommended ${defaultAgentModelForProvider(agentProvider)})`}`);
|
|
1236
|
+
logMessage("system", ` • router: mode ${routerMode} · provider ${routerProvider || "(unset)"} · model ${routerModel || "(unset)"}`);
|
|
1237
|
+
logMessage("system", ` • ucode: provider ${ucodeProvider || "(unset)"} · model ${ucodeModel || "(unset)"}`);
|
|
1238
|
+
logMessage("system", " • use: /settings agent | /settings router | /settings ucode");
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
async function handleAgentSettingsCommand(args = []) {
|
|
1242
|
+
const first = String(args[0] || "").trim().toLowerCase();
|
|
1243
|
+
const hasInlineKv = args.some((item) => String(item || "").includes("="));
|
|
1244
|
+
const action = !first ? "show" : (hasInlineKv ? "set" : first);
|
|
1245
|
+
|
|
1246
|
+
if (action === "show" || action === "status") {
|
|
1247
|
+
const config = loadConfig(projectRoot) || {};
|
|
1248
|
+
const provider = normalizeSettingsProvider(config.agentProvider);
|
|
1249
|
+
const key = agentProviderKey(provider);
|
|
1250
|
+
const model = String(config.agentModel || "").trim();
|
|
1251
|
+
logMessage("system", "{cyan-fg}ufoo-agent config:{/cyan-fg}");
|
|
1252
|
+
logMessage("system", ` • provider: ${key}`);
|
|
1253
|
+
logMessage("system", ` • model: ${model || `(unset, recommended ${defaultAgentModelForProvider(provider)})`}`);
|
|
1254
|
+
logMessage("system", ` • defaults: codex=${SETTINGS_MODEL_DEFAULTS.agent.codex}, claude=${SETTINGS_MODEL_DEFAULTS.agent.claude}`);
|
|
1255
|
+
logMessage("system", " • use: /settings agent set provider=<codex|claude> model=<id>");
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (action === "codex" || action === "claude") {
|
|
1260
|
+
const kv = parseKeyValueArgs(args.slice(1));
|
|
1261
|
+
const provider = action === "claude" ? "claude-cli" : "codex-cli";
|
|
1262
|
+
const model = String(kv.model || defaultAgentModelForProvider(provider)).trim();
|
|
1263
|
+
saveConfig(projectRoot, {
|
|
1264
|
+
agentProvider: provider,
|
|
1265
|
+
agentModel: model,
|
|
1266
|
+
});
|
|
1267
|
+
logMessage("system", "{white-fg}✓{/white-fg} ufoo-agent config updated");
|
|
1268
|
+
logMessage("system", ` • provider: ${agentProviderKey(provider)}`);
|
|
1269
|
+
logMessage("system", ` • model: ${model}`);
|
|
1270
|
+
await restartDaemon(projectRoot);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (action === "set") {
|
|
1275
|
+
const kvArgs = hasInlineKv ? args : args.slice(1);
|
|
1276
|
+
const kv = parseKeyValueArgs(kvArgs);
|
|
1277
|
+
const updates = {};
|
|
1278
|
+
let nextProvider = "";
|
|
1279
|
+
|
|
1280
|
+
if (Object.prototype.hasOwnProperty.call(kv, "provider")) {
|
|
1281
|
+
nextProvider = normalizeSettingsProvider(kv.provider, "");
|
|
1282
|
+
if (!nextProvider) {
|
|
1283
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings agent set provider=<codex|claude> model=<id>");
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
updates.agentProvider = nextProvider;
|
|
1287
|
+
}
|
|
1288
|
+
if (Object.prototype.hasOwnProperty.call(kv, "model")) {
|
|
1289
|
+
updates.agentModel = String(kv.model || "").trim();
|
|
1290
|
+
} else if (nextProvider) {
|
|
1291
|
+
updates.agentModel = defaultAgentModelForProvider(nextProvider);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
if (Object.keys(updates).length === 0) {
|
|
1295
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings agent set provider=<codex|claude> model=<id>");
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
saveConfig(projectRoot, updates);
|
|
1300
|
+
logMessage("system", "{white-fg}✓{/white-fg} ufoo-agent config updated");
|
|
1301
|
+
if (Object.prototype.hasOwnProperty.call(updates, "agentProvider")) {
|
|
1302
|
+
logMessage("system", ` • provider: ${agentProviderKey(updates.agentProvider)}`);
|
|
1303
|
+
}
|
|
1304
|
+
if (Object.prototype.hasOwnProperty.call(updates, "agentModel")) {
|
|
1305
|
+
logMessage("system", ` • model: ${updates.agentModel || "(unset)"}`);
|
|
1306
|
+
}
|
|
1307
|
+
await restartDaemon(projectRoot);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (action === "clear") {
|
|
1312
|
+
const fieldsRaw = args.slice(1).map((item) => String(item || "").trim().toLowerCase()).filter(Boolean);
|
|
1313
|
+
const fields = fieldsRaw.length === 0 ? ["model"] : fieldsRaw;
|
|
1314
|
+
const updates = {};
|
|
1315
|
+
const clearAll = fields.includes("all");
|
|
1316
|
+
if (clearAll || fields.includes("provider")) updates.agentProvider = "codex-cli";
|
|
1317
|
+
if (clearAll || fields.includes("model")) updates.agentModel = "";
|
|
1318
|
+
if (Object.keys(updates).length === 0) {
|
|
1319
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings agent clear [provider|model|all]");
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
saveConfig(projectRoot, updates);
|
|
1323
|
+
logMessage("system", "{white-fg}✓{/white-fg} ufoo-agent config cleared");
|
|
1324
|
+
await restartDaemon(projectRoot);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
logMessage("error", "{white-fg}✗{/white-fg} Unknown settings agent action. Use: show, set, clear, codex, claude");
|
|
1163
1329
|
}
|
|
1164
1330
|
|
|
1165
1331
|
async function handleRouterSettingsCommand(args = []) {
|
|
@@ -1170,11 +1336,27 @@ function createCommandExecutor(options = {}) {
|
|
|
1170
1336
|
if (action === "show" || action === "status") {
|
|
1171
1337
|
const config = loadConfig(projectRoot) || {};
|
|
1172
1338
|
const mode = normalizeControllerMode(config.controllerMode);
|
|
1173
|
-
logMessage("system", "{cyan-fg}router config:{/cyan-fg}");
|
|
1339
|
+
logMessage("system", "{cyan-fg}gate router config:{/cyan-fg}");
|
|
1174
1340
|
logMessage("system", ` • controllerMode: ${mode}`);
|
|
1175
1341
|
logMessage("system", ` • provider: ${String(config.routerProvider || "").trim() || "(unset)"}`);
|
|
1176
1342
|
logMessage("system", ` • model: ${String(config.routerModel || "").trim() || "(unset)"}`);
|
|
1177
1343
|
logMessage("system", " • allowed modes: main | loop | legacy | shadow");
|
|
1344
|
+
logMessage("system", ` • defaults: codex=${SETTINGS_MODEL_DEFAULTS.router.codex}, claude=${SETTINGS_MODEL_DEFAULTS.router.claude}`);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (action === "codex" || action === "claude") {
|
|
1349
|
+
const kv = parseKeyValueArgs(args.slice(1));
|
|
1350
|
+
const provider = action;
|
|
1351
|
+
const model = String(kv.model || defaultGateModelForProvider(provider)).trim();
|
|
1352
|
+
saveConfig(projectRoot, {
|
|
1353
|
+
routerProvider: provider,
|
|
1354
|
+
routerModel: model,
|
|
1355
|
+
});
|
|
1356
|
+
logMessage("system", "{white-fg}✓{/white-fg} gate router config updated");
|
|
1357
|
+
logMessage("system", ` • provider: ${provider}`);
|
|
1358
|
+
logMessage("system", ` • model: ${model}`);
|
|
1359
|
+
await restartDaemon(projectRoot);
|
|
1178
1360
|
return;
|
|
1179
1361
|
}
|
|
1180
1362
|
|
|
@@ -1200,7 +1382,7 @@ function createCommandExecutor(options = {}) {
|
|
|
1200
1382
|
}
|
|
1201
1383
|
|
|
1202
1384
|
saveConfig(projectRoot, updates);
|
|
1203
|
-
logMessage("system", "{white-fg}✓{/white-fg} router config updated");
|
|
1385
|
+
logMessage("system", "{white-fg}✓{/white-fg} gate router config updated");
|
|
1204
1386
|
if (Object.prototype.hasOwnProperty.call(updates, "controllerMode")) {
|
|
1205
1387
|
logMessage("system", ` • controllerMode: ${updates.controllerMode}`);
|
|
1206
1388
|
}
|
|
@@ -1227,7 +1409,7 @@ function createCommandExecutor(options = {}) {
|
|
|
1227
1409
|
return;
|
|
1228
1410
|
}
|
|
1229
1411
|
saveConfig(projectRoot, updates);
|
|
1230
|
-
logMessage("system", "{white-fg}✓{/white-fg} router config cleared");
|
|
1412
|
+
logMessage("system", "{white-fg}✓{/white-fg} gate router config cleared");
|
|
1231
1413
|
await restartDaemon(projectRoot);
|
|
1232
1414
|
return;
|
|
1233
1415
|
}
|
|
@@ -1239,7 +1421,7 @@ function createCommandExecutor(options = {}) {
|
|
|
1239
1421
|
}
|
|
1240
1422
|
|
|
1241
1423
|
saveConfig(projectRoot, { controllerMode: nextMode });
|
|
1242
|
-
logMessage("system", `{white-fg}✓{/white-fg} router mode set to ${nextMode}`);
|
|
1424
|
+
logMessage("system", `{white-fg}✓{/white-fg} gate router mode set to ${nextMode}`);
|
|
1243
1425
|
await restartDaemon(projectRoot);
|
|
1244
1426
|
}
|
|
1245
1427
|
|
package/src/chat/commands.js
CHANGED
|
@@ -87,20 +87,39 @@ const COMMAND_TREE = {
|
|
|
87
87
|
"/settings": {
|
|
88
88
|
desc: "Settings operations",
|
|
89
89
|
children: {
|
|
90
|
+
show: {
|
|
91
|
+
desc: "Show settings overview",
|
|
92
|
+
order: 1,
|
|
93
|
+
},
|
|
94
|
+
agent: {
|
|
95
|
+
desc: "Manage main ufoo-agent/router provider/model",
|
|
96
|
+
order: 2,
|
|
97
|
+
children: {
|
|
98
|
+
show: { desc: "Show main agent provider/model", order: 1 },
|
|
99
|
+
set: { desc: "Set provider=<codex|claude> model=<id>", order: 2 },
|
|
100
|
+
clear: { desc: "Clear agent model or reset provider", order: 3 },
|
|
101
|
+
codex: { desc: "Use Codex default model (gpt-5.5)", order: 4 },
|
|
102
|
+
claude: { desc: "Use Claude default model (opus-4.7)", order: 5 },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
90
105
|
router: {
|
|
91
|
-
desc: "Manage
|
|
106
|
+
desc: "Manage gate router mode/provider/model",
|
|
107
|
+
order: 3,
|
|
92
108
|
children: {
|
|
93
|
-
show: { desc: "Show router mode/provider/model", order: 1 },
|
|
94
|
-
set: { desc: "Set
|
|
95
|
-
clear: { desc: "Clear router provider/model or reset mode", order: 3 },
|
|
109
|
+
show: { desc: "Show gate router mode/provider/model", order: 1 },
|
|
110
|
+
set: { desc: "Set mode/provider/model", order: 2 },
|
|
111
|
+
clear: { desc: "Clear gate router provider/model or reset mode", order: 3 },
|
|
96
112
|
main: { desc: "Set router mode to main", order: 4 },
|
|
97
113
|
loop: { desc: "Set router mode to loop", order: 5 },
|
|
98
114
|
legacy: { desc: "Set router mode to legacy", order: 6 },
|
|
99
115
|
shadow: { desc: "Set router mode to shadow", order: 7 },
|
|
116
|
+
codex: { desc: "Use Codex gate model (gpt-5.4-mini)", order: 8 },
|
|
117
|
+
claude: { desc: "Use Claude gate model (sonnet-4.7)", order: 9 },
|
|
100
118
|
},
|
|
101
119
|
},
|
|
102
120
|
ucode: {
|
|
103
121
|
desc: "Manage ucode model provider config",
|
|
122
|
+
order: 4,
|
|
104
123
|
children: {
|
|
105
124
|
show: { desc: "Show ucode provider/model/url/key", order: 1 },
|
|
106
125
|
set: { desc: "Set ucode provider/model/url/key", order: 2 },
|
|
@@ -24,6 +24,34 @@ function mapSubcommandSuggestions(subs = [], parentCmd, tokenIndex, filterText =
|
|
|
24
24
|
.sort(sortSubcommandEntries);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function buildNestedSubcommandSuggestions(subs = [], mainCmd, parts = [], endsWithSpace = false) {
|
|
28
|
+
let currentSubs = Array.isArray(subs) ? subs : [];
|
|
29
|
+
if (currentSubs.length === 0) return [];
|
|
30
|
+
|
|
31
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
32
|
+
const token = String(parts[index] || "");
|
|
33
|
+
const exact = currentSubs.find((sub) =>
|
|
34
|
+
String(sub && sub.cmd ? sub.cmd : "").toLowerCase() === token.toLowerCase()
|
|
35
|
+
);
|
|
36
|
+
const isLastToken = index === parts.length - 1;
|
|
37
|
+
|
|
38
|
+
if (exact && Array.isArray(exact.subcommands) && exact.subcommands.length > 0) {
|
|
39
|
+
if (!isLastToken || endsWithSpace) {
|
|
40
|
+
currentSubs = exact.subcommands;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (exact && isLastToken && endsWithSpace) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return mapSubcommandSuggestions(currentSubs, mainCmd, index, token);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return mapSubcommandSuggestions(currentSubs, mainCmd, parts.length, "");
|
|
53
|
+
}
|
|
54
|
+
|
|
27
55
|
function createCompletionController(options = {}) {
|
|
28
56
|
const {
|
|
29
57
|
input,
|
|
@@ -230,14 +258,9 @@ function createCompletionController(options = {}) {
|
|
|
230
258
|
subs = Array.from(merged.values());
|
|
231
259
|
}
|
|
232
260
|
if (isLaunch) {
|
|
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);
|
|
261
|
+
return mapSubcommandSuggestions(subs, mainCmd, 1, subFilter);
|
|
239
262
|
}
|
|
240
|
-
return
|
|
263
|
+
return buildNestedSubcommandSuggestions(subs, mainCmd, parts, endsWithSpace);
|
|
241
264
|
}
|
|
242
265
|
return [];
|
|
243
266
|
}
|
|
@@ -229,7 +229,8 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
229
229
|
if (recoverableList.length > 0) {
|
|
230
230
|
logMessage("system", "{cyan-fg}Recoverable agents:{/cyan-fg}");
|
|
231
231
|
recoverableList.forEach((item) => {
|
|
232
|
-
const
|
|
232
|
+
const displayNickname = item.display_nickname || item.nickname || "";
|
|
233
|
+
const nickname = displayNickname ? ` (${displayNickname})` : "";
|
|
233
234
|
const meta = item.launchMode ? ` [${item.agent}/${item.launchMode}]` : ` [${item.agent}]`;
|
|
234
235
|
logMessage("system", ` • ${escapeBlessed(`${item.id}${nickname}${meta}`)}`);
|
|
235
236
|
});
|
package/src/chat/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const UfooInit = require("../init");
|
|
|
15
15
|
const AgentActivator = require("../bus/activate");
|
|
16
16
|
const { subscriberToSafeName } = require("../bus/utils");
|
|
17
17
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
18
|
+
const { resolveDisplayNickname } = require("../daemon/nicknameScope");
|
|
18
19
|
const { startDaemon, stopDaemon, connectWithRetry } = require("./transport");
|
|
19
20
|
const { escapeBlessed, stripBlessedTags, truncateText } = require("./text");
|
|
20
21
|
const { COMMAND_REGISTRY, parseCommand, parseAtTarget } = require("./commands");
|
|
@@ -562,6 +563,7 @@ async function runChat(projectRoot, options = {}) {
|
|
|
562
563
|
return registry.templates.map((item) => ({
|
|
563
564
|
alias: item.alias,
|
|
564
565
|
name: item.templateName || item.templateId || "",
|
|
566
|
+
desc: item.templateDescription || "",
|
|
565
567
|
source: item.source || "",
|
|
566
568
|
}));
|
|
567
569
|
},
|
|
@@ -915,7 +917,7 @@ async function runChat(projectRoot, options = {}) {
|
|
|
915
917
|
const busPath = getUfooPaths(activeProjectRoot).agentsFile;
|
|
916
918
|
const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
|
|
917
919
|
for (const [id, meta] of Object.entries(bus.agents || {})) {
|
|
918
|
-
if (meta && meta.nickname === nickname) return id;
|
|
920
|
+
if (meta && (meta.nickname === nickname || meta.scoped_nickname === nickname)) return id;
|
|
919
921
|
}
|
|
920
922
|
} catch {
|
|
921
923
|
// ignore lookup errors
|
|
@@ -934,7 +936,7 @@ async function runChat(projectRoot, options = {}) {
|
|
|
934
936
|
const busPath = getUfooPaths(activeProjectRoot).agentsFile;
|
|
935
937
|
const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
|
|
936
938
|
const meta = bus.agents && bus.agents[id];
|
|
937
|
-
if (meta
|
|
939
|
+
if (meta) return resolveDisplayNickname(activeProjectRoot, meta);
|
|
938
940
|
} catch {
|
|
939
941
|
// Keep original publisher ID
|
|
940
942
|
}
|
|
@@ -1275,7 +1277,9 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1275
1277
|
const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
|
|
1276
1278
|
fallbackMap = new Map();
|
|
1277
1279
|
for (const [id, meta] of Object.entries(bus.agents || {})) {
|
|
1278
|
-
if (meta
|
|
1280
|
+
if (!meta) continue;
|
|
1281
|
+
const displayNickname = resolveDisplayNickname(activeProjectRoot, meta);
|
|
1282
|
+
if (displayNickname) fallbackMap.set(id, displayNickname);
|
|
1279
1283
|
}
|
|
1280
1284
|
} catch {
|
|
1281
1285
|
fallbackMap = null;
|
|
@@ -2100,12 +2104,9 @@ async function runChat(projectRoot, options = {}) {
|
|
|
2100
2104
|
if (runtimeWatchDebounce) return;
|
|
2101
2105
|
runtimeWatchDebounce = setTimeout(() => {
|
|
2102
2106
|
runtimeWatchDebounce = null;
|
|
2103
|
-
const prevCount = projectRuntimes.length;
|
|
2104
2107
|
refreshProjectRuntimes();
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
screen.render();
|
|
2108
|
-
}
|
|
2108
|
+
renderDashboard();
|
|
2109
|
+
screen.render();
|
|
2109
2110
|
}, 300);
|
|
2110
2111
|
});
|
|
2111
2112
|
screen.on("destroy", () => watcher.close());
|
|
@@ -80,6 +80,9 @@ function printList({ templates, errors }, { write, json, cwd }) {
|
|
|
80
80
|
const displayPath = formatDisplayPath(item.filePath, cwd);
|
|
81
81
|
write(`- ${item.alias} [${item.source}]`);
|
|
82
82
|
write(` name: ${nameLabel}`);
|
|
83
|
+
if (item.templateDescription) {
|
|
84
|
+
write(` desc: ${item.templateDescription}`);
|
|
85
|
+
}
|
|
83
86
|
write(` id: ${idLabel} schema: ${verLabel}`);
|
|
84
87
|
write(` file: ${displayPath}`);
|
|
85
88
|
}
|
|
@@ -170,6 +173,7 @@ async function runGroupCoreCommand(subcmd, cmdArgs = [], options = {}) {
|
|
|
170
173
|
filePath: item.filePath,
|
|
171
174
|
templateId: item.templateId || "",
|
|
172
175
|
templateName: item.templateName || "",
|
|
176
|
+
templateDescription: item.templateDescription || "",
|
|
173
177
|
schemaVersion: item.schemaVersion,
|
|
174
178
|
}));
|
|
175
179
|
printList({ templates, errors: registry.errors }, { write, json, cwd });
|
|
@@ -190,6 +194,7 @@ async function runGroupCoreCommand(subcmd, cmdArgs = [], options = {}) {
|
|
|
190
194
|
filePath: item.filePath,
|
|
191
195
|
templateId: item.templateId || "",
|
|
192
196
|
templateName: item.templateName || "",
|
|
197
|
+
templateDescription: item.templateDescription || "",
|
|
193
198
|
schemaVersion: item.schemaVersion,
|
|
194
199
|
}));
|
|
195
200
|
printList({ templates, errors: registry.errors }, { write, json, cwd });
|