u-foo 1.2.14 → 1.2.16
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/package.json +1 -1
- package/src/code/agent.js +74 -3
- package/src/code/tui.js +73 -5
- package/src/online/bridge.js +8 -1
package/package.json
CHANGED
package/src/code/agent.js
CHANGED
|
@@ -43,6 +43,14 @@ function normalizeLine(input = "") {
|
|
|
43
43
|
return String(input || "").trim();
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function parseProbeMarkerCommand(input = "") {
|
|
47
|
+
const text = String(input || "").trim();
|
|
48
|
+
if (!text) return "";
|
|
49
|
+
// Accept only strict probe markers: "<prefix> <single-marker-token>".
|
|
50
|
+
const match = text.match(/^(?:\$ufoo|\/ufoo|ufoo)\s+([A-Za-z0-9][A-Za-z0-9._:-]{0,63})$/);
|
|
51
|
+
return match ? String(match[1] || "").trim() : "";
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
function parseJson(text = "") {
|
|
47
55
|
const raw = String(text || "").trim();
|
|
48
56
|
if (!raw) return {};
|
|
@@ -1209,16 +1217,18 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
1209
1217
|
" help",
|
|
1210
1218
|
" exit|quit",
|
|
1211
1219
|
" ubus|/ubus",
|
|
1220
|
+
" bg|/bg <task>",
|
|
1212
1221
|
" resume <session-id>",
|
|
1213
1222
|
" tool <read|write|edit|bash> <args-json>",
|
|
1214
1223
|
" run <read|write|edit|bash> <args-json>",
|
|
1215
1224
|
].join("\n"),
|
|
1216
1225
|
};
|
|
1217
1226
|
}
|
|
1218
|
-
|
|
1227
|
+
const probeMarker = parseProbeMarkerCommand(text);
|
|
1228
|
+
if (probeMarker) {
|
|
1219
1229
|
return {
|
|
1220
1230
|
kind: "probe",
|
|
1221
|
-
|
|
1231
|
+
marker: probeMarker,
|
|
1222
1232
|
};
|
|
1223
1233
|
}
|
|
1224
1234
|
if (text === "ubus" || text === "/ubus") {
|
|
@@ -1226,6 +1236,26 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
1226
1236
|
kind: "ubus",
|
|
1227
1237
|
};
|
|
1228
1238
|
}
|
|
1239
|
+
if (text === "bg" || text === "/bg") {
|
|
1240
|
+
return {
|
|
1241
|
+
kind: "error",
|
|
1242
|
+
output: "usage: bg <task>",
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
const bgMatch = text.match(/^(?:\/bg|bg)\s+(.+)$/i);
|
|
1246
|
+
if (bgMatch) {
|
|
1247
|
+
const task = String(bgMatch[1] || "").trim();
|
|
1248
|
+
if (!task) {
|
|
1249
|
+
return {
|
|
1250
|
+
kind: "error",
|
|
1251
|
+
output: "usage: bg <task>",
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
return {
|
|
1255
|
+
kind: "nl_bg",
|
|
1256
|
+
task,
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1229
1259
|
const resumeMatch = text.match(/^resume(?:\s+(.+))?$/i);
|
|
1230
1260
|
if (resumeMatch) {
|
|
1231
1261
|
const session = String(resumeMatch[1] || "").trim();
|
|
@@ -1345,6 +1375,8 @@ async function runUcodeCoreAgent({
|
|
|
1345
1375
|
});
|
|
1346
1376
|
return new Promise((resolve) => {
|
|
1347
1377
|
let chain = Promise.resolve();
|
|
1378
|
+
let backgroundSeq = 0;
|
|
1379
|
+
const backgroundRuns = new Map();
|
|
1348
1380
|
const subscriberId = String(process.env.UFOO_SUBSCRIBER_ID || "").trim();
|
|
1349
1381
|
const autoBusEnabled = shouldAutoConsumeBus(subscriberId);
|
|
1350
1382
|
let autoBusTimer = null;
|
|
@@ -1400,6 +1432,38 @@ async function runUcodeCoreAgent({
|
|
|
1400
1432
|
scheduleAutoBus();
|
|
1401
1433
|
}
|
|
1402
1434
|
|
|
1435
|
+
const startBackgroundTask = (task = "") => {
|
|
1436
|
+
backgroundSeq += 1;
|
|
1437
|
+
const jobId = `bg-${Date.now().toString(36)}-${backgroundSeq.toString(36)}`;
|
|
1438
|
+
const bgState = {
|
|
1439
|
+
workspaceRoot: state.workspaceRoot,
|
|
1440
|
+
provider: state.provider,
|
|
1441
|
+
model: state.model,
|
|
1442
|
+
engine: state.engine,
|
|
1443
|
+
context: state.context,
|
|
1444
|
+
nlMessages: Array.isArray(state.nlMessages) ? state.nlMessages.slice() : [],
|
|
1445
|
+
sessionId: "",
|
|
1446
|
+
timeoutMs: state.timeoutMs,
|
|
1447
|
+
jsonOutput: false,
|
|
1448
|
+
};
|
|
1449
|
+
const run = runNaturalLanguageTask(task, bgState)
|
|
1450
|
+
.then((nlResult) => {
|
|
1451
|
+
const summary = String(formatNlResult(nlResult, false) || "").trim();
|
|
1452
|
+
const title = nlResult && nlResult.ok ? "done" : "failed";
|
|
1453
|
+
stdout.write(`[${jobId}] ${title}: ${summary || "no summary"}\n`);
|
|
1454
|
+
printPrompt();
|
|
1455
|
+
})
|
|
1456
|
+
.catch((err) => {
|
|
1457
|
+
stdout.write(`[${jobId}] failed: ${err && err.message ? err.message : "background task failed"}\n`);
|
|
1458
|
+
printPrompt();
|
|
1459
|
+
})
|
|
1460
|
+
.finally(() => {
|
|
1461
|
+
backgroundRuns.delete(jobId);
|
|
1462
|
+
});
|
|
1463
|
+
backgroundRuns.set(jobId, run);
|
|
1464
|
+
return jobId;
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1403
1467
|
const handleLine = async (line) => {
|
|
1404
1468
|
const runtimeWorkspace = String(state.workspaceRoot || workspaceRoot || process.cwd());
|
|
1405
1469
|
const result = runSingleCommand(line, runtimeWorkspace);
|
|
@@ -1407,7 +1471,10 @@ async function runUcodeCoreAgent({
|
|
|
1407
1471
|
rl.close();
|
|
1408
1472
|
return;
|
|
1409
1473
|
}
|
|
1410
|
-
if (result.kind === "
|
|
1474
|
+
if (result.kind === "probe") {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (result.kind === "help" || result.kind === "tool" || result.kind === "error") {
|
|
1411
1478
|
stdout.write(`${result.output}\n`);
|
|
1412
1479
|
}
|
|
1413
1480
|
if (result.kind === "ubus") {
|
|
@@ -1442,6 +1509,10 @@ async function runUcodeCoreAgent({
|
|
|
1442
1509
|
stdout.write(`Resumed session ${resumed.sessionId} (${resumed.restoredMessages} messages).\n`);
|
|
1443
1510
|
}
|
|
1444
1511
|
}
|
|
1512
|
+
if (result.kind === "nl_bg") {
|
|
1513
|
+
const jobId = startBackgroundTask(result.task);
|
|
1514
|
+
stdout.write(`[${jobId}] started in background.\n`);
|
|
1515
|
+
}
|
|
1445
1516
|
if (result.kind === "nl") {
|
|
1446
1517
|
let streamBuffer = null;
|
|
1447
1518
|
let streamedVisible = false;
|
package/src/code/tui.js
CHANGED
|
@@ -468,7 +468,7 @@ function runUcodeTui({
|
|
|
468
468
|
} = {}) {
|
|
469
469
|
return new Promise((resolve) => {
|
|
470
470
|
const blessed = require("blessed");
|
|
471
|
-
const {
|
|
471
|
+
const { execFileSync } = require("child_process");
|
|
472
472
|
const { createChatLayout } = require("../chat/layout");
|
|
473
473
|
const { computeDashboardContent } = require("../chat/dashboardView");
|
|
474
474
|
const { escapeBlessed, stripBlessedTags } = require("../chat/text");
|
|
@@ -490,6 +490,8 @@ function runUcodeTui({
|
|
|
490
490
|
let agentListWindowStart = 0;
|
|
491
491
|
let agentSelectionMode = false;
|
|
492
492
|
let pendingTask = null;
|
|
493
|
+
const backgroundTasks = new Map();
|
|
494
|
+
let backgroundSeq = 0;
|
|
493
495
|
const logRenderState = { inCodeBlock: false };
|
|
494
496
|
const inputHistory = [];
|
|
495
497
|
let historyIndex = -1;
|
|
@@ -900,12 +902,27 @@ function runUcodeTui({
|
|
|
900
902
|
};
|
|
901
903
|
|
|
902
904
|
const updateStatus = (message = "", type = "thinking", options = {}) => {
|
|
905
|
+
const getBackgroundSuffix = () => {
|
|
906
|
+
if (!backgroundTasks || backgroundTasks.size === 0) return "";
|
|
907
|
+
let running = 0;
|
|
908
|
+
let done = 0;
|
|
909
|
+
let failed = 0;
|
|
910
|
+
for (const task of backgroundTasks.values()) {
|
|
911
|
+
const status = String(task && task.status || "").trim().toLowerCase();
|
|
912
|
+
if (status === "running") running += 1;
|
|
913
|
+
else if (status === "done") done += 1;
|
|
914
|
+
else if (status === "failed") failed += 1;
|
|
915
|
+
}
|
|
916
|
+
const total = running + done + failed;
|
|
917
|
+
if (total <= 0) return "";
|
|
918
|
+
return ` · BG ${running}/${done}/${failed}`;
|
|
919
|
+
};
|
|
903
920
|
if (statusInterval) {
|
|
904
921
|
clearInterval(statusInterval);
|
|
905
922
|
statusInterval = null;
|
|
906
923
|
}
|
|
907
924
|
if (!message) {
|
|
908
|
-
statusLine.setContent(
|
|
925
|
+
statusLine.setContent(escapeBlessed(`UCODE · Ready${getBackgroundSuffix()}`));
|
|
909
926
|
screen.render();
|
|
910
927
|
return;
|
|
911
928
|
}
|
|
@@ -918,7 +935,7 @@ function runUcodeTui({
|
|
|
918
935
|
const timerText = showTimer
|
|
919
936
|
? ` (${formatPendingElapsed(Date.now() - startedAt)},esc cancel)`
|
|
920
937
|
: "";
|
|
921
|
-
statusLine.setContent(escapeBlessed(`${indicator} ${message}${timerText}`));
|
|
938
|
+
statusLine.setContent(escapeBlessed(`${indicator} ${message}${timerText}${getBackgroundSuffix()}`));
|
|
922
939
|
statusIndex += 1;
|
|
923
940
|
screen.render();
|
|
924
941
|
};
|
|
@@ -1082,7 +1099,7 @@ function runUcodeTui({
|
|
|
1082
1099
|
if (isBusMessage && targetAgent) {
|
|
1083
1100
|
updateStatus("Sending message...", "typing");
|
|
1084
1101
|
try {
|
|
1085
|
-
|
|
1102
|
+
execFileSync("ufoo", ["bus", "send", targetAgent, actualLine], {
|
|
1086
1103
|
cwd: workspaceRoot,
|
|
1087
1104
|
encoding: "utf8",
|
|
1088
1105
|
});
|
|
@@ -1118,7 +1135,10 @@ function runUcodeTui({
|
|
|
1118
1135
|
}, payload);
|
|
1119
1136
|
return;
|
|
1120
1137
|
}
|
|
1121
|
-
if (result.kind === "
|
|
1138
|
+
if (result.kind === "probe") {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
if (result.kind === "help" || result.kind === "error") {
|
|
1122
1142
|
logText(result.output || "");
|
|
1123
1143
|
return;
|
|
1124
1144
|
}
|
|
@@ -1166,6 +1186,54 @@ function runUcodeTui({
|
|
|
1166
1186
|
return;
|
|
1167
1187
|
}
|
|
1168
1188
|
|
|
1189
|
+
if (result.kind === "nl_bg") {
|
|
1190
|
+
backgroundSeq += 1;
|
|
1191
|
+
const jobId = `bg-${Date.now().toString(36)}-${backgroundSeq.toString(36)}`;
|
|
1192
|
+
const taskRecord = {
|
|
1193
|
+
id: jobId,
|
|
1194
|
+
task: result.task,
|
|
1195
|
+
status: "running",
|
|
1196
|
+
startedAt: Date.now(),
|
|
1197
|
+
summary: "",
|
|
1198
|
+
};
|
|
1199
|
+
backgroundTasks.set(jobId, taskRecord);
|
|
1200
|
+
updateStatus("", "none");
|
|
1201
|
+
logText(`[${jobId}] started in background.`);
|
|
1202
|
+
|
|
1203
|
+
const bgState = {
|
|
1204
|
+
workspaceRoot: state.workspaceRoot,
|
|
1205
|
+
provider: state.provider,
|
|
1206
|
+
model: state.model,
|
|
1207
|
+
engine: state.engine,
|
|
1208
|
+
context: state.context,
|
|
1209
|
+
nlMessages: Array.isArray(state.nlMessages) ? state.nlMessages.slice() : [],
|
|
1210
|
+
sessionId: "",
|
|
1211
|
+
timeoutMs: state.timeoutMs,
|
|
1212
|
+
jsonOutput: false,
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
Promise.resolve()
|
|
1216
|
+
.then(() => runNaturalLanguageTask(result.task, bgState))
|
|
1217
|
+
.then((nlResult) => {
|
|
1218
|
+
taskRecord.status = nlResult && nlResult.ok ? "done" : "failed";
|
|
1219
|
+
taskRecord.finishedAt = Date.now();
|
|
1220
|
+
taskRecord.summary = String(formatNlResult(nlResult, false) || "").trim();
|
|
1221
|
+
const title = taskRecord.status === "done" ? "done" : "failed";
|
|
1222
|
+
logText(`[${jobId}] ${title}: ${taskRecord.summary || "no summary"}`);
|
|
1223
|
+
})
|
|
1224
|
+
.catch((err) => {
|
|
1225
|
+
taskRecord.status = "failed";
|
|
1226
|
+
taskRecord.finishedAt = Date.now();
|
|
1227
|
+
taskRecord.summary = err && err.message ? String(err.message) : "background task failed";
|
|
1228
|
+
logText(`[${jobId}] failed: ${taskRecord.summary}`);
|
|
1229
|
+
})
|
|
1230
|
+
.finally(() => {
|
|
1231
|
+
updateStatus("", "none");
|
|
1232
|
+
screen.render();
|
|
1233
|
+
});
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1169
1237
|
if (result.kind === "nl") {
|
|
1170
1238
|
const statusMessages = [
|
|
1171
1239
|
"Thinking...",
|
package/src/online/bridge.js
CHANGED
|
@@ -129,6 +129,12 @@ function messageSource(msg) {
|
|
|
129
129
|
return "channel";
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
function normalizeOnlineSender(from = "") {
|
|
133
|
+
const text = String(from || "").trim();
|
|
134
|
+
if (!text) return "remote";
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
137
|
+
|
|
132
138
|
function appendToInbox(nickname, msg) {
|
|
133
139
|
const dir = inboxDir();
|
|
134
140
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -322,7 +328,8 @@ class OnlineConnect {
|
|
|
322
328
|
if (!this.eventBus) return;
|
|
323
329
|
const from = msg.from || "remote";
|
|
324
330
|
const text = msg.payload.message || "";
|
|
325
|
-
const
|
|
331
|
+
const sender = normalizeOnlineSender(from);
|
|
332
|
+
const decorated = `[ufoo-online] ${sender}: ${String(text || "").trim()}`;
|
|
326
333
|
try {
|
|
327
334
|
this.eventBus.send("*", decorated, "remote:online");
|
|
328
335
|
} catch {
|