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.
- package/package.json +1 -1
- package/src/agents/prompts/native/environment.js +20 -8
- package/src/code/agent.js +517 -112
- package/src/code/commands.js +77 -0
- package/src/code/context/artifactGc.js +292 -0
- package/src/code/context/artifactIndex.js +161 -0
- package/src/code/context/artifacts.js +183 -0
- package/src/code/context/assembler.js +703 -0
- package/src/code/context/executionSegment.js +292 -0
- package/src/code/context/index.js +28 -0
- 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/projectSnapshot.js +201 -0
- package/src/code/context/promptLayers.js +175 -0
- package/src/code/context/reducers.js +328 -0
- package/src/code/context/stableJson.js +29 -0
- package/src/code/context/stateCommit.js +414 -0
- package/src/code/context/toolRuntime.js +172 -0
- package/src/code/context/transcript.js +182 -0
- package/src/code/context/transcriptSync.js +106 -0
- package/src/code/context/userInteraction.js +457 -0
- package/src/code/context/userNudge.js +116 -0
- package/src/code/context/workingSet.js +323 -0
- package/src/code/dispatch.js +20 -1
- package/src/code/index.js +8 -0
- package/src/code/modelCommand.js +87 -0
- package/src/code/nativeRunner.js +625 -34
- package/src/code/repl.js +196 -50
- 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 +217 -15
- package/src/code/skills/index.js +10 -0
- package/src/code/skills/injection.js +66 -3
- package/src/code/skills/loader.js +21 -0
- package/src/code/skills/manifest.js +87 -0
- package/src/code/skills/render.js +15 -1
- package/src/code/taskDecomposer.js +56 -2
- package/src/code/tools/artifactRead.js +40 -0
- package/src/code/tools/askUser.js +11 -0
- package/src/code/tools/planGraph.js +29 -0
- package/src/code/tui.js +2 -0
- package/src/code/usageStore.js +15 -0
- package/src/ui/format/index.js +285 -45
- package/src/ui/format/markdownRenderer.js +436 -71
- package/src/ui/ink/ChatApp.js +39 -8
- package/src/ui/ink/UcodeApp.js +592 -43
- 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
|
+
};
|