u-foo 2.5.13 → 2.5.15
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/agents/prompts/native/environment.js +20 -8
- package/src/code/agent.js +339 -24
- package/src/code/commands.js +61 -0
- package/src/code/context/artifactGc.js +292 -0
- package/src/code/context/artifactIndex.js +161 -0
- package/src/code/context/artifacts.js +183 -0
- package/src/code/context/assembler.js +698 -0
- package/src/code/context/executionSegment.js +314 -0
- package/src/code/context/featureFlag.js +13 -0
- package/src/code/context/index.js +18 -0
- package/src/code/context/projectSnapshot.js +201 -0
- package/src/code/context/promptLayers.js +159 -0
- package/src/code/context/reducers.js +328 -0
- package/src/code/context/stableJson.js +29 -0
- package/src/code/context/stateCommit.js +412 -0
- package/src/code/context/transcript.js +182 -0
- package/src/code/context/transcriptSync.js +106 -0
- package/src/code/context/workingSet.js +323 -0
- package/src/code/dispatch.js +4 -1
- package/src/code/index.js +6 -0
- package/src/code/modelCommand.js +87 -0
- package/src/code/nativeRunner.js +187 -31
- package/src/code/repl.js +36 -32
- package/src/code/sessionStore.js +227 -15
- package/src/code/skills/index.js +10 -0
- package/src/code/skills/injection.js +65 -3
- package/src/code/skills/loader.js +21 -0
- package/src/code/skills/manifest.js +87 -0
- package/src/code/skills/render.js +15 -1
- package/src/code/taskDecomposer.js +32 -2
- package/src/code/tools/artifactRead.js +40 -0
- package/src/code/tui.js +2 -0
- package/src/code/usageStore.js +15 -0
- package/src/ui/format/index.js +260 -44
- package/src/ui/format/markdownRenderer.js +215 -72
- package/src/ui/ink/ChatApp.js +39 -8
- package/src/ui/ink/UcodeApp.js +408 -55
- package/src/ui/ink/chatLogModel.js +102 -21
package/src/code/nativeRunner.js
CHANGED
|
@@ -6,12 +6,26 @@ const {
|
|
|
6
6
|
} = require("../agents/providers/credentials/kimi");
|
|
7
7
|
const { runToolCall } = require("./dispatch");
|
|
8
8
|
const { appendUsageRecord } = require("./usageStore");
|
|
9
|
+
const { isContextV2Enabled } = require("./context/featureFlag");
|
|
10
|
+
const {
|
|
11
|
+
persistToolResultToContext,
|
|
12
|
+
sanitizeModelMessages,
|
|
13
|
+
} = require("./context/assembler");
|
|
14
|
+
const { systemBlocksToAnthropicPayload } = require("./context/promptLayers");
|
|
15
|
+
const { parseStructuredSideEffects } = require("./context/stateCommit");
|
|
16
|
+
const {
|
|
17
|
+
parseExecutionSegment,
|
|
18
|
+
executeExecutionSegment,
|
|
19
|
+
formatSegmentResultMessage,
|
|
20
|
+
emptyExecutionState,
|
|
21
|
+
} = require("./context/executionSegment");
|
|
22
|
+
const { stableStringify } = require("./context/stableJson");
|
|
9
23
|
const { getReadToolDescription } = require("../agents/prompts/native/toolDescriptions/read");
|
|
10
24
|
const { getWriteToolDescription } = require("../agents/prompts/native/toolDescriptions/write");
|
|
11
25
|
const { getEditToolDescription } = require("../agents/prompts/native/toolDescriptions/edit");
|
|
12
26
|
const { getBashToolDescription } = require("../agents/prompts/native/toolDescriptions/bash");
|
|
13
27
|
|
|
14
|
-
const CORE_TOOL_NAMES = new Set(["read", "write", "edit", "bash"]);
|
|
28
|
+
const CORE_TOOL_NAMES = new Set(["read", "write", "edit", "bash", "artifact_read"]);
|
|
15
29
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
16
30
|
const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
17
31
|
const DEFAULT_KIMI_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
@@ -20,12 +34,18 @@ const DEFAULT_KIMI_MODEL = "k3";
|
|
|
20
34
|
// to 200 (fork). We count individual tool calls (not turns), so 100 leaves headroom
|
|
21
35
|
// for non-trivial tasks while still catching runaway loops. Override via env.
|
|
22
36
|
const DEFAULT_MAX_NATIVE_TOOL_CALLS = 100;
|
|
23
|
-
const DEFAULT_MAX_NATIVE_TOOL_ERRORS =
|
|
37
|
+
const DEFAULT_MAX_NATIVE_TOOL_ERRORS = 20;
|
|
38
|
+
const DEFAULT_NATIVE_TIMEOUT_MS = 43200000; // 12 hours
|
|
24
39
|
// Anthropic Messages rejects max_tokens above the model's real cap (64K on
|
|
25
40
|
// current models), so the transports use different defaults. Override either
|
|
26
41
|
// via UFOO_UCODE_MAX_TOKENS (positive integer).
|
|
27
42
|
const DEFAULT_OPENAI_MAX_TOKENS = 131072;
|
|
28
43
|
const DEFAULT_ANTHROPIC_MAX_TOKENS = 64000;
|
|
44
|
+
// Extended thinking is on by default for the anthropic transport; the budget
|
|
45
|
+
// stays well below the 64K max_tokens cap as the Messages API requires.
|
|
46
|
+
// UFOO_UCODE_THINKING_BUDGET_TOKENS overrides; 0 or a non-numeric value
|
|
47
|
+
// disables thinking (the payload then omits the field entirely).
|
|
48
|
+
const DEFAULT_ANTHROPIC_THINKING_BUDGET_TOKENS = 10000;
|
|
29
49
|
// Prompt caching is GA on the current Messages API: cache_control blocks need
|
|
30
50
|
// no anthropic-beta header. Kept as a constant so the marker shape stays in
|
|
31
51
|
// one place (system block + last history message, 2 of the 4 allowed
|
|
@@ -38,7 +58,7 @@ function nowMs() {
|
|
|
38
58
|
|
|
39
59
|
function normalizeTimeoutMs(value) {
|
|
40
60
|
const parsed = Number(value);
|
|
41
|
-
if (!Number.isFinite(parsed)) return
|
|
61
|
+
if (!Number.isFinite(parsed)) return DEFAULT_NATIVE_TIMEOUT_MS;
|
|
42
62
|
return Math.max(1000, Math.floor(parsed));
|
|
43
63
|
}
|
|
44
64
|
|
|
@@ -59,6 +79,16 @@ function resolveMaxTokens(fallback) {
|
|
|
59
79
|
return normalizePositiveInt(process.env.UFOO_UCODE_MAX_TOKENS, fallback);
|
|
60
80
|
}
|
|
61
81
|
|
|
82
|
+
function resolveThinkingBudgetTokens() {
|
|
83
|
+
const raw = process.env.UFOO_UCODE_THINKING_BUDGET_TOKENS;
|
|
84
|
+
if (raw === undefined || raw === null || String(raw).trim() === "") {
|
|
85
|
+
return DEFAULT_ANTHROPIC_THINKING_BUDGET_TOKENS;
|
|
86
|
+
}
|
|
87
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
88
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return 0;
|
|
89
|
+
return Math.floor(parsed);
|
|
90
|
+
}
|
|
91
|
+
|
|
62
92
|
function toUsageInt(value) {
|
|
63
93
|
const parsed = Number(value);
|
|
64
94
|
if (!Number.isFinite(parsed) || parsed <= 0) return 0;
|
|
@@ -128,7 +158,7 @@ function enforceNativeToolBudget({
|
|
|
128
158
|
}
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
function createGuards({ signal = null, timeoutMs =
|
|
161
|
+
function createGuards({ signal = null, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS } = {}) {
|
|
132
162
|
const startedAt = nowMs();
|
|
133
163
|
const budgetMs = normalizeTimeoutMs(timeoutMs);
|
|
134
164
|
|
|
@@ -349,6 +379,25 @@ function buildCoreToolSpecs() {
|
|
|
349
379
|
},
|
|
350
380
|
},
|
|
351
381
|
},
|
|
382
|
+
{
|
|
383
|
+
type: "function",
|
|
384
|
+
function: {
|
|
385
|
+
name: "artifact_read",
|
|
386
|
+
description: "Load a stored artifact by artifactId. Use selectors startLine/endLine, maxChars, or tailLines to read a slice.",
|
|
387
|
+
parameters: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
artifactId: { type: "string" },
|
|
391
|
+
sessionId: { type: "string" },
|
|
392
|
+
startLine: { type: "integer" },
|
|
393
|
+
endLine: { type: "integer" },
|
|
394
|
+
maxChars: { type: "integer" },
|
|
395
|
+
tailLines: { type: "integer" },
|
|
396
|
+
},
|
|
397
|
+
required: ["artifactId"],
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
352
401
|
];
|
|
353
402
|
}
|
|
354
403
|
|
|
@@ -360,7 +409,7 @@ function buildAnthropicToolSpecs() {
|
|
|
360
409
|
}));
|
|
361
410
|
}
|
|
362
411
|
|
|
363
|
-
function createRequestController({ signal = null, timeoutMs =
|
|
412
|
+
function createRequestController({ signal = null, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS } = {}) {
|
|
364
413
|
const controller = new AbortController();
|
|
365
414
|
let timedOut = false;
|
|
366
415
|
|
|
@@ -422,11 +471,7 @@ function normalizeToolName(value = "") {
|
|
|
422
471
|
}
|
|
423
472
|
|
|
424
473
|
function toJsonString(value) {
|
|
425
|
-
|
|
426
|
-
return JSON.stringify(value);
|
|
427
|
-
} catch {
|
|
428
|
-
return String(value || "");
|
|
429
|
-
}
|
|
474
|
+
return stableStringify(value);
|
|
430
475
|
}
|
|
431
476
|
|
|
432
477
|
function parseSseBlocks(text = "") {
|
|
@@ -475,7 +520,15 @@ function normalizeToolCallArgs(raw = "") {
|
|
|
475
520
|
return {};
|
|
476
521
|
}
|
|
477
522
|
|
|
478
|
-
function runCoreTool({
|
|
523
|
+
function runCoreTool({
|
|
524
|
+
tool = "",
|
|
525
|
+
args = {},
|
|
526
|
+
workspaceRoot = process.cwd(),
|
|
527
|
+
onToolEvent = null,
|
|
528
|
+
sessionId = "",
|
|
529
|
+
contextV2 = false,
|
|
530
|
+
onArtifactPersisted = null,
|
|
531
|
+
} = {}) {
|
|
479
532
|
const normalizedTool = normalizeToolName(tool);
|
|
480
533
|
if (!normalizedTool) {
|
|
481
534
|
emitToolEvent(onToolEvent, {
|
|
@@ -491,6 +544,9 @@ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onTo
|
|
|
491
544
|
}
|
|
492
545
|
|
|
493
546
|
const safeArgs = args && typeof args === "object" ? { ...args } : {};
|
|
547
|
+
if (normalizedTool === "artifact_read" && sessionId && !safeArgs.sessionId) {
|
|
548
|
+
safeArgs.sessionId = sessionId;
|
|
549
|
+
}
|
|
494
550
|
emitToolEvent(onToolEvent, {
|
|
495
551
|
tool: normalizedTool,
|
|
496
552
|
phase: "start",
|
|
@@ -498,9 +554,13 @@ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onTo
|
|
|
498
554
|
error: "",
|
|
499
555
|
});
|
|
500
556
|
|
|
557
|
+
const toolOptions = { workspaceRoot, cwd: workspaceRoot };
|
|
558
|
+
if (normalizedTool === "artifact_read" && sessionId) {
|
|
559
|
+
toolOptions.sessionId = sessionId;
|
|
560
|
+
}
|
|
501
561
|
const result = runToolCall(
|
|
502
562
|
{ tool: normalizedTool, args: safeArgs },
|
|
503
|
-
|
|
563
|
+
toolOptions,
|
|
504
564
|
);
|
|
505
565
|
|
|
506
566
|
if (!result || result.ok === false) {
|
|
@@ -510,6 +570,26 @@ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onTo
|
|
|
510
570
|
args: safeArgs,
|
|
511
571
|
error: String((result && result.error) || `${normalizedTool} failed`),
|
|
512
572
|
});
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const useContextV2 = contextV2 || isContextV2Enabled();
|
|
577
|
+
if (useContextV2 && normalizedTool !== "artifact_read") {
|
|
578
|
+
const persisted = persistToolResultToContext({
|
|
579
|
+
workspaceRoot,
|
|
580
|
+
sessionId,
|
|
581
|
+
tool: normalizedTool,
|
|
582
|
+
args: safeArgs,
|
|
583
|
+
rawResult: result,
|
|
584
|
+
});
|
|
585
|
+
if (typeof onArtifactPersisted === "function") {
|
|
586
|
+
try {
|
|
587
|
+
onArtifactPersisted(persisted);
|
|
588
|
+
} catch {
|
|
589
|
+
// ignore
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return persisted.modelPayload || result;
|
|
513
593
|
}
|
|
514
594
|
|
|
515
595
|
return result;
|
|
@@ -533,7 +613,7 @@ async function runSseRequest({
|
|
|
533
613
|
headers = {},
|
|
534
614
|
payload = {},
|
|
535
615
|
signal = null,
|
|
536
|
-
timeoutMs =
|
|
616
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
537
617
|
onPhase = null,
|
|
538
618
|
onNonStream,
|
|
539
619
|
onEvent,
|
|
@@ -625,7 +705,7 @@ async function runOpenAiLikeTurn({
|
|
|
625
705
|
onThinkingDelta = null,
|
|
626
706
|
onPhase = null,
|
|
627
707
|
signal = null,
|
|
628
|
-
timeoutMs =
|
|
708
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
629
709
|
} = {}) {
|
|
630
710
|
const payload = {
|
|
631
711
|
model,
|
|
@@ -794,6 +874,13 @@ function normalizeAnthropicMessageContent(raw = []) {
|
|
|
794
874
|
text: String(item.text || ""),
|
|
795
875
|
};
|
|
796
876
|
}
|
|
877
|
+
if (item.type === "thinking") {
|
|
878
|
+
return {
|
|
879
|
+
type: "thinking",
|
|
880
|
+
thinking: String(item.thinking || ""),
|
|
881
|
+
signature: String(item.signature || ""),
|
|
882
|
+
};
|
|
883
|
+
}
|
|
797
884
|
if (item.type === "tool_use") {
|
|
798
885
|
return {
|
|
799
886
|
type: "tool_use",
|
|
@@ -868,12 +955,13 @@ async function runAnthropicTurn({
|
|
|
868
955
|
apiKey = "",
|
|
869
956
|
model = "",
|
|
870
957
|
systemPrompt = "",
|
|
958
|
+
systemBlocks = null,
|
|
871
959
|
messages = [],
|
|
872
960
|
onTextDelta = null,
|
|
873
961
|
onThinkingDelta = null,
|
|
874
962
|
onPhase = null,
|
|
875
963
|
signal = null,
|
|
876
|
-
timeoutMs =
|
|
964
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
877
965
|
} = {}) {
|
|
878
966
|
const payload = {
|
|
879
967
|
model,
|
|
@@ -882,17 +970,23 @@ async function runAnthropicTurn({
|
|
|
882
970
|
tools: buildAnthropicToolSpecs(),
|
|
883
971
|
stream: true,
|
|
884
972
|
};
|
|
885
|
-
const
|
|
886
|
-
if (
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
973
|
+
const thinkingBudget = resolveThinkingBudgetTokens();
|
|
974
|
+
if (thinkingBudget > 0) {
|
|
975
|
+
payload.thinking = { type: "enabled", budget_tokens: thinkingBudget };
|
|
976
|
+
}
|
|
977
|
+
if (Array.isArray(systemBlocks) && systemBlocks.length > 0) {
|
|
978
|
+
payload.system = systemBlocksToAnthropicPayload(systemBlocks);
|
|
979
|
+
} else {
|
|
980
|
+
const systemText = String(systemPrompt || "").trim();
|
|
981
|
+
if (systemText) {
|
|
982
|
+
payload.system = [
|
|
983
|
+
{
|
|
984
|
+
type: "text",
|
|
985
|
+
text: systemText,
|
|
986
|
+
cache_control: { ...ANTHROPIC_CACHE_CONTROL },
|
|
987
|
+
},
|
|
988
|
+
];
|
|
989
|
+
}
|
|
896
990
|
}
|
|
897
991
|
|
|
898
992
|
const headers = {
|
|
@@ -994,6 +1088,7 @@ async function runAnthropicTurn({
|
|
|
994
1088
|
order: index,
|
|
995
1089
|
type: "thinking",
|
|
996
1090
|
text: String(contentBlock.thinking || ""),
|
|
1091
|
+
signature: String(contentBlock.signature || ""),
|
|
997
1092
|
});
|
|
998
1093
|
} else if (contentBlock.type === "tool_use") {
|
|
999
1094
|
blockMap.set(index, {
|
|
@@ -1058,6 +1153,16 @@ async function runAnthropicTurn({
|
|
|
1058
1153
|
return;
|
|
1059
1154
|
}
|
|
1060
1155
|
|
|
1156
|
+
if (delta.type === "signature_delta") {
|
|
1157
|
+
// Signed thinking blocks must be replayed verbatim on later turns
|
|
1158
|
+
// (tool-use continuation contract), so accumulate the signature
|
|
1159
|
+
// alongside the thinking text.
|
|
1160
|
+
current.type = "thinking";
|
|
1161
|
+
current.signature = `${String(current.signature || "")}${String(delta.signature || "")}`;
|
|
1162
|
+
blockMap.set(index, current);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1061
1166
|
if (delta.type === "input_json_delta") {
|
|
1062
1167
|
current.type = "tool_use";
|
|
1063
1168
|
current.inputJson = `${String(current.inputJson || "")}${String(delta.partial_json || "")}`;
|
|
@@ -1069,8 +1174,17 @@ async function runAnthropicTurn({
|
|
|
1069
1174
|
buildResult: () => {
|
|
1070
1175
|
const assistantContent = Array.from(blockMap.values())
|
|
1071
1176
|
.sort((a, b) => a.order - b.order)
|
|
1072
|
-
.filter((item) => item.type !== "thinking")
|
|
1073
1177
|
.map((item) => {
|
|
1178
|
+
if (item.type === "thinking") {
|
|
1179
|
+
// Kept (with signature) so tool-use continuation turns can
|
|
1180
|
+
// replay the thinking blocks the API requires.
|
|
1181
|
+
return {
|
|
1182
|
+
type: "thinking",
|
|
1183
|
+
thinking: String(item.text || ""),
|
|
1184
|
+
signature: String(item.signature || ""),
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1074
1188
|
if (item.type === "text") {
|
|
1075
1189
|
return {
|
|
1076
1190
|
type: "text",
|
|
@@ -1248,16 +1362,20 @@ async function runNativeLoop({
|
|
|
1248
1362
|
workspaceRoot = process.cwd(),
|
|
1249
1363
|
prompt = "",
|
|
1250
1364
|
systemPrompt = "",
|
|
1365
|
+
systemBlocks = null,
|
|
1251
1366
|
historyMessages = [],
|
|
1252
1367
|
model = "",
|
|
1253
1368
|
baseUrl = "",
|
|
1254
1369
|
apiKey = "",
|
|
1255
1370
|
provider = "",
|
|
1256
|
-
timeoutMs =
|
|
1371
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
1257
1372
|
onStreamDelta = null,
|
|
1258
1373
|
onThinkingDelta = null,
|
|
1259
1374
|
onPhase = null,
|
|
1260
1375
|
onToolEvent = null,
|
|
1376
|
+
onArtifactPersisted = null,
|
|
1377
|
+
sessionId = "",
|
|
1378
|
+
contextV2 = false,
|
|
1261
1379
|
signal = null,
|
|
1262
1380
|
guards,
|
|
1263
1381
|
} = {}) {
|
|
@@ -1271,13 +1389,14 @@ async function runNativeLoop({
|
|
|
1271
1389
|
throw new Error("ucode baseUrl is not configured");
|
|
1272
1390
|
}
|
|
1273
1391
|
|
|
1274
|
-
const messages = cloneMessageList(historyMessages);
|
|
1392
|
+
const messages = sanitizeModelMessages(cloneMessageList(historyMessages));
|
|
1275
1393
|
transport.prepareMessages({ messages, systemPrompt, prompt });
|
|
1276
1394
|
|
|
1277
1395
|
let aggregated = "";
|
|
1278
1396
|
let streamed = false;
|
|
1279
1397
|
let toolCallsExecuted = 0;
|
|
1280
1398
|
let toolErrors = 0;
|
|
1399
|
+
let executionState = emptyExecutionState();
|
|
1281
1400
|
const toolBudget = resolveNativeToolBudget();
|
|
1282
1401
|
const usage = createUsageTotals();
|
|
1283
1402
|
|
|
@@ -1290,6 +1409,7 @@ async function runNativeLoop({
|
|
|
1290
1409
|
model: requestModel,
|
|
1291
1410
|
provider,
|
|
1292
1411
|
systemPrompt,
|
|
1412
|
+
systemBlocks,
|
|
1293
1413
|
messages,
|
|
1294
1414
|
signal,
|
|
1295
1415
|
timeoutMs,
|
|
@@ -1312,8 +1432,34 @@ async function runNativeLoop({
|
|
|
1312
1432
|
const toolCalls = transport.getToolCalls(turnResult);
|
|
1313
1433
|
|
|
1314
1434
|
if (toolCalls.length === 0) {
|
|
1315
|
-
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1316
1435
|
const text = String(turnResult.text || "").trim();
|
|
1436
|
+
if (contextV2) {
|
|
1437
|
+
const sideEffects = parseStructuredSideEffects(text);
|
|
1438
|
+
const segment = parseExecutionSegment(sideEffects);
|
|
1439
|
+
if (segment && segment.steps && segment.steps.length > 0) {
|
|
1440
|
+
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1441
|
+
const exec = executeExecutionSegment({
|
|
1442
|
+
segment,
|
|
1443
|
+
executionState,
|
|
1444
|
+
runStep: ({ tool, args }) => runCoreTool({
|
|
1445
|
+
tool,
|
|
1446
|
+
args,
|
|
1447
|
+
workspaceRoot,
|
|
1448
|
+
onToolEvent,
|
|
1449
|
+
sessionId,
|
|
1450
|
+
contextV2,
|
|
1451
|
+
onArtifactPersisted,
|
|
1452
|
+
}),
|
|
1453
|
+
});
|
|
1454
|
+
executionState = exec.executionState;
|
|
1455
|
+
messages.push({
|
|
1456
|
+
role: "user",
|
|
1457
|
+
content: formatSegmentResultMessage(exec),
|
|
1458
|
+
});
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1317
1463
|
if (!aggregated.trim() && text) {
|
|
1318
1464
|
aggregated = text;
|
|
1319
1465
|
}
|
|
@@ -1344,6 +1490,9 @@ async function runNativeLoop({
|
|
|
1344
1490
|
args: pending.args,
|
|
1345
1491
|
workspaceRoot,
|
|
1346
1492
|
onToolEvent,
|
|
1493
|
+
sessionId,
|
|
1494
|
+
contextV2,
|
|
1495
|
+
onArtifactPersisted,
|
|
1347
1496
|
});
|
|
1348
1497
|
toolCallsExecuted += 1;
|
|
1349
1498
|
if (!toolResult || toolResult.ok === false) {
|
|
@@ -1375,15 +1524,18 @@ async function runNativeAgentTask({
|
|
|
1375
1524
|
workspaceRoot = process.cwd(),
|
|
1376
1525
|
prompt = "",
|
|
1377
1526
|
systemPrompt = "",
|
|
1527
|
+
systemBlocks = null,
|
|
1378
1528
|
provider = "",
|
|
1379
1529
|
model = "",
|
|
1380
1530
|
messages = [],
|
|
1381
1531
|
sessionId = "",
|
|
1382
|
-
timeoutMs =
|
|
1532
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
1383
1533
|
onStreamDelta = null,
|
|
1384
1534
|
onThinkingDelta = null,
|
|
1385
1535
|
onPhase = null,
|
|
1386
1536
|
onToolEvent = null,
|
|
1537
|
+
onArtifactPersisted = null,
|
|
1538
|
+
contextV2 = false,
|
|
1387
1539
|
signal = null,
|
|
1388
1540
|
} = {}) {
|
|
1389
1541
|
const guards = createGuards({ signal, timeoutMs });
|
|
@@ -1444,6 +1596,7 @@ async function runNativeAgentTask({
|
|
|
1444
1596
|
workspaceRoot,
|
|
1445
1597
|
prompt: promptText,
|
|
1446
1598
|
systemPrompt,
|
|
1599
|
+
systemBlocks,
|
|
1447
1600
|
historyMessages: messages,
|
|
1448
1601
|
model: runtime.model,
|
|
1449
1602
|
baseUrl: runtime.baseUrl,
|
|
@@ -1454,6 +1607,9 @@ async function runNativeAgentTask({
|
|
|
1454
1607
|
onThinkingDelta,
|
|
1455
1608
|
onPhase,
|
|
1456
1609
|
onToolEvent,
|
|
1610
|
+
onArtifactPersisted,
|
|
1611
|
+
sessionId: nextSessionId,
|
|
1612
|
+
contextV2: contextV2 || isContextV2Enabled(),
|
|
1457
1613
|
signal,
|
|
1458
1614
|
guards,
|
|
1459
1615
|
});
|
package/src/code/repl.js
CHANGED
|
@@ -21,7 +21,9 @@ const {
|
|
|
21
21
|
getPendingBusCount,
|
|
22
22
|
shouldAutoConsumeBus,
|
|
23
23
|
} = require("./busConsumer");
|
|
24
|
-
const { summarizeSessionUsage } = require("./usageStore");
|
|
24
|
+
const { summarizeSessionUsage, formatSessionUsageStatus } = require("./usageStore");
|
|
25
|
+
const { listUcodeCommandsForHelp } = require("./commands");
|
|
26
|
+
const { applyUcodeModelCommand, suggestUcodeModels } = require("./modelCommand");
|
|
25
27
|
|
|
26
28
|
function printPrompt(stdout = process.stdout) {
|
|
27
29
|
stdout.write("> ");
|
|
@@ -75,40 +77,14 @@ function extractAgentNickname(agentId = "") {
|
|
|
75
77
|
return base;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
function formatSessionUsageStatus(summary = {}) {
|
|
79
|
-
const source = summary && typeof summary === "object" ? summary : {};
|
|
80
|
-
const input = Number(source.input) || 0;
|
|
81
|
-
const output = Number(source.output) || 0;
|
|
82
|
-
const cacheRead = Number(source.cacheRead) || 0;
|
|
83
|
-
const cacheCreation = Number(source.cacheCreation) || 0;
|
|
84
|
-
const denominator = cacheRead + input;
|
|
85
|
-
const hitRate = denominator > 0 ? (cacheRead / denominator) * 100 : 0;
|
|
86
|
-
return [
|
|
87
|
-
`Session tokens: input=${input} output=${output} cache_read=${cacheRead} cache_creation=${cacheCreation}`,
|
|
88
|
-
`Cache hit rate: ${hitRate.toFixed(1)}% (cache_read/(cache_read+input))`,
|
|
89
|
-
].join("\n");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
80
|
function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
93
81
|
const text = normalizeLine(line);
|
|
94
82
|
if (!text) return { kind: "empty" };
|
|
95
|
-
if (text === "exit" || text === "quit") return { kind: "exit" };
|
|
96
|
-
if (text === "help") {
|
|
83
|
+
if (text === "exit" || text === "quit" || text === "/exit" || text === "/quit") return { kind: "exit" };
|
|
84
|
+
if (text === "help" || text === "/help") {
|
|
97
85
|
return {
|
|
98
86
|
kind: "help",
|
|
99
|
-
output:
|
|
100
|
-
"Commands:",
|
|
101
|
-
" help",
|
|
102
|
-
" exit|quit",
|
|
103
|
-
" ubus|/ubus",
|
|
104
|
-
" status|/status",
|
|
105
|
-
" skills [list]",
|
|
106
|
-
" skills show <name>",
|
|
107
|
-
" bg|/bg <task>",
|
|
108
|
-
" resume <session-id>",
|
|
109
|
-
" tool <read|write|edit|bash> <args-json>",
|
|
110
|
-
" run <read|write|edit|bash> <args-json>",
|
|
111
|
-
].join("\n"),
|
|
87
|
+
output: listUcodeCommandsForHelp(),
|
|
112
88
|
};
|
|
113
89
|
}
|
|
114
90
|
const legacyUfooMarker = parseLegacyUfooMarkerCommand(text);
|
|
@@ -128,6 +104,25 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
128
104
|
kind: "status",
|
|
129
105
|
};
|
|
130
106
|
}
|
|
107
|
+
const modelMatch = text.match(/^(?:\/model|model)(?:\s+(.*))?$/i);
|
|
108
|
+
if (modelMatch) {
|
|
109
|
+
const nextModel = String(modelMatch[1] || "").trim();
|
|
110
|
+
if (!nextModel) {
|
|
111
|
+
return { kind: "model", action: "show" };
|
|
112
|
+
}
|
|
113
|
+
// Reject accidental multi-token garbage; model ids are single tokens.
|
|
114
|
+
if (/\s/.test(nextModel)) {
|
|
115
|
+
return {
|
|
116
|
+
kind: "error",
|
|
117
|
+
output: "usage: /model [model-id]",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
kind: "model",
|
|
122
|
+
action: "set",
|
|
123
|
+
model: nextModel,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
131
126
|
const skillsMatch = text.match(/^(?:\/skills|skills)(?:\s+(.*))?$/i);
|
|
132
127
|
if (skillsMatch) {
|
|
133
128
|
const args = String(skillsMatch[1] || "").trim().split(/\s+/).filter(Boolean);
|
|
@@ -187,13 +182,13 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
187
182
|
task,
|
|
188
183
|
};
|
|
189
184
|
}
|
|
190
|
-
const resumeMatch = text.match(/^resume(?:\s+(.+))?$/i);
|
|
185
|
+
const resumeMatch = text.match(/^(?:\/resume|resume)(?:\s+(.+))?$/i);
|
|
191
186
|
if (resumeMatch) {
|
|
192
187
|
const session = String(resumeMatch[1] || "").trim();
|
|
193
188
|
if (!session) {
|
|
194
189
|
return {
|
|
195
190
|
kind: "error",
|
|
196
|
-
output: "usage: resume <session-id>",
|
|
191
|
+
output: "usage: /resume <session-id>",
|
|
197
192
|
};
|
|
198
193
|
}
|
|
199
194
|
return {
|
|
@@ -439,6 +434,13 @@ async function runUcodeCoreAgent({
|
|
|
439
434
|
});
|
|
440
435
|
stdout.write(`${formatSessionUsageStatus(usageSummary)}\n`);
|
|
441
436
|
}
|
|
437
|
+
if (result.kind === "model") {
|
|
438
|
+
const applied = applyUcodeModelCommand(state, result);
|
|
439
|
+
stdout.write(`${applied.output}\n`);
|
|
440
|
+
if (applied.ok && result.action === "set") {
|
|
441
|
+
persistSessionState(state);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
442
444
|
if (result.kind === "ubus") {
|
|
443
445
|
const ubusResult = await runUbusCommand(state, {
|
|
444
446
|
workspaceRoot: runtimeWorkspace,
|
|
@@ -637,4 +639,6 @@ module.exports = {
|
|
|
637
639
|
extractAgentNickname,
|
|
638
640
|
parseAgentArgs,
|
|
639
641
|
formatSessionUsageStatus,
|
|
642
|
+
applyUcodeModelCommand,
|
|
643
|
+
suggestUcodeModels,
|
|
640
644
|
};
|