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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/code/agent.js +333 -243
  3. package/src/code/commands.js +16 -0
  4. package/src/code/context/assembler.js +18 -13
  5. package/src/code/context/executionSegment.js +97 -119
  6. package/src/code/context/index.js +11 -1
  7. package/src/code/context/planGraph.js +1410 -0
  8. package/src/code/context/planGraphService.js +857 -0
  9. package/src/code/context/planMode.js +398 -0
  10. package/src/code/context/planProjection.js +432 -0
  11. package/src/code/context/promptLayers.js +21 -5
  12. package/src/code/context/stateCommit.js +2 -0
  13. package/src/code/context/toolRuntime.js +172 -0
  14. package/src/code/context/userInteraction.js +457 -0
  15. package/src/code/context/userNudge.js +116 -0
  16. package/src/code/dispatch.js +17 -1
  17. package/src/code/index.js +2 -0
  18. package/src/code/nativeRunner.js +518 -37
  19. package/src/code/repl.js +160 -18
  20. package/src/code/runtime/agentWakeup.js +58 -0
  21. package/src/code/runtime/graphOwner.js +41 -0
  22. package/src/code/runtime/graphYieldRouter.js +42 -0
  23. package/src/code/runtime/index.js +15 -0
  24. package/src/code/runtime/loopMailbox.js +124 -0
  25. package/src/code/runtime/runtimeEvents.js +39 -0
  26. package/src/code/runtime/taskControl.js +565 -0
  27. package/src/code/runtime/taskFocus.js +165 -0
  28. package/src/code/runtime/taskLoop.js +383 -0
  29. package/src/code/runtime/taskRun.js +187 -0
  30. package/src/code/runtime/toolProvenance.js +70 -0
  31. package/src/code/runtime/workspaceLease.js +208 -0
  32. package/src/code/sessionStore.js +0 -10
  33. package/src/code/skills/injection.js +1 -0
  34. package/src/code/taskDecomposer.js +32 -8
  35. package/src/code/tools/askUser.js +11 -0
  36. package/src/code/tools/planGraph.js +29 -0
  37. package/src/ui/format/index.js +25 -1
  38. package/src/ui/format/markdownRenderer.js +224 -2
  39. package/src/ui/ink/UcodeApp.js +285 -22
  40. package/src/code/context/featureFlag.js +0 -13
