u-foo 1.7.5 → 1.8.1
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 +184 -5
- package/src/assistant/constants.js +1 -1
- package/src/chat/commandExecutor.js +98 -3
- package/src/chat/commands.js +7 -0
- package/src/chat/completionController.js +40 -0
- package/src/chat/daemonMessageRouter.js +21 -1
- package/src/chat/dashboardKeyController.js +55 -3
- package/src/chat/dashboardView.js +26 -5
- package/src/chat/index.js +148 -41
- package/src/chat/inputListenerController.js +14 -0
- package/src/chat/inputMath.js +1 -1
- package/src/chat/inputSubmitHandler.js +9 -5
- package/src/chat/transientAgentState.js +64 -0
- package/src/cli/groupCoreCommands.js +21 -12
- package/src/cli.js +23 -1
- package/src/code/tui.js +1 -1
- package/src/daemon/cronOps.js +11 -4
- package/src/daemon/groupOrchestrator.js +581 -97
- package/src/daemon/index.js +418 -3
- 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 +5 -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
|
@@ -11,6 +11,7 @@ function createCompletionController(options = {}) {
|
|
|
11
11
|
completionPanel,
|
|
12
12
|
promptBox,
|
|
13
13
|
commandRegistry = [],
|
|
14
|
+
getGroupTemplateCandidates = () => [],
|
|
14
15
|
getMentionCandidates = () => [],
|
|
15
16
|
normalizeCommandPrefix = () => {},
|
|
16
17
|
truncateText = (text) => String(text || ""),
|
|
@@ -141,8 +142,33 @@ function createCompletionController(options = {}) {
|
|
|
141
142
|
const parts = trimmed.split(/\s+/);
|
|
142
143
|
const mainCmd = parts[0];
|
|
143
144
|
const isLaunch = mainCmd && mainCmd.toLowerCase() === "/launch";
|
|
145
|
+
const isGroup = mainCmd && mainCmd.toLowerCase() === "/group";
|
|
144
146
|
const wantsSubcommands = (parts.length > 1 || (endsWithSpace && parts.length === 1));
|
|
145
147
|
|
|
148
|
+
if (isGroup) {
|
|
149
|
+
const groupSubcommand = String(parts[1] || "").trim().toLowerCase();
|
|
150
|
+
const wantsGroupRunArgs = groupSubcommand === "run" && (parts.length > 2 || endsWithSpace);
|
|
151
|
+
if (wantsGroupRunArgs) {
|
|
152
|
+
const aliasFilter = String(parts[2] || "").trim().toLowerCase();
|
|
153
|
+
return (Array.isArray(getGroupTemplateCandidates()) ? getGroupTemplateCandidates() : [])
|
|
154
|
+
.map((item) => {
|
|
155
|
+
const alias = String(item && item.alias ? item.alias : item && item.cmd ? item.cmd : "").trim();
|
|
156
|
+
if (!alias) return null;
|
|
157
|
+
const desc = String(item && item.desc ? item.desc : item && item.name ? item.name : "").trim();
|
|
158
|
+
const source = String(item && item.source ? item.source : "").trim();
|
|
159
|
+
const detail = [desc, source].filter(Boolean).join(" · ");
|
|
160
|
+
return {
|
|
161
|
+
cmd: alias,
|
|
162
|
+
desc: detail,
|
|
163
|
+
isArgumentSuggestion: true,
|
|
164
|
+
argumentPrefix: "/group run",
|
|
165
|
+
};
|
|
166
|
+
})
|
|
167
|
+
.filter((item) => item && (!aliasFilter || item.cmd.toLowerCase().startsWith(aliasFilter)))
|
|
168
|
+
.sort((a, b) => a.cmd.localeCompare(b.cmd, "en", { sensitivity: "base" }));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
146
172
|
if ((wantsSubcommands || isLaunch) && mainCmd && mainCmd.startsWith("/")) {
|
|
147
173
|
const subFilter = parts[1] || "";
|
|
148
174
|
const mainCmdObj = commandRegistry.find((item) =>
|
|
@@ -261,6 +287,13 @@ function createCompletionController(options = {}) {
|
|
|
261
287
|
return { text: `${completedCore} `, isComplete };
|
|
262
288
|
}
|
|
263
289
|
|
|
290
|
+
if (selected.isArgumentSuggestion) {
|
|
291
|
+
const prefix = String(selected.argumentPrefix || "").trim();
|
|
292
|
+
const completedCore = prefix ? `${prefix} ${selected.cmd}` : selected.cmd;
|
|
293
|
+
const isComplete = trimmed === completedCore || trimmed.startsWith(`${completedCore} `);
|
|
294
|
+
return { text: `${completedCore} `, isComplete };
|
|
295
|
+
}
|
|
296
|
+
|
|
264
297
|
const completedCore = selected.cmd;
|
|
265
298
|
const hasChildren = selected.subcommands && selected.subcommands.length > 0;
|
|
266
299
|
const isComplete =
|
|
@@ -291,6 +324,9 @@ function createCompletionController(options = {}) {
|
|
|
291
324
|
const parts = input.value.split(/\s+/);
|
|
292
325
|
parts[parts.length - 1] = selected.cmd;
|
|
293
326
|
input.value = `${parts.join(" ")} `;
|
|
327
|
+
} else if (selected.isArgumentSuggestion) {
|
|
328
|
+
const prefix = String(selected.argumentPrefix || "").trim();
|
|
329
|
+
input.value = prefix ? `${prefix} ${selected.cmd} ` : `${selected.cmd} `;
|
|
294
330
|
} else {
|
|
295
331
|
input.value = `${selected.cmd} `;
|
|
296
332
|
}
|
|
@@ -304,6 +340,8 @@ function createCompletionController(options = {}) {
|
|
|
304
340
|
|
|
305
341
|
if (!selected.isSubcommand && selected.subcommands && selected.subcommands.length > 0) {
|
|
306
342
|
show(input.value);
|
|
343
|
+
} else if (selected.isSubcommand && selected.parentCmd === "/group" && selected.cmd === "run") {
|
|
344
|
+
show(input.value);
|
|
307
345
|
} else {
|
|
308
346
|
hide();
|
|
309
347
|
}
|
|
@@ -346,6 +384,8 @@ function createCompletionController(options = {}) {
|
|
|
346
384
|
applyPreview(nextPreview);
|
|
347
385
|
if (!selected.isSubcommand && selected.subcommands && selected.subcommands.length > 0) {
|
|
348
386
|
show(input.value);
|
|
387
|
+
} else if (selected.isSubcommand && selected.parentCmd === "/group" && selected.cmd === "run") {
|
|
388
|
+
show(input.value);
|
|
349
389
|
} else {
|
|
350
390
|
hide();
|
|
351
391
|
}
|
|
@@ -286,7 +286,14 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
286
286
|
payload.disambiguate.candidates.length > 0
|
|
287
287
|
) {
|
|
288
288
|
const pending = getPending();
|
|
289
|
-
|
|
289
|
+
const routedProjectRoot = payload.routed_project && payload.routed_project.project_root
|
|
290
|
+
? payload.routed_project.project_root
|
|
291
|
+
: (pending && pending.project_root ? pending.project_root : "");
|
|
292
|
+
setPending({
|
|
293
|
+
disambiguate: payload.disambiguate,
|
|
294
|
+
original: pending && pending.original,
|
|
295
|
+
project_root: routedProjectRoot || undefined,
|
|
296
|
+
});
|
|
290
297
|
const prompt = payload.disambiguate.prompt || "Choose target:";
|
|
291
298
|
resolveStatusLine(`{gray-fg}?{/gray-fg} ${escapeBlessed(prompt)}`);
|
|
292
299
|
logMessage("disambiguate", `{white-fg}?{/white-fg} ${escapeBlessed(prompt)}`);
|
|
@@ -323,6 +330,19 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
323
330
|
requestStatus();
|
|
324
331
|
return true;
|
|
325
332
|
}
|
|
333
|
+
if (data.event === "controller_report") {
|
|
334
|
+
const report = data.report && typeof data.report === "object" ? data.report : {};
|
|
335
|
+
const publisher = report.agent_id || data.publisher || "ufoo-agent";
|
|
336
|
+
const displayName = resolveAgentDisplayName(publisher);
|
|
337
|
+
const detail = report.summary || report.message || data.message || report.task_id || "report";
|
|
338
|
+
logMessage(
|
|
339
|
+
"system",
|
|
340
|
+
`{gray-fg}↥{/gray-fg} {cyan-fg}${escapeBlessed(displayName)}{/cyan-fg} {gray-fg}→ ufoo-agent{/gray-fg} ${escapeBlessed(detail)}`
|
|
341
|
+
);
|
|
342
|
+
requestStatus();
|
|
343
|
+
renderScreen();
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
326
346
|
const prefix = data.event === "broadcast" ? "{gray-fg}⇢{/gray-fg}" : "{gray-fg}↔{/gray-fg}";
|
|
327
347
|
const publisher = data.publisher && data.publisher !== "unknown"
|
|
328
348
|
? data.publisher
|
|
@@ -23,6 +23,9 @@ function createDashboardKeyController(options = {}) {
|
|
|
23
23
|
clampAgentWindowWithSelection = () => {},
|
|
24
24
|
requestProjectSwitch = () => {},
|
|
25
25
|
requestCloseProject = () => {},
|
|
26
|
+
requestCron = () => {},
|
|
27
|
+
setGlobalScope = () => {},
|
|
28
|
+
getGlobalScope = () => "",
|
|
26
29
|
renderDashboard = () => {},
|
|
27
30
|
renderAgentDashboard = () => {},
|
|
28
31
|
renderScreen = () => {},
|
|
@@ -232,6 +235,8 @@ function createDashboardKeyController(options = {}) {
|
|
|
232
235
|
|
|
233
236
|
if (key.name === "down") {
|
|
234
237
|
state.dashboardView = "cron";
|
|
238
|
+
const cronTasks = Array.isArray(state.cronTasks) ? state.cronTasks : [];
|
|
239
|
+
state.selectedCronIndex = cronTasks.length > 0 ? 0 : -1;
|
|
235
240
|
renderDashboardAndScreen();
|
|
236
241
|
return true;
|
|
237
242
|
}
|
|
@@ -258,6 +263,25 @@ function createDashboardKeyController(options = {}) {
|
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
function handleCronKey(key) {
|
|
266
|
+
const cronTasks = Array.isArray(state.cronTasks) ? state.cronTasks : [];
|
|
267
|
+
const maxIndex = cronTasks.length - 1;
|
|
268
|
+
|
|
269
|
+
if (key.name === "left") {
|
|
270
|
+
if (maxIndex >= 0 && state.selectedCronIndex > 0) {
|
|
271
|
+
state.selectedCronIndex -= 1;
|
|
272
|
+
renderDashboardAndScreen();
|
|
273
|
+
}
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (key.name === "right") {
|
|
278
|
+
if (maxIndex >= 0 && state.selectedCronIndex < maxIndex) {
|
|
279
|
+
state.selectedCronIndex += 1;
|
|
280
|
+
renderDashboardAndScreen();
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
261
285
|
if (key.name === "up") {
|
|
262
286
|
state.dashboardView = "provider";
|
|
263
287
|
renderDashboardAndScreen();
|
|
@@ -265,6 +289,14 @@ function createDashboardKeyController(options = {}) {
|
|
|
265
289
|
}
|
|
266
290
|
|
|
267
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
|
+
}
|
|
268
300
|
exitDashboardMode(false);
|
|
269
301
|
return true;
|
|
270
302
|
}
|
|
@@ -353,7 +385,9 @@ function createDashboardKeyController(options = {}) {
|
|
|
353
385
|
const next = current - 1;
|
|
354
386
|
state.selectedProjectIndex = next;
|
|
355
387
|
renderDashboardAndScreen();
|
|
356
|
-
|
|
388
|
+
if (getGlobalScope() === "project") {
|
|
389
|
+
requestProjectSwitch(next);
|
|
390
|
+
}
|
|
357
391
|
}
|
|
358
392
|
return true;
|
|
359
393
|
}
|
|
@@ -364,17 +398,35 @@ function createDashboardKeyController(options = {}) {
|
|
|
364
398
|
const next = current + 1;
|
|
365
399
|
state.selectedProjectIndex = next;
|
|
366
400
|
renderDashboardAndScreen();
|
|
367
|
-
|
|
401
|
+
if (getGlobalScope() === "project") {
|
|
402
|
+
requestProjectSwitch(next);
|
|
403
|
+
}
|
|
368
404
|
}
|
|
369
405
|
return true;
|
|
370
406
|
}
|
|
371
407
|
|
|
372
|
-
if (key.name === "up"
|
|
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
|
+
}
|
|
373
417
|
exitDashboardMode(false);
|
|
374
418
|
return true;
|
|
375
419
|
}
|
|
376
420
|
|
|
377
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
|
+
}
|
|
378
430
|
exitDashboardMode(false);
|
|
379
431
|
return true;
|
|
380
432
|
}
|
|
@@ -57,6 +57,8 @@ function buildProjectRailLine(options = {}) {
|
|
|
57
57
|
maxProjectWindow = 5,
|
|
58
58
|
activeProjectRoot = "",
|
|
59
59
|
projectsFocused = false,
|
|
60
|
+
globalScope = "",
|
|
61
|
+
dashboardHint = "",
|
|
60
62
|
} = options;
|
|
61
63
|
const rows = Array.isArray(projects) ? projects : [];
|
|
62
64
|
let windowStart = projectListWindowStart;
|
|
@@ -92,7 +94,7 @@ function buildProjectRailLine(options = {}) {
|
|
|
92
94
|
const absoluteIndex = start + i;
|
|
93
95
|
const name = String((row && row.project_name) || (row && row.project_root) || "-");
|
|
94
96
|
const rowRoot = String((row && row.project_root) || "");
|
|
95
|
-
const isActiveProject = Boolean(activeRoot && rowRoot === activeRoot);
|
|
97
|
+
const isActiveProject = globalScope !== "controller" && Boolean(activeRoot && rowRoot === activeRoot);
|
|
96
98
|
const isSelected = absoluteIndex === safeSelectedIndex;
|
|
97
99
|
if (projectsFocused && isSelected) {
|
|
98
100
|
return `{inverse}${name}{/inverse}`;
|
|
@@ -105,9 +107,10 @@ function buildProjectRailLine(options = {}) {
|
|
|
105
107
|
|
|
106
108
|
const leftMore = start > 0 ? "{gray-fg}<{/gray-fg} " : "";
|
|
107
109
|
const rightMore = end < rows.length ? " {gray-fg}>{/gray-fg}" : "";
|
|
110
|
+
const hintPart = dashboardHint ? `{|}{gray-fg}${dashboardHint}{/gray-fg}` : "";
|
|
108
111
|
return {
|
|
109
112
|
hasProjects: true,
|
|
110
|
-
line: ` {gray-fg}Projects:{/gray-fg} ${leftMore}${projectParts.join(" ")}${rightMore}`,
|
|
113
|
+
line: ` {gray-fg}Projects:{/gray-fg} ${leftMore}${projectParts.join(" ")}${rightMore}${hintPart}`,
|
|
111
114
|
windowStart,
|
|
112
115
|
};
|
|
113
116
|
}
|
|
@@ -125,6 +128,7 @@ function buildDashboardDetailLine(options = {}) {
|
|
|
125
128
|
selectedModeIndex = 0,
|
|
126
129
|
selectedProviderIndex = 0,
|
|
127
130
|
selectedResumeIndex = 0,
|
|
131
|
+
selectedCronIndex = -1,
|
|
128
132
|
cronTasks = [],
|
|
129
133
|
providerOptions = [],
|
|
130
134
|
resumeOptions = [],
|
|
@@ -174,9 +178,15 @@ function buildDashboardDetailLine(options = {}) {
|
|
|
174
178
|
if (dashboardView === "cron") {
|
|
175
179
|
const items = Array.isArray(cronTasks) ? cronTasks : [];
|
|
176
180
|
const summary = items.length > 0
|
|
177
|
-
? items.map((item) =>
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
? items.map((item, index) => {
|
|
182
|
+
const label = item.label || item.summary || item.id || "";
|
|
183
|
+
if (!label) return "";
|
|
184
|
+
return index === selectedCronIndex
|
|
185
|
+
? `{inverse}${label}{/inverse}`
|
|
186
|
+
: `{cyan-fg}${label}{/cyan-fg}`;
|
|
187
|
+
}).filter(Boolean).join(", ")
|
|
188
|
+
: "{cyan-fg}none{/cyan-fg}";
|
|
189
|
+
content += `{gray-fg}Cron:{/gray-fg} ${summary}`;
|
|
180
190
|
content += ` {gray-fg}│ ${dashHints.cron || ""}{/gray-fg}`;
|
|
181
191
|
return { content, windowStart };
|
|
182
192
|
}
|
|
@@ -220,6 +230,7 @@ function buildDashboardDetailLine(options = {}) {
|
|
|
220
230
|
function computeDashboardContent(options = {}) {
|
|
221
231
|
const {
|
|
222
232
|
globalMode = false,
|
|
233
|
+
globalScope = "controller",
|
|
223
234
|
focusMode = "input",
|
|
224
235
|
dashboardView = "agents",
|
|
225
236
|
activeAgents = [],
|
|
@@ -238,7 +249,9 @@ function computeDashboardContent(options = {}) {
|
|
|
238
249
|
selectedModeIndex = 0,
|
|
239
250
|
selectedProviderIndex = 0,
|
|
240
251
|
selectedResumeIndex = 0,
|
|
252
|
+
selectedCronIndex = -1,
|
|
241
253
|
cronTasks = [],
|
|
254
|
+
pendingReports = 0,
|
|
242
255
|
providerOptions = [],
|
|
243
256
|
resumeOptions = [],
|
|
244
257
|
dashHints = {},
|
|
@@ -247,6 +260,10 @@ function computeDashboardContent(options = {}) {
|
|
|
247
260
|
|
|
248
261
|
if (globalMode) {
|
|
249
262
|
const projectsFocused = focusMode === "dashboard" && dashboardView === "projects";
|
|
263
|
+
let dashboardHint = "";
|
|
264
|
+
if (projectsFocused) {
|
|
265
|
+
dashboardHint = globalScope === "controller" ? "Enter\u2192project" : "Esc\u2192global";
|
|
266
|
+
}
|
|
250
267
|
const rail = buildProjectRailLine({
|
|
251
268
|
projects,
|
|
252
269
|
selectedProjectIndex,
|
|
@@ -254,6 +271,8 @@ function computeDashboardContent(options = {}) {
|
|
|
254
271
|
maxProjectWindow,
|
|
255
272
|
activeProjectRoot,
|
|
256
273
|
projectsFocused,
|
|
274
|
+
globalScope,
|
|
275
|
+
dashboardHint,
|
|
257
276
|
});
|
|
258
277
|
if (!rail.hasProjects) {
|
|
259
278
|
const line2 = ` {gray-fg}${dashHints.projectsEmpty || "Run ufoo chat/daemon in projects to populate registry"}{/gray-fg}`;
|
|
@@ -290,6 +309,7 @@ function computeDashboardContent(options = {}) {
|
|
|
290
309
|
selectedModeIndex,
|
|
291
310
|
selectedProviderIndex,
|
|
292
311
|
selectedResumeIndex,
|
|
312
|
+
selectedCronIndex,
|
|
293
313
|
cronTasks,
|
|
294
314
|
providerOptions,
|
|
295
315
|
resumeOptions,
|
|
@@ -315,6 +335,7 @@ function computeDashboardContent(options = {}) {
|
|
|
315
335
|
selectedModeIndex,
|
|
316
336
|
selectedProviderIndex,
|
|
317
337
|
selectedResumeIndex,
|
|
338
|
+
selectedCronIndex,
|
|
318
339
|
cronTasks,
|
|
319
340
|
providerOptions,
|
|
320
341
|
resumeOptions,
|