u-foo 2.5.6 → 2.5.7
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/bin/ucode.js +9 -0
- package/package.json +1 -1
- package/src/agents/launch/notifier.js +6 -0
- package/src/agents/prompts/native/index.js +1 -1
- package/src/agents/prompts/native/toolDescriptions/edit.js +1 -0
- package/src/code/agent.js +53 -1076
- package/src/code/busConsumer.js +504 -0
- package/src/code/dispatch.js +1 -6
- package/src/code/launcher/ucode.js +3 -251
- package/src/code/launcher/ucodeBootstrap.js +18 -1
- package/src/code/launcher/ucodeBuild.js +0 -3
- package/src/code/launcher/ucodeDoctor.js +24 -9
- package/src/code/launcher/ucodeRuntimeConfig.js +12 -3
- package/src/code/nativeRunner.js +62 -109
- package/src/code/repl.js +610 -0
- package/src/code/sessionStore.js +5 -1
- package/src/code/skills/injection.js +17 -1
- package/src/code/taskDecomposer.js +36 -29
- package/src/code/tools/common.js +34 -0
- package/src/code/tools/edit.js +11 -3
- package/src/coordination/bus/inject.js +52 -7
- package/src/coordination/bus/subscriber.js +33 -6
- package/src/runtime/daemon/deliveryScheduler.js +102 -2
- package/src/runtime/daemon/index.js +8 -1
- package/src/runtime/daemon/ops.js +23 -0
package/src/code/nativeRunner.js
CHANGED
|
@@ -102,104 +102,6 @@ function clipText(value = "", maxChars = 6000) {
|
|
|
102
102
|
return `${text.slice(0, maxChars)}\n...[truncated]`;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function summarizeFileSnippet(file = "", content = "") {
|
|
106
|
-
const target = String(file || "").trim();
|
|
107
|
-
const body = String(content || "").trim();
|
|
108
|
-
if (!body) return `${target}: empty`;
|
|
109
|
-
|
|
110
|
-
if (target.toLowerCase().endsWith("package.json")) {
|
|
111
|
-
try {
|
|
112
|
-
const parsed = JSON.parse(body);
|
|
113
|
-
const name = String(parsed.name || "").trim() || "(unknown)";
|
|
114
|
-
const version = String(parsed.version || "").trim() || "(unknown)";
|
|
115
|
-
const scripts = parsed.scripts && typeof parsed.scripts === "object"
|
|
116
|
-
? Object.keys(parsed.scripts).slice(0, 4)
|
|
117
|
-
: [];
|
|
118
|
-
const scriptText = scripts.length > 0 ? ` scripts=${scripts.join(",")}` : "";
|
|
119
|
-
return `${target}: name=${name} version=${version}${scriptText}`;
|
|
120
|
-
} catch {
|
|
121
|
-
// fall through
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const lines = body
|
|
126
|
-
.split(/\r?\n/)
|
|
127
|
-
.map((line) => line.trim())
|
|
128
|
-
.filter(Boolean)
|
|
129
|
-
.slice(0, 3)
|
|
130
|
-
.map((line) => (line.length > 120 ? `${line.slice(0, 120)}...` : line));
|
|
131
|
-
|
|
132
|
-
if (lines.length === 0) return `${target}: empty`;
|
|
133
|
-
return `${target}: ${lines.join(" | ")}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function extractPreflightEvidence(systemPrompt = "") {
|
|
137
|
-
const source = String(systemPrompt || "");
|
|
138
|
-
if (!source) return [];
|
|
139
|
-
|
|
140
|
-
const results = [];
|
|
141
|
-
const fileRegex = /File:\s*([^\n]+)\n([\s\S]*?)(?=\n---\n(?:File|Command):|$)/g;
|
|
142
|
-
let match = fileRegex.exec(source);
|
|
143
|
-
while (match) {
|
|
144
|
-
const file = String(match[1] || "").trim();
|
|
145
|
-
const content = String(match[2] || "").trim();
|
|
146
|
-
if (file) {
|
|
147
|
-
results.push({ kind: "file", label: file, summary: summarizeFileSnippet(file, content) });
|
|
148
|
-
}
|
|
149
|
-
match = fileRegex.exec(source);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const cmdRegex = /Command:\s*([^\n]+)\n([\s\S]*?)(?=\n---\n(?:File|Command):|$)/g;
|
|
153
|
-
match = cmdRegex.exec(source);
|
|
154
|
-
while (match) {
|
|
155
|
-
const command = String(match[1] || "").trim();
|
|
156
|
-
const output = String(match[2] || "").trim();
|
|
157
|
-
if (command) {
|
|
158
|
-
const clipped = clipText(output, 300).replace(/\s+/g, " ").trim();
|
|
159
|
-
results.push({ kind: "command", label: command, summary: `${command}: ${clipped || "(no output)"}` });
|
|
160
|
-
}
|
|
161
|
-
match = cmdRegex.exec(source);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return results;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function isAnalysisPrompt(text = "") {
|
|
168
|
-
return /(?:analy[sz]e|analysis|review|audit|status|architecture|codebase|repo|project|现状|架构|审查|分析|项目|代码库)/i.test(text);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function parseReadIntent(prompt = "") {
|
|
172
|
-
const text = String(prompt || "");
|
|
173
|
-
const patterns = [
|
|
174
|
-
/(?:\bread\b|\bcat\b|查看|读取)\s+([A-Za-z0-9_./\\-]+(?:\.[A-Za-z0-9._-]+)?)/i,
|
|
175
|
-
/([A-Za-z0-9_./\\-]+\.(?:md|txt|json|js|ts|jsx|tsx|yml|yaml|toml|sh))/i,
|
|
176
|
-
];
|
|
177
|
-
for (const re of patterns) {
|
|
178
|
-
const match = text.match(re);
|
|
179
|
-
if (!match || !match[1]) continue;
|
|
180
|
-
const candidate = String(match[1]).trim().replace(/[),.;:]+$/, "");
|
|
181
|
-
if (!candidate) continue;
|
|
182
|
-
if (candidate.length > 260) continue;
|
|
183
|
-
return candidate;
|
|
184
|
-
}
|
|
185
|
-
return "";
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function parseBashIntent(prompt = "") {
|
|
189
|
-
const text = String(prompt || "").trim();
|
|
190
|
-
if (!text) return "";
|
|
191
|
-
if (
|
|
192
|
-
/\b(ls|dir|tree)\b/i.test(text)
|
|
193
|
-
|| /\b(list|show)\s+(files|dirs|directories|folders)\b/i.test(text)
|
|
194
|
-
|| /列出|目录|文件列表/.test(text)
|
|
195
|
-
) {
|
|
196
|
-
return "ls -la";
|
|
197
|
-
}
|
|
198
|
-
const cmdMatch = text.match(/(?:运行|执行|run|exec(?:ute)?)\s+`([^`]+)`/i);
|
|
199
|
-
if (cmdMatch && cmdMatch[1]) return String(cmdMatch[1]).trim();
|
|
200
|
-
return "";
|
|
201
|
-
}
|
|
202
|
-
|
|
203
105
|
function normalizeProvider(value = "") {
|
|
204
106
|
const text = String(value || "").trim().toLowerCase();
|
|
205
107
|
if (!text) return "";
|
|
@@ -317,6 +219,11 @@ function buildCoreToolSpecs() {
|
|
|
317
219
|
properties: {
|
|
318
220
|
path: { type: "string" },
|
|
319
221
|
content: { type: "string" },
|
|
222
|
+
mode: {
|
|
223
|
+
type: "string",
|
|
224
|
+
enum: ["overwrite", "append"],
|
|
225
|
+
description: 'Write mode: "overwrite" replaces the file (default), "append" adds to its end.',
|
|
226
|
+
},
|
|
320
227
|
append: { type: "boolean" },
|
|
321
228
|
},
|
|
322
229
|
required: ["path", "content"],
|
|
@@ -599,6 +506,7 @@ async function runOpenAiLikeTurn({
|
|
|
599
506
|
const announcedToolNames = new Set();
|
|
600
507
|
let nextSyntheticIndex = 0;
|
|
601
508
|
let lastSyntheticIndex = -1;
|
|
509
|
+
let sawDone = false;
|
|
602
510
|
|
|
603
511
|
while (true) {
|
|
604
512
|
const { done, value } = await reader.read();
|
|
@@ -612,8 +520,11 @@ async function runOpenAiLikeTurn({
|
|
|
612
520
|
const payloadText = parseSseDataBlock(block);
|
|
613
521
|
if (!payloadText) continue;
|
|
614
522
|
if (payloadText === "[DONE]") {
|
|
615
|
-
|
|
616
|
-
|
|
523
|
+
// Stop reading after this batch, but keep the buffered tail and
|
|
524
|
+
// finish the blocks already parsed alongside [DONE] instead of
|
|
525
|
+
// silently dropping them.
|
|
526
|
+
sawDone = true;
|
|
527
|
+
continue;
|
|
617
528
|
}
|
|
618
529
|
|
|
619
530
|
const chunk = parseJsonSafe(payloadText, null);
|
|
@@ -691,6 +602,8 @@ async function runOpenAiLikeTurn({
|
|
|
691
602
|
}
|
|
692
603
|
}
|
|
693
604
|
}
|
|
605
|
+
|
|
606
|
+
if (sawDone) break;
|
|
694
607
|
}
|
|
695
608
|
|
|
696
609
|
if (rawBuffer.trim()) {
|
|
@@ -839,6 +752,9 @@ async function runAnthropicTurn({
|
|
|
839
752
|
const blockMap = new Map();
|
|
840
753
|
let rawBuffer = "";
|
|
841
754
|
let responseText = "";
|
|
755
|
+
let nextSyntheticBlockIndex = 0;
|
|
756
|
+
let lastBlockIndex = -1;
|
|
757
|
+
let sawDone = false;
|
|
842
758
|
|
|
843
759
|
while (true) {
|
|
844
760
|
const { done, value } = await reader.read();
|
|
@@ -850,7 +766,13 @@ async function runAnthropicTurn({
|
|
|
850
766
|
|
|
851
767
|
for (const rawBlock of parsed.blocks) {
|
|
852
768
|
const { event, data } = parseSseEventBlock(rawBlock);
|
|
853
|
-
if (!data
|
|
769
|
+
if (!data) continue;
|
|
770
|
+
if (data === "[DONE]") {
|
|
771
|
+
// Stop reading after this batch instead of waiting for the server
|
|
772
|
+
// to close the connection, mirroring the OpenAI transport.
|
|
773
|
+
sawDone = true;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
854
776
|
|
|
855
777
|
const payloadChunk = parseJsonSafe(data, null);
|
|
856
778
|
if (!payloadChunk || typeof payloadChunk !== "object") continue;
|
|
@@ -863,7 +785,18 @@ async function runAnthropicTurn({
|
|
|
863
785
|
}
|
|
864
786
|
|
|
865
787
|
if (event === "content_block_start") {
|
|
866
|
-
|
|
788
|
+
let index;
|
|
789
|
+
if (Number.isFinite(payloadChunk.index)) {
|
|
790
|
+
index = payloadChunk.index;
|
|
791
|
+
} else {
|
|
792
|
+
// Provider omitted index: each start opens a new block, so give
|
|
793
|
+
// it its own synthetic index instead of collapsing every block
|
|
794
|
+
// into slot 0.
|
|
795
|
+
while (blockMap.has(nextSyntheticBlockIndex)) nextSyntheticBlockIndex += 1;
|
|
796
|
+
index = nextSyntheticBlockIndex;
|
|
797
|
+
nextSyntheticBlockIndex += 1;
|
|
798
|
+
}
|
|
799
|
+
lastBlockIndex = index;
|
|
867
800
|
const contentBlock = payloadChunk.content_block && typeof payloadChunk.content_block === "object"
|
|
868
801
|
? payloadChunk.content_block
|
|
869
802
|
: {};
|
|
@@ -900,7 +833,15 @@ async function runAnthropicTurn({
|
|
|
900
833
|
}
|
|
901
834
|
|
|
902
835
|
if (event === "content_block_delta") {
|
|
903
|
-
|
|
836
|
+
let index;
|
|
837
|
+
if (Number.isFinite(payloadChunk.index)) {
|
|
838
|
+
index = payloadChunk.index;
|
|
839
|
+
} else if (lastBlockIndex >= 0) {
|
|
840
|
+
// No index: continuation of the most recently started block.
|
|
841
|
+
index = lastBlockIndex;
|
|
842
|
+
} else {
|
|
843
|
+
index = 0;
|
|
844
|
+
}
|
|
904
845
|
const delta = payloadChunk.delta && typeof payloadChunk.delta === "object"
|
|
905
846
|
? payloadChunk.delta
|
|
906
847
|
: {};
|
|
@@ -943,6 +884,8 @@ async function runAnthropicTurn({
|
|
|
943
884
|
}
|
|
944
885
|
}
|
|
945
886
|
}
|
|
887
|
+
|
|
888
|
+
if (sawDone) break;
|
|
946
889
|
}
|
|
947
890
|
|
|
948
891
|
const assistantContent = Array.from(blockMap.values())
|
|
@@ -1303,6 +1246,17 @@ async function runNativeAgentTask({
|
|
|
1303
1246
|
const guards = createGuards({ signal, timeoutMs });
|
|
1304
1247
|
const nextSessionId = String(sessionId || "").trim() || `native-${randomUUID()}`;
|
|
1305
1248
|
const promptText = String(prompt || "").trim();
|
|
1249
|
+
// Track every text delta so the error path can return the partial output
|
|
1250
|
+
// the model already produced instead of discarding it.
|
|
1251
|
+
let partialOutput = "";
|
|
1252
|
+
const trackingStreamDelta = (chunk) => {
|
|
1253
|
+
const text = String(chunk || "");
|
|
1254
|
+
if (!text) return;
|
|
1255
|
+
partialOutput += text;
|
|
1256
|
+
if (typeof onStreamDelta === "function") {
|
|
1257
|
+
onStreamDelta(chunk);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1306
1260
|
|
|
1307
1261
|
try {
|
|
1308
1262
|
guards.ensureActive();
|
|
@@ -1336,7 +1290,7 @@ async function runNativeAgentTask({
|
|
|
1336
1290
|
baseUrl: runtime.baseUrl,
|
|
1337
1291
|
apiKey: runtime.apiKey,
|
|
1338
1292
|
timeoutMs,
|
|
1339
|
-
onStreamDelta,
|
|
1293
|
+
onStreamDelta: trackingStreamDelta,
|
|
1340
1294
|
onThinkingDelta,
|
|
1341
1295
|
onPhase,
|
|
1342
1296
|
onToolEvent,
|
|
@@ -1356,14 +1310,16 @@ async function runNativeAgentTask({
|
|
|
1356
1310
|
output: outputText,
|
|
1357
1311
|
messages: cloneMessageList(runResult.messages),
|
|
1358
1312
|
sessionId: nextSessionId,
|
|
1359
|
-
streamed
|
|
1313
|
+
// The loop marks streamed=true whenever it receives a stream callback;
|
|
1314
|
+
// only report it when the caller actually registered one.
|
|
1315
|
+
streamed: Boolean(runResult.streamed) && typeof onStreamDelta === "function",
|
|
1360
1316
|
};
|
|
1361
1317
|
} catch (err) {
|
|
1362
1318
|
const message = err && err.message ? err.message : "native runner failed";
|
|
1363
1319
|
return {
|
|
1364
1320
|
ok: false,
|
|
1365
1321
|
error: message,
|
|
1366
|
-
output:
|
|
1322
|
+
output: partialOutput.trim(),
|
|
1367
1323
|
sessionId: nextSessionId,
|
|
1368
1324
|
streamed: false,
|
|
1369
1325
|
};
|
|
@@ -1372,9 +1328,6 @@ async function runNativeAgentTask({
|
|
|
1372
1328
|
|
|
1373
1329
|
module.exports = {
|
|
1374
1330
|
runNativeAgentTask,
|
|
1375
|
-
parseReadIntent,
|
|
1376
|
-
parseBashIntent,
|
|
1377
|
-
extractPreflightEvidence,
|
|
1378
1331
|
resolveRuntimeConfig,
|
|
1379
1332
|
resolveCompletionUrl,
|
|
1380
1333
|
resolveAnthropicMessagesUrl,
|