u-foo 2.5.14 → 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 +140 -30
- 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 +313 -27
- 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,7 +34,8 @@ 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).
|
|
@@ -43,7 +58,7 @@ function nowMs() {
|
|
|
43
58
|
|
|
44
59
|
function normalizeTimeoutMs(value) {
|
|
45
60
|
const parsed = Number(value);
|
|
46
|
-
if (!Number.isFinite(parsed)) return
|
|
61
|
+
if (!Number.isFinite(parsed)) return DEFAULT_NATIVE_TIMEOUT_MS;
|
|
47
62
|
return Math.max(1000, Math.floor(parsed));
|
|
48
63
|
}
|
|
49
64
|
|
|
@@ -143,7 +158,7 @@ function enforceNativeToolBudget({
|
|
|
143
158
|
}
|
|
144
159
|
}
|
|
145
160
|
|
|
146
|
-
function createGuards({ signal = null, timeoutMs =
|
|
161
|
+
function createGuards({ signal = null, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS } = {}) {
|
|
147
162
|
const startedAt = nowMs();
|
|
148
163
|
const budgetMs = normalizeTimeoutMs(timeoutMs);
|
|
149
164
|
|
|
@@ -364,6 +379,25 @@ function buildCoreToolSpecs() {
|
|
|
364
379
|
},
|
|
365
380
|
},
|
|
366
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
|
+
},
|
|
367
401
|
];
|
|
368
402
|
}
|
|
369
403
|
|
|
@@ -375,7 +409,7 @@ function buildAnthropicToolSpecs() {
|
|
|
375
409
|
}));
|
|
376
410
|
}
|
|
377
411
|
|
|
378
|
-
function createRequestController({ signal = null, timeoutMs =
|
|
412
|
+
function createRequestController({ signal = null, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS } = {}) {
|
|
379
413
|
const controller = new AbortController();
|
|
380
414
|
let timedOut = false;
|
|
381
415
|
|
|
@@ -437,11 +471,7 @@ function normalizeToolName(value = "") {
|
|
|
437
471
|
}
|
|
438
472
|
|
|
439
473
|
function toJsonString(value) {
|
|
440
|
-
|
|
441
|
-
return JSON.stringify(value);
|
|
442
|
-
} catch {
|
|
443
|
-
return String(value || "");
|
|
444
|
-
}
|
|
474
|
+
return stableStringify(value);
|
|
445
475
|
}
|
|
446
476
|
|
|
447
477
|
function parseSseBlocks(text = "") {
|
|
@@ -490,7 +520,15 @@ function normalizeToolCallArgs(raw = "") {
|
|
|
490
520
|
return {};
|
|
491
521
|
}
|
|
492
522
|
|
|
493
|
-
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
|
+
} = {}) {
|
|
494
532
|
const normalizedTool = normalizeToolName(tool);
|
|
495
533
|
if (!normalizedTool) {
|
|
496
534
|
emitToolEvent(onToolEvent, {
|
|
@@ -506,6 +544,9 @@ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onTo
|
|
|
506
544
|
}
|
|
507
545
|
|
|
508
546
|
const safeArgs = args && typeof args === "object" ? { ...args } : {};
|
|
547
|
+
if (normalizedTool === "artifact_read" && sessionId && !safeArgs.sessionId) {
|
|
548
|
+
safeArgs.sessionId = sessionId;
|
|
549
|
+
}
|
|
509
550
|
emitToolEvent(onToolEvent, {
|
|
510
551
|
tool: normalizedTool,
|
|
511
552
|
phase: "start",
|
|
@@ -513,9 +554,13 @@ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onTo
|
|
|
513
554
|
error: "",
|
|
514
555
|
});
|
|
515
556
|
|
|
557
|
+
const toolOptions = { workspaceRoot, cwd: workspaceRoot };
|
|
558
|
+
if (normalizedTool === "artifact_read" && sessionId) {
|
|
559
|
+
toolOptions.sessionId = sessionId;
|
|
560
|
+
}
|
|
516
561
|
const result = runToolCall(
|
|
517
562
|
{ tool: normalizedTool, args: safeArgs },
|
|
518
|
-
|
|
563
|
+
toolOptions,
|
|
519
564
|
);
|
|
520
565
|
|
|
521
566
|
if (!result || result.ok === false) {
|
|
@@ -525,6 +570,26 @@ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onTo
|
|
|
525
570
|
args: safeArgs,
|
|
526
571
|
error: String((result && result.error) || `${normalizedTool} failed`),
|
|
527
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;
|
|
528
593
|
}
|
|
529
594
|
|
|
530
595
|
return result;
|
|
@@ -548,7 +613,7 @@ async function runSseRequest({
|
|
|
548
613
|
headers = {},
|
|
549
614
|
payload = {},
|
|
550
615
|
signal = null,
|
|
551
|
-
timeoutMs =
|
|
616
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
552
617
|
onPhase = null,
|
|
553
618
|
onNonStream,
|
|
554
619
|
onEvent,
|
|
@@ -640,7 +705,7 @@ async function runOpenAiLikeTurn({
|
|
|
640
705
|
onThinkingDelta = null,
|
|
641
706
|
onPhase = null,
|
|
642
707
|
signal = null,
|
|
643
|
-
timeoutMs =
|
|
708
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
644
709
|
} = {}) {
|
|
645
710
|
const payload = {
|
|
646
711
|
model,
|
|
@@ -890,12 +955,13 @@ async function runAnthropicTurn({
|
|
|
890
955
|
apiKey = "",
|
|
891
956
|
model = "",
|
|
892
957
|
systemPrompt = "",
|
|
958
|
+
systemBlocks = null,
|
|
893
959
|
messages = [],
|
|
894
960
|
onTextDelta = null,
|
|
895
961
|
onThinkingDelta = null,
|
|
896
962
|
onPhase = null,
|
|
897
963
|
signal = null,
|
|
898
|
-
timeoutMs =
|
|
964
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
899
965
|
} = {}) {
|
|
900
966
|
const payload = {
|
|
901
967
|
model,
|
|
@@ -908,17 +974,19 @@ async function runAnthropicTurn({
|
|
|
908
974
|
if (thinkingBudget > 0) {
|
|
909
975
|
payload.thinking = { type: "enabled", budget_tokens: thinkingBudget };
|
|
910
976
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
+
}
|
|
922
990
|
}
|
|
923
991
|
|
|
924
992
|
const headers = {
|
|
@@ -1294,16 +1362,20 @@ async function runNativeLoop({
|
|
|
1294
1362
|
workspaceRoot = process.cwd(),
|
|
1295
1363
|
prompt = "",
|
|
1296
1364
|
systemPrompt = "",
|
|
1365
|
+
systemBlocks = null,
|
|
1297
1366
|
historyMessages = [],
|
|
1298
1367
|
model = "",
|
|
1299
1368
|
baseUrl = "",
|
|
1300
1369
|
apiKey = "",
|
|
1301
1370
|
provider = "",
|
|
1302
|
-
timeoutMs =
|
|
1371
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
1303
1372
|
onStreamDelta = null,
|
|
1304
1373
|
onThinkingDelta = null,
|
|
1305
1374
|
onPhase = null,
|
|
1306
1375
|
onToolEvent = null,
|
|
1376
|
+
onArtifactPersisted = null,
|
|
1377
|
+
sessionId = "",
|
|
1378
|
+
contextV2 = false,
|
|
1307
1379
|
signal = null,
|
|
1308
1380
|
guards,
|
|
1309
1381
|
} = {}) {
|
|
@@ -1317,13 +1389,14 @@ async function runNativeLoop({
|
|
|
1317
1389
|
throw new Error("ucode baseUrl is not configured");
|
|
1318
1390
|
}
|
|
1319
1391
|
|
|
1320
|
-
const messages = cloneMessageList(historyMessages);
|
|
1392
|
+
const messages = sanitizeModelMessages(cloneMessageList(historyMessages));
|
|
1321
1393
|
transport.prepareMessages({ messages, systemPrompt, prompt });
|
|
1322
1394
|
|
|
1323
1395
|
let aggregated = "";
|
|
1324
1396
|
let streamed = false;
|
|
1325
1397
|
let toolCallsExecuted = 0;
|
|
1326
1398
|
let toolErrors = 0;
|
|
1399
|
+
let executionState = emptyExecutionState();
|
|
1327
1400
|
const toolBudget = resolveNativeToolBudget();
|
|
1328
1401
|
const usage = createUsageTotals();
|
|
1329
1402
|
|
|
@@ -1336,6 +1409,7 @@ async function runNativeLoop({
|
|
|
1336
1409
|
model: requestModel,
|
|
1337
1410
|
provider,
|
|
1338
1411
|
systemPrompt,
|
|
1412
|
+
systemBlocks,
|
|
1339
1413
|
messages,
|
|
1340
1414
|
signal,
|
|
1341
1415
|
timeoutMs,
|
|
@@ -1358,8 +1432,34 @@ async function runNativeLoop({
|
|
|
1358
1432
|
const toolCalls = transport.getToolCalls(turnResult);
|
|
1359
1433
|
|
|
1360
1434
|
if (toolCalls.length === 0) {
|
|
1361
|
-
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1362
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 });
|
|
1363
1463
|
if (!aggregated.trim() && text) {
|
|
1364
1464
|
aggregated = text;
|
|
1365
1465
|
}
|
|
@@ -1390,6 +1490,9 @@ async function runNativeLoop({
|
|
|
1390
1490
|
args: pending.args,
|
|
1391
1491
|
workspaceRoot,
|
|
1392
1492
|
onToolEvent,
|
|
1493
|
+
sessionId,
|
|
1494
|
+
contextV2,
|
|
1495
|
+
onArtifactPersisted,
|
|
1393
1496
|
});
|
|
1394
1497
|
toolCallsExecuted += 1;
|
|
1395
1498
|
if (!toolResult || toolResult.ok === false) {
|
|
@@ -1421,15 +1524,18 @@ async function runNativeAgentTask({
|
|
|
1421
1524
|
workspaceRoot = process.cwd(),
|
|
1422
1525
|
prompt = "",
|
|
1423
1526
|
systemPrompt = "",
|
|
1527
|
+
systemBlocks = null,
|
|
1424
1528
|
provider = "",
|
|
1425
1529
|
model = "",
|
|
1426
1530
|
messages = [],
|
|
1427
1531
|
sessionId = "",
|
|
1428
|
-
timeoutMs =
|
|
1532
|
+
timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
|
|
1429
1533
|
onStreamDelta = null,
|
|
1430
1534
|
onThinkingDelta = null,
|
|
1431
1535
|
onPhase = null,
|
|
1432
1536
|
onToolEvent = null,
|
|
1537
|
+
onArtifactPersisted = null,
|
|
1538
|
+
contextV2 = false,
|
|
1433
1539
|
signal = null,
|
|
1434
1540
|
} = {}) {
|
|
1435
1541
|
const guards = createGuards({ signal, timeoutMs });
|
|
@@ -1490,6 +1596,7 @@ async function runNativeAgentTask({
|
|
|
1490
1596
|
workspaceRoot,
|
|
1491
1597
|
prompt: promptText,
|
|
1492
1598
|
systemPrompt,
|
|
1599
|
+
systemBlocks,
|
|
1493
1600
|
historyMessages: messages,
|
|
1494
1601
|
model: runtime.model,
|
|
1495
1602
|
baseUrl: runtime.baseUrl,
|
|
@@ -1500,6 +1607,9 @@ async function runNativeAgentTask({
|
|
|
1500
1607
|
onThinkingDelta,
|
|
1501
1608
|
onPhase,
|
|
1502
1609
|
onToolEvent,
|
|
1610
|
+
onArtifactPersisted,
|
|
1611
|
+
sessionId: nextSessionId,
|
|
1612
|
+
contextV2: contextV2 || isContextV2Enabled(),
|
|
1503
1613
|
signal,
|
|
1504
1614
|
guards,
|
|
1505
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
|
};
|