u-foo 3.0.3 → 3.0.4
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
CHANGED
|
@@ -232,6 +232,7 @@ function renderPlanModeContext(executionState = null) {
|
|
|
232
232
|
" 2) expand_node on the current ready/waiting task when needed",
|
|
233
233
|
" 3) Runtime executes ready tools / TaskLoops; continue from results",
|
|
234
234
|
" 4) control.complete_task (inline) or control.start_task (task_loop)",
|
|
235
|
+
"Do not end the turn with text only while a task is waiting — advance it.",
|
|
235
236
|
"Do not mix plan_graph with data-plane tools in the same turn.",
|
|
236
237
|
);
|
|
237
238
|
|
|
@@ -56,6 +56,7 @@ function buildImmutablePrefix() {
|
|
|
56
56
|
"- After an accepted plan_graph create or patch, Runtime automatically advances ready tool nodes. Never invent or request an execute_graph tool.",
|
|
57
57
|
"- Do not call plan_graph or task_run together with read, read_image, write, edit, bash, or artifact_read in the same assistant turn.",
|
|
58
58
|
"- When an active graph is waiting on a task, advance that node through plan_graph instead of bypassing it with direct workspace tools: use patch.expand_node for execution.kind=expand, control.complete_task (nodeId) for execution.kind=inline_llm, or control.start_task for execution.kind=task_loop.",
|
|
59
|
+
"- Do not end a turn with text only while the plan is still waiting on a task; expand, start, or complete that node. Runtime will auto-continue if you stop early, but prefer advancing in the same turn.",
|
|
59
60
|
"- control.complete_task with nodeId completes a waiting_llm inline_llm task for the current Graph owner. control.complete_task with taskRunId (or task_run complete) is reserved for the owning TaskLoop. Do not directly complete expand or aggregate tasks.",
|
|
60
61
|
"- While Plan Mode is ON, workspace mutations must be represented as plan_graph tool nodes or performed inside a running TaskRun/task_loop.",
|
|
61
62
|
"- Treat a User reminder as the latest user instruction. Reconcile it before continuing from tool results. If it is compatible with the active plan, resume the waiting plan node; otherwise patch, cancel, or replan first.",
|
|
@@ -104,6 +104,52 @@ function buildContinuationUserPrompt(userText = "", executionState = null) {
|
|
|
104
104
|
return formatUserReminderMessage([text], { waitingFor: waiting });
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
const PLAN_AUTO_CONTINUE_STOP_REASONS = new Set([
|
|
108
|
+
"approval_required",
|
|
109
|
+
"graph_terminal",
|
|
110
|
+
"scheduler_deadlock",
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether the Agent Loop should keep going after a text-only model turn
|
|
115
|
+
* because the plan graph is still waiting on an agent-actionable task.
|
|
116
|
+
*/
|
|
117
|
+
function shouldAutoContinuePlan(executionState = null) {
|
|
118
|
+
if (!executionState || typeof executionState !== "object") return false;
|
|
119
|
+
if (executionState.pendingUserInteraction) return false;
|
|
120
|
+
const pg = executionState.planGraph && typeof executionState.planGraph === "object"
|
|
121
|
+
? executionState.planGraph
|
|
122
|
+
: null;
|
|
123
|
+
if (!pg) return false;
|
|
124
|
+
const waiting = pg.waitingFor && typeof pg.waitingFor === "object" ? pg.waitingFor : null;
|
|
125
|
+
if (!waiting || !waiting.id) return false;
|
|
126
|
+
if (String(waiting.type || "").trim().toLowerCase() !== "task") return false;
|
|
127
|
+
const yieldReason = String(pg.lastYieldReason || "").trim().toLowerCase();
|
|
128
|
+
if (yieldReason && PLAN_AUTO_CONTINUE_STOP_REASONS.has(yieldReason)) return false;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Internal reminder injected by runtime when the model ends a turn while the
|
|
134
|
+
* plan is still waiting on a task. Same shape as user nudges.
|
|
135
|
+
*/
|
|
136
|
+
function buildPlanAutoContinueReminder(executionState = null) {
|
|
137
|
+
const waiting = executionState
|
|
138
|
+
&& executionState.planGraph
|
|
139
|
+
&& executionState.planGraph.waitingFor
|
|
140
|
+
? executionState.planGraph.waitingFor
|
|
141
|
+
: null;
|
|
142
|
+
if (!waiting || !waiting.id) return "";
|
|
143
|
+
return formatUserReminderMessage(
|
|
144
|
+
[
|
|
145
|
+
"Continue the active plan. Serve the waiting task now via plan_graph "
|
|
146
|
+
+ "(expand_node, control.start_task, or control.complete_task as appropriate). "
|
|
147
|
+
+ "Do not end the turn with text only while this node is waiting.",
|
|
148
|
+
],
|
|
149
|
+
{ waitingFor: waiting },
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
107
153
|
module.exports = {
|
|
108
154
|
ensurePendingUserPrompts,
|
|
109
155
|
enqueueUserPrompt,
|
|
@@ -113,4 +159,7 @@ module.exports = {
|
|
|
113
159
|
shouldFrameAsUserReminder,
|
|
114
160
|
formatUserReminderMessage,
|
|
115
161
|
buildContinuationUserPrompt,
|
|
162
|
+
shouldAutoContinuePlan,
|
|
163
|
+
buildPlanAutoContinueReminder,
|
|
164
|
+
PLAN_AUTO_CONTINUE_STOP_REASONS,
|
|
116
165
|
};
|
package/src/code/nativeRunner.js
CHANGED
|
@@ -27,6 +27,8 @@ const {
|
|
|
27
27
|
clearUserPrompts,
|
|
28
28
|
formatUserReminderMessage,
|
|
29
29
|
ensurePendingUserPrompts,
|
|
30
|
+
shouldAutoContinuePlan,
|
|
31
|
+
buildPlanAutoContinueReminder,
|
|
30
32
|
} = require("./context/userNudge");
|
|
31
33
|
const {
|
|
32
34
|
runAskUserTool,
|
|
@@ -85,6 +87,8 @@ const DEFAULT_KIMI_MODEL = "k3";
|
|
|
85
87
|
const DEFAULT_MAX_NATIVE_TOOL_CALLS = 100;
|
|
86
88
|
const DEFAULT_MAX_NATIVE_TOOL_ERRORS = 20;
|
|
87
89
|
const DEFAULT_NATIVE_TIMEOUT_MS = 43200000; // 12 hours
|
|
90
|
+
/** Max text-only auto-continues while a plan is waiting on a task (per user submit). */
|
|
91
|
+
const DEFAULT_MAX_PLAN_AUTO_CONTINUES = 24;
|
|
88
92
|
// Anthropic Messages rejects max_tokens above the model's real cap (64K on
|
|
89
93
|
// current models), so the transports use different defaults. Override either
|
|
90
94
|
// via UFOO_UCODE_MAX_TOKENS (positive integer).
|
|
@@ -1759,6 +1763,9 @@ async function runNativeLoop({
|
|
|
1759
1763
|
// materialize Provider messages yet. STRICT via UFOO_UCODE_PROTOCOL_STRICT=1.
|
|
1760
1764
|
let activeLedger = null;
|
|
1761
1765
|
let lastProtocolLedger = null;
|
|
1766
|
+
let planAutoContinues = 0;
|
|
1767
|
+
let lastAutoContinueWaitingId = "";
|
|
1768
|
+
let consecutiveEmptyAutoContinues = 0;
|
|
1762
1769
|
|
|
1763
1770
|
if (resume) {
|
|
1764
1771
|
await withFaultPoint("before_provider_resume", () => {});
|
|
@@ -1775,6 +1782,29 @@ async function runNativeLoop({
|
|
|
1775
1782
|
messages.push({ role: "user", content });
|
|
1776
1783
|
}
|
|
1777
1784
|
|
|
1785
|
+
function tryInjectPlanAutoContinue() {
|
|
1786
|
+
if (!shouldAutoContinuePlan(executionState)) return false;
|
|
1787
|
+
if (planAutoContinues >= DEFAULT_MAX_PLAN_AUTO_CONTINUES) return false;
|
|
1788
|
+
const waitingId = String(
|
|
1789
|
+
(executionState.planGraph && executionState.planGraph.waitingFor
|
|
1790
|
+
&& executionState.planGraph.waitingFor.id) || ""
|
|
1791
|
+
).trim();
|
|
1792
|
+
if (
|
|
1793
|
+
consecutiveEmptyAutoContinues >= 2
|
|
1794
|
+
&& waitingId
|
|
1795
|
+
&& waitingId === lastAutoContinueWaitingId
|
|
1796
|
+
) {
|
|
1797
|
+
return false;
|
|
1798
|
+
}
|
|
1799
|
+
const reminder = buildPlanAutoContinueReminder(executionState);
|
|
1800
|
+
if (!reminder) return false;
|
|
1801
|
+
messages.push({ role: "user", content: reminder });
|
|
1802
|
+
planAutoContinues += 1;
|
|
1803
|
+
lastAutoContinueWaitingId = waitingId;
|
|
1804
|
+
consecutiveEmptyAutoContinues += 1;
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1778
1808
|
while (true) {
|
|
1779
1809
|
guards.ensureActive();
|
|
1780
1810
|
|
|
@@ -1846,6 +1876,9 @@ async function runNativeLoop({
|
|
|
1846
1876
|
if (!aggregated.trim() && text) {
|
|
1847
1877
|
aggregated = text;
|
|
1848
1878
|
}
|
|
1879
|
+
if (tryInjectPlanAutoContinue()) {
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1849
1882
|
return {
|
|
1850
1883
|
text: aggregated,
|
|
1851
1884
|
streamed,
|
|
@@ -1857,6 +1890,9 @@ async function runNativeLoop({
|
|
|
1857
1890
|
};
|
|
1858
1891
|
}
|
|
1859
1892
|
|
|
1893
|
+
// A tool-using turn resets the empty auto-continue streak (progress possible).
|
|
1894
|
+
consecutiveEmptyAutoContinues = 0;
|
|
1895
|
+
|
|
1860
1896
|
const pendingCalls = transport.prepareToolCalls({ messages, turnResult, toolCalls });
|
|
1861
1897
|
if (!pendingCalls) {
|
|
1862
1898
|
return {
|