u-foo 3.0.9 → 3.0.11
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/README.md +25 -9
- package/README.zh-CN.md +22 -9
- package/dist/tui/darwin-arm64/ufoo-tui +0 -0
- package/dist/tui/darwin-x64/ufoo-tui +0 -0
- package/dist/tui/linux-arm64/ufoo-tui +0 -0
- package/dist/tui/linux-x64/ufoo-tui +0 -0
- package/package.json +12 -4
- package/scripts/pack-tui.js +112 -0
- package/scripts/postinstall.js +11 -0
- package/src/agents/activity/activityReconcile.js +106 -0
- package/src/agents/activity/activityStatePublisher.js +31 -2
- package/src/agents/activity/index.js +1 -0
- package/src/agents/launch/launcher.js +19 -0
- package/src/agents/launch/ptyRunner.js +20 -1
- package/src/app/chat/ChatController.js +433 -0
- package/src/app/chat/agentDirectory.js +63 -0
- package/src/app/chat/agentEnter.js +70 -0
- package/src/app/chat/agentIdentity.js +50 -0
- package/src/app/chat/bootstrap.js +66 -0
- package/src/app/chat/commandExecutor.js +108 -0
- package/src/app/chat/commands.js +38 -1
- package/src/app/chat/dashboardView.js +6 -2
- package/src/app/chat/historyStore.js +181 -0
- package/src/app/chat/index.js +14 -2
- package/src/app/chat/inputSubmitHandler.js +21 -7
- package/src/app/chat/ipcBuilders.js +52 -0
- package/src/app/chat/multiWindow/paneManager.js +10 -1
- package/src/app/chat/multiWindow/renderer.js +1 -1
- package/src/app/chat/multiWindow/vtFrame.js +93 -0
- package/src/app/chat/streamState.js +182 -0
- package/src/app/cli/features/doctor.js +22 -0
- package/src/code/UcodeController.js +156 -0
- package/src/code/context/planGraphService.js +4 -0
- package/src/code/repl.js +4 -3
- package/src/code/runtime/taskLoop.js +46 -50
- package/src/code/tui.js +13 -2
- package/src/code/ucodeSlashDispatch.js +241 -0
- package/src/coordination/bus/activate.js +3 -0
- package/src/runtime/contracts/schemas/ufoo-ui-v1/envelope.json +28 -0
- package/src/runtime/contracts/uiProtocol.js +190 -0
- package/src/ui/{ink/chatLogModel.js → chatLogModel.js} +2 -2
- package/src/ui/dashboardBridge.js +81 -0
- package/src/ui/format/index.js +2 -2
- package/src/ui/index.js +8 -4
- package/src/ui/multiPaneBusMirror.js +137 -0
- package/src/ui/multiWindowHandoff.js +232 -0
- package/src/ui/ptyHandoff.js +23 -0
- package/src/ui/rustChatHost.js +1520 -0
- package/src/ui/rustMultiSession.js +497 -0
- package/src/ui/rustUcodeHost.js +999 -0
- package/src/ui/scrollbackReplay.js +82 -0
- package/src/ui/settingsBridge.js +49 -0
- package/src/ui/toolMergeBridge.js +66 -0
- package/src/ui/tuiLauncher.js +105 -0
- package/src/ui/ucodeStatusLine.js +74 -0
- package/src/ui/uiHostServer.js +339 -0
- package/src/ui/MIGRATION.md +0 -334
- package/src/ui/ink/ChatApp.js +0 -4152
- package/src/ui/ink/DashboardBar.js +0 -691
- package/src/ui/ink/InkDemo.js +0 -96
- package/src/ui/ink/MultilineInput.js +0 -662
- package/src/ui/ink/UcodeApp.js +0 -1675
- package/src/ui/ink/agentMirror.js +0 -730
- package/src/ui/ink/chatReducer.js +0 -473
- package/src/ui/runInk.js +0 -66
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Burst-coalescing sender + chat stream state (Phase 0B extraction from ChatApp).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { stripBlessedTags } = require("../../ui/chatLogModel");
|
|
8
|
+
|
|
9
|
+
const STREAM_FLUSH_INTERVAL_MS = 80;
|
|
10
|
+
|
|
11
|
+
function createThrottledSender(send, windowMs = 500) {
|
|
12
|
+
let lastSentAt = 0;
|
|
13
|
+
let timer = null;
|
|
14
|
+
const fire = () => {
|
|
15
|
+
timer = null;
|
|
16
|
+
lastSentAt = Date.now();
|
|
17
|
+
send();
|
|
18
|
+
};
|
|
19
|
+
return () => {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const elapsed = now - lastSentAt;
|
|
22
|
+
if (elapsed >= windowMs) {
|
|
23
|
+
if (timer) {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
timer = null;
|
|
26
|
+
}
|
|
27
|
+
lastSentAt = now;
|
|
28
|
+
send();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!timer) {
|
|
32
|
+
timer = setTimeout(fire, windowMs - elapsed);
|
|
33
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createChatStreamState({
|
|
39
|
+
dispatch,
|
|
40
|
+
appendHistory,
|
|
41
|
+
displayNameForPublisher = (value) => value,
|
|
42
|
+
flushIntervalMs = STREAM_FLUSH_INTERVAL_MS,
|
|
43
|
+
} = {}) {
|
|
44
|
+
const streams = new Map();
|
|
45
|
+
const pendingDeliveries = new Map();
|
|
46
|
+
const pendingDeltas = new Map();
|
|
47
|
+
let flushTimer = null;
|
|
48
|
+
|
|
49
|
+
function flushDeltas() {
|
|
50
|
+
if (flushTimer) {
|
|
51
|
+
clearTimeout(flushTimer);
|
|
52
|
+
flushTimer = null;
|
|
53
|
+
}
|
|
54
|
+
if (pendingDeltas.size === 0) return;
|
|
55
|
+
for (const batch of pendingDeltas.values()) {
|
|
56
|
+
dispatch({
|
|
57
|
+
type: "stream/delta",
|
|
58
|
+
publisher: batch.publisher,
|
|
59
|
+
delta: batch.parts.join(""),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
pendingDeltas.clear();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function scheduleFlush() {
|
|
66
|
+
if (flushTimer) return;
|
|
67
|
+
flushTimer = setTimeout(flushDeltas, flushIntervalMs);
|
|
68
|
+
if (typeof flushTimer.unref === "function") flushTimer.unref();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function deliveryKey(agentId, agentLabel) {
|
|
72
|
+
return String(agentId || agentLabel || "").trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function markPendingDelivery(agentId, agentLabel) {
|
|
76
|
+
const key = deliveryKey(agentId, agentLabel);
|
|
77
|
+
if (!key) return;
|
|
78
|
+
const existing = pendingDeliveries.get(key) || { count: 0, keys: new Set() };
|
|
79
|
+
existing.count += 1;
|
|
80
|
+
for (const candidate of [agentId, agentLabel]) {
|
|
81
|
+
const value = String(candidate || "").trim();
|
|
82
|
+
if (value) {
|
|
83
|
+
pendingDeliveries.set(value, existing);
|
|
84
|
+
existing.keys.add(value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getPendingState(publisher, displayName) {
|
|
90
|
+
for (const candidate of [publisher, displayName]) {
|
|
91
|
+
const key = String(candidate || "").trim();
|
|
92
|
+
if (key && pendingDeliveries.has(key)) {
|
|
93
|
+
return { key, state: pendingDeliveries.get(key) };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function consumePendingDelivery(publisher, displayName) {
|
|
100
|
+
const hit = getPendingState(publisher, displayName);
|
|
101
|
+
if (!hit) return false;
|
|
102
|
+
hit.state.count -= 1;
|
|
103
|
+
if (hit.state.count <= 0) {
|
|
104
|
+
for (const key of hit.state.keys || []) pendingDeliveries.delete(key);
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function beginStream(publisher, prefix, continuationPrefix, meta) {
|
|
110
|
+
const key = String(publisher || "bus");
|
|
111
|
+
let state = streams.get(key);
|
|
112
|
+
if (state) return state;
|
|
113
|
+
const displayName = stripBlessedTags(prefix || displayNameForPublisher(key) || key)
|
|
114
|
+
.replace(/\s*·\s*$/, "")
|
|
115
|
+
.trim() || displayNameForPublisher(key) || key;
|
|
116
|
+
state = {
|
|
117
|
+
publisher: key,
|
|
118
|
+
displayName,
|
|
119
|
+
prefix,
|
|
120
|
+
continuationPrefix,
|
|
121
|
+
parts: [],
|
|
122
|
+
meta: meta || {},
|
|
123
|
+
};
|
|
124
|
+
streams.set(key, state);
|
|
125
|
+
dispatch({ type: "stream/begin", publisher: displayName });
|
|
126
|
+
return state;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function appendStreamDelta(state, delta) {
|
|
130
|
+
if (!state || !delta) return;
|
|
131
|
+
const text = String(delta || "");
|
|
132
|
+
state.parts.push(text);
|
|
133
|
+
let batch = pendingDeltas.get(state.publisher);
|
|
134
|
+
if (!batch) {
|
|
135
|
+
batch = { publisher: state.displayName || state.publisher, parts: [] };
|
|
136
|
+
pendingDeltas.set(state.publisher, batch);
|
|
137
|
+
}
|
|
138
|
+
batch.parts.push(text);
|
|
139
|
+
scheduleFlush();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function finalizeStream(publisher, meta, reason = "") {
|
|
143
|
+
const key = String(publisher || "bus");
|
|
144
|
+
const state = streams.get(key);
|
|
145
|
+
if (!state) return;
|
|
146
|
+
flushDeltas();
|
|
147
|
+
dispatch({ type: "stream/end" });
|
|
148
|
+
if (typeof appendHistory === "function") {
|
|
149
|
+
const full = state.parts.join("");
|
|
150
|
+
const text = state.displayName
|
|
151
|
+
? `${state.displayName}: ${full}`
|
|
152
|
+
: full;
|
|
153
|
+
appendHistory("bus", text, { ...(meta || state.meta || {}), stream_done: true, stream_reason: reason });
|
|
154
|
+
}
|
|
155
|
+
streams.delete(key);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function hasStream(publisher) {
|
|
159
|
+
return streams.has(String(publisher || "bus"));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
markPendingDelivery,
|
|
164
|
+
getPendingState,
|
|
165
|
+
consumePendingDelivery,
|
|
166
|
+
beginStream,
|
|
167
|
+
appendStreamDelta,
|
|
168
|
+
finalizeStream,
|
|
169
|
+
hasStream,
|
|
170
|
+
flushDeltas,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @deprecated Prefer createChatStreamState. */
|
|
175
|
+
const createInkStreamState = createChatStreamState;
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
STREAM_FLUSH_INTERVAL_MS,
|
|
179
|
+
createThrottledSender,
|
|
180
|
+
createChatStreamState,
|
|
181
|
+
createInkStreamState,
|
|
182
|
+
};
|
|
@@ -13,6 +13,27 @@ class RepoDoctor {
|
|
|
13
13
|
this.failed = true;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
reportTui() {
|
|
17
|
+
try {
|
|
18
|
+
const { resolveTuiLaunchPlan, resolveUfooTuiBinary } = require("../../../ui/tuiLauncher");
|
|
19
|
+
const binary = resolveUfooTuiBinary();
|
|
20
|
+
const plan = resolveTuiLaunchPlan({ mode: process.env.UFOO_TUI || "auto" });
|
|
21
|
+
console.log("TUI:");
|
|
22
|
+
console.log(`- UFOO_TUI=${process.env.UFOO_TUI || "auto"} → ${plan.mode} (${plan.reason})`);
|
|
23
|
+
if (binary) {
|
|
24
|
+
console.log(`- binary: ${binary}${plan.version ? ` (${plan.version})` : ""}`);
|
|
25
|
+
} else {
|
|
26
|
+
console.log("- binary: missing (required; Ink TUI removed)");
|
|
27
|
+
}
|
|
28
|
+
console.log("- force: UFOO_TUI=rust | UFOO_TUI_BIN=/path/to/ufoo-tui");
|
|
29
|
+
if (plan.mode === "error") {
|
|
30
|
+
console.log(`- note: chat/ucode will fail until ufoo-tui is built (${plan.reason})`);
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.log(`TUI: unavailable (${err && err.message ? err.message : err})`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
16
37
|
run() {
|
|
17
38
|
const skillsDir = path.join(this.repoRoot, "SKILLS");
|
|
18
39
|
const contextSkill = path.join(skillsDir, "uctx", "SKILL.md");
|
|
@@ -30,6 +51,7 @@ class RepoDoctor {
|
|
|
30
51
|
console.log("Skills:");
|
|
31
52
|
if (fs.existsSync(contextSkill)) console.log(`- uctx: ${contextSkill}`);
|
|
32
53
|
if (fs.existsSync(busSkill)) console.log(`- ubus: ${busSkill}`);
|
|
54
|
+
this.reportTui();
|
|
33
55
|
|
|
34
56
|
if (this.failed) {
|
|
35
57
|
console.log("Status: FAILED");
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Headless UcodeController — task serial queue, cancel, view ports.
|
|
5
|
+
*
|
|
6
|
+
* Runner/tools/session stay in src/code/*; Ink/Rust hosts attach via ports.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function createUcodeController({
|
|
10
|
+
projectRoot = process.cwd(),
|
|
11
|
+
ports = {},
|
|
12
|
+
} = {}) {
|
|
13
|
+
const view = {
|
|
14
|
+
dispatch: typeof ports.dispatch === "function" ? ports.dispatch : () => {},
|
|
15
|
+
setStatus: typeof ports.setStatus === "function" ? ports.setStatus : () => {},
|
|
16
|
+
appendLog: typeof ports.appendLog === "function" ? ports.appendLog : () => {},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let started = false;
|
|
20
|
+
let abortController = null;
|
|
21
|
+
let chain = Promise.resolve();
|
|
22
|
+
let queueDepth = 0;
|
|
23
|
+
|
|
24
|
+
function start() {
|
|
25
|
+
started = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function stop() {
|
|
29
|
+
if (abortController) {
|
|
30
|
+
try {
|
|
31
|
+
abortController.abort();
|
|
32
|
+
} catch {
|
|
33
|
+
// ignore
|
|
34
|
+
}
|
|
35
|
+
abortController = null;
|
|
36
|
+
}
|
|
37
|
+
queueDepth = 0;
|
|
38
|
+
started = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function beginTask() {
|
|
42
|
+
abortController = new AbortController();
|
|
43
|
+
queueDepth = Math.max(1, queueDepth);
|
|
44
|
+
return abortController;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function endTask() {
|
|
48
|
+
if (queueDepth > 0) queueDepth -= 1;
|
|
49
|
+
if (queueDepth === 0) abortController = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cancelTask() {
|
|
53
|
+
if (abortController) abortController.abort();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isBusy() {
|
|
57
|
+
return queueDepth > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Serialize async work so NL / auto-bus / resume never overlap.
|
|
62
|
+
* AbortController is allocated synchronously so cancel works before the slot starts.
|
|
63
|
+
*/
|
|
64
|
+
function runExclusive(fn) {
|
|
65
|
+
const abort = new AbortController();
|
|
66
|
+
queueDepth += 1;
|
|
67
|
+
abortController = abort;
|
|
68
|
+
const run = chain.then(async () => {
|
|
69
|
+
if (!started) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
abortController = abort;
|
|
73
|
+
try {
|
|
74
|
+
if (abort.signal.aborted) {
|
|
75
|
+
const err = new Error("aborted");
|
|
76
|
+
err.name = "AbortError";
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
return await fn(abort);
|
|
80
|
+
} finally {
|
|
81
|
+
queueDepth = Math.max(0, queueDepth - 1);
|
|
82
|
+
if (abortController === abort) abortController = null;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
chain = run.catch(() => {});
|
|
86
|
+
return run;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
projectRoot,
|
|
91
|
+
view,
|
|
92
|
+
isStarted: () => started,
|
|
93
|
+
isBusy,
|
|
94
|
+
start,
|
|
95
|
+
stop,
|
|
96
|
+
beginTask,
|
|
97
|
+
endTask,
|
|
98
|
+
cancelTask,
|
|
99
|
+
runExclusive,
|
|
100
|
+
getAbortSignal: () => (abortController ? abortController.signal : null),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Throttle thinking_delta → status.set so fast streams don't flood the UI.
|
|
106
|
+
*/
|
|
107
|
+
function createThinkingStatusPublisher(publish, options = {}) {
|
|
108
|
+
const intervalMs = Number(options.intervalMs) > 0 ? Number(options.intervalMs) : 120;
|
|
109
|
+
let tail = "";
|
|
110
|
+
let timer = null;
|
|
111
|
+
let lastFlush = 0;
|
|
112
|
+
|
|
113
|
+
function collapse(text) {
|
|
114
|
+
const raw = String(text || "").replace(/\s+/g, " ").trim();
|
|
115
|
+
if (!raw) return "Thinking…";
|
|
116
|
+
return raw.length > 72 ? `${raw.slice(-72)}` : raw;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function flush() {
|
|
120
|
+
timer = null;
|
|
121
|
+
lastFlush = Date.now();
|
|
122
|
+
publish("status.set", {
|
|
123
|
+
text: collapse(tail),
|
|
124
|
+
busy: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function onThinkingDelta(chunk) {
|
|
129
|
+
tail += String(chunk || "");
|
|
130
|
+
const elapsed = Date.now() - lastFlush;
|
|
131
|
+
if (elapsed >= intervalMs) {
|
|
132
|
+
flush();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!timer) {
|
|
136
|
+
timer = setTimeout(flush, Math.max(16, intervalMs - elapsed));
|
|
137
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function reset() {
|
|
142
|
+
if (timer) {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
timer = null;
|
|
145
|
+
}
|
|
146
|
+
tail = "";
|
|
147
|
+
lastFlush = 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { onThinkingDelta, reset, flush };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
createUcodeController,
|
|
155
|
+
createThinkingStatusPublisher,
|
|
156
|
+
};
|
|
@@ -121,6 +121,10 @@ function finalizeTerminalPrimaryPlan(executionState = null, planGraph = null, ad
|
|
|
121
121
|
const pg = planGraph && typeof planGraph === "object" ? planGraph : null;
|
|
122
122
|
if (!pg || !pg.graphId) return null;
|
|
123
123
|
if (!advance || String(advance.yieldReason || "") !== "graph_terminal") return null;
|
|
124
|
+
// TaskLoop children may briefly sit in planGraph during processTaskRun;
|
|
125
|
+
// never archive them as the Agent Loop primary (they stay in graphs[]).
|
|
126
|
+
const ownerKind = String((pg.owner && pg.owner.kind) || "agent_loop").trim();
|
|
127
|
+
if (ownerKind === "task_loop") return null;
|
|
124
128
|
|
|
125
129
|
const completionSummary = buildPlanCompletionSummary(pg, advance);
|
|
126
130
|
const archived = archivePlanGraph(executionState, pg, { reason: "graph_terminal" });
|
package/src/code/repl.js
CHANGED
|
@@ -350,10 +350,11 @@ async function runUcodeCoreAgent({
|
|
|
350
350
|
runNaturalLanguageTask,
|
|
351
351
|
runUbusCommand,
|
|
352
352
|
formatNlResult,
|
|
353
|
+
submitUserInteractionAnswer,
|
|
353
354
|
workspaceRoot,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
state,
|
|
356
|
+
resumeSessionState,
|
|
357
|
+
persistSessionState,
|
|
357
358
|
autoBus: {
|
|
358
359
|
enabled: shouldAutoConsumeBus(process.env.UFOO_SUBSCRIBER_ID || ""),
|
|
359
360
|
getPendingCount: () => getPendingBusCount(state.workspaceRoot || workspaceRoot, process.env.UFOO_SUBSCRIBER_ID || ""),
|
|
@@ -263,10 +263,10 @@ function processTaskRun(executionState = null, taskRunId = "", options = {}) {
|
|
|
263
263
|
live.lastFocusText = renderTaskFocusText(focus);
|
|
264
264
|
putTaskRun(executionState, live);
|
|
265
265
|
|
|
266
|
-
// Advance child graph tools if runTool provided
|
|
266
|
+
// Advance child graph tools if runTool provided. Target via graphId so
|
|
267
|
+
// runPlanGraphCommand keeps the Agent Loop primary intact and does not
|
|
268
|
+
// treat the TaskLoop child as a terminal primary plan.
|
|
267
269
|
if (typeof options.runTool === "function") {
|
|
268
|
-
const previousActive = executionState.planGraph;
|
|
269
|
-
executionState.planGraph = child;
|
|
270
270
|
const wrappedRunTool = (toolInput = {}) => {
|
|
271
271
|
const current = getTaskRun(executionState, taskRunId);
|
|
272
272
|
if (!current || current.cancelRequested || current.status === "cancelling") {
|
|
@@ -296,62 +296,58 @@ function processTaskRun(executionState = null, taskRunId = "", options = {}) {
|
|
|
296
296
|
});
|
|
297
297
|
return options.runTool(toolInput);
|
|
298
298
|
};
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
299
|
+
const advanced = runPlanGraphCommand({
|
|
300
|
+
operation: "patch",
|
|
301
|
+
graphId: live.childGraphId,
|
|
302
|
+
operations: [],
|
|
303
|
+
commandId: `advance_${live.id}_${Date.now()}`,
|
|
304
|
+
}, {
|
|
305
|
+
executionState,
|
|
306
|
+
runTool: wrappedRunTool,
|
|
307
|
+
autoAdvance: true,
|
|
308
|
+
knownTools: options.knownTools,
|
|
309
|
+
});
|
|
310
|
+
const after = getGraph(executionState, live.childGraphId);
|
|
311
|
+
if (after) {
|
|
312
|
+
after.owner = child.owner || after.owner;
|
|
313
|
+
after.parentGraphId = child.parentGraphId || after.parentGraphId;
|
|
314
|
+
after.parentNodeId = child.parentNodeId || after.parentNodeId;
|
|
315
|
+
setGraph(executionState, after);
|
|
316
|
+
}
|
|
317
|
+
casTaskRunStatus(executionState, live.id, {
|
|
318
|
+
expectedStatus: "running",
|
|
319
|
+
nextStatus: "running",
|
|
320
|
+
phase: after && after.waitingFor ? "waiting_model" : "executing_tools",
|
|
321
|
+
});
|
|
322
|
+
syncParentNodeFromRun(executionState, getTaskRun(executionState, taskRunId));
|
|
323
|
+
|
|
324
|
+
if (after && after.waitingFor) {
|
|
325
|
+
routeGraphYield(executionState, {
|
|
326
|
+
graph: after,
|
|
327
|
+
reason: after.lastYieldReason || "llm_required",
|
|
328
|
+
waitingFor: after.waitingFor,
|
|
309
329
|
});
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
executionState.planGraph.parentNodeId = child.parentNodeId;
|
|
315
|
-
setGraph(executionState, executionState.planGraph);
|
|
316
|
-
}
|
|
317
|
-
const after = getGraph(executionState, live.childGraphId) || executionState.planGraph;
|
|
318
|
-
casTaskRunStatus(executionState, live.id, {
|
|
319
|
-
expectedStatus: "running",
|
|
320
|
-
nextStatus: "running",
|
|
321
|
-
phase: after && after.waitingFor ? "waiting_model" : "executing_tools",
|
|
330
|
+
enqueueTaskEvent(executionState, live.id, {
|
|
331
|
+
kind: "model_turn",
|
|
332
|
+
waitingFor: after.waitingFor,
|
|
333
|
+
focusText: live.lastFocusText,
|
|
322
334
|
});
|
|
323
|
-
syncParentNodeFromRun(executionState, getTaskRun(executionState, taskRunId));
|
|
324
|
-
|
|
325
|
-
if (after && after.waitingFor) {
|
|
326
|
-
routeGraphYield(executionState, {
|
|
327
|
-
graph: after,
|
|
328
|
-
reason: after.lastYieldReason || "llm_required",
|
|
329
|
-
waitingFor: after.waitingFor,
|
|
330
|
-
});
|
|
331
|
-
enqueueTaskEvent(executionState, live.id, {
|
|
332
|
-
kind: "model_turn",
|
|
333
|
-
waitingFor: after.waitingFor,
|
|
334
|
-
focusText: live.lastFocusText,
|
|
335
|
-
});
|
|
336
|
-
return {
|
|
337
|
-
ok: true,
|
|
338
|
-
status: "running",
|
|
339
|
-
yieldReason: "llm_required",
|
|
340
|
-
focusText: live.lastFocusText,
|
|
341
|
-
waitingFor: after.waitingFor,
|
|
342
|
-
advance: advanced.modelPayload || advanced,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
335
|
return {
|
|
346
336
|
ok: true,
|
|
347
337
|
status: "running",
|
|
348
|
-
yieldReason: "
|
|
338
|
+
yieldReason: "llm_required",
|
|
349
339
|
focusText: live.lastFocusText,
|
|
340
|
+
waitingFor: after.waitingFor,
|
|
350
341
|
advance: advanced.modelPayload || advanced,
|
|
351
342
|
};
|
|
352
|
-
} finally {
|
|
353
|
-
executionState.planGraph = previousActive;
|
|
354
343
|
}
|
|
344
|
+
return {
|
|
345
|
+
ok: true,
|
|
346
|
+
status: "running",
|
|
347
|
+
yieldReason: "awaiting_complete_task",
|
|
348
|
+
focusText: live.lastFocusText,
|
|
349
|
+
advance: advanced.modelPayload || advanced,
|
|
350
|
+
};
|
|
355
351
|
}
|
|
356
352
|
|
|
357
353
|
enqueueTaskEvent(executionState, live.id, {
|
package/src/code/tui.js
CHANGED
|
@@ -38,8 +38,19 @@ const {
|
|
|
38
38
|
} = fmt;
|
|
39
39
|
|
|
40
40
|
function runUcodeTui(props = {}) {
|
|
41
|
-
const {
|
|
42
|
-
|
|
41
|
+
const { resolveTuiLaunchPlan } = require("../ui/tuiLauncher");
|
|
42
|
+
const plan = resolveTuiLaunchPlan({
|
|
43
|
+
mode: props.tuiMode || process.env.UFOO_TUI,
|
|
44
|
+
surface: "ucode",
|
|
45
|
+
});
|
|
46
|
+
if (plan.mode !== "rust") {
|
|
47
|
+
const err = new Error(`Rust TUI unavailable (${plan.reason})`);
|
|
48
|
+
err.code = "UFOO_TUI_UNAVAILABLE";
|
|
49
|
+
err.plan = plan;
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
const { runUcodeRust } = require("../ui/rustUcodeHost");
|
|
53
|
+
return runUcodeRust({ ...props, tuiMode: "rust" });
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
module.exports = {
|