u-foo 2.5.14 → 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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/agents/prompts/native/environment.js +20 -8
  3. package/src/code/agent.js +517 -112
  4. package/src/code/commands.js +77 -0
  5. package/src/code/context/artifactGc.js +292 -0
  6. package/src/code/context/artifactIndex.js +161 -0
  7. package/src/code/context/artifacts.js +183 -0
  8. package/src/code/context/assembler.js +703 -0
  9. package/src/code/context/executionSegment.js +292 -0
  10. package/src/code/context/index.js +28 -0
  11. package/src/code/context/planGraph.js +1410 -0
  12. package/src/code/context/planGraphService.js +857 -0
  13. package/src/code/context/planMode.js +398 -0
  14. package/src/code/context/planProjection.js +432 -0
  15. package/src/code/context/projectSnapshot.js +201 -0
  16. package/src/code/context/promptLayers.js +175 -0
  17. package/src/code/context/reducers.js +328 -0
  18. package/src/code/context/stableJson.js +29 -0
  19. package/src/code/context/stateCommit.js +414 -0
  20. package/src/code/context/toolRuntime.js +172 -0
  21. package/src/code/context/transcript.js +182 -0
  22. package/src/code/context/transcriptSync.js +106 -0
  23. package/src/code/context/userInteraction.js +457 -0
  24. package/src/code/context/userNudge.js +116 -0
  25. package/src/code/context/workingSet.js +323 -0
  26. package/src/code/dispatch.js +20 -1
  27. package/src/code/index.js +8 -0
  28. package/src/code/modelCommand.js +87 -0
  29. package/src/code/nativeRunner.js +625 -34
  30. package/src/code/repl.js +196 -50
  31. package/src/code/runtime/agentWakeup.js +58 -0
  32. package/src/code/runtime/graphOwner.js +41 -0
  33. package/src/code/runtime/graphYieldRouter.js +42 -0
  34. package/src/code/runtime/index.js +15 -0
  35. package/src/code/runtime/loopMailbox.js +124 -0
  36. package/src/code/runtime/runtimeEvents.js +39 -0
  37. package/src/code/runtime/taskControl.js +565 -0
  38. package/src/code/runtime/taskFocus.js +165 -0
  39. package/src/code/runtime/taskLoop.js +383 -0
  40. package/src/code/runtime/taskRun.js +187 -0
  41. package/src/code/runtime/toolProvenance.js +70 -0
  42. package/src/code/runtime/workspaceLease.js +208 -0
  43. package/src/code/sessionStore.js +217 -15
  44. package/src/code/skills/index.js +10 -0
  45. package/src/code/skills/injection.js +66 -3
  46. package/src/code/skills/loader.js +21 -0
  47. package/src/code/skills/manifest.js +87 -0
  48. package/src/code/skills/render.js +15 -1
  49. package/src/code/taskDecomposer.js +56 -2
  50. package/src/code/tools/artifactRead.js +40 -0
  51. package/src/code/tools/askUser.js +11 -0
  52. package/src/code/tools/planGraph.js +29 -0
  53. package/src/code/tui.js +2 -0
  54. package/src/code/usageStore.js +15 -0
  55. package/src/ui/format/index.js +285 -45
  56. package/src/ui/format/markdownRenderer.js +436 -71
  57. package/src/ui/ink/ChatApp.js +39 -8
  58. package/src/ui/ink/UcodeApp.js +592 -43
  59. package/src/ui/ink/chatLogModel.js +102 -21
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Dynamic TaskFocus for each TaskLoop model turn (siblings + deps, no user text).
5
+ */
6
+
7
+ function nodeSummary(node = {}, run = null) {
8
+ if (!node) return null;
9
+ const result = (run && run.result) || node.result || null;
10
+ return {
11
+ id: node.id,
12
+ title: node.title || node.objective || node.id,
13
+ objective: node.objective || node.title || "",
14
+ status: (run && run.status) || node.status || "pending",
15
+ summary: result && result.summary ? String(result.summary) : "",
16
+ changedFiles: Array.isArray(run && run.changedFiles)
17
+ ? run.changedFiles.slice()
18
+ : (result && Array.isArray(result.changedFiles) ? result.changedFiles.slice() : []),
19
+ };
20
+ }
21
+
22
+ function canReach(nodesById = new Map(), fromId = "", toId = "", seen = new Set()) {
23
+ if (fromId === toId) return true;
24
+ if (seen.has(fromId)) return false;
25
+ seen.add(fromId);
26
+ const node = nodesById.get(fromId);
27
+ if (!node) return false;
28
+ for (const dep of node.dependsOn || []) {
29
+ if (canReach(nodesById, dep, toId, seen)) return true;
30
+ }
31
+ return false;
32
+ }
33
+
34
+ function listParallelSiblings(nodes = [], nodeId = "") {
35
+ const list = Array.isArray(nodes) ? nodes : [];
36
+ const byId = new Map(list.map((n) => [n.id, n]));
37
+ const self = byId.get(nodeId);
38
+ if (!self || self.type !== "task") return [];
39
+ const siblings = [];
40
+ for (const node of list) {
41
+ if (!node || node.id === nodeId || node.type !== "task") continue;
42
+ const exec = getExecutionKind(node);
43
+ if (exec !== "task_loop" && exec !== "llm" && exec !== "inline_llm" && exec !== "expand") {
44
+ // still show other tasks as siblings for awareness
45
+ }
46
+ if (canReach(byId, nodeId, node.id) || canReach(byId, node.id, nodeId)) continue;
47
+ siblings.push(node);
48
+ }
49
+ return siblings;
50
+ }
51
+
52
+ function getExecutionKind(node = {}) {
53
+ const exec = node.execution;
54
+ if (exec && typeof exec === "object") {
55
+ return String(exec.kind || "").trim().toLowerCase() || "inline_llm";
56
+ }
57
+ const raw = String(exec || "llm").trim().toLowerCase();
58
+ if (raw === "task_loop") return "task_loop";
59
+ if (raw === "expand") return "expand";
60
+ if (raw === "aggregate") return "aggregate";
61
+ if (raw === "inline_llm") return "inline_llm";
62
+ return raw === "llm" ? "inline_llm" : raw;
63
+ }
64
+
65
+ function listDependencySummaries(nodes = [], nodeId = "", taskRunsById = {}) {
66
+ const byId = new Map((Array.isArray(nodes) ? nodes : []).map((n) => [n.id, n]));
67
+ const self = byId.get(nodeId);
68
+ if (!self) return [];
69
+ const runs = taskRunsById && typeof taskRunsById === "object" ? taskRunsById : {};
70
+ return (self.dependsOn || []).map((depId) => {
71
+ const node = byId.get(depId);
72
+ if (!node) return { id: depId, status: "missing", title: depId, objective: "", summary: "", changedFiles: [] };
73
+ const activeRun = Object.values(runs).find((r) => (
74
+ r && r.parentNodeId === depId && (r.status === "succeeded" || r.status === "failed" || r.status === "cancelled")
75
+ )) || Object.values(runs).find((r) => r && r.parentNodeId === depId);
76
+ return nodeSummary(node, activeRun || null);
77
+ }).filter(Boolean);
78
+ }
79
+
80
+ function buildTaskFocus({
81
+ nodes = [],
82
+ currentNodeId = "",
83
+ taskRunsById = {},
84
+ recentlyChangedFiles = [],
85
+ } = {}) {
86
+ const byId = new Map((Array.isArray(nodes) ? nodes : []).map((n) => [n.id, n]));
87
+ const current = byId.get(currentNodeId);
88
+ const runs = taskRunsById && typeof taskRunsById === "object" ? taskRunsById : {};
89
+ const currentRun = Object.values(runs).find((r) => (
90
+ r && r.parentNodeId === currentNodeId
91
+ && (r.status === "queued" || r.status === "running" || r.status === "cancelling")
92
+ ));
93
+ const siblings = listParallelSiblings(nodes, currentNodeId).map((node) => {
94
+ const run = Object.values(runs).find((r) => r && r.parentNodeId === node.id) || null;
95
+ return nodeSummary(node, run);
96
+ });
97
+ const writers = Object.values(runs)
98
+ .filter((r) => r && (r.status === "running" || r.status === "cancelling"))
99
+ .map((r) => r.parentNodeId)
100
+ .filter(Boolean);
101
+
102
+ return {
103
+ currentTask: current
104
+ ? {
105
+ id: current.id,
106
+ objective: current.objective || current.title || "",
107
+ title: current.title || current.objective || current.id,
108
+ status: (currentRun && currentRun.status) || current.status,
109
+ }
110
+ : { id: currentNodeId, objective: "", title: currentNodeId, status: "unknown" },
111
+ dependencies: listDependencySummaries(nodes, currentNodeId, runs),
112
+ parallelSiblings: siblings,
113
+ workspace: {
114
+ concurrentWriters: writers,
115
+ recentlyChangedFiles: Array.isArray(recentlyChangedFiles)
116
+ ? recentlyChangedFiles.map(String).slice(-40)
117
+ : [],
118
+ },
119
+ };
120
+ }
121
+
122
+ function renderTaskFocusText(focus = {}) {
123
+ const lines = [
124
+ "TaskFocus (runtime; not a user message):",
125
+ `Current task: ${focus.currentTask && focus.currentTask.id} — ${focus.currentTask && focus.currentTask.objective}`,
126
+ ];
127
+ const deps = Array.isArray(focus.dependencies) ? focus.dependencies : [];
128
+ if (deps.length > 0) {
129
+ lines.push("Dependencies:");
130
+ for (const dep of deps) {
131
+ lines.push(` - ${dep.id} [${dep.status}] ${dep.summary || dep.objective || ""}`
132
+ + (dep.changedFiles && dep.changedFiles.length
133
+ ? ` files=[${dep.changedFiles.join(", ")}]`
134
+ : ""));
135
+ }
136
+ }
137
+ const siblings = Array.isArray(focus.parallelSiblings) ? focus.parallelSiblings : [];
138
+ if (siblings.length > 0) {
139
+ lines.push("Parallel siblings sharing this workspace:");
140
+ for (const sib of siblings) {
141
+ lines.push(` - ${sib.id} [${sib.status}] ${sib.summary || sib.objective || ""}`
142
+ + (sib.changedFiles && sib.changedFiles.length
143
+ ? ` files=[${sib.changedFiles.join(", ")}]`
144
+ : ""));
145
+ }
146
+ lines.push("Expect their edits already present or upcoming; coordinate rather than overwrite blindly.");
147
+ } else {
148
+ lines.push("Parallel siblings: (none)");
149
+ }
150
+ const writers = focus.workspace && Array.isArray(focus.workspace.concurrentWriters)
151
+ ? focus.workspace.concurrentWriters
152
+ : [];
153
+ if (writers.length > 0) {
154
+ lines.push(`Concurrent writers: ${writers.join(", ")}`);
155
+ }
156
+ return lines.join("\n");
157
+ }
158
+
159
+ module.exports = {
160
+ getExecutionKind,
161
+ listParallelSiblings,
162
+ buildTaskFocus,
163
+ renderTaskFocusText,
164
+ nodeSummary,
165
+ };
@@ -0,0 +1,383 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Event-driven TaskLoop worker (in-process, persist on executionState).
5
+ * Advances one step per processTaskRun call — recoverable across restarts
6
+ * as long as executionState.taskRuns is persisted with the session.
7
+ */
8
+
9
+ const { createPlanId } = require("../context/planGraph");
10
+ const {
11
+ emptyPlanGraphState,
12
+ runPlanGraphCommand,
13
+ ensurePlanGraphState,
14
+ } = require("../context/planGraphService");
15
+ const { taskLoopOwner } = require("./graphOwner");
16
+ const { createRuntimeEvent } = require("./runtimeEvents");
17
+ const {
18
+ enqueueAgentRuntime,
19
+ enqueueTaskEvent,
20
+ drainTaskMailbox,
21
+ } = require("./loopMailbox");
22
+ const {
23
+ getTaskRun,
24
+ putTaskRun,
25
+ casTaskRunStatus,
26
+ isTerminalTaskRun,
27
+ } = require("./taskRun");
28
+ const {
29
+ acquireTaskWriteLease,
30
+ releaseTaskWriteLease,
31
+ } = require("./workspaceLease");
32
+ const { buildTaskFocus, renderTaskFocusText } = require("./taskFocus");
33
+ const { recordToolProvenance, getProvenanceChangedFiles } = require("./toolProvenance");
34
+ const { routeGraphYield } = require("./graphYieldRouter");
35
+ const { checkWriteAllowed } = require("./workspaceLease");
36
+
37
+ function ensureGraphs(executionState = null) {
38
+ const state = ensurePlanGraphState(executionState);
39
+ if (!state.graphs || typeof state.graphs !== "object") {
40
+ state.graphs = {};
41
+ }
42
+ if (state.planGraph && state.planGraph.graphId) {
43
+ state.graphs[state.planGraph.graphId] = state.planGraph;
44
+ }
45
+ return state;
46
+ }
47
+
48
+ function getGraph(executionState = null, graphId = "") {
49
+ const state = ensureGraphs(executionState);
50
+ const id = String(graphId || "").trim();
51
+ if (id && state.graphs[id]) return state.graphs[id];
52
+ if (state.planGraph && (!id || state.planGraph.graphId === id)) return state.planGraph;
53
+ return null;
54
+ }
55
+
56
+ function setGraph(executionState = null, graph = null) {
57
+ const state = ensureGraphs(executionState);
58
+ if (!graph || !graph.graphId) return;
59
+ state.graphs[graph.graphId] = graph;
60
+ if (!state.planGraph || state.planGraph.graphId === graph.graphId || !state.planGraph.graphId) {
61
+ // Keep parent as planGraph when this is parent; child graphs stay in graphs map.
62
+ if (graph.owner && graph.owner.kind === "agent_loop") {
63
+ state.planGraph = graph;
64
+ }
65
+ }
66
+ }
67
+
68
+ function createChildGraphState({
69
+ parentGraphId = "",
70
+ parentNodeId = "",
71
+ taskRunId = "",
72
+ objective = "",
73
+ } = {}) {
74
+ const graph = emptyPlanGraphState();
75
+ graph.graphId = createPlanId("child");
76
+ graph.objective = String(objective || "").trim();
77
+ graph.owner = taskLoopOwner(taskRunId);
78
+ graph.parentGraphId = String(parentGraphId || "").trim();
79
+ graph.parentNodeId = String(parentNodeId || "").trim();
80
+ graph.nodes = [
81
+ {
82
+ id: "root",
83
+ type: "task",
84
+ title: objective || "Execute task",
85
+ objective: objective || "Execute task",
86
+ execution: { kind: "expand" },
87
+ dependsOn: [],
88
+ status: "pending",
89
+ },
90
+ ];
91
+ return graph;
92
+ }
93
+
94
+ function emitParentReadyChanged(executionState = null) {
95
+ const parent = executionState && executionState.planGraph;
96
+ if (!parent) return;
97
+ const readyNodes = (Array.isArray(parent.nodes) ? parent.nodes : [])
98
+ .filter((n) => n && n.status === "ready")
99
+ .map((n) => n.id);
100
+ enqueueAgentRuntime(executionState, createRuntimeEvent("parent_graph_ready_changed", {
101
+ readyNodes,
102
+ graphId: parent.graphId || "",
103
+ }));
104
+ }
105
+
106
+ function syncParentNodeFromRun(executionState = null, run = null) {
107
+ if (!run) return;
108
+ const parent = getGraph(executionState, run.parentGraphId) || executionState.planGraph;
109
+ if (!parent || !Array.isArray(parent.nodes)) return;
110
+ const node = parent.nodes.find((n) => n && n.id === run.parentNodeId);
111
+ if (!node) return;
112
+ if (!node.runtime || typeof node.runtime !== "object") node.runtime = {};
113
+ node.runtime.taskRunId = run.id;
114
+ node.runtime.childGraphId = run.childGraphId;
115
+ node.runtime.phase = run.phase;
116
+ if (run.status === "running" || run.status === "queued" || run.status === "cancelling") {
117
+ node.status = "running";
118
+ } else if (run.status === "succeeded") {
119
+ node.status = "succeeded";
120
+ node.result = run.result;
121
+ node.runtime.result = run.result;
122
+ } else if (run.status === "failed") {
123
+ node.status = "failed";
124
+ node.error = (run.error && run.error.message) || run.error || "task failed";
125
+ node.runtime.error = run.error;
126
+ } else if (run.status === "cancelled") {
127
+ node.status = "cancelled";
128
+ node.runtime.error = run.error;
129
+ }
130
+ setGraph(executionState, parent);
131
+ if (executionState.planGraph && executionState.planGraph.graphId === parent.graphId) {
132
+ executionState.planGraph = parent;
133
+ }
134
+ }
135
+
136
+ function finalizeSuccess(executionState = null, taskRunId = "", result = {}) {
137
+ const provenanceFiles = getProvenanceChangedFiles(executionState, taskRunId);
138
+ const mergedResult = result && typeof result === "object" ? { ...result } : {};
139
+ const fromResult = Array.isArray(mergedResult.changedFiles) ? mergedResult.changedFiles.map(String) : [];
140
+ mergedResult.changedFiles = Array.from(new Set(provenanceFiles.concat(fromResult)));
141
+ const cas = casTaskRunStatus(executionState, taskRunId, {
142
+ expectedStatus: "running",
143
+ nextStatus: "succeeded",
144
+ phase: "finalizing",
145
+ result: mergedResult,
146
+ changedFiles: mergedResult.changedFiles,
147
+ });
148
+ if (!cas.ok) return cas;
149
+ releaseTaskWriteLease(executionState, taskRunId);
150
+ syncParentNodeFromRun(executionState, cas.run);
151
+ enqueueAgentRuntime(executionState, createRuntimeEvent("task_succeeded", {
152
+ taskId: cas.run.parentNodeId,
153
+ taskRunId: cas.run.id,
154
+ result: mergedResult,
155
+ }));
156
+ emitParentReadyChanged(executionState);
157
+ return cas;
158
+ }
159
+
160
+ function finalizeFailure(executionState = null, taskRunId = "", error = {}, expectedStatus = "running") {
161
+ const cas = casTaskRunStatus(executionState, taskRunId, {
162
+ expectedStatus,
163
+ nextStatus: expectedStatus === "cancelling" ? "cancelled" : "failed",
164
+ phase: "finalizing",
165
+ error,
166
+ });
167
+ if (!cas.ok) {
168
+ // If already cancelling and we wanted failed, try cancelled path
169
+ if (expectedStatus === "running") {
170
+ return casTaskRunStatus(executionState, taskRunId, {
171
+ expectedStatus: "cancelling",
172
+ nextStatus: "failed",
173
+ phase: "finalizing",
174
+ error,
175
+ });
176
+ }
177
+ return cas;
178
+ }
179
+ releaseTaskWriteLease(executionState, taskRunId);
180
+ syncParentNodeFromRun(executionState, cas.run);
181
+ const eventType = cas.run.status === "cancelled" ? "task_cancelled" : "task_failed";
182
+ enqueueAgentRuntime(executionState, createRuntimeEvent(eventType, {
183
+ taskId: cas.run.parentNodeId,
184
+ taskRunId: cas.run.id,
185
+ error,
186
+ }));
187
+ emitParentReadyChanged(executionState);
188
+ return cas;
189
+ }
190
+
191
+ /**
192
+ * One worker tick for a TaskRun.
193
+ */
194
+ function processTaskRun(executionState = null, taskRunId = "", options = {}) {
195
+ const run = getTaskRun(executionState, taskRunId);
196
+ if (!run) return { ok: false, code: "TASK_RUN_NOT_FOUND" };
197
+ if (isTerminalTaskRun(run)) return { ok: true, status: run.status, terminal: true };
198
+
199
+ if (run.cancelRequested || run.status === "cancelling") {
200
+ return finalizeFailure(executionState, taskRunId, {
201
+ code: "TASK_CANCELLED",
202
+ message: (run.error && run.error.message) || "cancelled",
203
+ }, "cancelling");
204
+ }
205
+
206
+ if (run.status === "queued") {
207
+ const lease = acquireTaskWriteLease(executionState, run.id);
208
+ if (!lease.ok) {
209
+ return { ok: false, code: lease.code, owner: lease.owner, deferred: true };
210
+ }
211
+ casTaskRunStatus(executionState, run.id, {
212
+ expectedStatus: "queued",
213
+ nextStatus: "running",
214
+ phase: "planning",
215
+ });
216
+ enqueueAgentRuntime(executionState, createRuntimeEvent("task_started", {
217
+ taskId: run.parentNodeId,
218
+ taskRunId: run.id,
219
+ }));
220
+ }
221
+
222
+ const live = getTaskRun(executionState, taskRunId);
223
+ const child = getGraph(executionState, live.childGraphId);
224
+ if (!child) {
225
+ return finalizeFailure(executionState, taskRunId, {
226
+ code: "CHILD_GRAPH_MISSING",
227
+ message: "child graph missing",
228
+ });
229
+ }
230
+
231
+ // Drain control signals from task mailbox
232
+ const events = drainTaskMailbox(executionState, taskRunId);
233
+ for (const evt of events) {
234
+ if (evt.kind === "control" && evt.op === "complete_task") {
235
+ return finalizeSuccess(executionState, taskRunId, evt.result || {});
236
+ }
237
+ if (evt.kind === "control" && (evt.op === "fail_current_task" || evt.op === "fail_task")) {
238
+ return finalizeFailure(executionState, taskRunId, {
239
+ code: "TASK_FAILED",
240
+ message: String(evt.reason || "failed"),
241
+ });
242
+ }
243
+ }
244
+
245
+ const parent = getGraph(executionState, live.parentGraphId) || executionState.planGraph;
246
+ const focus = buildTaskFocus({
247
+ nodes: parent && parent.nodes ? parent.nodes : [],
248
+ currentNodeId: live.parentNodeId,
249
+ taskRunsById: (executionState.taskRuns && executionState.taskRuns.byId) || {},
250
+ recentlyChangedFiles: executionState.modifiedFiles || [],
251
+ });
252
+ live.lastFocusText = renderTaskFocusText(focus);
253
+ putTaskRun(executionState, live);
254
+
255
+ // Advance child graph tools if runTool provided
256
+ if (typeof options.runTool === "function") {
257
+ const previousActive = executionState.planGraph;
258
+ executionState.planGraph = child;
259
+ const wrappedRunTool = (toolInput = {}) => {
260
+ const current = getTaskRun(executionState, taskRunId);
261
+ if (!current || current.cancelRequested || current.status === "cancelling") {
262
+ return { ok: false, error: "task cancelled", code: "TASK_CANCELLED" };
263
+ }
264
+ const leaseCheck = checkWriteAllowed(executionState, {
265
+ tool: toolInput.tool || (toolInput.node && toolInput.node.tool),
266
+ originKind: "task_loop",
267
+ taskRunId,
268
+ });
269
+ if (!leaseCheck.ok) {
270
+ return {
271
+ ok: false,
272
+ error: leaseCheck.code,
273
+ code: leaseCheck.code,
274
+ owner: leaseCheck.owner,
275
+ };
276
+ }
277
+ const toolName = toolInput.tool || (toolInput.node && toolInput.node.tool) || "";
278
+ const args = toolInput.args || {};
279
+ recordToolProvenance(executionState, {
280
+ taskRunId,
281
+ tool: toolName,
282
+ args,
283
+ graphId: live.childGraphId,
284
+ nodeId: (toolInput.node && toolInput.node.id) || toolInput.stepId || "",
285
+ });
286
+ return options.runTool(toolInput);
287
+ };
288
+ try {
289
+ const advanced = runPlanGraphCommand({
290
+ operation: "patch",
291
+ operations: [],
292
+ commandId: `advance_${live.id}_${Date.now()}`,
293
+ }, {
294
+ executionState,
295
+ runTool: wrappedRunTool,
296
+ autoAdvance: true,
297
+ knownTools: options.knownTools,
298
+ });
299
+ // Persist child back
300
+ if (executionState.planGraph) {
301
+ executionState.planGraph.owner = child.owner;
302
+ executionState.planGraph.parentGraphId = child.parentGraphId;
303
+ executionState.planGraph.parentNodeId = child.parentNodeId;
304
+ setGraph(executionState, executionState.planGraph);
305
+ }
306
+ const after = getGraph(executionState, live.childGraphId) || executionState.planGraph;
307
+ casTaskRunStatus(executionState, live.id, {
308
+ expectedStatus: "running",
309
+ nextStatus: "running",
310
+ phase: after && after.waitingFor ? "waiting_model" : "executing_tools",
311
+ });
312
+ syncParentNodeFromRun(executionState, getTaskRun(executionState, taskRunId));
313
+
314
+ if (after && after.waitingFor) {
315
+ routeGraphYield(executionState, {
316
+ graph: after,
317
+ reason: after.lastYieldReason || "llm_required",
318
+ waitingFor: after.waitingFor,
319
+ });
320
+ enqueueTaskEvent(executionState, live.id, {
321
+ kind: "model_turn",
322
+ waitingFor: after.waitingFor,
323
+ focusText: live.lastFocusText,
324
+ });
325
+ return {
326
+ ok: true,
327
+ status: "running",
328
+ yieldReason: "llm_required",
329
+ focusText: live.lastFocusText,
330
+ waitingFor: after.waitingFor,
331
+ advance: advanced.modelPayload || advanced,
332
+ };
333
+ }
334
+ return {
335
+ ok: true,
336
+ status: "running",
337
+ yieldReason: "awaiting_complete_task",
338
+ focusText: live.lastFocusText,
339
+ advance: advanced.modelPayload || advanced,
340
+ };
341
+ } finally {
342
+ executionState.planGraph = previousActive;
343
+ }
344
+ }
345
+
346
+ enqueueTaskEvent(executionState, live.id, {
347
+ kind: "model_turn",
348
+ focusText: live.lastFocusText,
349
+ });
350
+ return {
351
+ ok: true,
352
+ status: "running",
353
+ yieldReason: "llm_required",
354
+ focusText: live.lastFocusText,
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Resume queued/running TaskRuns after process restart (executionState restored).
360
+ */
361
+ function resumePersistedTaskRuns(executionState = null, options = {}) {
362
+ const state = ensureGraphs(executionState);
363
+ const byId = state.taskRuns && state.taskRuns.byId ? state.taskRuns.byId : {};
364
+ const results = [];
365
+ for (const run of Object.values(byId)) {
366
+ if (!run) continue;
367
+ if (run.status !== "queued" && run.status !== "running") continue;
368
+ results.push(processTaskRun(state, run.id, options));
369
+ }
370
+ return results;
371
+ }
372
+
373
+ module.exports = {
374
+ ensureGraphs,
375
+ getGraph,
376
+ setGraph,
377
+ createChildGraphState,
378
+ processTaskRun,
379
+ finalizeSuccess,
380
+ finalizeFailure,
381
+ syncParentNodeFromRun,
382
+ resumePersistedTaskRuns,
383
+ };