u-foo 1.7.4 → 1.8.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/README.md +9 -1
- package/README.zh-CN.md +9 -1
- package/bin/ufoo.js +4 -2
- package/package.json +1 -1
- package/src/agent/cliRunner.js +3 -2
- package/src/agent/ucodeBootstrap.js +5 -3
- package/src/agent/ufooAgent.js +185 -6
- package/src/assistant/constants.js +1 -1
- package/src/assistant/engine.js +1 -6
- package/src/chat/commandExecutor.js +116 -19
- package/src/chat/commands.js +8 -1
- package/src/chat/completionController.js +40 -0
- package/src/chat/cronScheduler.js +37 -6
- package/src/chat/daemonMessageRouter.js +23 -3
- package/src/chat/dashboardKeyController.js +48 -59
- package/src/chat/dashboardView.js +31 -39
- package/src/chat/index.js +154 -77
- package/src/chat/inputListenerController.js +14 -0
- package/src/chat/inputSubmitHandler.js +9 -5
- package/src/chat/settingsController.js +0 -28
- package/src/chat/transientAgentState.js +64 -0
- package/src/cli/groupCoreCommands.js +21 -12
- package/src/cli.js +23 -1
- package/src/daemon/cronOps.js +48 -11
- package/src/daemon/groupOrchestrator.js +581 -97
- package/src/daemon/index.js +420 -5
- package/src/daemon/ops.js +25 -7
- package/src/daemon/promptLoop.js +16 -0
- package/src/daemon/promptRequest.js +126 -2
- package/src/daemon/reporting.js +18 -0
- package/src/daemon/soloBootstrap.js +435 -0
- package/src/daemon/status.js +7 -1
- package/src/globalMode.js +33 -0
- package/src/group/bootstrap.js +157 -0
- package/src/group/promptProfiles.js +646 -0
- package/src/group/templateValidation.js +99 -0
- package/src/group/validateTemplate.js +36 -5
- package/src/init/index.js +13 -7
- package/src/report/store.js +6 -0
- package/src/shared/eventContract.js +1 -0
- package/templates/groups/{dev-basic.json → build-lane.json} +38 -34
- package/templates/groups/product-discovery.json +79 -0
- package/templates/groups/ui-polish.json +87 -0
- package/templates/groups/verify-ship.json +79 -0
- package/templates/groups/research-quick.json +0 -49
|
@@ -6,7 +6,7 @@ const {
|
|
|
6
6
|
resolveTemplateReference,
|
|
7
7
|
createTemplateFromBuiltin,
|
|
8
8
|
} = require("../group/templates");
|
|
9
|
-
const {
|
|
9
|
+
const { validateTemplateTarget } = require("../group/templateValidation");
|
|
10
10
|
|
|
11
11
|
function parseTemplateNewArgs(args = []) {
|
|
12
12
|
const alias = String(args[0] || "").trim();
|
|
@@ -97,15 +97,13 @@ function printList({ templates, errors }, { write, json, cwd }) {
|
|
|
97
97
|
function formatResolveErrors(errors = []) {
|
|
98
98
|
if (!Array.isArray(errors) || errors.length === 0) return "";
|
|
99
99
|
return errors
|
|
100
|
-
.map((item) => `${item.filePath}: ${item.error}`)
|
|
100
|
+
.map((item) => `${item.filePath}: ${item.error || item.message || "unknown error"}`)
|
|
101
101
|
.join("; ");
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function throwResolveFailure(target, resolved = {}) {
|
|
105
105
|
const details = formatResolveErrors(resolved.errors || []);
|
|
106
|
-
if (details) {
|
|
107
|
-
throw new Error(`Failed to load template "${target}": ${details}`);
|
|
108
|
-
}
|
|
106
|
+
if (details) throw new Error(`Failed to load template "${target}": ${details}`);
|
|
109
107
|
throw new Error(`Template not found: ${target}`);
|
|
110
108
|
}
|
|
111
109
|
|
|
@@ -120,6 +118,7 @@ function printValidation(result, target, entry, { write, json }) {
|
|
|
120
118
|
filePath: entry.filePath,
|
|
121
119
|
ok: result.ok,
|
|
122
120
|
errors: result.errors,
|
|
121
|
+
prompt_profiles: result.promptProfiles || [],
|
|
123
122
|
},
|
|
124
123
|
null,
|
|
125
124
|
2
|
|
@@ -130,6 +129,15 @@ function printValidation(result, target, entry, { write, json }) {
|
|
|
130
129
|
|
|
131
130
|
if (result.ok) {
|
|
132
131
|
write(`✓ Template "${entry.alias}" is valid (${entry.source})`);
|
|
132
|
+
if (Array.isArray(result.promptProfiles) && result.promptProfiles.length > 0) {
|
|
133
|
+
for (const profile of result.promptProfiles) {
|
|
134
|
+
write(
|
|
135
|
+
` - ${profile.nickname || profile.agent_id || "agent"}: `
|
|
136
|
+
+ `${profile.requested_profile} -> ${profile.resolved_profile} `
|
|
137
|
+
+ `[${profile.profile_source}]`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
133
141
|
return;
|
|
134
142
|
}
|
|
135
143
|
|
|
@@ -144,6 +152,7 @@ async function runGroupCoreCommand(subcmd, cmdArgs = [], options = {}) {
|
|
|
144
152
|
const write = typeof options.write === "function" ? options.write : console.log;
|
|
145
153
|
const json = Boolean(options.json);
|
|
146
154
|
const templatesOptions = options.templatesOptions || {};
|
|
155
|
+
const promptProfilesOptions = options.promptProfilesOptions || {};
|
|
147
156
|
|
|
148
157
|
const args = Array.isArray(cmdArgs) ? cmdArgs.filter((item) => item !== undefined) : [];
|
|
149
158
|
const normalizedSubcmd = String(subcmd || "").trim().toLowerCase();
|
|
@@ -205,18 +214,18 @@ async function runGroupCoreCommand(subcmd, cmdArgs = [], options = {}) {
|
|
|
205
214
|
if (action === "validate") {
|
|
206
215
|
const target = String(args[1] || "").trim();
|
|
207
216
|
if (!target) throw new Error("group template validate requires <alias|path>");
|
|
208
|
-
const
|
|
217
|
+
const result = validateTemplateTarget(cwd, target, {
|
|
209
218
|
allowPath: true,
|
|
210
219
|
cwd,
|
|
211
|
-
|
|
220
|
+
templatesOptions,
|
|
221
|
+
promptProfilesOptions,
|
|
212
222
|
});
|
|
213
|
-
if (!
|
|
214
|
-
throwResolveFailure(target,
|
|
223
|
+
if (!result.entry) {
|
|
224
|
+
throwResolveFailure(target, { errors: result.errors || [] });
|
|
215
225
|
}
|
|
216
|
-
|
|
217
|
-
printValidation(result, target, resolved.entry, { write, json });
|
|
226
|
+
printValidation(result, target, result.entry, { write, json });
|
|
218
227
|
if (!result.ok) {
|
|
219
|
-
throw new Error(`Template validation failed: ${
|
|
228
|
+
throw new Error(`Template validation failed: ${result.entry.alias}`);
|
|
220
229
|
}
|
|
221
230
|
return;
|
|
222
231
|
}
|
package/src/cli.js
CHANGED
|
@@ -411,7 +411,8 @@ async function runCli(argv) {
|
|
|
411
411
|
.description("Launch an agent (ucode, uclaude, ucodex)")
|
|
412
412
|
.argument("<agent>", "Agent type: ucode|uclaude|ucodex|claude|codex")
|
|
413
413
|
.argument("[nickname]", "Optional nickname for the agent")
|
|
414
|
-
.
|
|
414
|
+
.option("--profile <id>", "Prompt profile to assign after launch")
|
|
415
|
+
.action(async (agent, nickname, opts) => {
|
|
415
416
|
try {
|
|
416
417
|
const projectRoot = process.cwd();
|
|
417
418
|
await ensureDaemonRunning(projectRoot);
|
|
@@ -436,6 +437,7 @@ async function runCli(argv) {
|
|
|
436
437
|
type: "launch_agent",
|
|
437
438
|
agent: normalizedAgent,
|
|
438
439
|
nickname: nickname || "",
|
|
440
|
+
prompt_profile: opts.profile || "",
|
|
439
441
|
count: 1,
|
|
440
442
|
...collectHostLaunchRequestContext(),
|
|
441
443
|
});
|
|
@@ -446,6 +448,26 @@ async function runCli(argv) {
|
|
|
446
448
|
process.exitCode = 1;
|
|
447
449
|
}
|
|
448
450
|
});
|
|
451
|
+
program
|
|
452
|
+
.command("role")
|
|
453
|
+
.description("Assign a preset role to an existing agent")
|
|
454
|
+
.argument("<target>", "Agent subscriber id or nickname")
|
|
455
|
+
.argument("<profile>", "Prompt profile id or alias")
|
|
456
|
+
.action(async (target, profile) => {
|
|
457
|
+
try {
|
|
458
|
+
const projectRoot = process.cwd();
|
|
459
|
+
await ensureDaemonRunning(projectRoot);
|
|
460
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
461
|
+
type: "assign_role",
|
|
462
|
+
target,
|
|
463
|
+
prompt_profile: profile,
|
|
464
|
+
});
|
|
465
|
+
console.log(resp?.data?.reply || `Assigned role ${profile}`);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
console.error(err.message || String(err));
|
|
468
|
+
process.exitCode = 1;
|
|
469
|
+
}
|
|
470
|
+
});
|
|
449
471
|
program
|
|
450
472
|
.command("resume")
|
|
451
473
|
.description("Resume agent sessions (optional nickname)")
|
package/src/daemon/cronOps.js
CHANGED
|
@@ -128,18 +128,41 @@ function sanitizeSummaryText(value = "") {
|
|
|
128
128
|
.trim();
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
function
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
function truncateCronText(value = "", maxLength = 24) {
|
|
132
|
+
const text = String(value || "").trim();
|
|
133
|
+
if (!text) return "";
|
|
134
|
+
if (text.length <= maxLength) return text;
|
|
135
|
+
return `${text.slice(0, Math.max(1, maxLength - 3))}...`;
|
|
136
|
+
}
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
function normalizeCronTitle(value = "", prompt = "") {
|
|
139
|
+
const explicitTitle = truncateCronText(
|
|
140
|
+
sanitizeSummaryText(value || "").replace(/:/g, "-"),
|
|
141
|
+
24
|
|
142
|
+
);
|
|
143
|
+
if (explicitTitle) return explicitTitle;
|
|
144
|
+
const fallbackTitle = truncateCronText(
|
|
145
|
+
sanitizeSummaryText(prompt || "").replace(/:/g, "-"),
|
|
146
|
+
24
|
|
147
|
+
);
|
|
148
|
+
return fallbackTitle || "(empty)";
|
|
149
|
+
}
|
|
140
150
|
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
function buildCronLabel(task = {}) {
|
|
152
|
+
const targets = Array.isArray(task.targets) && task.targets.length > 0
|
|
153
|
+
? task.targets.join("+")
|
|
154
|
+
: "unknown";
|
|
155
|
+
const title = normalizeCronTitle(task.title || "", task.prompt || "");
|
|
156
|
+
const schedule = Number(task.onceAtMs) > 0
|
|
157
|
+
? formatCronAtMs(task.onceAtMs)
|
|
158
|
+
: formatIntervalMs(task.intervalMs || 0);
|
|
159
|
+
return `${targets}:${title}:${schedule || "0s"}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function summarizeCronTask(task = {}) {
|
|
163
|
+
const id = String(task.id || "");
|
|
164
|
+
const label = buildCronLabel(task);
|
|
165
|
+
return id ? `${id} ${label}` : label;
|
|
143
166
|
}
|
|
144
167
|
|
|
145
168
|
function formatCronTask(task = {}) {
|
|
@@ -153,6 +176,8 @@ function formatCronTask(task = {}) {
|
|
|
153
176
|
onceAt: onceAtMs > 0 ? formatCronAtMs(onceAtMs) : "",
|
|
154
177
|
targets: Array.isArray(task.targets) ? task.targets.slice() : [],
|
|
155
178
|
prompt: String(task.prompt || ""),
|
|
179
|
+
title: normalizeCronTitle(task.title || "", task.prompt || ""),
|
|
180
|
+
label: buildCronLabel(task),
|
|
156
181
|
createdAt: Number(task.createdAt) || 0,
|
|
157
182
|
lastRunAt: Number(task.lastRunAt) || 0,
|
|
158
183
|
tickCount: Number(task.tickCount) || 0,
|
|
@@ -160,6 +185,10 @@ function formatCronTask(task = {}) {
|
|
|
160
185
|
};
|
|
161
186
|
}
|
|
162
187
|
|
|
188
|
+
function resolveCronTitle(op = {}) {
|
|
189
|
+
return String(op.title || op.name || op.label || "").trim();
|
|
190
|
+
}
|
|
191
|
+
|
|
163
192
|
function createDaemonCronController(options = {}) {
|
|
164
193
|
const {
|
|
165
194
|
projectRoot = "",
|
|
@@ -198,6 +227,7 @@ function createDaemonCronController(options = {}) {
|
|
|
198
227
|
onceAtMs: task.onceAtMs,
|
|
199
228
|
targets: task.targets.slice(),
|
|
200
229
|
prompt: task.prompt,
|
|
230
|
+
title: task.title,
|
|
201
231
|
createdAt: task.createdAt,
|
|
202
232
|
lastRunAt: task.lastRunAt,
|
|
203
233
|
tickCount: task.tickCount,
|
|
@@ -275,13 +305,14 @@ function createDaemonCronController(options = {}) {
|
|
|
275
305
|
}, task.intervalMs);
|
|
276
306
|
}
|
|
277
307
|
|
|
278
|
-
function addTask({ intervalMs = 0, onceAtMs = 0, targets = [], prompt = "" } = {}) {
|
|
308
|
+
function addTask({ intervalMs = 0, onceAtMs = 0, targets = [], prompt = "", title = "" } = {}) {
|
|
279
309
|
const safeInterval = Number.parseInt(intervalMs, 10);
|
|
280
310
|
const safeOnceAt = Number.parseInt(onceAtMs, 10);
|
|
281
311
|
const safeTargets = Array.isArray(targets)
|
|
282
312
|
? targets.map((item) => String(item || "").trim()).filter(Boolean)
|
|
283
313
|
: [];
|
|
284
314
|
const safePrompt = String(prompt || "").trim();
|
|
315
|
+
const safeTitle = normalizeCronTitle(title, safePrompt);
|
|
285
316
|
|
|
286
317
|
if (!safePrompt || safeTargets.length === 0) return null;
|
|
287
318
|
|
|
@@ -296,6 +327,7 @@ function createDaemonCronController(options = {}) {
|
|
|
296
327
|
onceAtMs: useOnce ? safeOnceAt : 0,
|
|
297
328
|
targets: Array.from(new Set(safeTargets)),
|
|
298
329
|
prompt: safePrompt,
|
|
330
|
+
title: safeTitle,
|
|
299
331
|
createdAt: nowFn(),
|
|
300
332
|
lastRunAt: 0,
|
|
301
333
|
tickCount: 0,
|
|
@@ -367,6 +399,7 @@ function createDaemonCronController(options = {}) {
|
|
|
367
399
|
? item.targets.map((v) => String(v || "").trim()).filter(Boolean)
|
|
368
400
|
: [];
|
|
369
401
|
const prompt = String(item && item.prompt ? item.prompt : "").trim();
|
|
402
|
+
const title = normalizeCronTitle(item && item.title ? item.title : "", prompt);
|
|
370
403
|
|
|
371
404
|
if (!prompt || targets.length === 0) {
|
|
372
405
|
changed = true;
|
|
@@ -389,6 +422,7 @@ function createDaemonCronController(options = {}) {
|
|
|
389
422
|
onceAtMs: Number.isFinite(onceAtMs) ? Math.floor(onceAtMs) : 0,
|
|
390
423
|
targets: Array.from(new Set(targets)),
|
|
391
424
|
prompt,
|
|
425
|
+
title,
|
|
392
426
|
createdAt: Number(item && item.createdAt) || now,
|
|
393
427
|
lastRunAt: Number(item && item.lastRunAt) || 0,
|
|
394
428
|
tickCount: Number(item && item.tickCount) || 0,
|
|
@@ -535,6 +569,7 @@ function createDaemonCronController(options = {}) {
|
|
|
535
569
|
onceAtMs,
|
|
536
570
|
targets,
|
|
537
571
|
prompt,
|
|
572
|
+
title: resolveCronTitle(op),
|
|
538
573
|
});
|
|
539
574
|
|
|
540
575
|
if (!task) {
|
|
@@ -567,8 +602,10 @@ module.exports = {
|
|
|
567
602
|
resolveCronOperation,
|
|
568
603
|
resolveCronIntervalMs,
|
|
569
604
|
resolveCronOnceAtMs,
|
|
605
|
+
resolveCronTitle,
|
|
570
606
|
resolveCronPrompt,
|
|
571
607
|
resolveCronTaskId,
|
|
572
608
|
parseCronAtMs,
|
|
609
|
+
buildCronLabel,
|
|
573
610
|
formatCronTask,
|
|
574
611
|
};
|