u-foo 2.3.3 → 2.3.5
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/chat/agentBar.js +3 -3
- package/src/code/agent.js +4 -1
- package/src/code/nativeRunner.js +58 -0
- package/src/code/taskDecomposer.js +4 -3
- package/src/code/tui.js +53 -48
- package/src/daemon/ops.js +11 -6
package/package.json
CHANGED
package/src/chat/agentBar.js
CHANGED
|
@@ -33,8 +33,8 @@ function computeAgentBar(options = {}) {
|
|
|
33
33
|
let windowItems = Math.max(1, Math.min(maxAgentWindow, activeAgents.length));
|
|
34
34
|
let start = agentListWindowStart;
|
|
35
35
|
const ufooItem = focusMode === "dashboard" && selectedAgentIndex === 0
|
|
36
|
-
? "\x1b[90;
|
|
37
|
-
: "\x1b[
|
|
36
|
+
? "\x1b[90;7mufoo\x1b[0m"
|
|
37
|
+
: "\x1b[36mufoo\x1b[0m";
|
|
38
38
|
const ufooLen = stripAnsi(ufooItem).length;
|
|
39
39
|
|
|
40
40
|
const computeStart = (items) => {
|
|
@@ -78,7 +78,7 @@ function computeAgentBar(options = {}) {
|
|
|
78
78
|
const prefix = indicator
|
|
79
79
|
? `${indicatorColor}${indicator}\x1b[0m`
|
|
80
80
|
: "";
|
|
81
|
-
const idx = s + i + 1; // +1 for
|
|
81
|
+
const idx = s + i + 1; // +1 for ufoo chat at index 0
|
|
82
82
|
if (focusMode === "dashboard" && idx === selectedAgentIndex) {
|
|
83
83
|
return `${prefix}\x1b[90;7m${label}\x1b[0m`;
|
|
84
84
|
}
|
package/src/code/agent.js
CHANGED
|
@@ -1119,6 +1119,7 @@ async function runUbusCommand(state = {}, options = {}) {
|
|
|
1119
1119
|
// eslint-disable-next-line no-await-in-loop
|
|
1120
1120
|
nlResult = await runNl(message.task, state, {
|
|
1121
1121
|
onProgress: progressReporter,
|
|
1122
|
+
signal: options.signal,
|
|
1122
1123
|
});
|
|
1123
1124
|
} catch (err) {
|
|
1124
1125
|
sendErrors.push(`task from ${message.publisher} failed: ${err && err.message ? err.message : "task failed"}`);
|
|
@@ -1180,7 +1181,9 @@ async function runUbusCommand(state = {}, options = {}) {
|
|
|
1180
1181
|
});
|
|
1181
1182
|
}
|
|
1182
1183
|
|
|
1183
|
-
const nlResult = await runNl(item.task, state
|
|
1184
|
+
const nlResult = await runNl(item.task, state, {
|
|
1185
|
+
signal: options.signal,
|
|
1186
|
+
});
|
|
1184
1187
|
const reply = String(formatNl(nlResult, false) || "").replace(/\s+/g, " ").trim() || "Done.";
|
|
1185
1188
|
const sendRes = shell(`ufoo bus send ${shellQuote(item.publisher)} ${shellQuote(reply.slice(0, 2000))}`);
|
|
1186
1189
|
if (!sendRes.ok) {
|
package/src/code/nativeRunner.js
CHANGED
|
@@ -9,6 +9,8 @@ const { getBashToolDescription } = require("./prompts/toolDescriptions/bash");
|
|
|
9
9
|
const CORE_TOOL_NAMES = new Set(["read", "write", "edit", "bash"]);
|
|
10
10
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
11
11
|
const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
12
|
+
const DEFAULT_MAX_NATIVE_TOOL_CALLS = 12;
|
|
13
|
+
const DEFAULT_MAX_NATIVE_TOOL_ERRORS = 1;
|
|
12
14
|
|
|
13
15
|
function nowMs() {
|
|
14
16
|
return Date.now();
|
|
@@ -20,6 +22,36 @@ function normalizeTimeoutMs(value) {
|
|
|
20
22
|
return Math.max(1000, Math.floor(parsed));
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
function normalizePositiveInt(value, fallback) {
|
|
26
|
+
const parsed = Number.parseInt(String(value || ""), 10);
|
|
27
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
28
|
+
return Math.floor(parsed);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveNativeToolBudget(env = process.env) {
|
|
32
|
+
return {
|
|
33
|
+
maxToolCalls: normalizePositiveInt(env.UFOO_UCODE_MAX_TOOL_CALLS, DEFAULT_MAX_NATIVE_TOOL_CALLS),
|
|
34
|
+
maxToolErrors: normalizePositiveInt(env.UFOO_UCODE_MAX_TOOL_ERRORS, DEFAULT_MAX_NATIVE_TOOL_ERRORS),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function enforceNativeToolBudget({
|
|
39
|
+
toolCallsExecuted = 0,
|
|
40
|
+
toolErrors = 0,
|
|
41
|
+
maxToolCalls = DEFAULT_MAX_NATIVE_TOOL_CALLS,
|
|
42
|
+
maxToolErrors = DEFAULT_MAX_NATIVE_TOOL_ERRORS,
|
|
43
|
+
lastTool = "",
|
|
44
|
+
lastError = "",
|
|
45
|
+
} = {}) {
|
|
46
|
+
if (toolCallsExecuted > maxToolCalls) {
|
|
47
|
+
throw new Error(`tool call budget exceeded (${maxToolCalls})`);
|
|
48
|
+
}
|
|
49
|
+
if (toolErrors >= maxToolErrors) {
|
|
50
|
+
const detail = [lastTool, lastError].filter(Boolean).join(": ");
|
|
51
|
+
throw new Error(`tool error budget exceeded (${maxToolErrors})${detail ? `: ${detail}` : ""}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
23
55
|
function createGuards({ signal = null, timeoutMs = 300000 } = {}) {
|
|
24
56
|
const startedAt = nowMs();
|
|
25
57
|
const budgetMs = normalizeTimeoutMs(timeoutMs);
|
|
@@ -907,6 +939,8 @@ async function runNativeLoopOpenAi({
|
|
|
907
939
|
let aggregated = "";
|
|
908
940
|
let streamed = false;
|
|
909
941
|
let toolCallsExecuted = 0;
|
|
942
|
+
let toolErrors = 0;
|
|
943
|
+
const toolBudget = resolveNativeToolBudget();
|
|
910
944
|
|
|
911
945
|
while (true) {
|
|
912
946
|
guards.ensureActive();
|
|
@@ -991,6 +1025,17 @@ async function runNativeLoopOpenAi({
|
|
|
991
1025
|
onToolEvent,
|
|
992
1026
|
});
|
|
993
1027
|
toolCallsExecuted += 1;
|
|
1028
|
+
if (!toolResult || toolResult.ok === false) {
|
|
1029
|
+
toolErrors += 1;
|
|
1030
|
+
}
|
|
1031
|
+
enforceNativeToolBudget({
|
|
1032
|
+
toolCallsExecuted,
|
|
1033
|
+
toolErrors,
|
|
1034
|
+
maxToolCalls: toolBudget.maxToolCalls,
|
|
1035
|
+
maxToolErrors: toolBudget.maxToolErrors,
|
|
1036
|
+
lastTool: toolCall.function.name,
|
|
1037
|
+
lastError: toolResult && toolResult.error ? String(toolResult.error) : "",
|
|
1038
|
+
});
|
|
994
1039
|
messages.push({
|
|
995
1040
|
role: "tool",
|
|
996
1041
|
tool_call_id: toolCall.id,
|
|
@@ -1034,6 +1079,8 @@ async function runNativeLoopAnthropic({
|
|
|
1034
1079
|
let aggregated = "";
|
|
1035
1080
|
let streamed = false;
|
|
1036
1081
|
let toolCallsExecuted = 0;
|
|
1082
|
+
let toolErrors = 0;
|
|
1083
|
+
const toolBudget = resolveNativeToolBudget();
|
|
1037
1084
|
|
|
1038
1085
|
while (true) {
|
|
1039
1086
|
guards.ensureActive();
|
|
@@ -1109,6 +1156,17 @@ async function runNativeLoopAnthropic({
|
|
|
1109
1156
|
onToolEvent,
|
|
1110
1157
|
});
|
|
1111
1158
|
toolCallsExecuted += 1;
|
|
1159
|
+
if (!toolResult || toolResult.ok === false) {
|
|
1160
|
+
toolErrors += 1;
|
|
1161
|
+
}
|
|
1162
|
+
enforceNativeToolBudget({
|
|
1163
|
+
toolCallsExecuted,
|
|
1164
|
+
toolErrors,
|
|
1165
|
+
maxToolCalls: toolBudget.maxToolCalls,
|
|
1166
|
+
maxToolErrors: toolBudget.maxToolErrors,
|
|
1167
|
+
lastTool: call.name,
|
|
1168
|
+
lastError: toolResult && toolResult.error ? String(toolResult.error) : "",
|
|
1169
|
+
});
|
|
1112
1170
|
toolResults.push({
|
|
1113
1171
|
type: "tool_result",
|
|
1114
1172
|
tool_use_id: String(call.id || ""),
|
|
@@ -147,8 +147,9 @@ async function runDecomposedTask({
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// Stop on
|
|
151
|
-
|
|
150
|
+
// Stop on any step failure. A failed tool/provider call means the
|
|
151
|
+
// current plan is no longer reliable, and continuing can trigger loops.
|
|
152
|
+
if (!stepResult.ok) {
|
|
152
153
|
return {
|
|
153
154
|
ok: false,
|
|
154
155
|
error: `Failed at ${step.name}: ${stepResult.error}`,
|
|
@@ -266,4 +267,4 @@ module.exports = {
|
|
|
266
267
|
runDecomposedTask,
|
|
267
268
|
compileSummary,
|
|
268
269
|
createBusProgressReporter,
|
|
269
|
-
};
|
|
270
|
+
};
|
package/src/code/tui.js
CHANGED
|
@@ -993,6 +993,7 @@ function runUcodeTui({
|
|
|
993
993
|
const ubusResult = await runUbusCommand(state, {
|
|
994
994
|
workspaceRoot,
|
|
995
995
|
subscriberId: autoBusSubscriberId,
|
|
996
|
+
signal: abortController.signal,
|
|
996
997
|
onMessageReceived: (msg) => {
|
|
997
998
|
// Display the incoming message immediately
|
|
998
999
|
const { extractAgentNickname } = require("./agent");
|
|
@@ -1254,59 +1255,63 @@ function runUcodeTui({
|
|
|
1254
1255
|
});
|
|
1255
1256
|
let streamState = null;
|
|
1256
1257
|
let renderedToolLogCount = 0;
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1258
|
+
let nlResult = null;
|
|
1259
|
+
try {
|
|
1260
|
+
nlResult = await runNaturalLanguageTask(result.task, state, {
|
|
1261
|
+
signal: abortController.signal,
|
|
1262
|
+
onDelta: (delta) => {
|
|
1263
|
+
const text = escapeStripper.write(String(delta || ""));
|
|
1264
|
+
if (!text) return;
|
|
1265
|
+
if (!streamState) {
|
|
1266
|
+
streamState = createNlStreamState();
|
|
1267
|
+
}
|
|
1268
|
+
appendNlStreamDelta(streamState, text);
|
|
1269
|
+
},
|
|
1270
|
+
onToolLog: (entry) => {
|
|
1271
|
+
renderedToolLogCount += 1;
|
|
1272
|
+
logToolHint(entry);
|
|
1273
|
+
},
|
|
1274
|
+
});
|
|
1275
|
+
const tail = escapeStripper.flush();
|
|
1276
|
+
if (tail) {
|
|
1262
1277
|
if (!streamState) {
|
|
1263
1278
|
streamState = createNlStreamState();
|
|
1264
1279
|
}
|
|
1265
|
-
appendNlStreamDelta(streamState,
|
|
1266
|
-
},
|
|
1267
|
-
onToolLog: (entry) => {
|
|
1268
|
-
renderedToolLogCount += 1;
|
|
1269
|
-
logToolHint(entry);
|
|
1270
|
-
},
|
|
1271
|
-
});
|
|
1272
|
-
const tail = escapeStripper.flush();
|
|
1273
|
-
if (tail) {
|
|
1274
|
-
if (!streamState) {
|
|
1275
|
-
streamState = createNlStreamState();
|
|
1280
|
+
appendNlStreamDelta(streamState, tail);
|
|
1276
1281
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
updateStatus("", "none");
|
|
1281
|
-
let finalStreamInfo = { lastChar: "" };
|
|
1282
|
-
if (streamState) {
|
|
1283
|
-
finalStreamInfo = finalizeNlStream(streamState);
|
|
1284
|
-
}
|
|
1285
|
-
if (Array.isArray(nlResult && nlResult.logs) && nlResult.logs.length > renderedToolLogCount) {
|
|
1286
|
-
for (const entry of nlResult.logs.slice(renderedToolLogCount)) {
|
|
1287
|
-
logToolHint(entry);
|
|
1282
|
+
let finalStreamInfo = { lastChar: "" };
|
|
1283
|
+
if (streamState) {
|
|
1284
|
+
finalStreamInfo = finalizeNlStream(streamState);
|
|
1288
1285
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
&&
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1286
|
+
if (Array.isArray(nlResult && nlResult.logs) && nlResult.logs.length > renderedToolLogCount) {
|
|
1287
|
+
for (const entry of nlResult.logs.slice(renderedToolLogCount)) {
|
|
1288
|
+
logToolHint(entry);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const streamed = Boolean(nlResult && nlResult.streamed);
|
|
1292
|
+
const hasVisibleStreamText = Boolean(
|
|
1293
|
+
streamState
|
|
1294
|
+
&& typeof streamState.full === "string"
|
|
1295
|
+
&& /[^\s]/.test(streamState.full)
|
|
1296
|
+
);
|
|
1297
|
+
const streamLastChar = nlResult && typeof nlResult.streamLastChar === "string"
|
|
1298
|
+
? nlResult.streamLastChar.slice(-1)
|
|
1299
|
+
: finalStreamInfo.lastChar;
|
|
1300
|
+
if (streamed && hasVisibleStreamText && streamLastChar !== "\n") {
|
|
1301
|
+
logBox.log("");
|
|
1302
|
+
screen.render();
|
|
1303
|
+
}
|
|
1304
|
+
const shouldSkipSummary = Boolean(streamed && nlResult && nlResult.ok && hasVisibleStreamText);
|
|
1305
|
+
if (!shouldSkipSummary) {
|
|
1306
|
+
logText(formatNlResult(nlResult, false));
|
|
1307
|
+
}
|
|
1308
|
+
const persisted = persistSessionState(state);
|
|
1309
|
+
if (!persisted || persisted.ok === false) {
|
|
1310
|
+
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
1311
|
+
}
|
|
1312
|
+
} finally {
|
|
1313
|
+
pendingTask = null;
|
|
1314
|
+
updateStatus("", "none");
|
|
1310
1315
|
}
|
|
1311
1316
|
}
|
|
1312
1317
|
};
|
package/src/daemon/ops.js
CHANGED
|
@@ -44,6 +44,10 @@ function toTmuxBinary(agent = "") {
|
|
|
44
44
|
return "";
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function resolveUfooRunnerPath() {
|
|
48
|
+
return path.resolve(__dirname, "../../bin/ufoo.js");
|
|
49
|
+
}
|
|
50
|
+
|
|
47
51
|
function normalizeLaunchScope(value, fallback = "inplace") {
|
|
48
52
|
const raw = String(value || "").trim().toLowerCase();
|
|
49
53
|
if (!raw) return fallback;
|
|
@@ -546,7 +550,7 @@ async function spawnManagedHostAgent(
|
|
|
546
550
|
if (hasPreRegisteredSubscriber) {
|
|
547
551
|
// Group mode: use ufoo launcher for activity_state monitoring
|
|
548
552
|
// This enables ReadyDetector and bootstrap to work correctly
|
|
549
|
-
const ufooRunner =
|
|
553
|
+
const ufooRunner = resolveUfooRunnerPath();
|
|
550
554
|
const launchCmd = `${shellEscape(process.execPath)} ${shellEscape(ufooRunner)} agent-pty-runner ${shellEscape(normalizedAgent)}${argText}`.trim();
|
|
551
555
|
runCmd = titleCmd
|
|
552
556
|
? `cd ${shellEscape(projectRoot)} && ${titleCmd} && ${launchCmd}`
|
|
@@ -591,7 +595,7 @@ async function spawnManagedHostAgent(
|
|
|
591
595
|
}
|
|
592
596
|
|
|
593
597
|
async function spawnInternalAgent(projectRoot, agent, count = 1, nickname = "", processManager = null, extraEnv = {}) {
|
|
594
|
-
const runner =
|
|
598
|
+
const runner = resolveUfooRunnerPath();
|
|
595
599
|
const logDir = getUfooPaths(projectRoot).runDir;
|
|
596
600
|
fs.mkdirSync(logDir, { recursive: true });
|
|
597
601
|
|
|
@@ -624,17 +628,18 @@ async function spawnInternalAgent(projectRoot, agent, count = 1, nickname = "",
|
|
|
624
628
|
bus.loadBusData();
|
|
625
629
|
process.env.UFOO_PARENT_PID = String(originalPid);
|
|
626
630
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
631
|
+
const requestedNickname = nickname
|
|
632
|
+
? (count > 1 ? `${nickname}-${i + 1}` : nickname)
|
|
633
|
+
: "";
|
|
630
634
|
const usePty = process.env.UFOO_INTERNAL_PTY !== "0";
|
|
631
635
|
const launchMode = usePty ? "internal-pty" : "internal";
|
|
632
636
|
|
|
633
637
|
// 传递 launch_mode 和 parent PID 到 join
|
|
634
|
-
await bus.subscriberManager.join(sessionId, agentType,
|
|
638
|
+
const joinResult = await bus.subscriberManager.join(sessionId, agentType, requestedNickname, {
|
|
635
639
|
launchMode,
|
|
636
640
|
parentPid: originalPid,
|
|
637
641
|
});
|
|
642
|
+
const finalNickname = joinResult.nickname || requestedNickname || "";
|
|
638
643
|
bus.saveBusData();
|
|
639
644
|
|
|
640
645
|
const runnerCmd = usePty ? "agent-pty-runner" : "agent-runner";
|