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.
Files changed (45) hide show
  1. package/README.md +9 -1
  2. package/README.zh-CN.md +9 -1
  3. package/bin/ufoo.js +4 -2
  4. package/package.json +1 -1
  5. package/src/agent/cliRunner.js +3 -2
  6. package/src/agent/ucodeBootstrap.js +5 -3
  7. package/src/agent/ufooAgent.js +185 -6
  8. package/src/assistant/constants.js +1 -1
  9. package/src/assistant/engine.js +1 -6
  10. package/src/chat/commandExecutor.js +116 -19
  11. package/src/chat/commands.js +8 -1
  12. package/src/chat/completionController.js +40 -0
  13. package/src/chat/cronScheduler.js +37 -6
  14. package/src/chat/daemonMessageRouter.js +23 -3
  15. package/src/chat/dashboardKeyController.js +48 -59
  16. package/src/chat/dashboardView.js +31 -39
  17. package/src/chat/index.js +154 -77
  18. package/src/chat/inputListenerController.js +14 -0
  19. package/src/chat/inputSubmitHandler.js +9 -5
  20. package/src/chat/settingsController.js +0 -28
  21. package/src/chat/transientAgentState.js +64 -0
  22. package/src/cli/groupCoreCommands.js +21 -12
  23. package/src/cli.js +23 -1
  24. package/src/daemon/cronOps.js +48 -11
  25. package/src/daemon/groupOrchestrator.js +581 -97
  26. package/src/daemon/index.js +420 -5
  27. package/src/daemon/ops.js +25 -7
  28. package/src/daemon/promptLoop.js +16 -0
  29. package/src/daemon/promptRequest.js +126 -2
  30. package/src/daemon/reporting.js +18 -0
  31. package/src/daemon/soloBootstrap.js +435 -0
  32. package/src/daemon/status.js +7 -1
  33. package/src/globalMode.js +33 -0
  34. package/src/group/bootstrap.js +157 -0
  35. package/src/group/promptProfiles.js +646 -0
  36. package/src/group/templateValidation.js +99 -0
  37. package/src/group/validateTemplate.js +36 -5
  38. package/src/init/index.js +13 -7
  39. package/src/report/store.js +6 -0
  40. package/src/shared/eventContract.js +1 -0
  41. package/templates/groups/{dev-basic.json → build-lane.json} +38 -34
  42. package/templates/groups/product-discovery.json +79 -0
  43. package/templates/groups/ui-polish.json +87 -0
  44. package/templates/groups/verify-ship.json +79 -0
  45. 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 { validateTemplate } = require("../group/validateTemplate");
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 resolved = resolveTemplateReference(cwd, target, {
217
+ const result = validateTemplateTarget(cwd, target, {
209
218
  allowPath: true,
210
219
  cwd,
211
- ...templatesOptions,
220
+ templatesOptions,
221
+ promptProfilesOptions,
212
222
  });
213
- if (!resolved.entry) {
214
- throwResolveFailure(target, resolved);
223
+ if (!result.entry) {
224
+ throwResolveFailure(target, { errors: result.errors || [] });
215
225
  }
216
- const result = validateTemplate(resolved.entry.data);
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: ${resolved.entry.alias}`);
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
- .action(async (agent, nickname) => {
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)")
@@ -128,18 +128,41 @@ function sanitizeSummaryText(value = "") {
128
128
  .trim();
129
129
  }
130
130
 
131
- function summarizeCronTask(task = {}) {
132
- const id = String(task.id || "");
133
- const targets = Array.isArray(task.targets) ? task.targets.join("+") : "";
134
- const promptRaw = sanitizeSummaryText(task.prompt || "");
135
- const prompt = promptRaw.length > 24 ? `${promptRaw.slice(0, 24)}...` : promptRaw;
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
- if (Number(task.onceAtMs) > 0) {
138
- return `${id}@once(${formatCronAtMs(task.onceAtMs)})->${targets}: ${prompt || "(empty)"}`;
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
- const interval = formatIntervalMs(task.intervalMs || 0);
142
- return `${id}@${interval}->${targets}: ${prompt || "(empty)"}`;
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
  };