u-foo 1.9.8 → 2.2.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 +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 +4 -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/defaultBootstrap.js +128 -5
- package/src/agent/internalRunner.js +333 -2
- package/src/agent/loopObservability.js +190 -0
- package/src/agent/loopRuntime.js +457 -0
- 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
|
@@ -26,6 +26,16 @@ function hasMetaCommandArgs(args = []) {
|
|
|
26
26
|
return hasArg(args, ["-h", "--help", "-v", "--version"]);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function readOptionalFile(filePath) {
|
|
30
|
+
const target = asTrimmedString(filePath);
|
|
31
|
+
if (!target) return "";
|
|
32
|
+
try {
|
|
33
|
+
return fs.readFileSync(target, "utf8");
|
|
34
|
+
} catch {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
/**
|
|
30
40
|
* Load the team activity timeline for prompt injection.
|
|
31
41
|
* The daemon syncs manual inputs every ~30s; bus messages are appended in real-time.
|
|
@@ -67,6 +77,11 @@ function defaultBootstrapFile(projectRoot, agentType = "") {
|
|
|
67
77
|
return path.join(getUfooPaths(projectRoot).agentDir, safeAgentType, "default-bootstrap.md");
|
|
68
78
|
}
|
|
69
79
|
|
|
80
|
+
function mergedBootstrapFile(projectRoot, agentType = "") {
|
|
81
|
+
const safeAgentType = asTrimmedString(agentType).replace(/[^a-zA-Z0-9._-]/g, "-") || "agent";
|
|
82
|
+
return path.join(getUfooPaths(projectRoot).agentDir, safeAgentType, "merged-bootstrap.md");
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
function prepareDefaultBootstrapFile({
|
|
71
86
|
projectRoot,
|
|
72
87
|
agentType = "",
|
|
@@ -80,6 +95,83 @@ function prepareDefaultBootstrapFile({
|
|
|
80
95
|
return { ok: true, file };
|
|
81
96
|
}
|
|
82
97
|
|
|
98
|
+
function mergePromptSegments(...segments) {
|
|
99
|
+
return segments
|
|
100
|
+
.map((item) => String(item || "").trim())
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
.join("\n\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function mergeClaudePromptArgs({
|
|
106
|
+
projectRoot,
|
|
107
|
+
agentType = "claude-code",
|
|
108
|
+
args = [],
|
|
109
|
+
bootstrapText = "",
|
|
110
|
+
} = {}) {
|
|
111
|
+
const currentArgs = Array.isArray(args) ? args.slice() : [];
|
|
112
|
+
for (let index = 0; index < currentArgs.length; index += 1) {
|
|
113
|
+
const item = asTrimmedString(currentArgs[index]);
|
|
114
|
+
if (!item) continue;
|
|
115
|
+
|
|
116
|
+
if (item === "--append-system-prompt") {
|
|
117
|
+
const existingFile = asTrimmedString(currentArgs[index + 1]);
|
|
118
|
+
const mergedText = mergePromptSegments(readOptionalFile(existingFile), bootstrapText);
|
|
119
|
+
const prepared = prepareDefaultBootstrapFile({
|
|
120
|
+
projectRoot,
|
|
121
|
+
agentType,
|
|
122
|
+
targetFile: mergedBootstrapFile(projectRoot, agentType),
|
|
123
|
+
promptText: mergedText,
|
|
124
|
+
});
|
|
125
|
+
currentArgs[index + 1] = prepared.file;
|
|
126
|
+
return { args: currentArgs, file: prepared.file, promptText: mergedText };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (item.startsWith("--append-system-prompt=")) {
|
|
130
|
+
const existingFile = item.slice("--append-system-prompt=".length);
|
|
131
|
+
const mergedText = mergePromptSegments(readOptionalFile(existingFile), bootstrapText);
|
|
132
|
+
const prepared = prepareDefaultBootstrapFile({
|
|
133
|
+
projectRoot,
|
|
134
|
+
agentType,
|
|
135
|
+
targetFile: mergedBootstrapFile(projectRoot, agentType),
|
|
136
|
+
promptText: mergedText,
|
|
137
|
+
});
|
|
138
|
+
currentArgs[index] = `--append-system-prompt=${prepared.file}`;
|
|
139
|
+
return { args: currentArgs, file: prepared.file, promptText: mergedText };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (item === "--system-prompt") {
|
|
143
|
+
const existingPrompt = String(currentArgs[index + 1] || "");
|
|
144
|
+
currentArgs[index + 1] = mergePromptSegments(existingPrompt, bootstrapText);
|
|
145
|
+
return { args: currentArgs, file: "", promptText: String(currentArgs[index + 1] || "") };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (item.startsWith("--system-prompt=")) {
|
|
149
|
+
const existingPrompt = item.slice("--system-prompt=".length);
|
|
150
|
+
const mergedText = mergePromptSegments(existingPrompt, bootstrapText);
|
|
151
|
+
currentArgs[index] = `--system-prompt=${mergedText}`;
|
|
152
|
+
return { args: currentArgs, file: "", promptText: mergedText };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function mergeCodexPromptArgs({ args = [], bootstrapText = "" } = {}) {
|
|
159
|
+
const currentArgs = Array.isArray(args) ? args.slice() : [];
|
|
160
|
+
const lastIndex = currentArgs.length - 1;
|
|
161
|
+
if (lastIndex < 0) return null;
|
|
162
|
+
const lastItem = asTrimmedString(currentArgs[lastIndex]);
|
|
163
|
+
const promptIndex = lastItem && !lastItem.startsWith("-") ? lastIndex : -1;
|
|
164
|
+
|
|
165
|
+
if (promptIndex < 0) return null;
|
|
166
|
+
|
|
167
|
+
currentArgs[promptIndex] = mergePromptSegments(bootstrapText, currentArgs[promptIndex]);
|
|
168
|
+
return {
|
|
169
|
+
args: currentArgs,
|
|
170
|
+
promptText: String(currentArgs[promptIndex] || ""),
|
|
171
|
+
promptIndex,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
83
175
|
function resolveDefaultManualBootstrap({
|
|
84
176
|
projectRoot,
|
|
85
177
|
agentType = "",
|
|
@@ -98,10 +190,22 @@ function resolveDefaultManualBootstrap({
|
|
|
98
190
|
}
|
|
99
191
|
|
|
100
192
|
if (normalizedAgent === "claude-code") {
|
|
101
|
-
if (hasArg(currentArgs, ["--append-system-prompt", "--system-prompt"])) {
|
|
102
|
-
return { args: currentArgs, env: {}, mode: "skip" };
|
|
103
|
-
}
|
|
104
193
|
const promptText = buildDefaultStartupBootstrapPrompt({ agentType: normalizedAgent, projectRoot });
|
|
194
|
+
const merged = mergeClaudePromptArgs({
|
|
195
|
+
projectRoot,
|
|
196
|
+
agentType: normalizedAgent,
|
|
197
|
+
args: currentArgs,
|
|
198
|
+
bootstrapText: promptText,
|
|
199
|
+
});
|
|
200
|
+
if (merged) {
|
|
201
|
+
return {
|
|
202
|
+
args: merged.args,
|
|
203
|
+
env: {},
|
|
204
|
+
mode: "merged-system-prompt",
|
|
205
|
+
file: merged.file,
|
|
206
|
+
promptText: merged.promptText,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
105
209
|
const prepared = prepareDefaultBootstrapFile({
|
|
106
210
|
projectRoot,
|
|
107
211
|
agentType: normalizedAgent,
|
|
@@ -117,10 +221,29 @@ function resolveDefaultManualBootstrap({
|
|
|
117
221
|
}
|
|
118
222
|
|
|
119
223
|
if (normalizedAgent === "codex") {
|
|
224
|
+
const promptText = buildDefaultStartupBootstrapPrompt({ agentType: normalizedAgent, projectRoot });
|
|
225
|
+
const merged = mergeCodexPromptArgs({
|
|
226
|
+
args: currentArgs,
|
|
227
|
+
bootstrapText: promptText,
|
|
228
|
+
});
|
|
229
|
+
if (merged) {
|
|
230
|
+
return {
|
|
231
|
+
args: merged.args,
|
|
232
|
+
env: {},
|
|
233
|
+
mode: "initial-prompt-arg",
|
|
234
|
+
promptText: merged.promptText,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
120
237
|
if (currentArgs.length > 0) {
|
|
121
|
-
return {
|
|
238
|
+
return {
|
|
239
|
+
args: currentArgs,
|
|
240
|
+
env: {
|
|
241
|
+
UFOO_STARTUP_BOOTSTRAP_TEXT: promptText,
|
|
242
|
+
},
|
|
243
|
+
mode: "post-launch-inject",
|
|
244
|
+
promptText,
|
|
245
|
+
};
|
|
122
246
|
}
|
|
123
|
-
const promptText = buildDefaultStartupBootstrapPrompt({ agentType: normalizedAgent, projectRoot });
|
|
124
247
|
return {
|
|
125
248
|
args: currentArgs,
|
|
126
249
|
env: {
|
|
@@ -6,11 +6,32 @@ const EventBus = require("../bus");
|
|
|
6
6
|
const { runCliAgent } = require("./cliRunner");
|
|
7
7
|
const { normalizeCliOutput } = require("./normalizeOutput");
|
|
8
8
|
const { createActivityStatePublisher } = require("./activityStatePublisher");
|
|
9
|
+
const { loadConfig, normalizeCodexInternalThreadMode } = require("../config");
|
|
10
|
+
const {
|
|
11
|
+
createCodexThreadProvider,
|
|
12
|
+
defaultCodexTransportStreamFactory,
|
|
13
|
+
} = require("./codexThreadProvider");
|
|
14
|
+
const {
|
|
15
|
+
createClaudeThreadProvider,
|
|
16
|
+
defaultClaudeTransportStreamFactory,
|
|
17
|
+
} = require("./claudeThreadProvider");
|
|
18
|
+
const { resolveClaudeUpstreamCredentials } = require("./credentials/claude");
|
|
19
|
+
const { buildUpstreamAuthFromCredential } = require("./credentials");
|
|
20
|
+
const { listToolsForCallerTier, CALLER_TIERS } = require("../tools");
|
|
21
|
+
const { redactToolCallPayload, redactSecrets } = require("../providerapi/redactor");
|
|
9
22
|
|
|
10
23
|
function sleep(ms) {
|
|
11
24
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
25
|
}
|
|
13
26
|
|
|
27
|
+
function normalizeWorkerThreadToolMode(value = "") {
|
|
28
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
29
|
+
if (raw === "worker-tier01" || raw === "tier01" || raw === "enabled" || raw === "1" || raw === "true") {
|
|
30
|
+
return "worker-tier01";
|
|
31
|
+
}
|
|
32
|
+
return "disabled";
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
function buildEnv(agentType, sessionId, publisher, nickname) {
|
|
15
36
|
const env = { ...process.env };
|
|
16
37
|
env.AI_BUS_PUBLISHER = publisher || env.AI_BUS_PUBLISHER || "";
|
|
@@ -63,6 +84,18 @@ function createBusSender(projectRoot, subscriber) {
|
|
|
63
84
|
return { enqueue, flush };
|
|
64
85
|
}
|
|
65
86
|
|
|
87
|
+
function shouldFallbackToLegacyThreadProvider(err, provider) {
|
|
88
|
+
if (provider !== "claude-cli" || !err || typeof err !== "object") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const code = String(err.code || "").trim().toUpperCase();
|
|
92
|
+
return (
|
|
93
|
+
code === "CLAUDE_AUTH_UNAVAILABLE"
|
|
94
|
+
|| code === "CLAUDE_OAUTH_SCHEMA_UNSUPPORTED"
|
|
95
|
+
|| code === "ANTHROPIC_SDK_UNAVAILABLE"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
66
99
|
function drainQueue(queueFile) {
|
|
67
100
|
if (!fs.existsSync(queueFile)) return [];
|
|
68
101
|
const processingFile = `${queueFile}.processing.${process.pid}.${Date.now()}`;
|
|
@@ -106,7 +139,8 @@ async function handleEvent(
|
|
|
106
139
|
evt,
|
|
107
140
|
cliSessionState,
|
|
108
141
|
busSender,
|
|
109
|
-
extraArgs = []
|
|
142
|
+
extraArgs = [],
|
|
143
|
+
threadRuntime = null
|
|
110
144
|
) {
|
|
111
145
|
if (!evt || !evt.data || !evt.data.message) return;
|
|
112
146
|
const prompt = evt.data.message;
|
|
@@ -122,6 +156,21 @@ async function handleEvent(
|
|
|
122
156
|
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: text }));
|
|
123
157
|
};
|
|
124
158
|
|
|
159
|
+
if (threadRuntime && threadRuntime.enabled && threadRuntime.thread) {
|
|
160
|
+
const threadedResult = await handleThreadedEvent({
|
|
161
|
+
agentType,
|
|
162
|
+
provider,
|
|
163
|
+
publisher,
|
|
164
|
+
prompt,
|
|
165
|
+
busSender,
|
|
166
|
+
emitStreamDelta,
|
|
167
|
+
threadRuntime,
|
|
168
|
+
});
|
|
169
|
+
if (!threadedResult || !threadedResult.fallbackToLegacy) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
125
174
|
let res = await runCliAgent({
|
|
126
175
|
provider,
|
|
127
176
|
model,
|
|
@@ -188,6 +237,270 @@ async function handleEvent(
|
|
|
188
237
|
await busSender.flush();
|
|
189
238
|
}
|
|
190
239
|
|
|
240
|
+
async function handleThreadedEvent({
|
|
241
|
+
agentType,
|
|
242
|
+
provider,
|
|
243
|
+
publisher,
|
|
244
|
+
prompt,
|
|
245
|
+
busSender,
|
|
246
|
+
emitStreamDelta,
|
|
247
|
+
threadRuntime,
|
|
248
|
+
}) {
|
|
249
|
+
try {
|
|
250
|
+
for await (const event of threadRuntime.thread.runStreamed(prompt, {})) {
|
|
251
|
+
if (!event || typeof event !== "object") continue;
|
|
252
|
+
if (event.type === "text_delta" && event.delta) {
|
|
253
|
+
emitStreamDelta(event.delta);
|
|
254
|
+
} else if (event.type === "turn_failed") {
|
|
255
|
+
throw new Error(event.error || `thread turn failed for ${agentType}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
busSender.enqueue(
|
|
260
|
+
publisher,
|
|
261
|
+
JSON.stringify({ stream: true, done: true, reason: "complete" })
|
|
262
|
+
);
|
|
263
|
+
await busSender.flush();
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (shouldFallbackToLegacyThreadProvider(err, provider)) {
|
|
266
|
+
return { fallbackToLegacy: true };
|
|
267
|
+
}
|
|
268
|
+
if (threadRuntime && typeof threadRuntime.rebuildThread === "function") {
|
|
269
|
+
await threadRuntime.rebuildThread();
|
|
270
|
+
}
|
|
271
|
+
busSender.enqueue(
|
|
272
|
+
publisher,
|
|
273
|
+
JSON.stringify({
|
|
274
|
+
stream: true,
|
|
275
|
+
delta: `[internal:${agentType}] error: ${err && err.message ? err.message : "unknown error"}`,
|
|
276
|
+
})
|
|
277
|
+
);
|
|
278
|
+
busSender.enqueue(
|
|
279
|
+
publisher,
|
|
280
|
+
JSON.stringify({ stream: true, done: true, reason: "error" })
|
|
281
|
+
);
|
|
282
|
+
await busSender.flush();
|
|
283
|
+
return { fallbackToLegacy: false };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getCodexThreadMode(projectRoot) {
|
|
288
|
+
const envValue = process.env.UFOO_CODEX_INTERNAL_THREAD_MODE;
|
|
289
|
+
if (typeof envValue === "string" && envValue.trim()) {
|
|
290
|
+
return normalizeCodexInternalThreadMode(envValue);
|
|
291
|
+
}
|
|
292
|
+
return loadConfig(projectRoot).codexInternalThreadMode;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getWorkerThreadToolMode() {
|
|
296
|
+
return normalizeWorkerThreadToolMode(process.env.UFOO_CODEX_INTERNAL_THREAD_TOOLS);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function buildWorkerThreadToolRuntime({ projectRoot, subscriber, observer }) {
|
|
300
|
+
const mode = getWorkerThreadToolMode();
|
|
301
|
+
if (mode !== "worker-tier01") {
|
|
302
|
+
return {
|
|
303
|
+
enabled: false,
|
|
304
|
+
mode,
|
|
305
|
+
tools: [],
|
|
306
|
+
executeToolCall: null,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const eventBus = new EventBus(projectRoot);
|
|
311
|
+
const toolDefinitions = listToolsForCallerTier(CALLER_TIERS.WORKER);
|
|
312
|
+
const toolsByName = new Map(toolDefinitions.map((tool) => [tool.name, tool]));
|
|
313
|
+
const emitAudit = (phase, payload) => {
|
|
314
|
+
if (observer && typeof observer.onToolCall === "function") {
|
|
315
|
+
try { observer.onToolCall({ phase, payload }); } catch { /* ignore observer errors */ }
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
enabled: toolDefinitions.length > 0,
|
|
321
|
+
mode,
|
|
322
|
+
tools: toolDefinitions.map((tool) => ({
|
|
323
|
+
name: tool.name,
|
|
324
|
+
description: tool.description,
|
|
325
|
+
input_schema: tool.input_schema,
|
|
326
|
+
})),
|
|
327
|
+
// Keep a shared-handler executor ready for a future continuation-capable SDK path.
|
|
328
|
+
// The current Codex seam injects tool descriptors only and does not execute live
|
|
329
|
+
// tool calls inside the SDK stream yet.
|
|
330
|
+
async executeToolCall(toolCall = {}) {
|
|
331
|
+
const name = String(toolCall.name || "").trim();
|
|
332
|
+
const definition = toolsByName.get(name);
|
|
333
|
+
const rawArgs = toolCall.arguments || toolCall.args || {};
|
|
334
|
+
// Slice 1 (§10.7 tool pre-call): build a redacted audit envelope before
|
|
335
|
+
// the handler receives args, so observability consumers never see raw secrets.
|
|
336
|
+
const redactedPayload = redactToolCallPayload({
|
|
337
|
+
name,
|
|
338
|
+
args: rawArgs,
|
|
339
|
+
tool_call_id: toolCall.tool_call_id || toolCall.toolCallId || "",
|
|
340
|
+
caller_tier: CALLER_TIERS.WORKER,
|
|
341
|
+
});
|
|
342
|
+
emitAudit("pre_call", redactedPayload);
|
|
343
|
+
if (!definition) {
|
|
344
|
+
const errorResult = {
|
|
345
|
+
ok: false,
|
|
346
|
+
error: {
|
|
347
|
+
code: "unsupported_tool",
|
|
348
|
+
message: `worker tool is unavailable: ${name}`,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
emitAudit("post_call", { ...redactedPayload, result: errorResult });
|
|
352
|
+
return errorResult;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
const result = await definition.handler({
|
|
357
|
+
caller_tier: CALLER_TIERS.WORKER,
|
|
358
|
+
projectRoot,
|
|
359
|
+
subscriber,
|
|
360
|
+
eventBus,
|
|
361
|
+
}, rawArgs);
|
|
362
|
+
const safeResult = redactSecrets(result);
|
|
363
|
+
emitAudit("post_call", { ...redactedPayload, result: safeResult });
|
|
364
|
+
return safeResult;
|
|
365
|
+
} catch (err) {
|
|
366
|
+
const errorResult = {
|
|
367
|
+
ok: false,
|
|
368
|
+
error: {
|
|
369
|
+
code: err && err.code ? err.code : "tool_execution_failed",
|
|
370
|
+
message: err && err.message ? err.message : String(err || "tool execution failed"),
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
const safeErrorResult = redactSecrets(errorResult);
|
|
374
|
+
emitAudit("post_call", { ...redactedPayload, result: safeErrorResult });
|
|
375
|
+
return safeErrorResult;
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function getClaudeThreadMode() {
|
|
382
|
+
const envValue = process.env.UFOO_CLAUDE_INTERNAL_THREAD_MODE;
|
|
383
|
+
const raw = String(envValue || "").trim().toLowerCase();
|
|
384
|
+
if (raw === "api") return "api";
|
|
385
|
+
return "legacy";
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function buildClaudeAuthProvider(projectRoot) {
|
|
389
|
+
const config = loadConfig(projectRoot);
|
|
390
|
+
return async () => {
|
|
391
|
+
const credential = await resolveClaudeUpstreamCredentials({
|
|
392
|
+
profile: config.claudeOauthProfile,
|
|
393
|
+
tokenPath: config.claudeOauthTokenPath,
|
|
394
|
+
refreshWindowMs: Number(config.claudeOauthRefreshWindowSec || 300) * 1000,
|
|
395
|
+
});
|
|
396
|
+
return buildUpstreamAuthFromCredential(credential);
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function createThreadRuntime({ projectRoot, provider, model, extraArgs = [], subscriber = "" }) {
|
|
401
|
+
const disabledRuntime = {
|
|
402
|
+
enabled: false,
|
|
403
|
+
thread: null,
|
|
404
|
+
toolRuntime: { enabled: false, mode: "disabled", tools: [] },
|
|
405
|
+
close: async () => {},
|
|
406
|
+
rebuildThread: async () => {},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
if (provider === "codex-cli") {
|
|
410
|
+
if (getCodexThreadMode(projectRoot) !== "sdk") {
|
|
411
|
+
return disabledRuntime;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
const toolRuntime = buildWorkerThreadToolRuntime({
|
|
416
|
+
projectRoot,
|
|
417
|
+
subscriber,
|
|
418
|
+
});
|
|
419
|
+
let providerInstance = createCodexThreadProvider({
|
|
420
|
+
model,
|
|
421
|
+
cwd: projectRoot,
|
|
422
|
+
extraArgs,
|
|
423
|
+
tools: toolRuntime.tools,
|
|
424
|
+
streamFactory: defaultCodexTransportStreamFactory,
|
|
425
|
+
});
|
|
426
|
+
let thread = providerInstance.startThread();
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
enabled: true,
|
|
430
|
+
toolRuntime,
|
|
431
|
+
get thread() {
|
|
432
|
+
return thread;
|
|
433
|
+
},
|
|
434
|
+
async rebuildThread() {
|
|
435
|
+
if (thread && typeof thread.close === "function") {
|
|
436
|
+
await thread.close();
|
|
437
|
+
}
|
|
438
|
+
providerInstance = createCodexThreadProvider({
|
|
439
|
+
model,
|
|
440
|
+
cwd: projectRoot,
|
|
441
|
+
extraArgs,
|
|
442
|
+
tools: toolRuntime.tools,
|
|
443
|
+
streamFactory: defaultCodexTransportStreamFactory,
|
|
444
|
+
});
|
|
445
|
+
thread = providerInstance.startThread();
|
|
446
|
+
},
|
|
447
|
+
async close() {
|
|
448
|
+
if (thread && typeof thread.close === "function") {
|
|
449
|
+
await thread.close();
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
} catch {
|
|
454
|
+
return disabledRuntime;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (provider === "claude-cli") {
|
|
459
|
+
if (getClaudeThreadMode() !== "api") {
|
|
460
|
+
return disabledRuntime;
|
|
461
|
+
}
|
|
462
|
+
if (typeof createClaudeThreadProvider !== "function" || typeof resolveClaudeUpstreamCredentials !== "function") {
|
|
463
|
+
return disabledRuntime;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
try {
|
|
467
|
+
let providerInstance = createClaudeThreadProvider({
|
|
468
|
+
model,
|
|
469
|
+
authProvider: buildClaudeAuthProvider(projectRoot),
|
|
470
|
+
streamFactory: defaultClaudeTransportStreamFactory,
|
|
471
|
+
});
|
|
472
|
+
let thread = providerInstance.startThread();
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
enabled: true,
|
|
476
|
+
get thread() {
|
|
477
|
+
return thread;
|
|
478
|
+
},
|
|
479
|
+
async rebuildThread() {
|
|
480
|
+
if (thread && typeof thread.close === "function") {
|
|
481
|
+
await thread.close();
|
|
482
|
+
}
|
|
483
|
+
providerInstance = createClaudeThreadProvider({
|
|
484
|
+
model,
|
|
485
|
+
authProvider: buildClaudeAuthProvider(projectRoot),
|
|
486
|
+
streamFactory: defaultClaudeTransportStreamFactory,
|
|
487
|
+
});
|
|
488
|
+
thread = providerInstance.startThread();
|
|
489
|
+
},
|
|
490
|
+
async close() {
|
|
491
|
+
if (thread && typeof thread.close === "function") {
|
|
492
|
+
await thread.close();
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
} catch {
|
|
497
|
+
return disabledRuntime;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return disabledRuntime;
|
|
502
|
+
}
|
|
503
|
+
|
|
191
504
|
async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs = [] }) {
|
|
192
505
|
// Internal runner 必须由 daemon 启动,UFOO_SUBSCRIBER_ID 应该已经设置
|
|
193
506
|
const { subscriber, agentType: parsedAgentType, sessionId } = parseSubscriberId();
|
|
@@ -202,6 +515,13 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
202
515
|
const provider = normalizedAgentType === "codex" ? "codex-cli" : "claude-cli";
|
|
203
516
|
const model = process.env.UFOO_AGENT_MODEL || "";
|
|
204
517
|
const busSender = createBusSender(projectRoot, subscriber);
|
|
518
|
+
const threadRuntime = createThreadRuntime({
|
|
519
|
+
projectRoot,
|
|
520
|
+
provider,
|
|
521
|
+
model,
|
|
522
|
+
extraArgs,
|
|
523
|
+
subscriber,
|
|
524
|
+
});
|
|
205
525
|
|
|
206
526
|
// Session state management for CLI continuity
|
|
207
527
|
// Use stable path based on nickname (if exists) or agent type, NOT subscriber ID
|
|
@@ -294,7 +614,8 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
294
614
|
evt,
|
|
295
615
|
cliSessionState,
|
|
296
616
|
busSender,
|
|
297
|
-
extraArgs
|
|
617
|
+
extraArgs,
|
|
618
|
+
threadRuntime
|
|
298
619
|
);
|
|
299
620
|
}
|
|
300
621
|
|
|
@@ -324,10 +645,20 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
324
645
|
// eslint-disable-next-line no-await-in-loop
|
|
325
646
|
await sleep(1000);
|
|
326
647
|
}
|
|
648
|
+
|
|
649
|
+
await threadRuntime.close();
|
|
327
650
|
}
|
|
328
651
|
|
|
329
652
|
module.exports = {
|
|
330
653
|
runInternalRunner,
|
|
331
654
|
createBusSender,
|
|
332
655
|
handleEvent,
|
|
656
|
+
createThreadRuntime,
|
|
657
|
+
getCodexThreadMode,
|
|
658
|
+
getWorkerThreadToolMode,
|
|
659
|
+
buildWorkerThreadToolRuntime,
|
|
660
|
+
normalizeWorkerThreadToolMode,
|
|
661
|
+
getClaudeThreadMode,
|
|
662
|
+
buildClaudeAuthProvider,
|
|
663
|
+
shouldFallbackToLegacyThreadProvider,
|
|
333
664
|
};
|