u-foo 2.3.27 → 2.3.29
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
CHANGED
|
@@ -52,6 +52,7 @@ function createAgentViewController(options = {}) {
|
|
|
52
52
|
sendResize = () => {},
|
|
53
53
|
requestScreenSnapshot = () => {},
|
|
54
54
|
sendBusWatch = () => {},
|
|
55
|
+
getBusLogHistory = () => [],
|
|
55
56
|
} = options;
|
|
56
57
|
|
|
57
58
|
if (!screen || typeof screen.render !== "function") {
|
|
@@ -503,7 +504,14 @@ function createAgentViewController(options = {}) {
|
|
|
503
504
|
busStartupAgentId = agentId || "";
|
|
504
505
|
const label = getAgentLabel(agentId);
|
|
505
506
|
const startupLines = staticStartupLines(agentId, label, getCols());
|
|
506
|
-
|
|
507
|
+
let historyLines = [];
|
|
508
|
+
try {
|
|
509
|
+
const loaded = getBusLogHistory(agentId);
|
|
510
|
+
historyLines = Array.isArray(loaded) ? loaded : [];
|
|
511
|
+
} catch {
|
|
512
|
+
historyLines = [];
|
|
513
|
+
}
|
|
514
|
+
busLogLines = startupLines.concat("", historyLines);
|
|
507
515
|
busStartupLineCount = startupLines.length;
|
|
508
516
|
}
|
|
509
517
|
|
|
@@ -516,6 +524,13 @@ function createAgentViewController(options = {}) {
|
|
|
516
524
|
busStartupLineCount = startupLines.length;
|
|
517
525
|
}
|
|
518
526
|
|
|
527
|
+
function trimBusLogLines() {
|
|
528
|
+
if (busLogLines.length <= 1000) return;
|
|
529
|
+
const removed = busLogLines.length - 1000;
|
|
530
|
+
busLogLines = busLogLines.slice(-1000);
|
|
531
|
+
busStartupLineCount = Math.max(0, busStartupLineCount - removed);
|
|
532
|
+
}
|
|
533
|
+
|
|
519
534
|
function appendBusLog(text = "") {
|
|
520
535
|
const clean = stripAnsi(String(text || "")).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
521
536
|
if (busLogLines.length === 0) busLogLines.push("");
|
|
@@ -526,9 +541,7 @@ function createAgentViewController(options = {}) {
|
|
|
526
541
|
busLogLines[busLogLines.length - 1] += char;
|
|
527
542
|
}
|
|
528
543
|
}
|
|
529
|
-
|
|
530
|
-
busLogLines = busLogLines.slice(-1000);
|
|
531
|
-
}
|
|
544
|
+
trimBusLogLines();
|
|
532
545
|
if (clean.endsWith("\n")) {
|
|
533
546
|
busAgentReplyActive = false;
|
|
534
547
|
}
|
|
@@ -562,9 +575,7 @@ function createAgentViewController(options = {}) {
|
|
|
562
575
|
}
|
|
563
576
|
busLogLines[busLogLines.length - 1] += char;
|
|
564
577
|
}
|
|
565
|
-
|
|
566
|
-
busLogLines = busLogLines.slice(-1000);
|
|
567
|
-
}
|
|
578
|
+
trimBusLogLines();
|
|
568
579
|
}
|
|
569
580
|
|
|
570
581
|
function getBusInputViewport(width) {
|
|
@@ -654,18 +665,22 @@ function createAgentViewController(options = {}) {
|
|
|
654
665
|
|
|
655
666
|
function enterAgentView(agentId, options = {}) {
|
|
656
667
|
if (currentView === "agent" && viewingAgent === agentId) return;
|
|
668
|
+
const wasInAgentView = currentView === "agent";
|
|
657
669
|
if (currentView === "agent") {
|
|
658
670
|
if (agentViewUsesBus && viewingAgent) sendBusWatch(viewingAgent, false);
|
|
659
671
|
disconnectAgentOutput();
|
|
660
672
|
disconnectAgentInput();
|
|
673
|
+
stopBusStatusTimer();
|
|
661
674
|
}
|
|
662
675
|
|
|
663
676
|
currentView = "agent";
|
|
664
677
|
viewingAgent = agentId;
|
|
665
678
|
setFocusMode("input");
|
|
666
679
|
|
|
667
|
-
|
|
668
|
-
|
|
680
|
+
if (!wasInAgentView) {
|
|
681
|
+
detachedChildren = [...screen.children];
|
|
682
|
+
for (const child of detachedChildren) screen.remove(child);
|
|
683
|
+
}
|
|
669
684
|
|
|
670
685
|
renderFrozen = true;
|
|
671
686
|
|
package/src/chat/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const { createDaemonMessageRouter } = require("./daemonMessageRouter");
|
|
|
38
38
|
const { createChatLogController } = require("./chatLogController");
|
|
39
39
|
const { createPasteController } = require("./pasteController");
|
|
40
40
|
const { createAgentViewController } = require("./agentViewController");
|
|
41
|
+
const { loadInternalAgentLogHistory } = require("./internalAgentLogHistory");
|
|
41
42
|
const { createSettingsController } = require("./settingsController");
|
|
42
43
|
const { createProjectCloseController } = require("./projectCloseController");
|
|
43
44
|
const { createChatLayout } = require("./layout");
|
|
@@ -1628,6 +1629,7 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1628
1629
|
requestScreenSnapshot: () => {
|
|
1629
1630
|
requestSnapshotWithCapabilities();
|
|
1630
1631
|
},
|
|
1632
|
+
getBusLogHistory: (agentId) => loadInternalAgentLogHistory(activeProjectRoot, agentId),
|
|
1631
1633
|
});
|
|
1632
1634
|
|
|
1633
1635
|
function requestStatus() {
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { getUfooPaths } = require("../ufoo/paths");
|
|
6
|
+
|
|
7
|
+
function stripAnsi(text = "") {
|
|
8
|
+
return String(text || "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
|
|
9
|
+
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function decodeEscapedNewlines(text = "") {
|
|
13
|
+
return String(text || "").replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\r/g, "\n");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeText(text = "") {
|
|
17
|
+
return stripAnsi(decodeEscapedNewlines(text)).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readMatchingEventsFromFileReverse(filePath, aliases, limit) {
|
|
21
|
+
if (limit <= 0) return [];
|
|
22
|
+
try {
|
|
23
|
+
const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
24
|
+
const events = [];
|
|
25
|
+
for (let index = lines.length - 1; index >= 0 && events.length < limit; index -= 1) {
|
|
26
|
+
const line = lines[index];
|
|
27
|
+
if (!line) continue;
|
|
28
|
+
let evt = null;
|
|
29
|
+
try {
|
|
30
|
+
evt = JSON.parse(line);
|
|
31
|
+
} catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (isEventForAgent(evt, aliases)) events.push(evt);
|
|
35
|
+
}
|
|
36
|
+
return events;
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildAgentAliases(agentId = "", agents = {}) {
|
|
43
|
+
const aliases = new Set();
|
|
44
|
+
const id = String(agentId || "").trim();
|
|
45
|
+
if (!id) return aliases;
|
|
46
|
+
aliases.add(id);
|
|
47
|
+
const meta = agents && agents[id] ? agents[id] : null;
|
|
48
|
+
if (meta) {
|
|
49
|
+
for (const key of ["nickname", "scoped_nickname", "display_nickname"]) {
|
|
50
|
+
if (meta[key]) aliases.add(String(meta[key]));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return aliases;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function eventData(evt = {}) {
|
|
57
|
+
return evt && evt.data && typeof evt.data === "object" ? evt.data : {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isEventForAgent(evt, aliases) {
|
|
61
|
+
if (!evt || evt.event !== "message") return false;
|
|
62
|
+
const data = eventData(evt);
|
|
63
|
+
const candidates = [
|
|
64
|
+
evt.publisher,
|
|
65
|
+
evt.target,
|
|
66
|
+
data.publisher,
|
|
67
|
+
data.target,
|
|
68
|
+
data.subscriber,
|
|
69
|
+
].filter(Boolean).map(String);
|
|
70
|
+
return candidates.some((value) => aliases.has(value));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseStreamMessage(raw = "") {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(raw);
|
|
76
|
+
if (!parsed || typeof parsed !== "object" || !parsed.stream) return null;
|
|
77
|
+
if (typeof parsed.delta === "string") return normalizeText(parsed.delta);
|
|
78
|
+
if (parsed.done) return "";
|
|
79
|
+
return "";
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function appendPrefixedText(lines, prefix, text) {
|
|
86
|
+
const clean = normalizeText(text);
|
|
87
|
+
if (!clean) return;
|
|
88
|
+
const parts = clean.split("\n");
|
|
89
|
+
parts.forEach((part, index) => {
|
|
90
|
+
if (index === parts.length - 1 && part === "") return;
|
|
91
|
+
lines.push(`${index === 0 ? prefix : " "}${part}`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function appendUserText(lines, text, state) {
|
|
96
|
+
appendPrefixedText(lines, "> ", text);
|
|
97
|
+
state.replyActive = false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function ensureAgentReplyPrefix(lines, prefix = "• ") {
|
|
101
|
+
if (lines.length === 0) {
|
|
102
|
+
lines.push(prefix);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (lines[lines.length - 1] === "") {
|
|
106
|
+
lines[lines.length - 1] = prefix;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
lines.push(prefix);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function appendAgentStreamText(lines, text, state) {
|
|
113
|
+
const clean = normalizeText(text);
|
|
114
|
+
if (!clean) return;
|
|
115
|
+
for (const char of clean) {
|
|
116
|
+
if (char === "\n") {
|
|
117
|
+
lines.push("");
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!state.replyActive) {
|
|
121
|
+
ensureAgentReplyPrefix(lines, "• ");
|
|
122
|
+
state.replyActive = true;
|
|
123
|
+
} else if (lines.length === 0 || lines[lines.length - 1] === "") {
|
|
124
|
+
ensureAgentReplyPrefix(lines, " ");
|
|
125
|
+
}
|
|
126
|
+
lines[lines.length - 1] += char;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function appendAgentMessage(lines, text, state) {
|
|
131
|
+
appendPrefixedText(lines, "• ", text);
|
|
132
|
+
state.replyActive = false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function loadInternalAgentLogHistory(projectRoot, agentId, options = {}) {
|
|
136
|
+
const paths = getUfooPaths(projectRoot || process.cwd());
|
|
137
|
+
const maxEvents = Number.isFinite(options.maxEvents) ? Math.max(1, options.maxEvents) : 400;
|
|
138
|
+
const maxLines = Number.isFinite(options.maxLines) ? Math.max(1, options.maxLines) : 1000;
|
|
139
|
+
let agents = {};
|
|
140
|
+
try {
|
|
141
|
+
const bus = JSON.parse(fs.readFileSync(paths.agentsFile, "utf8"));
|
|
142
|
+
agents = bus.agents || {};
|
|
143
|
+
} catch {
|
|
144
|
+
agents = {};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const aliases = buildAgentAliases(agentId, agents);
|
|
148
|
+
if (aliases.size === 0) return [];
|
|
149
|
+
|
|
150
|
+
let files = [];
|
|
151
|
+
try {
|
|
152
|
+
files = fs.readdirSync(paths.busEventsDir)
|
|
153
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
154
|
+
.sort()
|
|
155
|
+
.map((name) => path.join(paths.busEventsDir, name));
|
|
156
|
+
} catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const reversedEvents = [];
|
|
161
|
+
for (const file of files.slice(-7).reverse()) {
|
|
162
|
+
if (reversedEvents.length >= maxEvents) break;
|
|
163
|
+
const remaining = maxEvents - reversedEvents.length;
|
|
164
|
+
reversedEvents.push(...readMatchingEventsFromFileReverse(file, aliases, remaining));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const lines = [];
|
|
168
|
+
const state = { replyActive: false };
|
|
169
|
+
for (const evt of reversedEvents.reverse()) {
|
|
170
|
+
const data = eventData(evt);
|
|
171
|
+
const rawMessage = typeof data.message === "string" ? data.message : "";
|
|
172
|
+
const streamDelta = parseStreamMessage(rawMessage);
|
|
173
|
+
const publisher = String(evt.publisher || "");
|
|
174
|
+
const target = String(evt.target || data.target || "");
|
|
175
|
+
const isFromAgent = aliases.has(publisher);
|
|
176
|
+
const isToAgent = aliases.has(target) || aliases.has(String(data.subscriber || ""));
|
|
177
|
+
|
|
178
|
+
if (streamDelta !== null) {
|
|
179
|
+
if (isFromAgent && streamDelta) {
|
|
180
|
+
appendAgentStreamText(lines, streamDelta, state);
|
|
181
|
+
} else if (isFromAgent) {
|
|
182
|
+
state.replyActive = false;
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (isFromAgent) {
|
|
188
|
+
appendAgentMessage(lines, rawMessage, state);
|
|
189
|
+
} else if (isToAgent) {
|
|
190
|
+
appendUserText(lines, rawMessage, state);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return lines.slice(-maxLines);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
loadInternalAgentLogHistory,
|
|
199
|
+
buildAgentAliases,
|
|
200
|
+
};
|
package/src/code/nativeRunner.js
CHANGED
|
@@ -9,8 +9,11 @@ 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
|
-
|
|
13
|
-
|
|
12
|
+
// Claude Code SDK defaults to no turn limit; built-in agents cap at 30 (DreamTask)
|
|
13
|
+
// to 200 (fork). We count individual tool calls (not turns), so 100 leaves headroom
|
|
14
|
+
// for non-trivial tasks while still catching runaway loops. Override via env.
|
|
15
|
+
const DEFAULT_MAX_NATIVE_TOOL_CALLS = 100;
|
|
16
|
+
const DEFAULT_MAX_NATIVE_TOOL_ERRORS = 5;
|
|
14
17
|
|
|
15
18
|
function nowMs() {
|
|
16
19
|
return Date.now();
|