u-foo 2.5.15 → 3.0.1
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 +350 -246
- package/src/code/commands.js +16 -0
- package/src/code/context/assembler.js +18 -13
- package/src/code/context/executionSegment.js +102 -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 +405 -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 +4 -0
- package/src/code/nativeRunner.js +589 -172
- package/src/code/protocol/controlPlane.js +93 -0
- package/src/code/protocol/faultHarness.js +90 -0
- package/src/code/protocol/index.js +20 -0
- package/src/code/protocol/loopEvents.js +102 -0
- package/src/code/protocol/materialize.js +107 -0
- package/src/code/protocol/messageFixtures.js +116 -0
- package/src/code/protocol/ownership.js +147 -0
- package/src/code/protocol/protocolValidator.js +165 -0
- package/src/code/protocol/suspension.js +173 -0
- package/src/code/protocol/toolCallLedger.js +222 -0
- package/src/code/protocol/transitions.js +97 -0
- package/src/code/providers/anthropicMessagesTransport.js +93 -0
- package/src/code/providers/index.js +7 -0
- package/src/code/providers/openaiChatTransport.js +98 -0
- package/src/code/providers/transportContract.js +46 -0
- package/src/code/repl.js +147 -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 +394 -0
- package/src/code/runtime/taskRun.js +348 -0
- package/src/code/runtime/toolProvenance.js +70 -0
- package/src/code/runtime/workspaceLease.js +249 -0
- package/src/code/sessionStore.js +1 -10
- package/src/code/skills/injection.js +1 -0
- package/src/code/taskDecomposer.js +32 -8
- package/src/code/taskRoute.js +73 -0
- 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 +268 -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;
|
|
@@ -545,37 +520,41 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
545
520
|
: null;
|
|
546
521
|
const pushToolLog = createToolLogCollector(logs, onToolLog);
|
|
547
522
|
|
|
548
|
-
//
|
|
549
|
-
const
|
|
550
|
-
const
|
|
523
|
+
// Structural / explicit upgrade to decomposed runner (not keyword "fix").
|
|
524
|
+
const { shouldUpgradeToDecomposition } = require("./taskRoute");
|
|
525
|
+
const routeDecision = shouldUpgradeToDecomposition(taskText, {
|
|
526
|
+
disableDecomposition: options.disableDecomposition,
|
|
527
|
+
forceDecomposition: options.forceDecomposition,
|
|
528
|
+
forceDirect: options.forceDirect,
|
|
529
|
+
failureCount: options.failureCount,
|
|
530
|
+
modelRequestedUpgrade: options.modelRequestedUpgrade,
|
|
531
|
+
hasPlanGraph: Boolean(
|
|
532
|
+
state.executionState
|
|
533
|
+
&& state.executionState.planGraph
|
|
534
|
+
&& state.executionState.planGraph.graphId
|
|
535
|
+
),
|
|
536
|
+
});
|
|
537
|
+
const useDecomposition = Boolean(routeDecision.upgrade);
|
|
538
|
+
state.lastRouteDecision = routeDecision;
|
|
551
539
|
const analysisTask = isProjectAnalysisTask(taskText);
|
|
552
540
|
const workspaceRoot = String(state.workspaceRoot || process.cwd());
|
|
553
|
-
|
|
554
|
-
if (contextV2) ensureContextSessionState(state);
|
|
541
|
+
ensureContextSessionState(state);
|
|
555
542
|
|
|
556
|
-
let preflightContext = "";
|
|
557
543
|
let projectSnapshot = state.projectSnapshot || null;
|
|
558
544
|
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) {
|
|
545
|
+
projectSnapshot = createProjectPreflightContextV2({
|
|
546
|
+
workspaceRoot,
|
|
547
|
+
sessionId: String(state.sessionId || ""),
|
|
548
|
+
pushToolLog,
|
|
549
|
+
existingSnapshot: state.projectSnapshot,
|
|
550
|
+
});
|
|
551
|
+
state.projectSnapshot = projectSnapshot;
|
|
552
|
+
} else {
|
|
574
553
|
projectSnapshot = ensureProjectSnapshot(state, workspaceRoot);
|
|
575
554
|
}
|
|
576
555
|
|
|
577
|
-
|
|
578
|
-
|
|
556
|
+
ensureTaskContract(state, taskText);
|
|
557
|
+
if (!shouldFrameAsUserReminder(state.executionState)) {
|
|
579
558
|
state.taskContract = patchTaskContractFromUserMessage(state.taskContract, taskText);
|
|
580
559
|
}
|
|
581
560
|
|
|
@@ -586,45 +565,33 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
586
565
|
prompt: taskPrompt,
|
|
587
566
|
workspaceRoot,
|
|
588
567
|
sessionId: String(state.sessionId || ""),
|
|
589
|
-
persistBodies:
|
|
590
|
-
useActiveSkillTag:
|
|
568
|
+
persistBodies: true,
|
|
569
|
+
useActiveSkillTag: true,
|
|
591
570
|
});
|
|
592
571
|
for (const warning of skillInjections.warnings || []) {
|
|
593
572
|
pushSkillWarning(logs, onToolLog, warning);
|
|
594
573
|
}
|
|
595
|
-
if (
|
|
574
|
+
if (Array.isArray(skillInjections.activeSkills) && skillInjections.activeSkills.length > 0) {
|
|
596
575
|
state.activeSkills = skillInjections.activeSkills;
|
|
597
576
|
}
|
|
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
|
|
577
|
+
const skillBodyBlocks = buildSkillBodyBlocks(skillInjections);
|
|
578
|
+
// Skill body goes into turnDynamic (system layered prompt) to avoid
|
|
603
579
|
// 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");
|
|
580
|
+
let effectiveTaskPrompt = taskPrompt;
|
|
581
|
+
if (shouldFrameAsUserReminder(state.executionState)) {
|
|
582
|
+
effectiveTaskPrompt = buildContinuationUserPrompt(effectiveTaskPrompt, state.executionState);
|
|
626
583
|
}
|
|
627
584
|
|
|
585
|
+
const assembled = assembleModelContext(state, {
|
|
586
|
+
workspaceRoot,
|
|
587
|
+
model,
|
|
588
|
+
provider,
|
|
589
|
+
turnDynamic: skillBodyBlocks.join("\n\n"),
|
|
590
|
+
latestUserMessage: effectiveTaskPrompt,
|
|
591
|
+
});
|
|
592
|
+
const systemContext = assembled.systemPrompt;
|
|
593
|
+
state.summary = assembled.summary || state.summary;
|
|
594
|
+
|
|
628
595
|
const onStream = onDelta
|
|
629
596
|
? (delta) => {
|
|
630
597
|
const text = String(delta || "");
|
|
@@ -646,9 +613,7 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
646
613
|
let lastTranscriptBaseline = 0;
|
|
647
614
|
const invokeNative = (sessionIdValue = "", timeoutOverrideMs = timeoutMs) => {
|
|
648
615
|
toolEventsThisAttempt = 0;
|
|
649
|
-
const historyMessages =
|
|
650
|
-
? assembled.messages
|
|
651
|
-
: (Array.isArray(state.nlMessages) ? state.nlMessages : []);
|
|
616
|
+
const historyMessages = assembled.messages;
|
|
652
617
|
// Sanitized length matches what nativeRunner clones before appending this
|
|
653
618
|
// turn's user/tool/assistant messages — used as the transcript sync baseline.
|
|
654
619
|
lastTranscriptBaseline = sanitizeModelMessages(historyMessages).length;
|
|
@@ -658,17 +623,15 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
658
623
|
model,
|
|
659
624
|
prompt: effectiveTaskPrompt,
|
|
660
625
|
systemPrompt: systemContext,
|
|
661
|
-
systemBlocks:
|
|
626
|
+
systemBlocks: assembled.systemBlocks || null,
|
|
662
627
|
messages: historyMessages,
|
|
663
628
|
sessionId: String(sessionIdValue || state.sessionId || ""),
|
|
664
629
|
timeoutMs: timeoutOverrideMs,
|
|
665
630
|
onStreamDelta: onStream,
|
|
666
631
|
onThinkingDelta,
|
|
667
632
|
onPhase,
|
|
668
|
-
|
|
669
|
-
onArtifactPersisted:
|
|
670
|
-
? (persisted) => recordToolCallInSession(state, persisted, workspaceRoot)
|
|
671
|
-
: null,
|
|
633
|
+
executionState: state.executionState || null,
|
|
634
|
+
onArtifactPersisted: (persisted) => recordToolCallInSession(state, persisted, workspaceRoot),
|
|
672
635
|
onToolEvent: (event) => {
|
|
673
636
|
toolEventsThisAttempt += 1;
|
|
674
637
|
pushToolLog(event);
|
|
@@ -680,22 +643,24 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
680
643
|
try {
|
|
681
644
|
let cliRes;
|
|
682
645
|
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
646
|
+
const requestedPlan = normalizePlanGraphCommand(
|
|
647
|
+
options.executionSegment || options.nextSegment || options.planGraph || null,
|
|
648
|
+
);
|
|
649
|
+
if (requestedPlan) {
|
|
650
|
+
const planResult = await runPlanGraphSteps({
|
|
651
|
+
command: requestedPlan,
|
|
687
652
|
workspaceRoot,
|
|
688
653
|
sessionId: String(state.sessionId || ""),
|
|
689
654
|
state,
|
|
690
655
|
pushToolLog,
|
|
691
656
|
});
|
|
692
|
-
if (!
|
|
657
|
+
if (!planResult.ok) {
|
|
693
658
|
return {
|
|
694
659
|
ok: false,
|
|
695
660
|
summary: "",
|
|
696
661
|
artifacts: [],
|
|
697
662
|
logs: logs.slice(),
|
|
698
|
-
error:
|
|
663
|
+
error: planResult.error,
|
|
699
664
|
metrics: {},
|
|
700
665
|
streamed: false,
|
|
701
666
|
streamLastChar: "",
|
|
@@ -716,9 +681,8 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
716
681
|
systemPrompt: systemContext,
|
|
717
682
|
messages: Array.isArray(state.nlMessages) ? state.nlMessages : [],
|
|
718
683
|
sessionId: String(state.sessionId || ""),
|
|
719
|
-
state
|
|
720
|
-
|
|
721
|
-
systemBlocks: contextV2 && assembled ? assembled.systemBlocks : null,
|
|
684
|
+
state,
|
|
685
|
+
systemBlocks: assembled.systemBlocks || null,
|
|
722
686
|
});
|
|
723
687
|
|
|
724
688
|
if (decomposedResult.ok) {
|
|
@@ -751,6 +715,9 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
751
715
|
|
|
752
716
|
if (!cliRes || cliRes.ok === false) {
|
|
753
717
|
const errMsg = String((cliRes && cliRes.error) || "");
|
|
718
|
+
if (isCliCancelledError(errMsg) && state.executionState) {
|
|
719
|
+
clearUserPrompts(state.executionState);
|
|
720
|
+
}
|
|
754
721
|
return {
|
|
755
722
|
ok: false,
|
|
756
723
|
summary: "",
|
|
@@ -766,22 +733,40 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
766
733
|
if (cliRes && typeof cliRes.sessionId === "string" && cliRes.sessionId.trim()) {
|
|
767
734
|
state.sessionId = cliRes.sessionId.trim();
|
|
768
735
|
}
|
|
769
|
-
if (cliRes &&
|
|
770
|
-
state
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
736
|
+
if (cliRes && cliRes.executionState && typeof cliRes.executionState === "object") {
|
|
737
|
+
// Preserve planMode if the runner returned a fresh empty state without it.
|
|
738
|
+
const priorPlanMode = Boolean(state.executionState && state.executionState.planMode);
|
|
739
|
+
const priorSource = state.executionState && state.executionState.planModeSource
|
|
740
|
+
? String(state.executionState.planModeSource)
|
|
741
|
+
: "";
|
|
742
|
+
state.executionState = cliRes.executionState;
|
|
743
|
+
if (typeof state.executionState.planMode !== "boolean") {
|
|
744
|
+
state.executionState.planMode = priorPlanMode;
|
|
745
|
+
}
|
|
746
|
+
if (!state.executionState.planModeSource && priorSource) {
|
|
747
|
+
state.executionState.planModeSource = priorSource;
|
|
775
748
|
}
|
|
776
749
|
}
|
|
750
|
+
if (cliRes && Array.isArray(cliRes.messages)) {
|
|
751
|
+
// Sync first so ensureTranscript does not migrate the just-assigned
|
|
752
|
+
// nlMessages and then append the same delta again.
|
|
753
|
+
syncMessagesToTranscript(state, cliRes.messages, workspaceRoot, {
|
|
754
|
+
baselineCount: lastTranscriptBaseline,
|
|
755
|
+
});
|
|
756
|
+
state.nlMessages = stripSkillBlocksFromMessages(
|
|
757
|
+
Array.isArray(state.nlMessages) && state.nlMessages.length > 0
|
|
758
|
+
? state.nlMessages
|
|
759
|
+
: cliRes.messages,
|
|
760
|
+
);
|
|
761
|
+
}
|
|
777
762
|
const normalized = String(cliRes.output || "").trim();
|
|
778
|
-
const sideEffects =
|
|
779
|
-
if (
|
|
763
|
+
const sideEffects = parseStructuredSideEffects(normalized);
|
|
764
|
+
if (sideEffects) {
|
|
780
765
|
applyContextSideEffects(state, sideEffects);
|
|
781
|
-
const
|
|
782
|
-
if (
|
|
783
|
-
await
|
|
784
|
-
|
|
766
|
+
const planCommand = normalizePlanGraphCommand(sideEffects);
|
|
767
|
+
if (planCommand) {
|
|
768
|
+
await runPlanGraphSteps({
|
|
769
|
+
command: planCommand,
|
|
785
770
|
workspaceRoot,
|
|
786
771
|
sessionId: String(state.sessionId || ""),
|
|
787
772
|
state,
|
|
@@ -791,9 +776,23 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
791
776
|
}
|
|
792
777
|
const summary = extractJsonSummary(normalized);
|
|
793
778
|
const resolvedSummary = String(summary || "").trim() || buildNlFallbackSummary(logs);
|
|
794
|
-
const artifactIds =
|
|
779
|
+
const artifactIds = Array.isArray(state.workingSet)
|
|
795
780
|
? state.workingSet.map((entry) => entry.artifactId).filter(Boolean)
|
|
796
781
|
: [];
|
|
782
|
+
if (cliRes && cliRes.waitingUserInteraction) {
|
|
783
|
+
return {
|
|
784
|
+
ok: true,
|
|
785
|
+
summary: resolvedSummary || "Waiting for your reply",
|
|
786
|
+
artifacts: artifactIds,
|
|
787
|
+
logs: logs.slice(),
|
|
788
|
+
error: "",
|
|
789
|
+
metrics: {},
|
|
790
|
+
streamed: Boolean(streamed || cliRes.streamed),
|
|
791
|
+
streamLastChar,
|
|
792
|
+
waitingUserInteraction: true,
|
|
793
|
+
interactionId: cliRes.interactionId || "",
|
|
794
|
+
};
|
|
795
|
+
}
|
|
797
796
|
return {
|
|
798
797
|
ok: true,
|
|
799
798
|
summary: resolvedSummary,
|
|
@@ -870,9 +869,7 @@ function buildNlContext({
|
|
|
870
869
|
}
|
|
871
870
|
|
|
872
871
|
/**
|
|
873
|
-
* Single wire entry for system prompt assembly.
|
|
874
|
-
* v2 (default): layered Context Manager prompt.
|
|
875
|
-
* v1 (explicit off): legacy flat buildPromptContext.
|
|
872
|
+
* Single wire entry for system prompt assembly (layered Context Manager).
|
|
876
873
|
*/
|
|
877
874
|
function resolveWireSystemPrompt({
|
|
878
875
|
workspaceRoot = process.cwd(),
|
|
@@ -886,25 +883,15 @@ function resolveWireSystemPrompt({
|
|
|
886
883
|
} = {}) {
|
|
887
884
|
if (overrideSystemPrompt) return String(overrideSystemPrompt);
|
|
888
885
|
|
|
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({
|
|
886
|
+
return buildLayeredSystemPrompt({
|
|
903
887
|
workspaceRoot,
|
|
904
888
|
model,
|
|
905
889
|
provider,
|
|
906
890
|
appendSystemPrompt,
|
|
907
|
-
|
|
891
|
+
epochDynamic,
|
|
892
|
+
turnDynamic,
|
|
893
|
+
sessionStableExtras,
|
|
894
|
+
}).flatText;
|
|
908
895
|
}
|
|
909
896
|
|
|
910
897
|
function buildSessionSnapshotFromState(state = {}) {
|
|
@@ -1012,14 +999,12 @@ function resumeSessionState(state = {}, sessionId = "", workspaceRoot = process.
|
|
|
1012
999
|
? snapshot.toolCallsSinceCommit
|
|
1013
1000
|
: 0;
|
|
1014
1001
|
state.activeSkills = Array.isArray(snapshot.activeSkills) ? snapshot.activeSkills : [];
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
state.nlMessages = transcriptEventsToMessages(state.transcriptEvents, { preferArtifact: true });
|
|
1022
|
-
}
|
|
1002
|
+
ensureContextSessionState(state);
|
|
1003
|
+
const { ensureTranscript } = require("./context/assembler");
|
|
1004
|
+
ensureTranscript(state, state.workspaceRoot);
|
|
1005
|
+
if (Array.isArray(state.transcriptEvents) && state.transcriptEvents.length > 0) {
|
|
1006
|
+
const { transcriptEventsToMessages } = require("./context/transcript");
|
|
1007
|
+
state.nlMessages = transcriptEventsToMessages(state.transcriptEvents, { preferArtifact: true });
|
|
1023
1008
|
}
|
|
1024
1009
|
|
|
1025
1010
|
return {
|
|
@@ -1030,10 +1015,129 @@ function resumeSessionState(state = {}, sessionId = "", workspaceRoot = process.
|
|
|
1030
1015
|
};
|
|
1031
1016
|
}
|
|
1032
1017
|
|
|
1018
|
+
/**
|
|
1019
|
+
* Continue after TUI resolves approval/choice/chat.
|
|
1020
|
+
* ask_user: answer is written as the deferred tool_result (contiguous, no question echo).
|
|
1021
|
+
* checkpoint: short answer-only user message referencing interaction/node.
|
|
1022
|
+
*/
|
|
1023
|
+
async function resumeAfterUserInteraction(answerText = "", state = {}, options = {}) {
|
|
1024
|
+
ensureContextSessionState(state);
|
|
1025
|
+
const { resolveUserInteraction } = require("./context/userInteraction");
|
|
1026
|
+
const { appendAnswerToolResult } = require("./nativeRunner");
|
|
1027
|
+
const resolved = resolveUserInteraction(state.executionState, answerText);
|
|
1028
|
+
if (!resolved.ok) {
|
|
1029
|
+
return {
|
|
1030
|
+
ok: false,
|
|
1031
|
+
error: resolved.error || "failed to resolve user interaction",
|
|
1032
|
+
code: resolved.code || "",
|
|
1033
|
+
waitingUserInteraction: true,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const logs = [];
|
|
1038
|
+
const pushToolLog = (event) => {
|
|
1039
|
+
try {
|
|
1040
|
+
logs.push(normalizeToolLogEvent(event));
|
|
1041
|
+
} catch { /* ignore */ }
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
let messages = Array.isArray(state.nlMessages) ? state.nlMessages.slice() : [];
|
|
1045
|
+
if (resolved.continueMode === "tool_result" && resolved.resume && resolved.resume.call) {
|
|
1046
|
+
const appended = appendAnswerToolResult(messages, resolved.resume, resolved.answer);
|
|
1047
|
+
if (!appended.ok) {
|
|
1048
|
+
return { ok: false, error: appended.error || "failed to append answer tool_result" };
|
|
1049
|
+
}
|
|
1050
|
+
} else {
|
|
1051
|
+
// Checkpoint / non-tool path: answer-only contiguous user message (no question).
|
|
1052
|
+
messages.push({
|
|
1053
|
+
role: "user",
|
|
1054
|
+
content: JSON.stringify(resolved.answer),
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
state.nlMessages = messages;
|
|
1058
|
+
|
|
1059
|
+
const workspaceRoot = state.workspaceRoot || process.cwd();
|
|
1060
|
+
const assembled = assembleModelContext(state, {
|
|
1061
|
+
workspaceRoot,
|
|
1062
|
+
provider: state.provider,
|
|
1063
|
+
model: state.model,
|
|
1064
|
+
});
|
|
1065
|
+
const systemContext = assembled.systemPrompt || "";
|
|
1066
|
+
|
|
1067
|
+
let streamLastChar = "";
|
|
1068
|
+
const onDelta = typeof options.onDelta === "function" ? options.onDelta : null;
|
|
1069
|
+
const trackingOnDelta = onDelta
|
|
1070
|
+
? (delta) => {
|
|
1071
|
+
const text = String(delta || "");
|
|
1072
|
+
if (text) streamLastChar = text.slice(-1);
|
|
1073
|
+
return onDelta(delta);
|
|
1074
|
+
}
|
|
1075
|
+
: null;
|
|
1076
|
+
|
|
1077
|
+
const cliRes = await runNativeAgentTask({
|
|
1078
|
+
workspaceRoot,
|
|
1079
|
+
provider: state.provider,
|
|
1080
|
+
model: state.model,
|
|
1081
|
+
prompt: "",
|
|
1082
|
+
systemPrompt: systemContext,
|
|
1083
|
+
systemBlocks: assembled.systemBlocks || null,
|
|
1084
|
+
messages,
|
|
1085
|
+
sessionId: String(state.sessionId || ""),
|
|
1086
|
+
onToolEvent: pushToolLog,
|
|
1087
|
+
onStreamDelta: trackingOnDelta,
|
|
1088
|
+
onThinkingDelta: typeof options.onThinkingDelta === "function" ? options.onThinkingDelta : null,
|
|
1089
|
+
onPhase: typeof options.onPhase === "function" ? options.onPhase : null,
|
|
1090
|
+
executionState: state.executionState,
|
|
1091
|
+
signal: options.signal,
|
|
1092
|
+
resume: true,
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
if (cliRes && cliRes.executionState) {
|
|
1096
|
+
state.executionState = cliRes.executionState;
|
|
1097
|
+
}
|
|
1098
|
+
if (cliRes && Array.isArray(cliRes.messages)) {
|
|
1099
|
+
state.nlMessages = stripSkillBlocksFromMessages(cliRes.messages);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (!cliRes || cliRes.ok === false) {
|
|
1103
|
+
return {
|
|
1104
|
+
ok: false,
|
|
1105
|
+
error: (cliRes && cliRes.error) || "resume failed",
|
|
1106
|
+
logs,
|
|
1107
|
+
waitingUserInteraction: false,
|
|
1108
|
+
streamed: false,
|
|
1109
|
+
streamLastChar: "",
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (cliRes.waitingUserInteraction) {
|
|
1114
|
+
return {
|
|
1115
|
+
ok: true,
|
|
1116
|
+
summary: "Waiting for your reply",
|
|
1117
|
+
logs,
|
|
1118
|
+
waitingUserInteraction: true,
|
|
1119
|
+
interactionId: cliRes.interactionId || "",
|
|
1120
|
+
streamed: Boolean(cliRes.streamed),
|
|
1121
|
+
streamLastChar,
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return {
|
|
1126
|
+
ok: true,
|
|
1127
|
+
summary: String(cliRes.output || "").trim() || "continued",
|
|
1128
|
+
logs,
|
|
1129
|
+
waitingUserInteraction: false,
|
|
1130
|
+
streamed: Boolean(cliRes.streamed),
|
|
1131
|
+
streamLastChar,
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1033
1135
|
module.exports = {
|
|
1034
1136
|
runUcodeCoreAgent,
|
|
1035
1137
|
runSingleCommand,
|
|
1036
1138
|
runNaturalLanguageTask,
|
|
1139
|
+
resumeAfterUserInteraction,
|
|
1140
|
+
submitUserInteractionAnswer: (...args) => require("./protocol/suspension").submitUserInteractionAnswer(...args),
|
|
1037
1141
|
formatNlResult,
|
|
1038
1142
|
normalizeToolLogEvent,
|
|
1039
1143
|
isProjectAnalysisTask,
|