@@ -8,6 +8,7 @@ const UCODE_COMMAND_REGISTRY = [
8
8
  { cmd: "/help", desc: "Show available commands", order: 10 },
9
9
  { cmd: "/status", desc: "Show session / usage status", order: 20 },
10
10
  { cmd: "/model", desc: "Show or switch the active model", order: 25 },
11
+ { cmd: "/plan", desc: "Show plan progress or set plan mode", order: 27 },
11
12
  { cmd: "/ubus", desc: "Check pending bus messages", order: 30 },
12
13
  { cmd: "/resume", desc: "Resume a saved session", order: 40 },
13
14
  { cmd: "/skills", desc: "List or show skills", order: 50 },
@@ -23,6 +24,20 @@ const UCODE_COMMAND_TREE = {
23
24
  hasArguments: true,
24
25
  optionalArguments: true,
25
26
  },
27
+ "/plan": {
28
+ desc: "Show plan progress or set plan mode",
29
+ hasArguments: true,
30
+ optionalArguments: true,
31
+ children: {
32
+ on: { desc: "Enable plan mode", order: 1 },
33
+ off: { desc: "Disable plan mode", order: 2 },
34
+ show: { desc: "Show plan band and status", order: 3 },
35
+ hide: { desc: "Hide plan band", order: 4 },
36
+ focus: { desc: "Expand plan band with task detail", order: 5 },
37
+ debug: { desc: "Show raw graph debug band", order: 6 },
38
+ clear: { desc: "Clear plan graph (stay in mode)", order: 7 },
39
+ },
40
+ },
26
41
  "/ubus": { desc: "Check pending bus messages" },
27
42
  "/resume": { desc: "Resume a saved session", hasArguments: true },
28
43
  "/skills": {
@@ -45,6 +60,7 @@ function listUcodeCommandsForHelp() {
45
60
  " /ubus",
46
61
  " /status",
47
62
  " /model [model-id]",
63
+ " /plan [on|off|show|hide|focus|debug|clear]",
48
64
  " /skills [list]",
49
65
  " /skills show <name>",
50
66
  " /bg <task>",
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
 
3
- const { isContextV2Enabled } = require("./featureFlag");
4
3
  const {
5
4
  loadTranscript,
6
5
  transcriptEventsToMessages,
@@ -33,6 +32,8 @@ const {
33
32
  pruneWorkingSetByRetention,
34
33
  } = require("./workingSet");
35
34
  const { renderExecutionSegmentContext } = require("./executionSegment");
35
+ const { renderPlanModeContext } = require("./planMode");
36
+ const { drainAgentMailboxForTurn } = require("../runtime/agentWakeup");
36
37
 
37
38
  const DEFAULT_TRANSCRIPT_WINDOW = 12;
38
39
  const DEFAULT_RECENT_TOOL_EVENTS = 4;
@@ -47,7 +48,6 @@ function defaultContextPolicy(env = process.env) {
47
48
  return {
48
49
  transcriptWindow: resolveTranscriptWindow(env),
49
50
  commitInterval: resolveCommitInterval(env),
50
- v2: isContextV2Enabled(env),
51
51
  };
52
52
  }
53
53
 
@@ -342,11 +342,11 @@ function buildModelMessagesFromTranscript(transcriptEvents = [], session = {}, w
342
342
  }
343
343
 
344
344
  function buildRecentMessages(transcriptEvents = [], windowSize = DEFAULT_TRANSCRIPT_WINDOW, session = null) {
345
- if (session && isContextV2Enabled()) {
345
+ if (session) {
346
346
  return buildModelMessagesFromTranscript(transcriptEvents, session, windowSize);
347
347
  }
348
348
  const recent = eventsSliceWindow(transcriptEvents, windowSize);
349
- return recent.map((event) => eventToModelMessage(event, { preferArtifact: isContextV2Enabled() })).filter(Boolean);
349
+ return recent.map((event) => eventToModelMessage(event, { preferArtifact: true })).filter(Boolean);
350
350
  }
351
351
 
352
352
  function eventsSliceWindow(events = [], windowSize = DEFAULT_TRANSCRIPT_WINDOW) {
@@ -397,6 +397,16 @@ function assembleModelContext(session = {}, request = {}, env = process.env) {
397
397
  ].filter(Boolean).join("\n\n"),
398
398
  turnDynamic: [
399
399
  request.turnDynamic || "",
400
+ (() => {
401
+ try {
402
+ const { MAX_CONCURRENT_WRITE_LEASES } = require("../runtime/workspaceLease");
403
+ return `Current max concurrent writing TaskRuns: ${MAX_CONCURRENT_WRITE_LEASES}`;
404
+ } catch {
405
+ return "";
406
+ }
407
+ })(),
408
+ renderPlanModeContext(session.executionState),
409
+ drainAgentMailboxForTurn(session.executionState).text,
400
410
  renderProjectSnapshotContext(session.projectSnapshot),
401
411
  renderWorkingSetContext(session.workingSet, session),
402
412
  renderExecutionSegmentContext(session.executionState),
@@ -541,7 +551,7 @@ function syncMessagesToTranscript(session = {}, messages = [], workspaceRoot = p
541
551
  : null;
542
552
  if (baseline == null) {
543
553
  const existingMessages = transcriptEventsToMessages(prior, {
544
- preferArtifact: isContextV2Enabled(),
554
+ preferArtifact: true,
545
555
  });
546
556
  baseline = matchTranscriptBaseline(existingMessages, full);
547
557
  }
@@ -549,7 +559,7 @@ function syncMessagesToTranscript(session = {}, messages = [], workspaceRoot = p
549
559
 
550
560
  if (full.length <= baseline) {
551
561
  session.nlMessages = transcriptEventsToMessages(session.transcriptEvents, {
552
- preferArtifact: isContextV2Enabled(),
562
+ preferArtifact: true,
553
563
  });
554
564
  return session.transcriptEvents || prior;
555
565
  }
@@ -560,15 +570,10 @@ function syncMessagesToTranscript(session = {}, messages = [], workspaceRoot = p
560
570
  ? session.executionState.currentSegmentId
561
571
  : "",
562
572
  };
563
- if (isContextV2Enabled()) {
564
- appendTranscriptMessagesForStorage(workspaceRoot, sessionId, delta, extra);
565
- } else {
566
- const { appendTranscriptMessages } = require("./transcript");
567
- appendTranscriptMessages(workspaceRoot, sessionId, delta, extra);
568
- }
573
+ appendTranscriptMessagesForStorage(workspaceRoot, sessionId, delta, extra);
569
574
  session.transcriptEvents = loadTranscript(workspaceRoot, sessionId).events;
570
575
  session.nlMessages = transcriptEventsToMessages(session.transcriptEvents, {
571
- preferArtifact: isContextV2Enabled(),
576
+ preferArtifact: true,
572
577
  });
573
578
  session.summary = buildRollingSummary(session.transcriptEvents, session.summary, session);
574
579
  return session.transcriptEvents;
@@ -1,17 +1,33 @@
1
1
  "use strict";
2
2
 
3
3
  const { randomUUID } = require("crypto");
4
+ const {
5
+ executePlanGraph,
6
+ planGraphFromExecutionSegment,
7
+ compilePlanGraph,
8
+ } = require("./planGraph");
4
9
 
5
10
  function emptyExecutionState() {
6
11
  return {
7
12
  currentSegmentId: "",
8
13
  mode: "single_action",
14
+ planMode: false,
15
+ planModeSource: "",
9
16
  steps: {},
10
17
  modifiedFiles: [],
11
18
  lastExitCodes: [],
12
19
  approvals: [],
13
20
  retries: {},
14
21
  segments: [],
22
+ pendingUserPrompts: [],
23
+ planGraph: require("./planGraphService").emptyPlanGraphState(),
24
+ graphs: {},
25
+ taskRuns: require("../runtime/taskRun").emptyTaskRunStore(),
26
+ agentMailbox: require("../runtime/loopMailbox").emptyMailbox(),
27
+ taskMailboxes: {},
28
+ workspaceLease: require("../runtime/workspaceLease").emptyWorkspaceLease(),
29
+ planUi: { bandMode: "auto" },
30
+ pendingUserInteraction: null,
15
31
  };
16
32
  }
17
33
 
@@ -93,19 +109,29 @@ function shouldStopSegment(executionState = null, {
93
109
  }
94
110
 
95
111
  function renderExecutionSegmentContext(executionState = null) {
96
- if (!executionState || !executionState.currentSegmentId) return "";
97
- const lines = [
98
- "Current Execution Segment:",
99
- `- Segment: ${executionState.currentSegmentId}`,
100
- `- Mode: ${executionState.mode || "single_action"}`,
101
- ];
102
- const steps = executionState.steps && typeof executionState.steps === "object"
103
- ? Object.entries(executionState.steps)
104
- : [];
105
- if (steps.length > 0) {
106
- lines.push("Step status:");
107
- for (const [id, info] of steps) {
108
- lines.push(`- ${id}: ${info.status}${info.error ? ` (${info.error})` : ""}`);
112
+ if (!executionState || typeof executionState !== "object") return "";
113
+ const lines = [];
114
+ if (executionState.planMode) {
115
+ // Detailed plan-mode instructions come from renderPlanModeContext.
116
+ lines.push("Execution mode: plan_mode");
117
+ }
118
+ if (!executionState.currentSegmentId && !(executionState.planGraph && executionState.planGraph.graphId)) {
119
+ return lines.join("\n");
120
+ }
121
+ if (executionState.currentSegmentId) {
122
+ lines.push(
123
+ "Current Execution Segment:",
124
+ `- Segment: ${executionState.currentSegmentId}`,
125
+ `- Mode: ${executionState.mode || "single_action"}`,
126
+ );
127
+ const steps = executionState.steps && typeof executionState.steps === "object"
128
+ ? Object.entries(executionState.steps)
129
+ : [];
130
+ if (steps.length > 0) {
131
+ lines.push("Step status:");
132
+ for (const [id, info] of steps) {
133
+ lines.push(`- ${id}: ${info.status}${info.error ? ` (${info.error})` : ""}`);
134
+ }
109
135
  }
110
136
  }
111
137
  return lines.join("\n");
@@ -118,21 +144,9 @@ function parseExecutionSegment(sideEffects = null) {
118
144
  return null;
119
145
  }
120
146
 
121
- const DEFAULT_MAX_SEGMENT_STEPS = 4;
147
+ const DEFAULT_MAX_SEGMENT_STEPS = 16;
122
148
  const SIDE_EFFECT_TOOLS = new Set(["write", "edit"]);
123
149
 
124
- function resolveStepArgs(args = {}, stepOutputs = new Map()) {
125
- const next = args && typeof args === "object" ? { ...args } : {};
126
- const argsJson = JSON.stringify(next);
127
- for (const [depId, depValue] of stepOutputs.entries()) {
128
- const token = `\${${depId}.matches}`;
129
- if (argsJson.includes(token) && depValue && depValue.matches) {
130
- next.matches = depValue.matches;
131
- }
132
- }
133
- return next;
134
- }
135
-
136
150
  function isSideEffectTool(tool = "") {
137
151
  return SIDE_EFFECT_TOOLS.has(String(tool || "").trim().toLowerCase());
138
152
  }
@@ -146,9 +160,14 @@ function formatSegmentResultMessage(result = {}) {
146
160
  stoppedAt: result.stoppedAt || "",
147
161
  steps: Array.isArray(result.results) ? result.results : [],
148
162
  error: result.error || "",
163
+ plan: result.summary || null,
149
164
  });
150
165
  }
151
166
 
167
+ /**
168
+ * Execute a legacy execution_segment via the unified plan graph engine.
169
+ * Preserves the previous return shape used by agent/nativeRunner.
170
+ */
152
171
  function executeExecutionSegment({
153
172
  segment = {},
154
173
  executionState = null,
@@ -162,113 +181,69 @@ function executeExecutionSegment({
162
181
  const cappedSegment = { ...normalized, steps: cappedSteps };
163
182
  const { state: startedState, segmentId } = startExecutionSegment(executionState, cappedSegment);
164
183
  let state = startedState;
165
- const stepOutputs = new Map();
166
- const results = [];
167
- const checkpointAfter = new Set(
168
- Array.isArray(cappedSegment.checkpoint && cappedSegment.checkpoint.after)
169
- ? cappedSegment.checkpoint.after.map(String)
170
- : [],
171
- );
172
- let stoppedAt = "";
173
- let fatalError = "";
174
184
 
175
- for (const step of cappedSteps) {
176
- const deps = Array.isArray(step.dependsOn) ? step.dependsOn : [];
177
- for (const dep of deps) {
178
- if (!stepOutputs.has(dep)) {
185
+ const plan = planGraphFromExecutionSegment(cappedSegment);
186
+ plan.id = segmentId;
187
+
188
+ const graphResult = executePlanGraph(plan, {
189
+ maxNodeRuns: Math.max(1, Math.floor(maxSteps)) * 2,
190
+ runStep: ({ stepId, tool, args }) => {
191
+ if (typeof onStepStart === "function") {
192
+ try {
193
+ onStepStart({ stepId, tool, args });
194
+ } catch {
195
+ // ignore
196
+ }
197
+ }
198
+ const result = runStep({ stepId, tool, args }) || { ok: false, error: "step failed" };
199
+ if (typeof onStepComplete === "function") {
200
+ try {
201
+ onStepComplete({ stepId, tool, args, result });
202
+ } catch {
203
+ // ignore
204
+ }
205
+ }
206
+ if (result && result.ok !== false) {
179
207
  state = recordStepResult(state, {
180
- stepId: step.id,
208
+ stepId,
209
+ status: "success",
210
+ artifactId: result.artifactId || "",
211
+ exitCode: Number.isFinite(result.code) ? result.code : null,
212
+ });
213
+ } else {
214
+ state = recordStepResult(state, {
215
+ stepId,
181
216
  status: "failed",
182
- error: `missing dependency ${dep}`,
217
+ error: String((result && result.error) || "step failed"),
183
218
  });
184
- fatalError = `segment dependency missing: ${dep}`;
185
- state = completeExecutionSegment(state, { status: "failed", error: fatalError });
186
- return {
187
- ok: false,
188
- segmentId,
189
- objective: cappedSegment.objective,
190
- executionState: state,
191
- results,
192
- error: fatalError,
193
- stoppedAt: "dependency",
194
- };
195
219
  }
196
- }
197
-
198
- const args = resolveStepArgs(step.args, stepOutputs);
199
- if (typeof onStepStart === "function") {
200
- try {
201
- onStepStart({ stepId: step.id, tool: step.tool, args });
202
- } catch {
203
- // ignore
204
- }
205
- }
206
-
207
- const result = runStep({ stepId: step.id, tool: step.tool, args }) || { ok: false, error: "step failed" };
208
- const stepRecord = {
209
- stepId: step.id,
210
- tool: step.tool,
211
- ok: result.ok !== false,
212
- artifactId: result.artifactId || "",
213
- error: result.error || "",
214
- };
215
- results.push(stepRecord);
216
-
217
- if (result.ok === false) {
218
- state = recordStepResult(state, {
219
- stepId: step.id,
220
- status: "failed",
221
- error: String(result.error || "step failed"),
222
- });
223
- fatalError = String(result.error || "segment step failed");
224
- state = completeExecutionSegment(state, { status: "failed", error: fatalError });
225
- return {
226
- ok: false,
227
- segmentId,
228
- objective: cappedSegment.objective,
229
- executionState: state,
230
- results,
231
- error: fatalError,
232
- stoppedAt: "error",
233
- };
234
- }
235
-
236
- stepOutputs.set(step.id, result);
237
- state = recordStepResult(state, {
238
- stepId: step.id,
239
- status: "success",
240
- artifactId: result.artifactId || "",
241
- exitCode: Number.isFinite(result.code) ? result.code : null,
242
- });
220
+ return result;
221
+ },
222
+ });
243
223
 
244
- if (typeof onStepComplete === "function") {
245
- try {
246
- onStepComplete({ stepId: step.id, tool: step.tool, args, result });
247
- } catch {
248
- // ignore
249
- }
250
- }
224
+ const results = Array.isArray(graphResult.results) ? graphResult.results : [];
225
+ const stoppedAt = String(graphResult.stoppedAt || "");
226
+ const fatalError = graphResult.ok === false
227
+ ? String(graphResult.error || "segment failed")
228
+ : "";
251
229
 
252
- if (checkpointAfter.has(step.id)) {
253
- stoppedAt = "checkpoint";
254
- break;
255
- }
256
- if (isSideEffectTool(step.tool)) {
257
- stoppedAt = "side_effect";
258
- break;
259
- }
260
- }
230
+ let finalStatus = "success";
231
+ if (fatalError) finalStatus = "failed";
232
+ else if (stoppedAt === "checkpoint" || stoppedAt === "waiting_llm") finalStatus = "checkpoint";
233
+ else if (stoppedAt === "side_effect") finalStatus = "success";
261
234
 
262
- const finalStatus = stoppedAt === "checkpoint" ? "checkpoint" : "success";
263
- state = completeExecutionSegment(state, { status: finalStatus });
235
+ state = completeExecutionSegment(state, { status: finalStatus, error: fatalError });
264
236
  return {
265
- ok: true,
237
+ ok: graphResult.ok !== false,
266
238
  segmentId,
267
239
  objective: cappedSegment.objective,
268
240
  executionState: state,
269
241
  results,
270
- error: "",
242
+ error: fatalError,
271
243
  stoppedAt,
244
+ waitingFor: graphResult.waitingFor || null,
245
+ summary: graphResult.summary || null,
246
+ compile: graphResult.compile || null,
272
247
  };
273
248
  }
274
249
 
@@ -311,4 +286,7 @@ module.exports = {
311
286
  executeExecutionSegment,
312
287
  formatSegmentResultMessage,
313
288
  isSideEffectTool,
289
+ planGraphFromExecutionSegment,
290
+ compilePlanGraph,
291
+ executePlanGraph,
314
292
  };
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  module.exports = {
4
- ...require("./featureFlag"),
5
4
  ...require("./transcript"),
6
5
  ...require("./transcriptSync"),
7
6
  ...require("./artifacts"),
@@ -14,5 +13,16 @@ module.exports = {
14
13
  ...require("./stateCommit"),
15
14
  ...require("./workingSet"),
16
15
  ...require("./executionSegment"),
16
+ ...require("./planGraph"),
17
+ ...require("./planGraphService"),
18
+ ...require("./toolRuntime"),
19
+ ...require("./planMode"),
20
+ ...require("./planProjection"),
21
+ ...require("./userNudge"),
22
+ ...require("./userInteraction"),
17
23
  ...require("./assembler"),
18
24
  };
25
+
26
+ // Runtime TaskLoop surface (avoid name clashes by nesting under .runtime if needed by callers)
27
+ module.exports.runtime = require("../runtime");
28
+