u-foo 1.9.7 → 2.1.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/bin/ufoo.js +5 -3
- package/package.json +2 -4
- package/src/agent/claudeEventTranslator.js +267 -0
- package/src/agent/claudeOauthTokenReader.js +52 -0
- package/src/agent/claudeThreadProvider.js +343 -0
- package/src/agent/cliRunner.js +10 -16
- package/src/agent/codexEventTranslator.js +78 -0
- package/src/agent/codexThreadProvider.js +181 -0
- package/src/agent/controllerToolExecutor.js +233 -0
- package/src/agent/credentials/claude.js +324 -0
- package/src/agent/credentials/codex.js +203 -0
- package/src/agent/credentials/index.js +106 -0
- package/src/agent/internalRunner.js +348 -3
- package/src/agent/loopObservability.js +190 -0
- package/src/agent/loopRuntime.js +457 -0
- package/src/agent/ptyRunner.js +8 -7
- package/src/agent/ufooAgent.js +178 -120
- package/src/agent/upstreamTransport.js +464 -0
- package/src/bus/utils.js +3 -2
- package/src/chat/dashboardView.js +51 -1
- package/src/chat/index.js +3 -1
- package/src/config.js +53 -17
- package/src/controller/flags.js +160 -0
- package/src/controller/gateRouter.js +201 -0
- package/src/controller/routerFastPath.js +22 -0
- package/src/controller/shadowGuard.js +280 -0
- package/src/daemon/index.js +2 -3
- package/src/daemon/promptLoop.js +33 -224
- package/src/daemon/promptRequest.js +360 -5
- package/src/daemon/status.js +2 -0
- package/src/history/inputTimeline.js +9 -4
- package/src/memory/index.js +24 -0
- package/src/providerapi/redactor.js +87 -0
- package/src/providerapi/shadowDiff.js +174 -0
- package/src/report/store.js +4 -3
- package/src/tools/handlers/ackBus.js +26 -0
- package/src/tools/handlers/common.js +64 -0
- package/src/tools/handlers/dispatchMessage.js +81 -0
- package/src/tools/handlers/listAgents.js +14 -0
- package/src/tools/handlers/readBusSummary.js +34 -0
- package/src/tools/handlers/readOpenDecisions.js +26 -0
- package/src/tools/handlers/readProjectRegistry.js +20 -0
- package/src/tools/handlers/readPromptHistory.js +123 -0
- package/src/tools/handlers/tier2.js +134 -0
- package/src/tools/index.js +55 -0
- package/src/tools/registry.js +69 -0
- package/src/tools/schemaFixtures.js +415 -0
- package/src/tools/tier0/listAgents.js +14 -0
- package/src/tools/tier0/readBusSummary.js +14 -0
- package/src/tools/tier0/readOpenDecisions.js +14 -0
- package/src/tools/tier0/readProjectRegistry.js +14 -0
- package/src/tools/tier0/readPromptHistory.js +14 -0
- package/src/tools/tier1/ackBus.js +14 -0
- package/src/tools/tier1/dispatchMessage.js +14 -0
- package/src/tools/tier1/routeAgent.js +14 -0
- package/src/tools/tier2/closeAgent.js +14 -0
- package/src/tools/tier2/launchAgent.js +14 -0
- package/src/tools/tier2/manageCron.js +14 -0
- package/src/tools/tier2/renameAgent.js +14 -0
- package/src/tools/types.js +75 -0
- package/src/tools/unimplemented.js +13 -0
- package/src/ufoo/paths.js +4 -0
- package/bin/ufoo-assistant-agent.js +0 -5
- package/bin/ufoo-engine.js +0 -25
- package/src/assistant/agent.js +0 -261
- package/src/assistant/bridge.js +0 -178
- package/src/assistant/constants.js +0 -15
- package/src/assistant/engine.js +0 -252
- package/src/assistant/stdio.js +0 -58
- package/src/assistant/ufooEngineCli.js +0 -312
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { executeControllerTool } = require("./controllerToolExecutor");
|
|
4
|
+
const { createLoopObserver } = require("./loopObservability");
|
|
5
|
+
|
|
6
|
+
const DEFAULT_LOOP_OPTIONS = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
maxRounds: 3,
|
|
9
|
+
maxToolCalls: 3,
|
|
10
|
+
maxToolErrors: 2,
|
|
11
|
+
maxPromptChars: 12000,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const TERMINAL_REASONS = Object.freeze({
|
|
15
|
+
FINAL_ANSWER: "final_answer",
|
|
16
|
+
BUDGET_EXCEEDED: "budget_exceeded",
|
|
17
|
+
TOOL_FAILURE: "tool_failure",
|
|
18
|
+
USER_CANCEL: "user_cancel",
|
|
19
|
+
PROVIDER_ERROR: "provider_error",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const FALLBACK_USED_VALUES = Object.freeze({
|
|
23
|
+
NONE: "none",
|
|
24
|
+
ASSISTANT_CALL: "assistant_call",
|
|
25
|
+
LEGACY_ROUTER: "legacy_router",
|
|
26
|
+
HELPER_AGENT: "helper_agent",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function normalizeTerminalReason(value) {
|
|
30
|
+
const raw = String(value || "").trim();
|
|
31
|
+
if (!raw) return TERMINAL_REASONS.FINAL_ANSWER;
|
|
32
|
+
if (Object.values(TERMINAL_REASONS).includes(raw)) return raw;
|
|
33
|
+
return TERMINAL_REASONS.FINAL_ANSWER;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeFallbackUsed(value) {
|
|
37
|
+
const raw = String(value || "").trim();
|
|
38
|
+
if (!raw) return FALLBACK_USED_VALUES.NONE;
|
|
39
|
+
if (Object.values(FALLBACK_USED_VALUES).includes(raw)) return raw;
|
|
40
|
+
return FALLBACK_USED_VALUES.NONE;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function toNonNegativeInt(value) {
|
|
44
|
+
const num = Number(value);
|
|
45
|
+
if (!Number.isFinite(num) || num < 0) return 0;
|
|
46
|
+
return Math.floor(num);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractModelMetrics(result) {
|
|
50
|
+
const meta = result && result.meta && typeof result.meta === "object" ? result.meta : null;
|
|
51
|
+
const payloadMeta = result && result.payload && typeof result.payload === "object"
|
|
52
|
+
&& result.payload.meta && typeof result.payload.meta === "object"
|
|
53
|
+
? result.payload.meta
|
|
54
|
+
: null;
|
|
55
|
+
const source = { ...(payloadMeta || {}), ...(meta || {}) };
|
|
56
|
+
return {
|
|
57
|
+
input_tokens: toNonNegativeInt(source.input_tokens),
|
|
58
|
+
output_tokens: toNonNegativeInt(source.output_tokens),
|
|
59
|
+
cache_read_tokens: toNonNegativeInt(source.cache_read_tokens),
|
|
60
|
+
cache_creation_tokens: toNonNegativeInt(source.cache_creation_tokens),
|
|
61
|
+
latency_ms: toNonNegativeInt(source.latency_ms),
|
|
62
|
+
first_token_ms: toNonNegativeInt(source.first_token_ms),
|
|
63
|
+
stop_reason: String(source.stop_reason || "").trim(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizePositiveInt(value, fallback) {
|
|
68
|
+
const num = Number.parseInt(value, 10);
|
|
69
|
+
if (Number.isFinite(num) && num > 0) return num;
|
|
70
|
+
return fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveLoopRuntimeOptions(env = process.env) {
|
|
74
|
+
const mode = String(env.UFOO_AGENT_RUNTIME_MODE || env.UFOO_AGENT_LOOP_MODE || "").trim().toLowerCase();
|
|
75
|
+
const enabled = mode === "loop" || String(env.UFOO_AGENT_ENABLE_LOOP || "").trim() === "1";
|
|
76
|
+
return {
|
|
77
|
+
enabled,
|
|
78
|
+
maxRounds: normalizePositiveInt(env.UFOO_AGENT_LOOP_MAX_ROUNDS, DEFAULT_LOOP_OPTIONS.maxRounds),
|
|
79
|
+
maxToolCalls: normalizePositiveInt(env.UFOO_AGENT_LOOP_MAX_TOOL_CALLS, DEFAULT_LOOP_OPTIONS.maxToolCalls),
|
|
80
|
+
maxToolErrors: normalizePositiveInt(env.UFOO_AGENT_LOOP_MAX_TOOL_ERRORS, DEFAULT_LOOP_OPTIONS.maxToolErrors),
|
|
81
|
+
maxPromptChars: normalizePositiveInt(env.UFOO_AGENT_LOOP_MAX_PROMPT_CHARS, DEFAULT_LOOP_OPTIONS.maxPromptChars),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizePayload(payload) {
|
|
86
|
+
if (!payload || typeof payload !== "object") {
|
|
87
|
+
return { reply: "", dispatch: [], ops: [], done: true };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
...payload,
|
|
91
|
+
reply: typeof payload.reply === "string" ? payload.reply : "",
|
|
92
|
+
dispatch: Array.isArray(payload.dispatch) ? payload.dispatch : [],
|
|
93
|
+
ops: Array.isArray(payload.ops) ? payload.ops : [],
|
|
94
|
+
done: payload.done !== false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function buildLoopContinuationPrompt({
|
|
99
|
+
originalPrompt,
|
|
100
|
+
toolResults,
|
|
101
|
+
lastReply,
|
|
102
|
+
loopState,
|
|
103
|
+
}) {
|
|
104
|
+
const lines = [];
|
|
105
|
+
lines.push(String(originalPrompt || ""));
|
|
106
|
+
lines.push("");
|
|
107
|
+
if (lastReply) {
|
|
108
|
+
lines.push("Previous draft reply:");
|
|
109
|
+
lines.push(String(lastReply || ""));
|
|
110
|
+
lines.push("");
|
|
111
|
+
}
|
|
112
|
+
lines.push("Controller loop state (JSON):");
|
|
113
|
+
lines.push(JSON.stringify(loopState, null, 2));
|
|
114
|
+
lines.push("");
|
|
115
|
+
lines.push("Controller tool results so far (JSON):");
|
|
116
|
+
lines.push(JSON.stringify(toolResults, null, 2));
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push("Use these results to decide the next tool_call or final JSON response.");
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function finalizeLoopRun({
|
|
123
|
+
projectRoot,
|
|
124
|
+
payload,
|
|
125
|
+
processManager,
|
|
126
|
+
dispatchMessages,
|
|
127
|
+
handleOps,
|
|
128
|
+
markPending,
|
|
129
|
+
finalizeLocally = true,
|
|
130
|
+
}) {
|
|
131
|
+
if (finalizeLocally === false) {
|
|
132
|
+
return { ok: true, payload, opsResults: [] };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const dispatch = Array.isArray(payload.dispatch) ? payload.dispatch : [];
|
|
136
|
+
const ops = Array.isArray(payload.ops) ? payload.ops : [];
|
|
137
|
+
|
|
138
|
+
for (const item of dispatch) {
|
|
139
|
+
if (item && item.target && item.target !== "broadcast" && typeof markPending === "function") {
|
|
140
|
+
markPending(item.target);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (typeof dispatchMessages === "function") {
|
|
145
|
+
await dispatchMessages(projectRoot, dispatch);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const opsResults = typeof handleOps === "function"
|
|
149
|
+
? await handleOps(projectRoot, ops, processManager || null)
|
|
150
|
+
: [];
|
|
151
|
+
|
|
152
|
+
return { ok: true, payload, opsResults };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildTerminalPayload(reason, lastPayload, rounds, toolCalls, toolErrors, totals = {}) {
|
|
156
|
+
const payload = normalizePayload(lastPayload);
|
|
157
|
+
const canonicalReason = normalizeTerminalReason(reason);
|
|
158
|
+
if (!payload.reply) {
|
|
159
|
+
payload.reply = `Controller loop stopped: ${canonicalReason}.`;
|
|
160
|
+
}
|
|
161
|
+
payload.dispatch = [];
|
|
162
|
+
payload.ops = [];
|
|
163
|
+
payload.loop = {
|
|
164
|
+
terminal_reason: canonicalReason,
|
|
165
|
+
rounds,
|
|
166
|
+
tool_calls: toolCalls,
|
|
167
|
+
tool_errors: toolErrors,
|
|
168
|
+
fallback_used: normalizeFallbackUsed(totals.fallback_used),
|
|
169
|
+
total_tokens: toNonNegativeInt(totals.total_tokens),
|
|
170
|
+
total_latency_ms: toNonNegativeInt(totals.total_latency_ms),
|
|
171
|
+
};
|
|
172
|
+
return payload;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function runPromptWithControllerLoop({
|
|
176
|
+
projectRoot,
|
|
177
|
+
prompt,
|
|
178
|
+
provider,
|
|
179
|
+
model,
|
|
180
|
+
processManager = null,
|
|
181
|
+
runUfooAgent,
|
|
182
|
+
dispatchMessages,
|
|
183
|
+
handleOps,
|
|
184
|
+
ackBus,
|
|
185
|
+
markPending = () => {},
|
|
186
|
+
log = () => {},
|
|
187
|
+
ufooAgentOptions = {},
|
|
188
|
+
finalizeLocally = true,
|
|
189
|
+
loopRuntime = DEFAULT_LOOP_OPTIONS,
|
|
190
|
+
observer: providedObserver = null,
|
|
191
|
+
observabilityDefaults = {},
|
|
192
|
+
now = () => Date.now(),
|
|
193
|
+
isCancelled = null,
|
|
194
|
+
}) {
|
|
195
|
+
const options = { ...DEFAULT_LOOP_OPTIONS, ...(loopRuntime || {}) };
|
|
196
|
+
const observer = providedObserver || createLoopObserver({
|
|
197
|
+
projectRoot,
|
|
198
|
+
enabled: options.enabled !== false,
|
|
199
|
+
defaults: observabilityDefaults,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
let currentPrompt = String(prompt || "");
|
|
203
|
+
let lastPayload = null;
|
|
204
|
+
let toolCalls = 0;
|
|
205
|
+
let toolErrors = 0;
|
|
206
|
+
let totalTokens = 0;
|
|
207
|
+
let totalLatencyMs = 0;
|
|
208
|
+
const toolResults = [];
|
|
209
|
+
|
|
210
|
+
const checkCancellation = () => {
|
|
211
|
+
if (typeof isCancelled !== "function") return false;
|
|
212
|
+
try {
|
|
213
|
+
return isCancelled() === true;
|
|
214
|
+
} catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const totals = () => ({
|
|
220
|
+
fallback_used: FALLBACK_USED_VALUES.NONE,
|
|
221
|
+
total_tokens: totalTokens,
|
|
222
|
+
total_latency_ms: totalLatencyMs,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const terminate = (reason, payloadBase, roundsCount) => {
|
|
226
|
+
const finalPayload = buildTerminalPayload(
|
|
227
|
+
reason,
|
|
228
|
+
payloadBase,
|
|
229
|
+
roundsCount,
|
|
230
|
+
toolCalls,
|
|
231
|
+
toolErrors,
|
|
232
|
+
totals()
|
|
233
|
+
);
|
|
234
|
+
observer.emit("loop_terminal", finalPayload.loop);
|
|
235
|
+
return finalPayload;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
for (let round = 1; round <= options.maxRounds; round += 1) {
|
|
239
|
+
if (checkCancellation()) {
|
|
240
|
+
const payload = terminate(TERMINAL_REASONS.USER_CANCEL, lastPayload, round - 1);
|
|
241
|
+
return finalizeLoopRun({
|
|
242
|
+
projectRoot,
|
|
243
|
+
payload,
|
|
244
|
+
processManager,
|
|
245
|
+
dispatchMessages,
|
|
246
|
+
handleOps,
|
|
247
|
+
markPending,
|
|
248
|
+
finalizeLocally,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (currentPrompt.length > options.maxPromptChars) {
|
|
253
|
+
const payload = terminate(TERMINAL_REASONS.BUDGET_EXCEEDED, lastPayload, round - 1);
|
|
254
|
+
return finalizeLoopRun({
|
|
255
|
+
projectRoot,
|
|
256
|
+
payload,
|
|
257
|
+
processManager,
|
|
258
|
+
dispatchMessages,
|
|
259
|
+
handleOps,
|
|
260
|
+
markPending,
|
|
261
|
+
finalizeLocally,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const roundStartedAt = now();
|
|
266
|
+
observer.emit("model_call_started", {
|
|
267
|
+
round,
|
|
268
|
+
provider: String(provider || ""),
|
|
269
|
+
model: String(model || ""),
|
|
270
|
+
prompt_chars: currentPrompt.length,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const result = await runUfooAgent({
|
|
274
|
+
projectRoot,
|
|
275
|
+
prompt: currentPrompt,
|
|
276
|
+
provider,
|
|
277
|
+
model,
|
|
278
|
+
...ufooAgentOptions,
|
|
279
|
+
loopRuntime: {
|
|
280
|
+
enabled: true,
|
|
281
|
+
round,
|
|
282
|
+
maxRounds: options.maxRounds,
|
|
283
|
+
maxToolCalls: options.maxToolCalls,
|
|
284
|
+
remainingToolCalls: Math.max(options.maxToolCalls - toolCalls, 0),
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const metrics = extractModelMetrics(result);
|
|
289
|
+
const modelLatency = metrics.latency_ms > 0 ? metrics.latency_ms : Math.max(0, now() - roundStartedAt);
|
|
290
|
+
totalTokens += metrics.input_tokens + metrics.output_tokens;
|
|
291
|
+
totalLatencyMs += modelLatency;
|
|
292
|
+
|
|
293
|
+
const toolCall = result && result.payload && typeof result.payload === "object"
|
|
294
|
+
&& result.payload.tool_call && typeof result.payload.tool_call === "object"
|
|
295
|
+
? result.payload.tool_call
|
|
296
|
+
: null;
|
|
297
|
+
|
|
298
|
+
observer.emit("model_call", {
|
|
299
|
+
round,
|
|
300
|
+
provider: String(provider || ""),
|
|
301
|
+
model: String(model || ""),
|
|
302
|
+
ok: result && result.ok === true,
|
|
303
|
+
input_tokens: metrics.input_tokens,
|
|
304
|
+
output_tokens: metrics.output_tokens,
|
|
305
|
+
cache_read_tokens: metrics.cache_read_tokens,
|
|
306
|
+
cache_creation_tokens: metrics.cache_creation_tokens,
|
|
307
|
+
latency_ms: modelLatency,
|
|
308
|
+
first_token_ms: metrics.first_token_ms,
|
|
309
|
+
tool_call_count: toolCall ? 1 : 0,
|
|
310
|
+
stop_reason: metrics.stop_reason,
|
|
311
|
+
error: result && result.ok === false ? String(result.error || "") : "",
|
|
312
|
+
});
|
|
313
|
+
observer.emit("model_call_finished", {
|
|
314
|
+
round,
|
|
315
|
+
ok: result && result.ok === true,
|
|
316
|
+
error: result && result.ok === false ? String(result.error || "") : "",
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (!result || result.ok !== true) {
|
|
320
|
+
const payload = terminate(TERMINAL_REASONS.PROVIDER_ERROR, lastPayload, round);
|
|
321
|
+
return {
|
|
322
|
+
ok: false,
|
|
323
|
+
error: result && result.error ? result.error : "ufoo-agent loop failed",
|
|
324
|
+
payload,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const payload = normalizePayload(result.payload);
|
|
329
|
+
lastPayload = payload;
|
|
330
|
+
|
|
331
|
+
if (!toolCall) {
|
|
332
|
+
const finalPayload = {
|
|
333
|
+
...payload,
|
|
334
|
+
loop: {
|
|
335
|
+
terminal_reason: TERMINAL_REASONS.FINAL_ANSWER,
|
|
336
|
+
rounds: round,
|
|
337
|
+
tool_calls: toolCalls,
|
|
338
|
+
tool_errors: toolErrors,
|
|
339
|
+
fallback_used: FALLBACK_USED_VALUES.NONE,
|
|
340
|
+
total_tokens: totalTokens,
|
|
341
|
+
total_latency_ms: totalLatencyMs,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
observer.emit("loop_terminal", finalPayload.loop);
|
|
345
|
+
return finalizeLoopRun({
|
|
346
|
+
projectRoot,
|
|
347
|
+
payload: finalPayload,
|
|
348
|
+
processManager,
|
|
349
|
+
dispatchMessages,
|
|
350
|
+
handleOps,
|
|
351
|
+
markPending,
|
|
352
|
+
finalizeLocally,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (toolCalls >= options.maxToolCalls) {
|
|
357
|
+
const finalPayload = terminate(TERMINAL_REASONS.BUDGET_EXCEEDED, payload, round);
|
|
358
|
+
return finalizeLoopRun({
|
|
359
|
+
projectRoot,
|
|
360
|
+
payload: finalPayload,
|
|
361
|
+
processManager,
|
|
362
|
+
dispatchMessages,
|
|
363
|
+
handleOps,
|
|
364
|
+
markPending,
|
|
365
|
+
finalizeLocally,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
toolCalls += 1;
|
|
370
|
+
const toolStartedAt = now();
|
|
371
|
+
const toolResult = await executeControllerTool({
|
|
372
|
+
projectRoot,
|
|
373
|
+
subscriber: "ufoo-agent",
|
|
374
|
+
processManager,
|
|
375
|
+
dispatchMessages,
|
|
376
|
+
handleOps,
|
|
377
|
+
ackBus,
|
|
378
|
+
markPending,
|
|
379
|
+
observer,
|
|
380
|
+
turnId: `loop-round-${round}`,
|
|
381
|
+
}, toolCall);
|
|
382
|
+
const toolDuration = Math.max(0, now() - toolStartedAt);
|
|
383
|
+
|
|
384
|
+
let toolResultSize = 0;
|
|
385
|
+
try {
|
|
386
|
+
toolResultSize = toolResult && toolResult.result !== undefined
|
|
387
|
+
? JSON.stringify(toolResult.result).length
|
|
388
|
+
: 0;
|
|
389
|
+
} catch {
|
|
390
|
+
toolResultSize = 0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
observer.emit("tool_call", {
|
|
394
|
+
round,
|
|
395
|
+
tool_name: String(toolResult && toolResult.name ? toolResult.name : toolCall.name || ""),
|
|
396
|
+
tool_call_id: String(toolResult && toolResult.tool_call_id ? toolResult.tool_call_id : ""),
|
|
397
|
+
turn_id: toolResult && toolResult.turn_id ? String(toolResult.turn_id) : `loop-round-${round}`,
|
|
398
|
+
duration_ms: toolDuration,
|
|
399
|
+
result_size: toolResultSize,
|
|
400
|
+
retry_count: 0,
|
|
401
|
+
final_status: toolResult && toolResult.ok === true ? "ok" : "error",
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
if (!toolResult.ok) {
|
|
405
|
+
toolErrors += 1;
|
|
406
|
+
}
|
|
407
|
+
toolResults.push(toolResult);
|
|
408
|
+
|
|
409
|
+
if (toolErrors >= options.maxToolErrors) {
|
|
410
|
+
const finalPayload = terminate(TERMINAL_REASONS.TOOL_FAILURE, payload, round);
|
|
411
|
+
return finalizeLoopRun({
|
|
412
|
+
projectRoot,
|
|
413
|
+
payload: finalPayload,
|
|
414
|
+
processManager,
|
|
415
|
+
dispatchMessages,
|
|
416
|
+
handleOps,
|
|
417
|
+
markPending,
|
|
418
|
+
finalizeLocally,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
currentPrompt = buildLoopContinuationPrompt({
|
|
423
|
+
originalPrompt: prompt,
|
|
424
|
+
toolResults,
|
|
425
|
+
lastReply: payload.reply,
|
|
426
|
+
loopState: {
|
|
427
|
+
round,
|
|
428
|
+
max_rounds: options.maxRounds,
|
|
429
|
+
tool_calls_used: toolCalls,
|
|
430
|
+
tool_calls_remaining: Math.max(options.maxToolCalls - toolCalls, 0),
|
|
431
|
+
tool_errors: toolErrors,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const payload = terminate(TERMINAL_REASONS.BUDGET_EXCEEDED, lastPayload, options.maxRounds);
|
|
437
|
+
return finalizeLoopRun({
|
|
438
|
+
projectRoot,
|
|
439
|
+
payload,
|
|
440
|
+
processManager,
|
|
441
|
+
dispatchMessages,
|
|
442
|
+
handleOps,
|
|
443
|
+
markPending,
|
|
444
|
+
finalizeLocally,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
module.exports = {
|
|
449
|
+
DEFAULT_LOOP_OPTIONS,
|
|
450
|
+
FALLBACK_USED_VALUES,
|
|
451
|
+
TERMINAL_REASONS,
|
|
452
|
+
buildLoopContinuationPrompt,
|
|
453
|
+
normalizeFallbackUsed,
|
|
454
|
+
normalizeTerminalReason,
|
|
455
|
+
resolveLoopRuntimeOptions,
|
|
456
|
+
runPromptWithControllerLoop,
|
|
457
|
+
};
|
package/src/agent/ptyRunner.js
CHANGED
|
@@ -113,24 +113,25 @@ function buildPrompt(text, marker) {
|
|
|
113
113
|
return `${text}\n\n请在完成后输出以下标记(单独一行):\n${marker}\n`;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function resolveCommand(agentType) {
|
|
116
|
+
function resolveCommand(agentType, extraArgs = []) {
|
|
117
117
|
const normalizedAgent = String(agentType || "").trim().toLowerCase();
|
|
118
|
+
const extra = Array.isArray(extraArgs) ? extraArgs : [];
|
|
118
119
|
const rawCmd = String(process.env.UFOO_PTY_CMD || "").trim();
|
|
119
120
|
if (rawCmd) {
|
|
120
121
|
const rawArgs = String(process.env.UFOO_PTY_ARGS || "").trim();
|
|
121
122
|
const args = rawArgs ? rawArgs.split(/\s+/).filter(Boolean) : [];
|
|
122
|
-
return { command: rawCmd, args };
|
|
123
|
+
return { command: rawCmd, args: [...args, ...extra] };
|
|
123
124
|
}
|
|
124
125
|
if (normalizedAgent === "claude" || normalizedAgent === "claude-code") {
|
|
125
|
-
return { command: "claude", args: [] };
|
|
126
|
+
return { command: "claude", args: [...extra] };
|
|
126
127
|
}
|
|
127
128
|
if (normalizedAgent === "ufoo" || normalizedAgent === "ucode" || normalizedAgent === "ufoo-code") {
|
|
128
|
-
return { command: "ucode", args: [] };
|
|
129
|
+
return { command: "ucode", args: [...extra] };
|
|
129
130
|
}
|
|
130
|
-
return { command: "codex", args: ["--no-alt-screen", "--sandbox", "workspace-write"] };
|
|
131
|
+
return { command: "codex", args: ["--no-alt-screen", "--sandbox", "workspace-write", ...extra] };
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
async function runPtyRunner({ projectRoot, agentType = "codex" }) {
|
|
134
|
+
async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }) {
|
|
134
135
|
let pty;
|
|
135
136
|
try {
|
|
136
137
|
// eslint-disable-next-line global-require
|
|
@@ -156,7 +157,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex" }) {
|
|
|
156
157
|
const logFile = path.join(runDir, "pty-runner.log");
|
|
157
158
|
const injectSockPath = path.join(queueDir, "inject.sock");
|
|
158
159
|
|
|
159
|
-
const { command, args } = resolveCommand(agentType);
|
|
160
|
+
const { command, args } = resolveCommand(agentType, extraArgs);
|
|
160
161
|
const env = {
|
|
161
162
|
...process.env,
|
|
162
163
|
UFOO_LAUNCH_MODE: "internal-pty",
|