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
@@ -18,12 +18,14 @@ function createDashboardKeyController(options = {}) {
18
18
  exitDashboardMode = () => {},
19
19
  setLaunchMode = () => {},
20
20
  setAgentProvider = () => {},
21
- setAssistantEngine = () => {},
22
21
  setAutoResume = () => {},
23
22
  clampAgentWindow = () => {},
24
23
  clampAgentWindowWithSelection = () => {},
25
24
  requestProjectSwitch = () => {},
26
25
  requestCloseProject = () => {},
26
+ requestCron = () => {},
27
+ setGlobalScope = () => {},
28
+ getGlobalScope = () => "",
27
29
  renderDashboard = () => {},
28
30
  renderAgentDashboard = () => {},
29
31
  renderScreen = () => {},
@@ -232,10 +234,9 @@ function createDashboardKeyController(options = {}) {
232
234
  }
233
235
 
234
236
  if (key.name === "down") {
235
- state.dashboardView = "assistant";
236
- const list = Array.isArray(state.assistantOptions) ? state.assistantOptions : [];
237
- const nextIndex = list.findIndex((opt) => opt.value === state.assistantEngine);
238
- state.selectedAssistantIndex = nextIndex >= 0 ? nextIndex : 0;
237
+ state.dashboardView = "cron";
238
+ const cronTasks = Array.isArray(state.cronTasks) ? state.cronTasks : [];
239
+ state.selectedCronIndex = cronTasks.length > 0 ? 0 : -1;
239
240
  renderDashboardAndScreen();
240
241
  return true;
241
242
  }
@@ -261,34 +262,23 @@ function createDashboardKeyController(options = {}) {
261
262
  return true;
262
263
  }
263
264
 
264
- function handleAssistantKey(key) {
265
- const options = Array.isArray(state.assistantOptions) ? state.assistantOptions : [];
266
- if (options.length === 0) {
267
- if (key.name === "up") {
268
- state.dashboardView = "provider";
269
- renderDashboardAndScreen();
270
- return true;
271
- }
272
- if (key.name === "escape" || key.name === "enter" || key.name === "return") {
273
- exitDashboardMode(false);
274
- return true;
275
- }
276
- return true;
277
- }
265
+ function handleCronKey(key) {
266
+ const cronTasks = Array.isArray(state.cronTasks) ? state.cronTasks : [];
267
+ const maxIndex = cronTasks.length - 1;
278
268
 
279
269
  if (key.name === "left") {
280
- state.selectedAssistantIndex = state.selectedAssistantIndex <= 0
281
- ? options.length - 1
282
- : state.selectedAssistantIndex - 1;
283
- renderDashboardAndScreen();
270
+ if (maxIndex >= 0 && state.selectedCronIndex > 0) {
271
+ state.selectedCronIndex -= 1;
272
+ renderDashboardAndScreen();
273
+ }
284
274
  return true;
285
275
  }
286
276
 
287
277
  if (key.name === "right") {
288
- state.selectedAssistantIndex = state.selectedAssistantIndex >= options.length - 1
289
- ? 0
290
- : state.selectedAssistantIndex + 1;
291
- renderDashboardAndScreen();
278
+ if (maxIndex >= 0 && state.selectedCronIndex < maxIndex) {
279
+ state.selectedCronIndex += 1;
280
+ renderDashboardAndScreen();
281
+ }
292
282
  return true;
293
283
  }
294
284
 
@@ -298,35 +288,15 @@ function createDashboardKeyController(options = {}) {
298
288
  return true;
299
289
  }
300
290
 
301
- if (key.name === "down") {
302
- state.dashboardView = "cron";
303
- renderDashboardAndScreen();
304
- return true;
305
- }
306
-
307
- if (key.name === "enter" || key.name === "return") {
308
- const selected = options[state.selectedAssistantIndex];
309
- if (selected) setAssistantEngine(selected.value);
310
- exitDashboardMode(false);
311
- return true;
312
- }
313
-
314
- if (key.name === "escape") {
315
- exitDashboardMode(false);
316
- return true;
317
- }
318
-
319
- return true;
320
- }
321
-
322
- function handleCronKey(key) {
323
- if (key.name === "up") {
324
- state.dashboardView = "assistant";
325
- renderDashboardAndScreen();
326
- return true;
327
- }
328
-
329
291
  if (key.name === "x" && key.ctrl) {
292
+ if (maxIndex >= 0 && state.selectedCronIndex >= 0 && state.selectedCronIndex <= maxIndex) {
293
+ const task = cronTasks[state.selectedCronIndex];
294
+ const id = task && task.id ? String(task.id).trim() : "";
295
+ if (id) {
296
+ requestCron({ operation: "stop", id });
297
+ return true;
298
+ }
299
+ }
330
300
  exitDashboardMode(false);
331
301
  return true;
332
302
  }
@@ -415,7 +385,9 @@ function createDashboardKeyController(options = {}) {
415
385
  const next = current - 1;
416
386
  state.selectedProjectIndex = next;
417
387
  renderDashboardAndScreen();
418
- requestProjectSwitch(next);
388
+ if (getGlobalScope() === "project") {
389
+ requestProjectSwitch(next);
390
+ }
419
391
  }
420
392
  return true;
421
393
  }
@@ -426,17 +398,35 @@ function createDashboardKeyController(options = {}) {
426
398
  const next = current + 1;
427
399
  state.selectedProjectIndex = next;
428
400
  renderDashboardAndScreen();
429
- requestProjectSwitch(next);
401
+ if (getGlobalScope() === "project") {
402
+ requestProjectSwitch(next);
403
+ }
430
404
  }
431
405
  return true;
432
406
  }
433
407
 
434
- if (key.name === "up" || key.name === "escape") {
408
+ if (key.name === "up") {
409
+ exitDashboardMode(false);
410
+ return true;
411
+ }
412
+
413
+ if (key.name === "escape") {
414
+ if (getGlobalScope() === "project") {
415
+ setGlobalScope("controller");
416
+ }
435
417
  exitDashboardMode(false);
436
418
  return true;
437
419
  }
438
420
 
439
421
  if (key.name === "enter" || key.name === "return") {
422
+ const current = Number.isFinite(state.selectedProjectIndex) ? state.selectedProjectIndex : 0;
423
+ if (current >= 0 && current < projects.length) {
424
+ const selectedRow = projects[current];
425
+ const selectedProjectRoot = String((selectedRow && selectedRow.project_root) || "");
426
+ if (selectedProjectRoot) {
427
+ setGlobalScope("project", selectedProjectRoot);
428
+ }
429
+ }
440
430
  exitDashboardMode(false);
441
431
  return true;
442
432
  }
@@ -552,7 +542,6 @@ function createDashboardKeyController(options = {}) {
552
542
 
553
543
  if (state.dashboardView === "mode") return handleModeKey(key);
554
544
  if (state.dashboardView === "provider") return handleProviderKey(key);
555
- if (state.dashboardView === "assistant") return handleAssistantKey(key);
556
545
  if (state.dashboardView === "resume") return handleResumeKey(key);
557
546
  if (state.dashboardView === "cron") return handleCronKey(key);
558
547
 
@@ -8,14 +8,6 @@ function providerLabel(value) {
8
8
  return "codex";
9
9
  }
10
10
 
11
- function assistantLabel(value) {
12
- if (value === "codex") return "codex";
13
- if (value === "claude") return "claude";
14
- if (value === "ufoo") return "ucode";
15
- if (value === "ucode") return "ucode";
16
- return "auto";
17
- }
18
-
19
11
  function ensureAtPrefix(value) {
20
12
  const text = String(value || "").trim();
21
13
  if (!text) return text;
@@ -43,8 +35,8 @@ function buildSummaryLine(options = {}) {
43
35
  getAgentState = () => "",
44
36
  launchMode = "terminal",
45
37
  agentProvider = "codex-cli",
46
- assistantEngine = "auto",
47
38
  cronTasks = [],
39
+ pendingReports = 0,
48
40
  } = options;
49
41
  const agents = activeAgents.length > 0
50
42
  ? activeAgents.slice(0, 3)
@@ -55,7 +47,7 @@ function buildSummaryLine(options = {}) {
55
47
  return `{gray-fg}Agents:{/gray-fg} {cyan-fg}${agents}{/cyan-fg}`
56
48
  + ` {gray-fg}Mode:{/gray-fg} {cyan-fg}${launchMode}{/cyan-fg}`
57
49
  + ` {gray-fg}Agent:{/gray-fg} {cyan-fg}${providerLabel(agentProvider)}{/cyan-fg}`
58
- + ` {gray-fg}Assistant:{/gray-fg} {cyan-fg}${assistantLabel(assistantEngine)}{/cyan-fg}`
50
+ + ` {gray-fg}Reports:{/gray-fg} {cyan-fg}${Number.isFinite(pendingReports) ? pendingReports : 0}{/cyan-fg}`
59
51
  + ` {gray-fg}Cron:{/gray-fg} {cyan-fg}${Array.isArray(cronTasks) ? cronTasks.length : 0}{/cyan-fg}`;
60
52
  }
61
53
 
@@ -67,6 +59,8 @@ function buildProjectRailLine(options = {}) {
67
59
  maxProjectWindow = 5,
68
60
  activeProjectRoot = "",
69
61
  projectsFocused = false,
62
+ globalScope = "",
63
+ dashboardHint = "",
70
64
  } = options;
71
65
  const rows = Array.isArray(projects) ? projects : [];
72
66
  let windowStart = projectListWindowStart;
@@ -102,7 +96,7 @@ function buildProjectRailLine(options = {}) {
102
96
  const absoluteIndex = start + i;
103
97
  const name = String((row && row.project_name) || (row && row.project_root) || "-");
104
98
  const rowRoot = String((row && row.project_root) || "");
105
- const isActiveProject = Boolean(activeRoot && rowRoot === activeRoot);
99
+ const isActiveProject = globalScope !== "controller" && Boolean(activeRoot && rowRoot === activeRoot);
106
100
  const isSelected = absoluteIndex === safeSelectedIndex;
107
101
  if (projectsFocused && isSelected) {
108
102
  return `{inverse}${name}{/inverse}`;
@@ -115,9 +109,10 @@ function buildProjectRailLine(options = {}) {
115
109
 
116
110
  const leftMore = start > 0 ? "{gray-fg}<{/gray-fg} " : "";
117
111
  const rightMore = end < rows.length ? " {gray-fg}>{/gray-fg}" : "";
112
+ const hintPart = dashboardHint ? `{|}{gray-fg}${dashboardHint}{/gray-fg}` : "";
118
113
  return {
119
114
  hasProjects: true,
120
- line: ` {gray-fg}Projects:{/gray-fg} ${leftMore}${projectParts.join(" ")}${rightMore}`,
115
+ line: ` {gray-fg}Projects:{/gray-fg} ${leftMore}${projectParts.join(" ")}${rightMore}${hintPart}`,
121
116
  windowStart,
122
117
  };
123
118
  }
@@ -134,11 +129,11 @@ function buildDashboardDetailLine(options = {}) {
134
129
  getAgentState = () => "",
135
130
  selectedModeIndex = 0,
136
131
  selectedProviderIndex = 0,
137
- selectedAssistantIndex = 0,
138
132
  selectedResumeIndex = 0,
133
+ selectedCronIndex = -1,
139
134
  cronTasks = [],
135
+ pendingReports = 0,
140
136
  providerOptions = [],
141
- assistantOptions = [],
142
137
  resumeOptions = [],
143
138
  dashHints = {},
144
139
  modeOptions = DEFAULT_MODE_OPTIONS,
@@ -171,18 +166,6 @@ function buildDashboardDetailLine(options = {}) {
171
166
  return { content, windowStart };
172
167
  }
173
168
 
174
- if (dashboardView === "assistant") {
175
- const assistantParts = assistantOptions.map((opt, i) => {
176
- if (i === selectedAssistantIndex) {
177
- return `{inverse}${opt.label}{/inverse}`;
178
- }
179
- return `{cyan-fg}${opt.label}{/cyan-fg}`;
180
- });
181
- content += `{gray-fg}Assistant:{/gray-fg} ${assistantParts.join(" ")}`;
182
- content += ` {gray-fg}│ ${dashHints.assistant || ""}{/gray-fg}`;
183
- return { content, windowStart };
184
- }
185
-
186
169
  if (dashboardView === "resume") {
187
170
  const resumeParts = resumeOptions.map((opt, i) => {
188
171
  if (i === selectedResumeIndex) {
@@ -198,9 +181,15 @@ function buildDashboardDetailLine(options = {}) {
198
181
  if (dashboardView === "cron") {
199
182
  const items = Array.isArray(cronTasks) ? cronTasks : [];
200
183
  const summary = items.length > 0
201
- ? items.map((item) => item.summary || item.id || "").filter(Boolean).join(", ")
202
- : "none";
203
- content += `{gray-fg}Cron:{/gray-fg} {cyan-fg}${summary}{/cyan-fg}`;
184
+ ? items.map((item, index) => {
185
+ const label = item.label || item.summary || item.id || "";
186
+ if (!label) return "";
187
+ return index === selectedCronIndex
188
+ ? `{inverse}${label}{/inverse}`
189
+ : `{cyan-fg}${label}{/cyan-fg}`;
190
+ }).filter(Boolean).join(", ")
191
+ : "{cyan-fg}none{/cyan-fg}";
192
+ content += `{gray-fg}Cron:{/gray-fg} ${summary}`;
204
193
  content += ` {gray-fg}│ ${dashHints.cron || ""}{/gray-fg}`;
205
194
  return { content, windowStart };
206
195
  }
@@ -244,6 +233,7 @@ function buildDashboardDetailLine(options = {}) {
244
233
  function computeDashboardContent(options = {}) {
245
234
  const {
246
235
  globalMode = false,
236
+ globalScope = "controller",
247
237
  focusMode = "input",
248
238
  dashboardView = "agents",
249
239
  activeAgents = [],
@@ -259,14 +249,13 @@ function computeDashboardContent(options = {}) {
259
249
  getAgentState = () => "",
260
250
  launchMode = "terminal",
261
251
  agentProvider = "codex-cli",
262
- assistantEngine = "auto",
263
252
  selectedModeIndex = 0,
264
253
  selectedProviderIndex = 0,
265
- selectedAssistantIndex = 0,
266
254
  selectedResumeIndex = 0,
255
+ selectedCronIndex = -1,
267
256
  cronTasks = [],
257
+ pendingReports = 0,
268
258
  providerOptions = [],
269
- assistantOptions = [],
270
259
  resumeOptions = [],
271
260
  dashHints = {},
272
261
  modeOptions = DEFAULT_MODE_OPTIONS,
@@ -274,6 +263,10 @@ function computeDashboardContent(options = {}) {
274
263
 
275
264
  if (globalMode) {
276
265
  const projectsFocused = focusMode === "dashboard" && dashboardView === "projects";
266
+ let dashboardHint = "";
267
+ if (projectsFocused) {
268
+ dashboardHint = globalScope === "controller" ? "Enter\u2192project" : "Esc\u2192global";
269
+ }
277
270
  const rail = buildProjectRailLine({
278
271
  projects,
279
272
  selectedProjectIndex,
@@ -281,6 +274,8 @@ function computeDashboardContent(options = {}) {
281
274
  maxProjectWindow,
282
275
  activeProjectRoot,
283
276
  projectsFocused,
277
+ globalScope,
278
+ dashboardHint,
284
279
  });
285
280
  if (!rail.hasProjects) {
286
281
  const line2 = ` {gray-fg}${dashHints.projectsEmpty || "Run ufoo chat/daemon in projects to populate registry"}{/gray-fg}`;
@@ -297,8 +292,8 @@ function computeDashboardContent(options = {}) {
297
292
  getAgentState,
298
293
  launchMode,
299
294
  agentProvider,
300
- assistantEngine,
301
295
  cronTasks,
296
+ pendingReports,
302
297
  });
303
298
  return {
304
299
  content: `${rail.line}\n ${line2}`,
@@ -317,11 +312,10 @@ function computeDashboardContent(options = {}) {
317
312
  getAgentState,
318
313
  selectedModeIndex,
319
314
  selectedProviderIndex,
320
- selectedAssistantIndex,
321
315
  selectedResumeIndex,
316
+ selectedCronIndex,
322
317
  cronTasks,
323
318
  providerOptions,
324
- assistantOptions,
325
319
  resumeOptions,
326
320
  dashHints,
327
321
  modeOptions,
@@ -344,11 +338,10 @@ function computeDashboardContent(options = {}) {
344
338
  getAgentState,
345
339
  selectedModeIndex,
346
340
  selectedProviderIndex,
347
- selectedAssistantIndex,
348
341
  selectedResumeIndex,
342
+ selectedCronIndex,
349
343
  cronTasks,
350
344
  providerOptions,
351
- assistantOptions,
352
345
  resumeOptions,
353
346
  dashHints,
354
347
  modeOptions,
@@ -362,8 +355,8 @@ function computeDashboardContent(options = {}) {
362
355
  getAgentState,
363
356
  launchMode,
364
357
  agentProvider,
365
- assistantEngine,
366
358
  cronTasks,
359
+ pendingReports,
367
360
  });
368
361
 
369
362
  return { content, windowStart: agentListWindowStart };
@@ -372,5 +365,4 @@ function computeDashboardContent(options = {}) {
372
365
  module.exports = {
373
366
  computeDashboardContent,
374
367
  providerLabel,
375
- assistantLabel,
376
368
  };