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/package.json
CHANGED
|
@@ -24,13 +24,12 @@ function getShellName() {
|
|
|
24
24
|
return shell || "unknown";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function
|
|
27
|
+
function getSessionStableEnvironmentSection({ workspaceRoot = "", model = "", provider = "" } = {}) {
|
|
28
28
|
const cwd = workspaceRoot || process.cwd();
|
|
29
29
|
const isGit = getIsGit(cwd);
|
|
30
30
|
const platform = process.platform;
|
|
31
31
|
const shell = getShellName();
|
|
32
32
|
const osInfo = `${os.type()} ${os.release()}`;
|
|
33
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
34
33
|
|
|
35
34
|
const lines = [
|
|
36
35
|
`Working directory: ${cwd}`,
|
|
@@ -38,16 +37,11 @@ function getEnvironmentSection({ workspaceRoot = "", model = "", provider = "" }
|
|
|
38
37
|
`Platform: ${platform}`,
|
|
39
38
|
`Shell: ${shell}`,
|
|
40
39
|
`OS: ${osInfo}`,
|
|
41
|
-
`Date: ${date}`,
|
|
42
40
|
];
|
|
43
41
|
|
|
44
42
|
if (provider) lines.push(`Provider: ${provider}`);
|
|
45
43
|
if (model) lines.push(`Model: ${model}`);
|
|
46
44
|
|
|
47
|
-
// Tell the model who it is on the bus. Without this, the only identities
|
|
48
|
-
// it ever sees are other agents' records in shared context — and it
|
|
49
|
-
// adopts them (observed in the wild: ucode-3 introducing itself as
|
|
50
|
-
// claude-6, then accepting a wrong name from the user).
|
|
51
45
|
const subscriberId = String(process.env.UFOO_SUBSCRIBER_ID || "").trim();
|
|
52
46
|
const nickname = String(process.env.UFOO_NICKNAME || "").trim();
|
|
53
47
|
if (subscriberId || nickname) {
|
|
@@ -58,4 +52,22 @@ function getEnvironmentSection({ workspaceRoot = "", model = "", provider = "" }
|
|
|
58
52
|
return `# Environment\n${lines.map((l) => ` - ${l}`).join("\n")}`;
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
|
|
55
|
+
function getTurnDynamicEnvironmentSection({ workspaceRoot = "" } = {}) {
|
|
56
|
+
const cwd = workspaceRoot || process.cwd();
|
|
57
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
58
|
+
return `# Turn Environment\n - Date: ${date}\n - Working directory (current): ${cwd}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getEnvironmentSection(options = {}) {
|
|
62
|
+
return [
|
|
63
|
+
getSessionStableEnvironmentSection(options),
|
|
64
|
+
getTurnDynamicEnvironmentSection(options),
|
|
65
|
+
].join("\n\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
getEnvironmentSection,
|
|
70
|
+
getSessionStableEnvironmentSection,
|
|
71
|
+
getTurnDynamicEnvironmentSection,
|
|
72
|
+
getIsGit,
|
|
73
|
+
};
|
package/src/code/agent.js
CHANGED
|
@@ -12,6 +12,31 @@ const {
|
|
|
12
12
|
} = require("./sessionStore");
|
|
13
13
|
const { buildPromptContext } = require("../agents/prompts/native");
|
|
14
14
|
const { buildSkillInjections } = require("./skills");
|
|
15
|
+
const { isContextV2Enabled } = require("./context/featureFlag");
|
|
16
|
+
const {
|
|
17
|
+
assembleModelContext,
|
|
18
|
+
syncMessagesToTranscript,
|
|
19
|
+
applyContextSideEffects,
|
|
20
|
+
ensureProjectSnapshot,
|
|
21
|
+
recordToolCallInSession,
|
|
22
|
+
commitAfterSegmentEnd,
|
|
23
|
+
sanitizeModelMessages,
|
|
24
|
+
} = require("./context/assembler");
|
|
25
|
+
const { buildLayeredSystemPrompt } = require("./context/promptLayers");
|
|
26
|
+
const {
|
|
27
|
+
createProjectPreflightContextV2,
|
|
28
|
+
} = require("./context/projectSnapshot");
|
|
29
|
+
const {
|
|
30
|
+
ensureTaskContract,
|
|
31
|
+
ensureStateEpoch,
|
|
32
|
+
parseStructuredSideEffects,
|
|
33
|
+
patchTaskContractFromUserMessage,
|
|
34
|
+
} = require("./context/stateCommit");
|
|
35
|
+
const { applyWorkingSetPlan } = require("./context/workingSet");
|
|
36
|
+
const {
|
|
37
|
+
parseExecutionSegment,
|
|
38
|
+
executeExecutionSegment,
|
|
39
|
+
} = require("./context/executionSegment");
|
|
15
40
|
const {
|
|
16
41
|
runUbusCommand,
|
|
17
42
|
parseBusCheckOutput,
|
|
@@ -34,6 +59,104 @@ const {
|
|
|
34
59
|
parseAgentArgs,
|
|
35
60
|
} = require("./repl");
|
|
36
61
|
|
|
62
|
+
function ensureContextSessionState(state = {}) {
|
|
63
|
+
if (!Array.isArray(state.workingSet)) state.workingSet = [];
|
|
64
|
+
if (!state.executionState || typeof state.executionState !== "object") {
|
|
65
|
+
const { emptyExecutionState } = require("./context/executionSegment");
|
|
66
|
+
state.executionState = emptyExecutionState();
|
|
67
|
+
}
|
|
68
|
+
if (!state.contextPolicy || typeof state.contextPolicy !== "object") {
|
|
69
|
+
const { defaultContextPolicy } = require("./context/assembler");
|
|
70
|
+
state.contextPolicy = defaultContextPolicy();
|
|
71
|
+
}
|
|
72
|
+
if (!Number.isFinite(state.toolCallsSinceCommit)) state.toolCallsSinceCommit = 0;
|
|
73
|
+
ensureStateEpoch(state);
|
|
74
|
+
return state;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function buildSkillBodyBlocks(skillInjections = {}) {
|
|
78
|
+
const blocks = Array.isArray(skillInjections.blocks) ? skillInjections.blocks : [];
|
|
79
|
+
return blocks.map((block) => {
|
|
80
|
+
const text = String(block || "");
|
|
81
|
+
if (text.includes("<active_skill>")) return text;
|
|
82
|
+
return text.replace(/^<skill>/, "<active_skill>").replace(/<\/skill>/, "</active_skill>");
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runExecutionSegmentSteps({
|
|
87
|
+
segment = {},
|
|
88
|
+
workspaceRoot = process.cwd(),
|
|
89
|
+
sessionId = "",
|
|
90
|
+
state = {},
|
|
91
|
+
pushToolLog = () => null,
|
|
92
|
+
} = {}) {
|
|
93
|
+
const exec = executeExecutionSegment({
|
|
94
|
+
segment,
|
|
95
|
+
executionState: state.executionState,
|
|
96
|
+
onStepStart: ({ tool, args }) => {
|
|
97
|
+
pushToolLog({ tool, phase: "start", args, error: "" });
|
|
98
|
+
},
|
|
99
|
+
onStepComplete: ({ tool, args, result }) => {
|
|
100
|
+
pushToolLog({
|
|
101
|
+
tool,
|
|
102
|
+
phase: result && result.ok === false ? "error" : "",
|
|
103
|
+
args,
|
|
104
|
+
error: result && result.ok === false ? String(result.error || "") : "",
|
|
105
|
+
});
|
|
106
|
+
if (isContextV2Enabled() && result && result.ok !== false) {
|
|
107
|
+
const plan = require("./context/workingSet").defaultContextPlanFromToolEvent(
|
|
108
|
+
tool,
|
|
109
|
+
result.artifactId,
|
|
110
|
+
args,
|
|
111
|
+
);
|
|
112
|
+
if (plan) state.workingSet = applyWorkingSetPlan(state.workingSet, plan, state);
|
|
113
|
+
}
|
|
114
|
+
if ((tool === "write" || tool === "edit") && args && args.path) {
|
|
115
|
+
const filePath = String(args.path);
|
|
116
|
+
if (!state.executionState || typeof state.executionState !== "object") {
|
|
117
|
+
state.executionState = require("./context/executionSegment").emptyExecutionState();
|
|
118
|
+
}
|
|
119
|
+
const files = Array.isArray(state.executionState.modifiedFiles)
|
|
120
|
+
? state.executionState.modifiedFiles.slice()
|
|
121
|
+
: [];
|
|
122
|
+
if (!files.includes(filePath)) files.push(filePath);
|
|
123
|
+
state.executionState.modifiedFiles = files;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
runStep: ({ tool, args }) => {
|
|
127
|
+
const { runToolCall: dispatchToolCall } = require("./dispatch");
|
|
128
|
+
const { persistToolResultToContext } = require("./context/assembler");
|
|
129
|
+
const result = dispatchToolCall(
|
|
130
|
+
{ tool, args },
|
|
131
|
+
{ workspaceRoot, cwd: workspaceRoot, sessionId },
|
|
132
|
+
);
|
|
133
|
+
if (!result || result.ok === false || !isContextV2Enabled()) {
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
const persisted = persistToolResultToContext({
|
|
137
|
+
workspaceRoot,
|
|
138
|
+
sessionId,
|
|
139
|
+
tool,
|
|
140
|
+
args,
|
|
141
|
+
rawResult: result,
|
|
142
|
+
});
|
|
143
|
+
recordToolCallInSession(state, persisted, workspaceRoot);
|
|
144
|
+
return persisted.modelPayload || result;
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
state.executionState = exec.executionState;
|
|
148
|
+
if (isContextV2Enabled()) {
|
|
149
|
+
commitAfterSegmentEnd(state, exec, workspaceRoot);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
ok: exec.ok,
|
|
153
|
+
segmentId: exec.segmentId,
|
|
154
|
+
error: exec.error,
|
|
155
|
+
stoppedAt: exec.stoppedAt,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
37
160
|
function readTextOrFile(value = "") {
|
|
38
161
|
const raw = String(value || "").trim();
|
|
39
162
|
if (!raw) return "";
|
|
@@ -137,14 +260,14 @@ function isCliCancelledError(message = "") {
|
|
|
137
260
|
}
|
|
138
261
|
|
|
139
262
|
function computeExtendedTimeout(baseTimeoutMs) {
|
|
140
|
-
const base = Number.isFinite(baseTimeoutMs) ? Math.max(1000, Math.floor(baseTimeoutMs)) :
|
|
141
|
-
return Math.min(
|
|
263
|
+
const base = Number.isFinite(baseTimeoutMs) ? Math.max(1000, Math.floor(baseTimeoutMs)) : 43200000;
|
|
264
|
+
return Math.min(43200000, Math.max(base * 2, base + 120000));
|
|
142
265
|
}
|
|
143
266
|
|
|
144
267
|
// Reasoning models routinely blew the old 10min budget across a multi-turn
|
|
145
|
-
// tool loop. Total per-task budget defaults to
|
|
268
|
+
// tool loop. Total per-task budget defaults to 12h and can be raised per
|
|
146
269
|
// call, via --timeout-ms, or via UFOO_UCODE_TASK_TIMEOUT_MS.
|
|
147
|
-
const DEFAULT_NL_TASK_TIMEOUT_MS =
|
|
270
|
+
const DEFAULT_NL_TASK_TIMEOUT_MS = 43200000;
|
|
148
271
|
|
|
149
272
|
function resolveNlTaskTimeoutMs(value) {
|
|
150
273
|
if (Number.isFinite(value) && value > 0) return Math.max(1000, Math.floor(value));
|
|
@@ -190,7 +313,7 @@ function normalizeToolLogEvent(event = {}) {
|
|
|
190
313
|
if (!event || typeof event !== "object") return null;
|
|
191
314
|
const tool = String(event.tool || event.name || "").trim().toLowerCase();
|
|
192
315
|
if (!tool) return null;
|
|
193
|
-
if (tool !== "read" && tool !== "write" && tool !== "edit" && tool !== "bash") return null;
|
|
316
|
+
if (tool !== "read" && tool !== "write" && tool !== "edit" && tool !== "bash" && tool !== "artifact_read") return null;
|
|
194
317
|
const phase = String(event.phase || "update").trim().toLowerCase();
|
|
195
318
|
const normalizedPhase = phase === "error" ? "error" : (phase === "start" ? "start" : "");
|
|
196
319
|
if (!normalizedPhase) return null;
|
|
@@ -247,7 +370,7 @@ function pushSkillWarning(logs = [], onToolLog = null, warning = "") {
|
|
|
247
370
|
|
|
248
371
|
function stripSkillBlocksFromText(value = "") {
|
|
249
372
|
return String(value || "")
|
|
250
|
-
.replace(/<skill>\s*[\s\S]*?<\/skill>\s*/
|
|
373
|
+
.replace(/<(?:active_)?skill>\s*[\s\S]*?<\/(?:active_)?skill>\s*/gi, "")
|
|
251
374
|
.trim();
|
|
252
375
|
}
|
|
253
376
|
|
|
@@ -427,28 +550,80 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
427
550
|
const useDecomposition = isBugFixTask && !options.disableDecomposition;
|
|
428
551
|
const analysisTask = isProjectAnalysisTask(taskText);
|
|
429
552
|
const workspaceRoot = String(state.workspaceRoot || process.cwd());
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
553
|
+
const contextV2 = isContextV2Enabled();
|
|
554
|
+
if (contextV2) ensureContextSessionState(state);
|
|
555
|
+
|
|
556
|
+
let preflightContext = "";
|
|
557
|
+
let projectSnapshot = state.projectSnapshot || null;
|
|
558
|
+
if (analysisTask) {
|
|
559
|
+
if (contextV2) {
|
|
560
|
+
projectSnapshot = createProjectPreflightContextV2({
|
|
561
|
+
workspaceRoot,
|
|
562
|
+
sessionId: String(state.sessionId || ""),
|
|
563
|
+
pushToolLog,
|
|
564
|
+
existingSnapshot: state.projectSnapshot,
|
|
565
|
+
});
|
|
566
|
+
state.projectSnapshot = projectSnapshot;
|
|
567
|
+
} else {
|
|
568
|
+
preflightContext = createProjectPreflightContext({
|
|
569
|
+
workspaceRoot,
|
|
570
|
+
pushToolLog,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
} else if (contextV2) {
|
|
574
|
+
projectSnapshot = ensureProjectSnapshot(state, workspaceRoot);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (contextV2) {
|
|
578
|
+
ensureTaskContract(state, taskText);
|
|
579
|
+
state.taskContract = patchTaskContractFromUserMessage(state.taskContract, taskText);
|
|
580
|
+
}
|
|
581
|
+
|
|
436
582
|
const taskPrompt = analysisTask
|
|
437
583
|
? `${taskText}\n\nAnalysis requirements:\n- Inspect repository evidence before concluding.\n- Cite concrete file observations.\n- Keep findings concise and actionable.`
|
|
438
584
|
: taskText;
|
|
439
585
|
const skillInjections = buildSkillInjections({
|
|
440
586
|
prompt: taskPrompt,
|
|
441
587
|
workspaceRoot,
|
|
588
|
+
sessionId: String(state.sessionId || ""),
|
|
589
|
+
persistBodies: contextV2,
|
|
590
|
+
useActiveSkillTag: contextV2,
|
|
442
591
|
});
|
|
443
592
|
for (const warning of skillInjections.warnings || []) {
|
|
444
593
|
pushSkillWarning(logs, onToolLog, warning);
|
|
445
594
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
.
|
|
595
|
+
if (contextV2 && Array.isArray(skillInjections.activeSkills) && skillInjections.activeSkills.length > 0) {
|
|
596
|
+
state.activeSkills = skillInjections.activeSkills;
|
|
597
|
+
}
|
|
598
|
+
const skillBodyBlocks = contextV2
|
|
599
|
+
? buildSkillBodyBlocks(skillInjections)
|
|
600
|
+
: (skillInjections.blocks || []);
|
|
601
|
+
// v1: skill body rides in the user prompt.
|
|
602
|
+
// v2: skill body goes only into turnDynamic (system layered prompt) to avoid
|
|
603
|
+
// double injection and mixed system/user privilege semantics.
|
|
604
|
+
const effectiveTaskPrompt = contextV2
|
|
605
|
+
? taskPrompt
|
|
606
|
+
: (skillBodyBlocks.length > 0
|
|
607
|
+
? `${skillBodyBlocks.join("\n\n")}\n\n${taskPrompt}`
|
|
608
|
+
: taskPrompt);
|
|
609
|
+
|
|
610
|
+
let assembled = null;
|
|
611
|
+
let systemContext = "";
|
|
612
|
+
if (contextV2) {
|
|
613
|
+
assembled = assembleModelContext(state, {
|
|
614
|
+
workspaceRoot,
|
|
615
|
+
model,
|
|
616
|
+
provider,
|
|
617
|
+
turnDynamic: skillBodyBlocks.join("\n\n"),
|
|
618
|
+
latestUserMessage: effectiveTaskPrompt,
|
|
619
|
+
});
|
|
620
|
+
systemContext = assembled.systemPrompt;
|
|
621
|
+
state.summary = assembled.summary || state.summary;
|
|
622
|
+
} else {
|
|
623
|
+
systemContext = [String(state.context || "").trim(), preflightContext]
|
|
624
|
+
.filter(Boolean)
|
|
625
|
+
.join("\n\n");
|
|
626
|
+
}
|
|
452
627
|
|
|
453
628
|
const onStream = onDelta
|
|
454
629
|
? (delta) => {
|
|
@@ -468,20 +643,32 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
468
643
|
: runNativeAgentTask;
|
|
469
644
|
const onPhase = typeof options.onPhase === "function" ? options.onPhase : null;
|
|
470
645
|
const onThinkingDelta = typeof options.onThinkingDelta === "function" ? options.onThinkingDelta : null;
|
|
646
|
+
let lastTranscriptBaseline = 0;
|
|
471
647
|
const invokeNative = (sessionIdValue = "", timeoutOverrideMs = timeoutMs) => {
|
|
472
648
|
toolEventsThisAttempt = 0;
|
|
649
|
+
const historyMessages = contextV2 && assembled
|
|
650
|
+
? assembled.messages
|
|
651
|
+
: (Array.isArray(state.nlMessages) ? state.nlMessages : []);
|
|
652
|
+
// Sanitized length matches what nativeRunner clones before appending this
|
|
653
|
+
// turn's user/tool/assistant messages — used as the transcript sync baseline.
|
|
654
|
+
lastTranscriptBaseline = sanitizeModelMessages(historyMessages).length;
|
|
473
655
|
return runNativeAgentImpl({
|
|
474
656
|
workspaceRoot,
|
|
475
657
|
provider,
|
|
476
658
|
model,
|
|
477
659
|
prompt: effectiveTaskPrompt,
|
|
478
660
|
systemPrompt: systemContext,
|
|
479
|
-
|
|
480
|
-
|
|
661
|
+
systemBlocks: contextV2 && assembled ? assembled.systemBlocks : null,
|
|
662
|
+
messages: historyMessages,
|
|
663
|
+
sessionId: String(sessionIdValue || state.sessionId || ""),
|
|
481
664
|
timeoutMs: timeoutOverrideMs,
|
|
482
665
|
onStreamDelta: onStream,
|
|
483
666
|
onThinkingDelta,
|
|
484
667
|
onPhase,
|
|
668
|
+
contextV2,
|
|
669
|
+
onArtifactPersisted: contextV2
|
|
670
|
+
? (persisted) => recordToolCallInSession(state, persisted, workspaceRoot)
|
|
671
|
+
: null,
|
|
485
672
|
onToolEvent: (event) => {
|
|
486
673
|
toolEventsThisAttempt += 1;
|
|
487
674
|
pushToolLog(event);
|
|
@@ -493,6 +680,29 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
493
680
|
try {
|
|
494
681
|
let cliRes;
|
|
495
682
|
|
|
683
|
+
const requestedSegment = parseExecutionSegment(options.executionSegment || options.nextSegment || null);
|
|
684
|
+
if (contextV2 && requestedSegment && requestedSegment.steps && requestedSegment.steps.length > 0) {
|
|
685
|
+
const segmentResult = await runExecutionSegmentSteps({
|
|
686
|
+
segment: requestedSegment,
|
|
687
|
+
workspaceRoot,
|
|
688
|
+
sessionId: String(state.sessionId || ""),
|
|
689
|
+
state,
|
|
690
|
+
pushToolLog,
|
|
691
|
+
});
|
|
692
|
+
if (!segmentResult.ok) {
|
|
693
|
+
return {
|
|
694
|
+
ok: false,
|
|
695
|
+
summary: "",
|
|
696
|
+
artifacts: [],
|
|
697
|
+
logs: logs.slice(),
|
|
698
|
+
error: segmentResult.error,
|
|
699
|
+
metrics: {},
|
|
700
|
+
streamed: false,
|
|
701
|
+
streamLastChar: "",
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
496
706
|
// Use decomposed runner for bug fix tasks
|
|
497
707
|
if (useDecomposition) {
|
|
498
708
|
const decomposedResult = await runDecomposedTask({
|
|
@@ -506,6 +716,9 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
506
716
|
systemPrompt: systemContext,
|
|
507
717
|
messages: Array.isArray(state.nlMessages) ? state.nlMessages : [],
|
|
508
718
|
sessionId: String(state.sessionId || ""),
|
|
719
|
+
state: contextV2 ? state : null,
|
|
720
|
+
contextV2,
|
|
721
|
+
systemBlocks: contextV2 && assembled ? assembled.systemBlocks : null,
|
|
509
722
|
});
|
|
510
723
|
|
|
511
724
|
if (decomposedResult.ok) {
|
|
@@ -555,14 +768,36 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
555
768
|
}
|
|
556
769
|
if (cliRes && Array.isArray(cliRes.messages)) {
|
|
557
770
|
state.nlMessages = stripSkillBlocksFromMessages(cliRes.messages);
|
|
771
|
+
if (contextV2) {
|
|
772
|
+
syncMessagesToTranscript(state, cliRes.messages, workspaceRoot, {
|
|
773
|
+
baselineCount: lastTranscriptBaseline,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
558
776
|
}
|
|
559
777
|
const normalized = String(cliRes.output || "").trim();
|
|
778
|
+
const sideEffects = contextV2 ? parseStructuredSideEffects(normalized) : null;
|
|
779
|
+
if (contextV2 && sideEffects) {
|
|
780
|
+
applyContextSideEffects(state, sideEffects);
|
|
781
|
+
const nextSegment = parseExecutionSegment(sideEffects);
|
|
782
|
+
if (nextSegment && nextSegment.steps && nextSegment.steps.length > 0) {
|
|
783
|
+
await runExecutionSegmentSteps({
|
|
784
|
+
segment: nextSegment,
|
|
785
|
+
workspaceRoot,
|
|
786
|
+
sessionId: String(state.sessionId || ""),
|
|
787
|
+
state,
|
|
788
|
+
pushToolLog,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
560
792
|
const summary = extractJsonSummary(normalized);
|
|
561
793
|
const resolvedSummary = String(summary || "").trim() || buildNlFallbackSummary(logs);
|
|
794
|
+
const artifactIds = contextV2 && Array.isArray(state.workingSet)
|
|
795
|
+
? state.workingSet.map((entry) => entry.artifactId).filter(Boolean)
|
|
796
|
+
: [];
|
|
562
797
|
return {
|
|
563
798
|
ok: true,
|
|
564
799
|
summary: resolvedSummary,
|
|
565
|
-
artifacts:
|
|
800
|
+
artifacts: artifactIds,
|
|
566
801
|
logs: logs.slice(),
|
|
567
802
|
error: "",
|
|
568
803
|
metrics: {},
|
|
@@ -626,8 +861,7 @@ function buildNlContext({
|
|
|
626
861
|
|| readTextOrFile(process.env.UFOO_UCODE_PROMPT_FILE)
|
|
627
862
|
|| "";
|
|
628
863
|
|
|
629
|
-
|
|
630
|
-
return clampContext(buildPromptContext({
|
|
864
|
+
return clampContext(resolveWireSystemPrompt({
|
|
631
865
|
workspaceRoot: workspaceRoot || process.cwd(),
|
|
632
866
|
model,
|
|
633
867
|
provider,
|
|
@@ -635,6 +869,44 @@ function buildNlContext({
|
|
|
635
869
|
}));
|
|
636
870
|
}
|
|
637
871
|
|
|
872
|
+
/**
|
|
873
|
+
* Single wire entry for system prompt assembly.
|
|
874
|
+
* v2 (default): layered Context Manager prompt.
|
|
875
|
+
* v1 (explicit off): legacy flat buildPromptContext.
|
|
876
|
+
*/
|
|
877
|
+
function resolveWireSystemPrompt({
|
|
878
|
+
workspaceRoot = process.cwd(),
|
|
879
|
+
model = "",
|
|
880
|
+
provider = "",
|
|
881
|
+
appendSystemPrompt = "",
|
|
882
|
+
overrideSystemPrompt = "",
|
|
883
|
+
epochDynamic = "",
|
|
884
|
+
turnDynamic = "",
|
|
885
|
+
sessionStableExtras = "",
|
|
886
|
+
} = {}) {
|
|
887
|
+
if (overrideSystemPrompt) return String(overrideSystemPrompt);
|
|
888
|
+
|
|
889
|
+
if (isContextV2Enabled()) {
|
|
890
|
+
return buildLayeredSystemPrompt({
|
|
891
|
+
workspaceRoot,
|
|
892
|
+
model,
|
|
893
|
+
provider,
|
|
894
|
+
appendSystemPrompt,
|
|
895
|
+
epochDynamic,
|
|
896
|
+
turnDynamic,
|
|
897
|
+
sessionStableExtras,
|
|
898
|
+
}).flatText;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Legacy v1 path — kept for UFOO_UCODE_CONTEXT_V2=0 compatibility only.
|
|
902
|
+
return buildPromptContext({
|
|
903
|
+
workspaceRoot,
|
|
904
|
+
model,
|
|
905
|
+
provider,
|
|
906
|
+
appendSystemPrompt,
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
638
910
|
function buildSessionSnapshotFromState(state = {}) {
|
|
639
911
|
const source = state && typeof state === "object" ? state : {};
|
|
640
912
|
return {
|
|
@@ -645,6 +917,27 @@ function buildSessionSnapshotFromState(state = {}) {
|
|
|
645
917
|
context: String(source.context || ""),
|
|
646
918
|
nlMessages: Array.isArray(source.nlMessages) ? source.nlMessages : [],
|
|
647
919
|
createdAt: String(source.sessionCreatedAt || "").trim(),
|
|
920
|
+
summary: String(source.summary || "").trim(),
|
|
921
|
+
projectSnapshot: source.projectSnapshot && typeof source.projectSnapshot === "object"
|
|
922
|
+
? source.projectSnapshot
|
|
923
|
+
: null,
|
|
924
|
+
taskContract: source.taskContract && typeof source.taskContract === "object"
|
|
925
|
+
? source.taskContract
|
|
926
|
+
: null,
|
|
927
|
+
stateEpoch: source.stateEpoch && typeof source.stateEpoch === "object"
|
|
928
|
+
? source.stateEpoch
|
|
929
|
+
: null,
|
|
930
|
+
workingSet: Array.isArray(source.workingSet) ? source.workingSet : [],
|
|
931
|
+
executionState: source.executionState && typeof source.executionState === "object"
|
|
932
|
+
? source.executionState
|
|
933
|
+
: null,
|
|
934
|
+
contextPolicy: source.contextPolicy && typeof source.contextPolicy === "object"
|
|
935
|
+
? source.contextPolicy
|
|
936
|
+
: null,
|
|
937
|
+
toolCallsSinceCommit: Number.isFinite(source.toolCallsSinceCommit)
|
|
938
|
+
? Math.max(0, Math.floor(source.toolCallsSinceCommit))
|
|
939
|
+
: 0,
|
|
940
|
+
activeSkills: Array.isArray(source.activeSkills) ? source.activeSkills : [],
|
|
648
941
|
};
|
|
649
942
|
}
|
|
650
943
|
|
|
@@ -708,12 +1001,32 @@ function resumeSessionState(state = {}, sessionId = "", workspaceRoot = process.
|
|
|
708
1001
|
state.context = String(snapshot.context || "");
|
|
709
1002
|
state.nlMessages = Array.isArray(snapshot.nlMessages) ? snapshot.nlMessages : [];
|
|
710
1003
|
state.sessionCreatedAt = String(snapshot.createdAt || "").trim();
|
|
1004
|
+
state.summary = String(snapshot.summary || "").trim();
|
|
1005
|
+
state.projectSnapshot = snapshot.projectSnapshot || null;
|
|
1006
|
+
state.taskContract = snapshot.taskContract || null;
|
|
1007
|
+
state.stateEpoch = snapshot.stateEpoch || null;
|
|
1008
|
+
state.workingSet = Array.isArray(snapshot.workingSet) ? snapshot.workingSet : [];
|
|
1009
|
+
state.executionState = snapshot.executionState || null;
|
|
1010
|
+
state.contextPolicy = snapshot.contextPolicy || null;
|
|
1011
|
+
state.toolCallsSinceCommit = Number.isFinite(snapshot.toolCallsSinceCommit)
|
|
1012
|
+
? snapshot.toolCallsSinceCommit
|
|
1013
|
+
: 0;
|
|
1014
|
+
state.activeSkills = Array.isArray(snapshot.activeSkills) ? snapshot.activeSkills : [];
|
|
1015
|
+
if (isContextV2Enabled()) {
|
|
1016
|
+
ensureContextSessionState(state);
|
|
1017
|
+
const { ensureTranscript } = require("./context/assembler");
|
|
1018
|
+
ensureTranscript(state, state.workspaceRoot);
|
|
1019
|
+
if (Array.isArray(state.transcriptEvents) && state.transcriptEvents.length > 0) {
|
|
1020
|
+
const { transcriptEventsToMessages } = require("./context/transcript");
|
|
1021
|
+
state.nlMessages = transcriptEventsToMessages(state.transcriptEvents, { preferArtifact: true });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
711
1024
|
|
|
712
1025
|
return {
|
|
713
1026
|
ok: true,
|
|
714
1027
|
error: "",
|
|
715
1028
|
sessionId: state.sessionId,
|
|
716
|
-
restoredMessages: state.nlMessages.length,
|
|
1029
|
+
restoredMessages: Array.isArray(state.nlMessages) ? state.nlMessages.length : 0,
|
|
717
1030
|
};
|
|
718
1031
|
}
|
|
719
1032
|
|
|
@@ -726,7 +1039,9 @@ module.exports = {
|
|
|
726
1039
|
isProjectAnalysisTask,
|
|
727
1040
|
buildNlFallbackSummary,
|
|
728
1041
|
buildNlContext,
|
|
1042
|
+
resolveWireSystemPrompt,
|
|
729
1043
|
stripSkillBlocksFromMessages,
|
|
1044
|
+
stripSkillBlocksFromText,
|
|
730
1045
|
resolvePlannerProvider,
|
|
731
1046
|
extractJsonSummary,
|
|
732
1047
|
enrichNativeError,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native ucode slash-command registry (shared by REPL help + TUI completions).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const UCODE_COMMAND_REGISTRY = [
|
|
8
|
+
{ cmd: "/help", desc: "Show available commands", order: 10 },
|
|
9
|
+
{ cmd: "/status", desc: "Show session / usage status", order: 20 },
|
|
10
|
+
{ cmd: "/model", desc: "Show or switch the active model", order: 25 },
|
|
11
|
+
{ cmd: "/ubus", desc: "Check pending bus messages", order: 30 },
|
|
12
|
+
{ cmd: "/resume", desc: "Resume a saved session", order: 40 },
|
|
13
|
+
{ cmd: "/skills", desc: "List or show skills", order: 50 },
|
|
14
|
+
{ cmd: "/bg", desc: "Run a task in the background", order: 60 },
|
|
15
|
+
{ cmd: "/exit", desc: "Exit ucode", order: 90 },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const UCODE_COMMAND_TREE = {
|
|
19
|
+
"/help": { desc: "Show available commands" },
|
|
20
|
+
"/status": { desc: "Show session / usage status" },
|
|
21
|
+
"/model": {
|
|
22
|
+
desc: "Show or switch the active model",
|
|
23
|
+
hasArguments: true,
|
|
24
|
+
optionalArguments: true,
|
|
25
|
+
},
|
|
26
|
+
"/ubus": { desc: "Check pending bus messages" },
|
|
27
|
+
"/resume": { desc: "Resume a saved session", hasArguments: true },
|
|
28
|
+
"/skills": {
|
|
29
|
+
desc: "List or show skills",
|
|
30
|
+
children: {
|
|
31
|
+
list: { desc: "List available skills", order: 1 },
|
|
32
|
+
show: { desc: "Show a skill by name", order: 2, hasArguments: true },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
"/bg": { desc: "Run a task in the background", hasArguments: true },
|
|
36
|
+
"/exit": { desc: "Exit ucode" },
|
|
37
|
+
"/quit": { desc: "Exit ucode" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function listUcodeCommandsForHelp() {
|
|
41
|
+
return [
|
|
42
|
+
"Commands:",
|
|
43
|
+
" /help",
|
|
44
|
+
" /exit|/quit",
|
|
45
|
+
" /ubus",
|
|
46
|
+
" /status",
|
|
47
|
+
" /model [model-id]",
|
|
48
|
+
" /skills [list]",
|
|
49
|
+
" /skills show <name>",
|
|
50
|
+
" /bg <task>",
|
|
51
|
+
" /resume <session-id>",
|
|
52
|
+
" tool <read|write|edit|bash> <args-json>",
|
|
53
|
+
" run <read|write|edit|bash> <args-json>",
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
UCODE_COMMAND_REGISTRY,
|
|
59
|
+
UCODE_COMMAND_TREE,
|
|
60
|
+
listUcodeCommandsForHelp,
|
|
61
|
+
};
|