u-foo 2.5.15 → 3.0.0
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/code/agent.js +333 -243
- package/src/code/commands.js +16 -0
- package/src/code/context/assembler.js +18 -13
- package/src/code/context/executionSegment.js +97 -119
- package/src/code/context/index.js +11 -1
- package/src/code/context/planGraph.js +1410 -0
- package/src/code/context/planGraphService.js +857 -0
- package/src/code/context/planMode.js +398 -0
- package/src/code/context/planProjection.js +432 -0
- package/src/code/context/promptLayers.js +21 -5
- package/src/code/context/stateCommit.js +2 -0
- package/src/code/context/toolRuntime.js +172 -0
- package/src/code/context/userInteraction.js +457 -0
- package/src/code/context/userNudge.js +116 -0
- package/src/code/dispatch.js +17 -1
- package/src/code/index.js +2 -0
- package/src/code/nativeRunner.js +518 -37
- package/src/code/repl.js +160 -18
- package/src/code/runtime/agentWakeup.js +58 -0
- package/src/code/runtime/graphOwner.js +41 -0
- package/src/code/runtime/graphYieldRouter.js +42 -0
- package/src/code/runtime/index.js +15 -0
- package/src/code/runtime/loopMailbox.js +124 -0
- package/src/code/runtime/runtimeEvents.js +39 -0
- package/src/code/runtime/taskControl.js +565 -0
- package/src/code/runtime/taskFocus.js +165 -0
- package/src/code/runtime/taskLoop.js +383 -0
- package/src/code/runtime/taskRun.js +187 -0
- package/src/code/runtime/toolProvenance.js +70 -0
- package/src/code/runtime/workspaceLease.js +208 -0
- package/src/code/sessionStore.js +0 -10
- package/src/code/skills/injection.js +1 -0
- package/src/code/taskDecomposer.js +32 -8
- package/src/code/tools/askUser.js +11 -0
- package/src/code/tools/planGraph.js +29 -0
- package/src/ui/format/index.js +25 -1
- package/src/ui/format/markdownRenderer.js +224 -2
- package/src/ui/ink/UcodeApp.js +285 -22
- package/src/code/context/featureFlag.js +0 -13
package/src/code/agent.js
CHANGED
|
@@ -10,9 +10,7 @@ const {
|
|
|
10
10
|
saveSessionSnapshot,
|
|
11
11
|
loadSessionSnapshot,
|
|
12
12
|
} = require("./sessionStore");
|
|
13
|
-
const { buildPromptContext } = require("../agents/prompts/native");
|
|
14
13
|
const { buildSkillInjections } = require("./skills");
|
|
15
|
-
const { isContextV2Enabled } = require("./context/featureFlag");
|
|
16
14
|
const {
|
|
17
15
|
assembleModelContext,
|
|
18
16
|
syncMessagesToTranscript,
|
|
@@ -34,9 +32,14 @@ const {
|
|
|
34
32
|
} = require("./context/stateCommit");
|
|
35
33
|
const { applyWorkingSetPlan } = require("./context/workingSet");
|
|
36
34
|
const {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} = require("./context/
|
|
35
|
+
normalizePlanGraphCommand,
|
|
36
|
+
runPlanGraphCommand,
|
|
37
|
+
} = require("./context/planGraphService");
|
|
38
|
+
const {
|
|
39
|
+
shouldFrameAsUserReminder,
|
|
40
|
+
buildContinuationUserPrompt,
|
|
41
|
+
clearUserPrompts,
|
|
42
|
+
} = require("./context/userNudge");
|
|
40
43
|
const {
|
|
41
44
|
runUbusCommand,
|
|
42
45
|
parseBusCheckOutput,
|
|
@@ -65,6 +68,16 @@ function ensureContextSessionState(state = {}) {
|
|
|
65
68
|
const { emptyExecutionState } = require("./context/executionSegment");
|
|
66
69
|
state.executionState = emptyExecutionState();
|
|
67
70
|
}
|
|
71
|
+
if (typeof state.executionState.planMode !== "boolean") {
|
|
72
|
+
state.executionState.planMode = false;
|
|
73
|
+
}
|
|
74
|
+
if (!Array.isArray(state.executionState.pendingUserPrompts)) {
|
|
75
|
+
state.executionState.pendingUserPrompts = [];
|
|
76
|
+
}
|
|
77
|
+
if (!state.executionState.planGraph || typeof state.executionState.planGraph !== "object") {
|
|
78
|
+
state.executionState.planGraph = require("./context/planGraphService").emptyPlanGraphState();
|
|
79
|
+
}
|
|
80
|
+
require("./context/planProjection").ensurePlanUiState(state.executionState);
|
|
68
81
|
if (!state.contextPolicy || typeof state.contextPolicy !== "object") {
|
|
69
82
|
const { defaultContextPolicy } = require("./context/assembler");
|
|
70
83
|
state.contextPolicy = defaultContextPolicy();
|
|
@@ -83,79 +96,129 @@ function buildSkillBodyBlocks(skillInjections = {}) {
|
|
|
83
96
|
});
|
|
84
97
|
}
|
|
85
98
|
|
|
86
|
-
async function
|
|
87
|
-
|
|
99
|
+
async function runPlanGraphSteps({
|
|
100
|
+
command = null,
|
|
101
|
+
segment = null,
|
|
88
102
|
workspaceRoot = process.cwd(),
|
|
89
103
|
sessionId = "",
|
|
90
104
|
state = {},
|
|
91
105
|
pushToolLog = () => null,
|
|
92
106
|
} = {}) {
|
|
93
|
-
|
|
94
|
-
|
|
107
|
+
if (!state.executionState || typeof state.executionState !== "object") {
|
|
108
|
+
state.executionState = require("./context/executionSegment").emptyExecutionState();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const normalized = command
|
|
112
|
+
|| (segment ? normalizePlanGraphCommand(segment) : null);
|
|
113
|
+
if (!normalized) {
|
|
114
|
+
return { ok: false, error: "missing plan_graph command" };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = runPlanGraphCommand(normalized, {
|
|
95
118
|
executionState: state.executionState,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
onStepComplete: ({ tool, args, result }) => {
|
|
119
|
+
autoAdvance: true,
|
|
120
|
+
parallel: true,
|
|
121
|
+
runTool: ({ node, args, tool, stepId }) => {
|
|
100
122
|
pushToolLog({
|
|
101
123
|
tool,
|
|
102
|
-
phase:
|
|
124
|
+
phase: "start",
|
|
103
125
|
args,
|
|
104
|
-
error:
|
|
126
|
+
error: "",
|
|
127
|
+
origin: {
|
|
128
|
+
kind: "plan_graph",
|
|
129
|
+
graphRevision: Number(state.executionState.planGraph && state.executionState.planGraph.revision) || 0,
|
|
130
|
+
nodeId: stepId || (node && node.id) || "",
|
|
131
|
+
},
|
|
105
132
|
});
|
|
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
133
|
const { runToolCall: dispatchToolCall } = require("./dispatch");
|
|
128
134
|
const { persistToolResultToContext } = require("./context/assembler");
|
|
129
|
-
const
|
|
135
|
+
const toolResult = dispatchToolCall(
|
|
130
136
|
{ tool, args },
|
|
131
137
|
{ workspaceRoot, cwd: workspaceRoot, sessionId },
|
|
132
138
|
);
|
|
133
|
-
if (!
|
|
134
|
-
|
|
139
|
+
if (!toolResult || toolResult.ok === false) {
|
|
140
|
+
pushToolLog({
|
|
141
|
+
tool,
|
|
142
|
+
phase: "error",
|
|
143
|
+
args,
|
|
144
|
+
error: String((toolResult && toolResult.error) || "tool failed"),
|
|
145
|
+
origin: {
|
|
146
|
+
kind: "plan_graph",
|
|
147
|
+
nodeId: stepId || (node && node.id) || "",
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
return toolResult;
|
|
135
151
|
}
|
|
136
152
|
const persisted = persistToolResultToContext({
|
|
137
153
|
workspaceRoot,
|
|
138
154
|
sessionId,
|
|
139
155
|
tool,
|
|
140
156
|
args,
|
|
141
|
-
rawResult:
|
|
157
|
+
rawResult: toolResult,
|
|
142
158
|
});
|
|
143
159
|
recordToolCallInSession(state, persisted, workspaceRoot);
|
|
144
|
-
|
|
160
|
+
const plan = require("./context/workingSet").defaultContextPlanFromToolEvent(
|
|
161
|
+
tool,
|
|
162
|
+
persisted.artifactId || (persisted.modelPayload && persisted.modelPayload.artifactId),
|
|
163
|
+
args,
|
|
164
|
+
);
|
|
165
|
+
if (plan) state.workingSet = applyWorkingSetPlan(state.workingSet, plan, state);
|
|
166
|
+
if ((tool === "write" || tool === "edit") && args && args.path) {
|
|
167
|
+
const filePath = String(args.path);
|
|
168
|
+
const files = Array.isArray(state.executionState.modifiedFiles)
|
|
169
|
+
? state.executionState.modifiedFiles.slice()
|
|
170
|
+
: [];
|
|
171
|
+
if (!files.includes(filePath)) files.push(filePath);
|
|
172
|
+
state.executionState.modifiedFiles = files;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
...(persisted.modelPayload || toolResult),
|
|
176
|
+
origin: {
|
|
177
|
+
kind: "plan_graph",
|
|
178
|
+
graphRevision: Number(state.executionState.planGraph && state.executionState.planGraph.revision) || 0,
|
|
179
|
+
nodeId: stepId || (node && node.id) || "",
|
|
180
|
+
},
|
|
181
|
+
};
|
|
145
182
|
},
|
|
146
183
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
|
|
185
|
+
state.executionState = result.executionState || state.executionState;
|
|
186
|
+
commitAfterSegmentEnd(state, {
|
|
187
|
+
ok: result.status === "accepted",
|
|
188
|
+
segmentId: result.graphId || "",
|
|
189
|
+
error: result.status === "accepted" ? "" : "plan_graph rejected",
|
|
190
|
+
stoppedAt: result.stoppedAt || "",
|
|
191
|
+
}, workspaceRoot);
|
|
151
192
|
return {
|
|
152
|
-
ok:
|
|
153
|
-
|
|
154
|
-
error:
|
|
155
|
-
|
|
193
|
+
ok: result.status === "accepted",
|
|
194
|
+
graphId: result.graphId || "",
|
|
195
|
+
error: result.status === "accepted"
|
|
196
|
+
? ""
|
|
197
|
+
: (Array.isArray(result.errors) ? result.errors.map((e) => e.message || e.code).join("; ") : "plan_graph rejected"),
|
|
198
|
+
stoppedAt: result.stoppedAt || "",
|
|
199
|
+
modelPayload: result.modelPayload || result,
|
|
156
200
|
};
|
|
157
201
|
}
|
|
158
202
|
|
|
203
|
+
async function runExecutionSegmentSteps({
|
|
204
|
+
segment = {},
|
|
205
|
+
workspaceRoot = process.cwd(),
|
|
206
|
+
sessionId = "",
|
|
207
|
+
state = {},
|
|
208
|
+
pushToolLog = () => null,
|
|
209
|
+
} = {}) {
|
|
210
|
+
return runPlanGraphSteps({
|
|
211
|
+
command: normalizePlanGraphCommand(segment) || normalizePlanGraphCommand({
|
|
212
|
+
type: "execution_segment",
|
|
213
|
+
...segment,
|
|
214
|
+
}),
|
|
215
|
+
workspaceRoot,
|
|
216
|
+
sessionId,
|
|
217
|
+
state,
|
|
218
|
+
pushToolLog,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
159
222
|
|
|
160
223
|
function readTextOrFile(value = "") {
|
|
161
224
|
const raw = String(value || "").trim();
|
|
@@ -409,94 +472,6 @@ function isProjectAnalysisTask(task = "") {
|
|
|
409
472
|
return /(?:analy[sz]e|analysis|review|audit|status|architecture|codebase|repo|project|现状|架构|审查|分析|项目|代码库)/i.test(text);
|
|
410
473
|
}
|
|
411
474
|
|
|
412
|
-
function createProjectPreflightContext({
|
|
413
|
-
workspaceRoot = process.cwd(),
|
|
414
|
-
pushToolLog = () => null,
|
|
415
|
-
} = {}) {
|
|
416
|
-
const root = String(workspaceRoot || process.cwd());
|
|
417
|
-
const readCandidates = [
|
|
418
|
-
"AGENTS.md",
|
|
419
|
-
"README.md",
|
|
420
|
-
"README.zh-CN.md",
|
|
421
|
-
"package.json",
|
|
422
|
-
];
|
|
423
|
-
const blocks = [];
|
|
424
|
-
|
|
425
|
-
for (const relPath of readCandidates) {
|
|
426
|
-
pushToolLog({
|
|
427
|
-
tool: "read",
|
|
428
|
-
phase: "start",
|
|
429
|
-
args: { path: relPath },
|
|
430
|
-
error: "",
|
|
431
|
-
});
|
|
432
|
-
const readRes = runToolCall(
|
|
433
|
-
{
|
|
434
|
-
tool: "read",
|
|
435
|
-
args: { path: relPath, maxBytes: 12000 },
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
workspaceRoot: root,
|
|
439
|
-
cwd: root,
|
|
440
|
-
}
|
|
441
|
-
);
|
|
442
|
-
pushToolLog({
|
|
443
|
-
tool: "read",
|
|
444
|
-
phase: readRes && readRes.ok === false ? "error" : "",
|
|
445
|
-
args: { path: relPath },
|
|
446
|
-
error: readRes && readRes.ok === false ? String(readRes.error || "") : "",
|
|
447
|
-
});
|
|
448
|
-
if (!readRes || readRes.ok === false) continue;
|
|
449
|
-
const content = String(readRes.content || "").trim();
|
|
450
|
-
if (!content) continue;
|
|
451
|
-
const clipped = content.length > 2400
|
|
452
|
-
? `${content.slice(0, 2400)}\n...[truncated]`
|
|
453
|
-
: content;
|
|
454
|
-
blocks.push(`File: ${relPath}\n${clipped}`);
|
|
455
|
-
if (blocks.length >= 2) break;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (blocks.length === 0) {
|
|
459
|
-
const command = "ls -la";
|
|
460
|
-
pushToolLog({
|
|
461
|
-
tool: "bash",
|
|
462
|
-
phase: "start",
|
|
463
|
-
args: { command },
|
|
464
|
-
error: "",
|
|
465
|
-
});
|
|
466
|
-
const bashRes = runToolCall(
|
|
467
|
-
{
|
|
468
|
-
tool: "bash",
|
|
469
|
-
args: { command, timeoutMs: 4000 },
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
workspaceRoot: root,
|
|
473
|
-
cwd: root,
|
|
474
|
-
}
|
|
475
|
-
);
|
|
476
|
-
pushToolLog({
|
|
477
|
-
tool: "bash",
|
|
478
|
-
phase: bashRes && bashRes.ok === false ? "error" : "",
|
|
479
|
-
args: { command },
|
|
480
|
-
error: bashRes && bashRes.ok === false ? String(bashRes.error || "") : "",
|
|
481
|
-
});
|
|
482
|
-
if (bashRes && bashRes.ok !== false) {
|
|
483
|
-
const stdout = String(bashRes.stdout || "").trim();
|
|
484
|
-
const clipped = stdout.length > 1200
|
|
485
|
-
? `${stdout.slice(0, 1200)}\n...[truncated]`
|
|
486
|
-
: stdout;
|
|
487
|
-
if (clipped) {
|
|
488
|
-
blocks.push(`Command: ${command}\n${clipped}`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (blocks.length === 0) return "";
|
|
494
|
-
return [
|
|
495
|
-
"Preflight snapshot (captured by ucode):",
|
|
496
|
-
...blocks.map((block) => `---\n${block}`),
|
|
497
|
-
].join("\n");
|
|
498
|
-
}
|
|
499
|
-
|
|
500
475
|
function buildNlFallbackSummary(logs = []) {
|
|
501
476
|
const list = Array.isArray(logs) ? logs : [];
|
|
502
477
|
const started = list.filter((entry) => entry && entry.phase === "start").length;
|
|
@@ -550,32 +525,23 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
550
525
|
const useDecomposition = isBugFixTask && !options.disableDecomposition;
|
|
551
526
|
const analysisTask = isProjectAnalysisTask(taskText);
|
|
552
527
|
const workspaceRoot = String(state.workspaceRoot || process.cwd());
|
|
553
|
-
|
|
554
|
-
if (contextV2) ensureContextSessionState(state);
|
|
528
|
+
ensureContextSessionState(state);
|
|
555
529
|
|
|
556
|
-
let preflightContext = "";
|
|
557
530
|
let projectSnapshot = state.projectSnapshot || null;
|
|
558
531
|
if (analysisTask) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
} else {
|
|
568
|
-
preflightContext = createProjectPreflightContext({
|
|
569
|
-
workspaceRoot,
|
|
570
|
-
pushToolLog,
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
} else if (contextV2) {
|
|
532
|
+
projectSnapshot = createProjectPreflightContextV2({
|
|
533
|
+
workspaceRoot,
|
|
534
|
+
sessionId: String(state.sessionId || ""),
|
|
535
|
+
pushToolLog,
|
|
536
|
+
existingSnapshot: state.projectSnapshot,
|
|
537
|
+
});
|
|
538
|
+
state.projectSnapshot = projectSnapshot;
|
|
539
|
+
} else {
|
|
574
540
|
projectSnapshot = ensureProjectSnapshot(state, workspaceRoot);
|
|
575
541
|
}
|
|
576
542
|
|
|
577
|
-
|
|
578
|
-
|
|
543
|
+
ensureTaskContract(state, taskText);
|
|
544
|
+
if (!shouldFrameAsUserReminder(state.executionState)) {
|
|
579
545
|
state.taskContract = patchTaskContractFromUserMessage(state.taskContract, taskText);
|
|
580
546
|
}
|
|
581
547
|
|
|
@@ -586,45 +552,33 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
586
552
|
prompt: taskPrompt,
|
|
587
553
|
workspaceRoot,
|
|
588
554
|
sessionId: String(state.sessionId || ""),
|
|
589
|
-
persistBodies:
|
|
590
|
-
useActiveSkillTag:
|
|
555
|
+
persistBodies: true,
|
|
556
|
+
useActiveSkillTag: true,
|
|
591
557
|
});
|
|
592
558
|
for (const warning of skillInjections.warnings || []) {
|
|
593
559
|
pushSkillWarning(logs, onToolLog, warning);
|
|
594
560
|
}
|
|
595
|
-
if (
|
|
561
|
+
if (Array.isArray(skillInjections.activeSkills) && skillInjections.activeSkills.length > 0) {
|
|
596
562
|
state.activeSkills = skillInjections.activeSkills;
|
|
597
563
|
}
|
|
598
|
-
const skillBodyBlocks =
|
|
599
|
-
|
|
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
|
|
564
|
+
const skillBodyBlocks = buildSkillBodyBlocks(skillInjections);
|
|
565
|
+
// Skill body goes into turnDynamic (system layered prompt) to avoid
|
|
603
566
|
// double injection and mixed system/user privilege semantics.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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");
|
|
567
|
+
let effectiveTaskPrompt = taskPrompt;
|
|
568
|
+
if (shouldFrameAsUserReminder(state.executionState)) {
|
|
569
|
+
effectiveTaskPrompt = buildContinuationUserPrompt(effectiveTaskPrompt, state.executionState);
|
|
626
570
|
}
|
|
627
571
|
|
|
572
|
+
const assembled = assembleModelContext(state, {
|
|
573
|
+
workspaceRoot,
|
|
574
|
+
model,
|
|
575
|
+
provider,
|
|
576
|
+
turnDynamic: skillBodyBlocks.join("\n\n"),
|
|
577
|
+
latestUserMessage: effectiveTaskPrompt,
|
|
578
|
+
});
|
|
579
|
+
const systemContext = assembled.systemPrompt;
|
|
580
|
+
state.summary = assembled.summary || state.summary;
|
|
581
|
+
|
|
628
582
|
const onStream = onDelta
|
|
629
583
|
? (delta) => {
|
|
630
584
|
const text = String(delta || "");
|
|
@@ -646,9 +600,7 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
646
600
|
let lastTranscriptBaseline = 0;
|
|
647
601
|
const invokeNative = (sessionIdValue = "", timeoutOverrideMs = timeoutMs) => {
|
|
648
602
|
toolEventsThisAttempt = 0;
|
|
649
|
-
const historyMessages =
|
|
650
|
-
? assembled.messages
|
|
651
|
-
: (Array.isArray(state.nlMessages) ? state.nlMessages : []);
|
|
603
|
+
const historyMessages = assembled.messages;
|
|
652
604
|
// Sanitized length matches what nativeRunner clones before appending this
|
|
653
605
|
// turn's user/tool/assistant messages — used as the transcript sync baseline.
|
|
654
606
|
lastTranscriptBaseline = sanitizeModelMessages(historyMessages).length;
|
|
@@ -658,17 +610,15 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
658
610
|
model,
|
|
659
611
|
prompt: effectiveTaskPrompt,
|
|
660
612
|
systemPrompt: systemContext,
|
|
661
|
-
systemBlocks:
|
|
613
|
+
systemBlocks: assembled.systemBlocks || null,
|
|
662
614
|
messages: historyMessages,
|
|
663
615
|
sessionId: String(sessionIdValue || state.sessionId || ""),
|
|
664
616
|
timeoutMs: timeoutOverrideMs,
|
|
665
617
|
onStreamDelta: onStream,
|
|
666
618
|
onThinkingDelta,
|
|
667
619
|
onPhase,
|
|
668
|
-
|
|
669
|
-
onArtifactPersisted:
|
|
670
|
-
? (persisted) => recordToolCallInSession(state, persisted, workspaceRoot)
|
|
671
|
-
: null,
|
|
620
|
+
executionState: state.executionState || null,
|
|
621
|
+
onArtifactPersisted: (persisted) => recordToolCallInSession(state, persisted, workspaceRoot),
|
|
672
622
|
onToolEvent: (event) => {
|
|
673
623
|
toolEventsThisAttempt += 1;
|
|
674
624
|
pushToolLog(event);
|
|
@@ -680,22 +630,24 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
680
630
|
try {
|
|
681
631
|
let cliRes;
|
|
682
632
|
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
633
|
+
const requestedPlan = normalizePlanGraphCommand(
|
|
634
|
+
options.executionSegment || options.nextSegment || options.planGraph || null,
|
|
635
|
+
);
|
|
636
|
+
if (requestedPlan) {
|
|
637
|
+
const planResult = await runPlanGraphSteps({
|
|
638
|
+
command: requestedPlan,
|
|
687
639
|
workspaceRoot,
|
|
688
640
|
sessionId: String(state.sessionId || ""),
|
|
689
641
|
state,
|
|
690
642
|
pushToolLog,
|
|
691
643
|
});
|
|
692
|
-
if (!
|
|
644
|
+
if (!planResult.ok) {
|
|
693
645
|
return {
|
|
694
646
|
ok: false,
|
|
695
647
|
summary: "",
|
|
696
648
|
artifacts: [],
|
|
697
649
|
logs: logs.slice(),
|
|
698
|
-
error:
|
|
650
|
+
error: planResult.error,
|
|
699
651
|
metrics: {},
|
|
700
652
|
streamed: false,
|
|
701
653
|
streamLastChar: "",
|
|
@@ -716,9 +668,8 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
716
668
|
systemPrompt: systemContext,
|
|
717
669
|
messages: Array.isArray(state.nlMessages) ? state.nlMessages : [],
|
|
718
670
|
sessionId: String(state.sessionId || ""),
|
|
719
|
-
state
|
|
720
|
-
|
|
721
|
-
systemBlocks: contextV2 && assembled ? assembled.systemBlocks : null,
|
|
671
|
+
state,
|
|
672
|
+
systemBlocks: assembled.systemBlocks || null,
|
|
722
673
|
});
|
|
723
674
|
|
|
724
675
|
if (decomposedResult.ok) {
|
|
@@ -751,6 +702,9 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
751
702
|
|
|
752
703
|
if (!cliRes || cliRes.ok === false) {
|
|
753
704
|
const errMsg = String((cliRes && cliRes.error) || "");
|
|
705
|
+
if (isCliCancelledError(errMsg) && state.executionState) {
|
|
706
|
+
clearUserPrompts(state.executionState);
|
|
707
|
+
}
|
|
754
708
|
return {
|
|
755
709
|
ok: false,
|
|
756
710
|
summary: "",
|
|
@@ -766,22 +720,40 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
766
720
|
if (cliRes && typeof cliRes.sessionId === "string" && cliRes.sessionId.trim()) {
|
|
767
721
|
state.sessionId = cliRes.sessionId.trim();
|
|
768
722
|
}
|
|
769
|
-
if (cliRes &&
|
|
770
|
-
state
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
723
|
+
if (cliRes && cliRes.executionState && typeof cliRes.executionState === "object") {
|
|
724
|
+
// Preserve planMode if the runner returned a fresh empty state without it.
|
|
725
|
+
const priorPlanMode = Boolean(state.executionState && state.executionState.planMode);
|
|
726
|
+
const priorSource = state.executionState && state.executionState.planModeSource
|
|
727
|
+
? String(state.executionState.planModeSource)
|
|
728
|
+
: "";
|
|
729
|
+
state.executionState = cliRes.executionState;
|
|
730
|
+
if (typeof state.executionState.planMode !== "boolean") {
|
|
731
|
+
state.executionState.planMode = priorPlanMode;
|
|
775
732
|
}
|
|
733
|
+
if (!state.executionState.planModeSource && priorSource) {
|
|
734
|
+
state.executionState.planModeSource = priorSource;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (cliRes && Array.isArray(cliRes.messages)) {
|
|
738
|
+
// Sync first so ensureTranscript does not migrate the just-assigned
|
|
739
|
+
// nlMessages and then append the same delta again.
|
|
740
|
+
syncMessagesToTranscript(state, cliRes.messages, workspaceRoot, {
|
|
741
|
+
baselineCount: lastTranscriptBaseline,
|
|
742
|
+
});
|
|
743
|
+
state.nlMessages = stripSkillBlocksFromMessages(
|
|
744
|
+
Array.isArray(state.nlMessages) && state.nlMessages.length > 0
|
|
745
|
+
? state.nlMessages
|
|
746
|
+
: cliRes.messages,
|
|
747
|
+
);
|
|
776
748
|
}
|
|
777
749
|
const normalized = String(cliRes.output || "").trim();
|
|
778
|
-
const sideEffects =
|
|
779
|
-
if (
|
|
750
|
+
const sideEffects = parseStructuredSideEffects(normalized);
|
|
751
|
+
if (sideEffects) {
|
|
780
752
|
applyContextSideEffects(state, sideEffects);
|
|
781
|
-
const
|
|
782
|
-
if (
|
|
783
|
-
await
|
|
784
|
-
|
|
753
|
+
const planCommand = normalizePlanGraphCommand(sideEffects);
|
|
754
|
+
if (planCommand) {
|
|
755
|
+
await runPlanGraphSteps({
|
|
756
|
+
command: planCommand,
|
|
785
757
|
workspaceRoot,
|
|
786
758
|
sessionId: String(state.sessionId || ""),
|
|
787
759
|
state,
|
|
@@ -791,9 +763,23 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
791
763
|
}
|
|
792
764
|
const summary = extractJsonSummary(normalized);
|
|
793
765
|
const resolvedSummary = String(summary || "").trim() || buildNlFallbackSummary(logs);
|
|
794
|
-
const artifactIds =
|
|
766
|
+
const artifactIds = Array.isArray(state.workingSet)
|
|
795
767
|
? state.workingSet.map((entry) => entry.artifactId).filter(Boolean)
|
|
796
768
|
: [];
|
|
769
|
+
if (cliRes && cliRes.waitingUserInteraction) {
|
|
770
|
+
return {
|
|
771
|
+
ok: true,
|
|
772
|
+
summary: resolvedSummary || "Waiting for your reply",
|
|
773
|
+
artifacts: artifactIds,
|
|
774
|
+
logs: logs.slice(),
|
|
775
|
+
error: "",
|
|
776
|
+
metrics: {},
|
|
777
|
+
streamed: Boolean(streamed || cliRes.streamed),
|
|
778
|
+
streamLastChar,
|
|
779
|
+
waitingUserInteraction: true,
|
|
780
|
+
interactionId: cliRes.interactionId || "",
|
|
781
|
+
};
|
|
782
|
+
}
|
|
797
783
|
return {
|
|
798
784
|
ok: true,
|
|
799
785
|
summary: resolvedSummary,
|
|
@@ -870,9 +856,7 @@ function buildNlContext({
|
|
|
870
856
|
}
|
|
871
857
|
|
|
872
858
|
/**
|
|
873
|
-
* Single wire entry for system prompt assembly.
|
|
874
|
-
* v2 (default): layered Context Manager prompt.
|
|
875
|
-
* v1 (explicit off): legacy flat buildPromptContext.
|
|
859
|
+
* Single wire entry for system prompt assembly (layered Context Manager).
|
|
876
860
|
*/
|
|
877
861
|
function resolveWireSystemPrompt({
|
|
878
862
|
workspaceRoot = process.cwd(),
|
|
@@ -886,25 +870,15 @@ function resolveWireSystemPrompt({
|
|
|
886
870
|
} = {}) {
|
|
887
871
|
if (overrideSystemPrompt) return String(overrideSystemPrompt);
|
|
888
872
|
|
|
889
|
-
|
|
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({
|
|
873
|
+
return buildLayeredSystemPrompt({
|
|
903
874
|
workspaceRoot,
|
|
904
875
|
model,
|
|
905
876
|
provider,
|
|
906
877
|
appendSystemPrompt,
|
|
907
|
-
|
|
878
|
+
epochDynamic,
|
|
879
|
+
turnDynamic,
|
|
880
|
+
sessionStableExtras,
|
|
881
|
+
}).flatText;
|
|
908
882
|
}
|
|
909
883
|
|
|
910
884
|
function buildSessionSnapshotFromState(state = {}) {
|
|
@@ -1012,14 +986,12 @@ function resumeSessionState(state = {}, sessionId = "", workspaceRoot = process.
|
|
|
1012
986
|
? snapshot.toolCallsSinceCommit
|
|
1013
987
|
: 0;
|
|
1014
988
|
state.activeSkills = Array.isArray(snapshot.activeSkills) ? snapshot.activeSkills : [];
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
state.nlMessages = transcriptEventsToMessages(state.transcriptEvents, { preferArtifact: true });
|
|
1022
|
-
}
|
|
989
|
+
ensureContextSessionState(state);
|
|
990
|
+
const { ensureTranscript } = require("./context/assembler");
|
|
991
|
+
ensureTranscript(state, state.workspaceRoot);
|
|
992
|
+
if (Array.isArray(state.transcriptEvents) && state.transcriptEvents.length > 0) {
|
|
993
|
+
const { transcriptEventsToMessages } = require("./context/transcript");
|
|
994
|
+
state.nlMessages = transcriptEventsToMessages(state.transcriptEvents, { preferArtifact: true });
|
|
1023
995
|
}
|
|
1024
996
|
|
|
1025
997
|
return {
|
|
@@ -1030,10 +1002,128 @@ function resumeSessionState(state = {}, sessionId = "", workspaceRoot = process.
|
|
|
1030
1002
|
};
|
|
1031
1003
|
}
|
|
1032
1004
|
|
|
1005
|
+
/**
|
|
1006
|
+
* Continue after TUI resolves approval/choice/chat.
|
|
1007
|
+
* ask_user: answer is written as the deferred tool_result (contiguous, no question echo).
|
|
1008
|
+
* checkpoint: short answer-only user message referencing interaction/node.
|
|
1009
|
+
*/
|
|
1010
|
+
async function resumeAfterUserInteraction(answerText = "", state = {}, options = {}) {
|
|
1011
|
+
ensureContextSessionState(state);
|
|
1012
|
+
const { resolveUserInteraction } = require("./context/userInteraction");
|
|
1013
|
+
const { appendAnswerToolResult } = require("./nativeRunner");
|
|
1014
|
+
const resolved = resolveUserInteraction(state.executionState, answerText);
|
|
1015
|
+
if (!resolved.ok) {
|
|
1016
|
+
return {
|
|
1017
|
+
ok: false,
|
|
1018
|
+
error: resolved.error || "failed to resolve user interaction",
|
|
1019
|
+
code: resolved.code || "",
|
|
1020
|
+
waitingUserInteraction: true,
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const logs = [];
|
|
1025
|
+
const pushToolLog = (event) => {
|
|
1026
|
+
try {
|
|
1027
|
+
logs.push(normalizeToolLogEvent(event));
|
|
1028
|
+
} catch { /* ignore */ }
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
let messages = Array.isArray(state.nlMessages) ? state.nlMessages.slice() : [];
|
|
1032
|
+
if (resolved.continueMode === "tool_result" && resolved.resume && resolved.resume.call) {
|
|
1033
|
+
const appended = appendAnswerToolResult(messages, resolved.resume, resolved.answer);
|
|
1034
|
+
if (!appended.ok) {
|
|
1035
|
+
return { ok: false, error: appended.error || "failed to append answer tool_result" };
|
|
1036
|
+
}
|
|
1037
|
+
} else {
|
|
1038
|
+
// Checkpoint / non-tool path: answer-only contiguous user message (no question).
|
|
1039
|
+
messages.push({
|
|
1040
|
+
role: "user",
|
|
1041
|
+
content: JSON.stringify(resolved.answer),
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
state.nlMessages = messages;
|
|
1045
|
+
|
|
1046
|
+
const workspaceRoot = state.workspaceRoot || process.cwd();
|
|
1047
|
+
const assembled = assembleModelContext(state, {
|
|
1048
|
+
workspaceRoot,
|
|
1049
|
+
provider: state.provider,
|
|
1050
|
+
model: state.model,
|
|
1051
|
+
});
|
|
1052
|
+
const systemContext = assembled.systemPrompt || "";
|
|
1053
|
+
|
|
1054
|
+
let streamLastChar = "";
|
|
1055
|
+
const onDelta = typeof options.onDelta === "function" ? options.onDelta : null;
|
|
1056
|
+
const trackingOnDelta = onDelta
|
|
1057
|
+
? (delta) => {
|
|
1058
|
+
const text = String(delta || "");
|
|
1059
|
+
if (text) streamLastChar = text.slice(-1);
|
|
1060
|
+
return onDelta(delta);
|
|
1061
|
+
}
|
|
1062
|
+
: null;
|
|
1063
|
+
|
|
1064
|
+
const cliRes = await runNativeAgentTask({
|
|
1065
|
+
workspaceRoot,
|
|
1066
|
+
provider: state.provider,
|
|
1067
|
+
model: state.model,
|
|
1068
|
+
prompt: "",
|
|
1069
|
+
systemPrompt: systemContext,
|
|
1070
|
+
systemBlocks: assembled.systemBlocks || null,
|
|
1071
|
+
messages,
|
|
1072
|
+
sessionId: String(state.sessionId || ""),
|
|
1073
|
+
onToolEvent: pushToolLog,
|
|
1074
|
+
onStreamDelta: trackingOnDelta,
|
|
1075
|
+
onThinkingDelta: typeof options.onThinkingDelta === "function" ? options.onThinkingDelta : null,
|
|
1076
|
+
onPhase: typeof options.onPhase === "function" ? options.onPhase : null,
|
|
1077
|
+
executionState: state.executionState,
|
|
1078
|
+
signal: options.signal,
|
|
1079
|
+
resume: true,
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
if (cliRes && cliRes.executionState) {
|
|
1083
|
+
state.executionState = cliRes.executionState;
|
|
1084
|
+
}
|
|
1085
|
+
if (cliRes && Array.isArray(cliRes.messages)) {
|
|
1086
|
+
state.nlMessages = stripSkillBlocksFromMessages(cliRes.messages);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (!cliRes || cliRes.ok === false) {
|
|
1090
|
+
return {
|
|
1091
|
+
ok: false,
|
|
1092
|
+
error: (cliRes && cliRes.error) || "resume failed",
|
|
1093
|
+
logs,
|
|
1094
|
+
waitingUserInteraction: false,
|
|
1095
|
+
streamed: false,
|
|
1096
|
+
streamLastChar: "",
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
if (cliRes.waitingUserInteraction) {
|
|
1101
|
+
return {
|
|
1102
|
+
ok: true,
|
|
1103
|
+
summary: "Waiting for your reply",
|
|
1104
|
+
logs,
|
|
1105
|
+
waitingUserInteraction: true,
|
|
1106
|
+
interactionId: cliRes.interactionId || "",
|
|
1107
|
+
streamed: Boolean(cliRes.streamed),
|
|
1108
|
+
streamLastChar,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
return {
|
|
1113
|
+
ok: true,
|
|
1114
|
+
summary: String(cliRes.output || "").trim() || "continued",
|
|
1115
|
+
logs,
|
|
1116
|
+
waitingUserInteraction: false,
|
|
1117
|
+
streamed: Boolean(cliRes.streamed),
|
|
1118
|
+
streamLastChar,
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1033
1122
|
module.exports = {
|
|
1034
1123
|
runUcodeCoreAgent,
|
|
1035
1124
|
runSingleCommand,
|
|
1036
1125
|
runNaturalLanguageTask,
|
|
1126
|
+
resumeAfterUserInteraction,
|
|
1037
1127
|
formatNlResult,
|
|
1038
1128
|
normalizeToolLogEvent,
|
|
1039
1129
|
isProjectAnalysisTask,
|