u-foo 2.3.11 → 2.3.13
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/agent/activityStateWriter.js +14 -3
- package/src/agent/defaultBootstrap.js +28 -3
- package/src/agent/internalRunner.js +139 -127
- package/src/agent/notifier.js +15 -4
- package/src/agent/ptyRunner.js +70 -8
- package/src/agent/ucodeBootstrap.js +23 -1
- package/src/agent/ufooAgent.js +7 -36
- package/src/bus/index.js +2 -1
- package/src/bus/store.js +17 -5
- package/src/bus/subscriber.js +57 -1
- package/src/bus/utils.js +6 -0
- package/src/chat/daemonMessageRouter.js +3 -1
- package/src/chat/dashboardKeyController.js +1 -1
- package/src/chat/dashboardView.js +1 -1
- package/src/chat/index.js +1 -1
- package/src/config.js +1 -0
- package/src/daemon/ops.js +57 -12
- package/src/ufoo/agentRegistryDiagnostics.js +91 -0
- package/src/ufoo/agentsStore.js +38 -2
- package/src/agent/cliRunner.js +0 -706
- package/src/agent/normalizeOutput.js +0 -41
package/package.json
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
|
+
const { readJSON, writeJSON } = require("../bus/utils");
|
|
3
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Centralized helper for writing activity_state to all-agents.json.
|
|
@@ -12,8 +14,17 @@ function writeActivityState(agentsFilePath, subscriber, state, options = {}) {
|
|
|
12
14
|
const { since, force = false } = options;
|
|
13
15
|
try {
|
|
14
16
|
if (!agentsFilePath || !fs.existsSync(agentsFilePath)) return false;
|
|
15
|
-
const data =
|
|
16
|
-
if (!data
|
|
17
|
+
const data = readJSON(agentsFilePath, null);
|
|
18
|
+
if (!data) return false;
|
|
19
|
+
if (!data.agents || !data.agents[subscriber]) {
|
|
20
|
+
appendAgentRegistryDiagnostic(agentsFilePath, "activity_state_subscriber_missing", {
|
|
21
|
+
source: "agent.activityStateWriter.writeActivityState",
|
|
22
|
+
subscriber,
|
|
23
|
+
state,
|
|
24
|
+
known_ids: Object.keys(data.agents || {}).sort(),
|
|
25
|
+
});
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
17
28
|
|
|
18
29
|
const current = data.agents[subscriber].activity_state;
|
|
19
30
|
|
|
@@ -30,7 +41,7 @@ function writeActivityState(agentsFilePath, subscriber, state, options = {}) {
|
|
|
30
41
|
data.agents[subscriber].activity_since = since
|
|
31
42
|
? new Date(since).toISOString()
|
|
32
43
|
: new Date().toISOString();
|
|
33
|
-
|
|
44
|
+
writeJSON(agentsFilePath, data);
|
|
34
45
|
return true;
|
|
35
46
|
} catch {
|
|
36
47
|
return false;
|
|
@@ -23,7 +23,7 @@ function hasArg(args = [], names = []) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function hasMetaCommandArgs(args = []) {
|
|
26
|
-
return hasArg(args, ["-h", "--help", "-v", "--version"]);
|
|
26
|
+
return hasArg(args, ["-h", "--help", "-v", "--version", "resume"]);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function readOptionalFile(filePath) {
|
|
@@ -54,7 +54,7 @@ function buildDefaultStartupBootstrapPrompt({ agentType = "", projectRoot = "" }
|
|
|
54
54
|
const normalizedAgent = asTrimmedString(agentType).toLowerCase();
|
|
55
55
|
const displayAgent = normalizedAgent === "claude-code"
|
|
56
56
|
? "Claude"
|
|
57
|
-
: (normalizedAgent === "codex" ? "Codex" : "agent");
|
|
57
|
+
: (normalizedAgent === "codex" ? "Codex" : (normalizedAgent === "ufoo-code" ? "ucode" : "agent"));
|
|
58
58
|
|
|
59
59
|
const segments = [
|
|
60
60
|
`Session bootstrap for ${displayAgent}.`,
|
|
@@ -102,6 +102,28 @@ function mergePromptSegments(...segments) {
|
|
|
102
102
|
.join("\n\n");
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
const CODEX_OPTIONS_WITH_VALUE = new Set([
|
|
106
|
+
"-m",
|
|
107
|
+
"--model",
|
|
108
|
+
"-c",
|
|
109
|
+
"--config",
|
|
110
|
+
"--profile",
|
|
111
|
+
"--sandbox",
|
|
112
|
+
"-s",
|
|
113
|
+
"--approval-mode",
|
|
114
|
+
"--ask-for-approval",
|
|
115
|
+
"--cd",
|
|
116
|
+
"--cwd",
|
|
117
|
+
"--color",
|
|
118
|
+
"--output-schema",
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
function isValueForCodexOption(args = [], index = -1) {
|
|
122
|
+
if (!Array.isArray(args) || index <= 0) return false;
|
|
123
|
+
const previous = asTrimmedString(args[index - 1]);
|
|
124
|
+
return CODEX_OPTIONS_WITH_VALUE.has(previous);
|
|
125
|
+
}
|
|
126
|
+
|
|
105
127
|
function mergeClaudePromptArgs({
|
|
106
128
|
projectRoot,
|
|
107
129
|
agentType = "claude-code",
|
|
@@ -160,7 +182,9 @@ function mergeCodexPromptArgs({ args = [], bootstrapText = "" } = {}) {
|
|
|
160
182
|
const lastIndex = currentArgs.length - 1;
|
|
161
183
|
if (lastIndex < 0) return null;
|
|
162
184
|
const lastItem = asTrimmedString(currentArgs[lastIndex]);
|
|
163
|
-
const promptIndex = lastItem && !lastItem.startsWith("-")
|
|
185
|
+
const promptIndex = lastItem && !lastItem.startsWith("-") && !isValueForCodexOption(currentArgs, lastIndex)
|
|
186
|
+
? lastIndex
|
|
187
|
+
: -1;
|
|
164
188
|
|
|
165
189
|
if (promptIndex < 0) return null;
|
|
166
190
|
|
|
@@ -262,6 +286,7 @@ function resolveDefaultManualBootstrap({
|
|
|
262
286
|
module.exports = {
|
|
263
287
|
hasArg,
|
|
264
288
|
hasMetaCommandArgs,
|
|
289
|
+
isValueForCodexOption,
|
|
265
290
|
buildDefaultStartupBootstrapPrompt,
|
|
266
291
|
defaultBootstrapFile,
|
|
267
292
|
prepareDefaultBootstrapFile,
|
|
@@ -3,8 +3,7 @@ const path = require("path");
|
|
|
3
3
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
4
4
|
const { spawnSync } = require("child_process");
|
|
5
5
|
const EventBus = require("../bus");
|
|
6
|
-
const {
|
|
7
|
-
const { normalizeCliOutput } = require("./normalizeOutput");
|
|
6
|
+
const { readJSON, writeJSON } = require("../bus/utils");
|
|
8
7
|
const { createActivityStatePublisher } = require("./activityStatePublisher");
|
|
9
8
|
const { loadConfig, normalizeCodexInternalThreadMode } = require("../config");
|
|
10
9
|
const { createCodexThreadProvider } = require("./codexThreadProvider");
|
|
@@ -15,6 +14,11 @@ const { listToolsForCallerTier, CALLER_TIERS } = require("../tools");
|
|
|
15
14
|
const { redactToolCallPayload, redactSecrets } = require("../providerapi/redactor");
|
|
16
15
|
const { buildCachedMemoryPrefix } = require("../memory");
|
|
17
16
|
const { shouldForwardStreamToPublisher } = require("./publisherRouting");
|
|
17
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
18
|
+
const {
|
|
19
|
+
buildDefaultStartupBootstrapPrompt,
|
|
20
|
+
isValueForCodexOption,
|
|
21
|
+
} = require("./defaultBootstrap");
|
|
18
22
|
|
|
19
23
|
function sleep(ms) {
|
|
20
24
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -56,6 +60,98 @@ function safeSubscriber(subscriber) {
|
|
|
56
60
|
return subscriber.replace(/:/g, "_");
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
function readFileSafe(filePath = "") {
|
|
64
|
+
const target = String(filePath || "").trim();
|
|
65
|
+
if (!target) return "";
|
|
66
|
+
try {
|
|
67
|
+
return fs.readFileSync(target, "utf8");
|
|
68
|
+
} catch {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasUfooProtocolPrompt(promptText = "") {
|
|
74
|
+
const text = String(promptText || "");
|
|
75
|
+
return text.includes("ufoo protocol:") && text.includes("ufoo ctx decisions -l");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hasPromptArg(args = []) {
|
|
79
|
+
if (!Array.isArray(args) || args.length === 0) return false;
|
|
80
|
+
const lastIndex = args.length - 1;
|
|
81
|
+
const lastItem = String(args[lastIndex] || "").trim();
|
|
82
|
+
if (!lastItem || lastItem.startsWith("-")) return false;
|
|
83
|
+
return !isValueForCodexOption(args, lastIndex);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function consumeClaudeAppendSystemPrompt(args = []) {
|
|
87
|
+
const nextArgs = [];
|
|
88
|
+
const promptSegments = [];
|
|
89
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
90
|
+
const item = String(args[index] || "");
|
|
91
|
+
if (item === "--append-system-prompt") {
|
|
92
|
+
const filePath = String(args[index + 1] || "");
|
|
93
|
+
const content = readFileSafe(filePath);
|
|
94
|
+
if (content.trim()) promptSegments.push(content.trim());
|
|
95
|
+
index += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (item.startsWith("--append-system-prompt=")) {
|
|
99
|
+
const filePath = item.slice("--append-system-prompt=".length);
|
|
100
|
+
const content = readFileSafe(filePath);
|
|
101
|
+
if (content.trim()) promptSegments.push(content.trim());
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
nextArgs.push(item);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
args: nextArgs,
|
|
108
|
+
promptText: promptSegments.filter(Boolean).join("\n\n"),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveInternalBootstrap({
|
|
113
|
+
projectRoot = process.cwd(),
|
|
114
|
+
agentType = "codex",
|
|
115
|
+
extraArgs = [],
|
|
116
|
+
env = process.env,
|
|
117
|
+
} = {}) {
|
|
118
|
+
const normalizedAgent = String(agentType || "").trim().toLowerCase();
|
|
119
|
+
const bootstrapAgentType = normalizedAgent === "claude" || normalizedAgent === "claude-code"
|
|
120
|
+
? "claude-code"
|
|
121
|
+
: (normalizedAgent === "codex" ? "codex" : "");
|
|
122
|
+
const args = Array.isArray(extraArgs) ? extraArgs.slice() : [];
|
|
123
|
+
if (!bootstrapAgentType) {
|
|
124
|
+
return { promptText: "", extraArgs: args };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let promptText = "";
|
|
128
|
+
let nextArgs = args;
|
|
129
|
+
|
|
130
|
+
if (bootstrapAgentType === "claude-code") {
|
|
131
|
+
const consumed = consumeClaudeAppendSystemPrompt(args);
|
|
132
|
+
promptText = consumed.promptText;
|
|
133
|
+
nextArgs = consumed.args;
|
|
134
|
+
} else if (hasPromptArg(args)) {
|
|
135
|
+
promptText = String(args[args.length - 1] || "").trim();
|
|
136
|
+
nextArgs = args.slice(0, -1);
|
|
137
|
+
} else {
|
|
138
|
+
promptText = String(env.UFOO_STARTUP_BOOTSTRAP_TEXT || "").trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!hasUfooProtocolPrompt(promptText)) {
|
|
142
|
+
const defaultPrompt = buildDefaultStartupBootstrapPrompt({
|
|
143
|
+
agentType: bootstrapAgentType,
|
|
144
|
+
projectRoot,
|
|
145
|
+
}).trim();
|
|
146
|
+
promptText = [defaultPrompt, promptText.trim()].filter(Boolean).join("\n\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
promptText,
|
|
151
|
+
extraArgs: nextArgs,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
59
155
|
function buildMemoryPrefix(projectRoot, limit = 50) {
|
|
60
156
|
try {
|
|
61
157
|
return buildCachedMemoryPrefix(projectRoot, { limit }).prefix.trim();
|
|
@@ -88,10 +184,6 @@ function createBusSender(projectRoot, subscriber) {
|
|
|
88
184
|
return { enqueue, flush };
|
|
89
185
|
}
|
|
90
186
|
|
|
91
|
-
function shouldFallbackToLegacyThreadProvider() {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
187
|
function drainQueue(queueFile) {
|
|
96
188
|
if (!fs.existsSync(queueFile)) return [];
|
|
97
189
|
const processingFile = `${queueFile}.processing.${process.pid}.${Date.now()}`;
|
|
@@ -200,32 +292,29 @@ async function handleEvent(
|
|
|
200
292
|
subscriber,
|
|
201
293
|
nickname,
|
|
202
294
|
evt,
|
|
203
|
-
cliSessionState,
|
|
204
295
|
busSender,
|
|
205
296
|
extraArgs = [],
|
|
206
|
-
threadRuntime = null
|
|
297
|
+
threadRuntime = null,
|
|
298
|
+
bootstrapText = ""
|
|
207
299
|
) {
|
|
208
300
|
if (!evt || !evt.data || !evt.data.message) return;
|
|
209
301
|
const memoryPrefix = buildMemoryPrefix(projectRoot);
|
|
210
|
-
const prompt = memoryPrefix
|
|
211
|
-
|
|
212
|
-
|
|
302
|
+
const prompt = [bootstrapText, memoryPrefix, evt.data.message]
|
|
303
|
+
.map((item) => String(item || "").trim())
|
|
304
|
+
.filter(Boolean)
|
|
305
|
+
.join("\n\n");
|
|
213
306
|
const publisher = evt.publisher || "unknown";
|
|
214
|
-
const sandbox = "workspace-write";
|
|
215
|
-
const streamState = { emitted: false, lastChar: "" };
|
|
216
307
|
const streamToPublisher = shouldForwardStreamToPublisher(projectRoot, publisher);
|
|
217
308
|
|
|
218
309
|
const emitStreamDelta = (delta) => {
|
|
219
310
|
const text = String(delta || "");
|
|
220
311
|
if (!text) return;
|
|
221
312
|
if (!streamToPublisher) return;
|
|
222
|
-
streamState.emitted = true;
|
|
223
|
-
streamState.lastChar = text.slice(-1);
|
|
224
313
|
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: text }));
|
|
225
314
|
};
|
|
226
315
|
|
|
227
316
|
if (threadRuntime && threadRuntime.enabled && threadRuntime.thread) {
|
|
228
|
-
|
|
317
|
+
await handleThreadedEvent({
|
|
229
318
|
agentType,
|
|
230
319
|
provider,
|
|
231
320
|
publisher,
|
|
@@ -235,74 +324,18 @@ async function handleEvent(
|
|
|
235
324
|
streamToPublisher,
|
|
236
325
|
threadRuntime,
|
|
237
326
|
});
|
|
238
|
-
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
let res = await runCliAgent({
|
|
244
|
-
provider,
|
|
245
|
-
model,
|
|
246
|
-
prompt,
|
|
247
|
-
sessionId: cliSessionState.cliSessionId,
|
|
248
|
-
sandbox,
|
|
249
|
-
cwd: projectRoot,
|
|
250
|
-
extraArgs,
|
|
251
|
-
onStreamDelta: emitStreamDelta,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Handle session errors with immediate retry (only for claude)
|
|
255
|
-
if (!res.ok && provider === "claude-cli") {
|
|
256
|
-
const errMsg = (res.error || "").toLowerCase();
|
|
257
|
-
if (errMsg.includes("session") || errMsg.includes("already in use")) {
|
|
258
|
-
// Clear session and retry immediately with new session
|
|
259
|
-
cliSessionState.cliSessionId = null;
|
|
260
|
-
cliSessionState.needsSave = true;
|
|
261
|
-
|
|
262
|
-
res = await runCliAgent({
|
|
263
|
-
provider,
|
|
264
|
-
model,
|
|
265
|
-
prompt,
|
|
266
|
-
sessionId: null, // Let runCliAgent generate new session
|
|
267
|
-
sandbox,
|
|
268
|
-
cwd: projectRoot,
|
|
269
|
-
extraArgs,
|
|
270
|
-
onStreamDelta: emitStreamDelta,
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Update CLI session ID for continuity (only for claude)
|
|
276
|
-
if (res.ok && res.sessionId && provider === "claude-cli") {
|
|
277
|
-
cliSessionState.cliSessionId = res.sessionId;
|
|
278
|
-
cliSessionState.needsSave = true;
|
|
327
|
+
return;
|
|
279
328
|
}
|
|
280
329
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
330
|
+
const errorText = `[internal:${agentType}] error: no thread runtime available for provider ${provider}; cliRunner fallback has been removed`;
|
|
331
|
+
// eslint-disable-next-line no-console
|
|
332
|
+
console.error(errorText);
|
|
333
|
+
if (streamToPublisher) {
|
|
334
|
+
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: errorText }));
|
|
335
|
+
busSender.enqueue(publisher, JSON.stringify({ stream: true, done: true, reason: "error" }));
|
|
284
336
|
} else {
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (streamState.emitted) {
|
|
289
|
-
if (!res.ok) {
|
|
290
|
-
if (streamState.lastChar !== "\n") {
|
|
291
|
-
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: "\n" }));
|
|
292
|
-
}
|
|
293
|
-
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: reply }));
|
|
294
|
-
}
|
|
295
|
-
busSender.enqueue(
|
|
296
|
-
publisher,
|
|
297
|
-
JSON.stringify({ stream: true, done: true, reason: res.ok ? "complete" : "error" })
|
|
298
|
-
);
|
|
299
|
-
await busSender.flush();
|
|
300
|
-
return;
|
|
337
|
+
busSender.enqueue(publisher, errorText);
|
|
301
338
|
}
|
|
302
|
-
|
|
303
|
-
if (!reply) return;
|
|
304
|
-
|
|
305
|
-
busSender.enqueue(publisher, reply);
|
|
306
339
|
await busSender.flush();
|
|
307
340
|
}
|
|
308
341
|
|
|
@@ -348,13 +381,12 @@ async function handleThreadedEvent({
|
|
|
348
381
|
}
|
|
349
382
|
await busSender.flush();
|
|
350
383
|
} catch (err) {
|
|
351
|
-
if (shouldFallbackToLegacyThreadProvider(err, provider)) {
|
|
352
|
-
return { fallbackToLegacy: true };
|
|
353
|
-
}
|
|
354
384
|
if (threadRuntime && typeof threadRuntime.rebuildThread === "function") {
|
|
355
385
|
await threadRuntime.rebuildThread();
|
|
356
386
|
}
|
|
357
387
|
const errorText = `[internal:${agentType}] error: ${err && err.message ? err.message : "unknown error"}`;
|
|
388
|
+
// eslint-disable-next-line no-console
|
|
389
|
+
console.error(errorText);
|
|
358
390
|
if (streamToPublisher) {
|
|
359
391
|
busSender.enqueue(
|
|
360
392
|
publisher,
|
|
@@ -368,7 +400,6 @@ async function handleThreadedEvent({
|
|
|
368
400
|
busSender.enqueue(publisher, errorText);
|
|
369
401
|
}
|
|
370
402
|
await busSender.flush();
|
|
371
|
-
return { fallbackToLegacy: false };
|
|
372
403
|
}
|
|
373
404
|
}
|
|
374
405
|
|
|
@@ -471,8 +502,9 @@ function buildWorkerThreadToolRuntime({ projectRoot, subscriber, observer }) {
|
|
|
471
502
|
function getClaudeThreadMode() {
|
|
472
503
|
const envValue = process.env.UFOO_CLAUDE_INTERNAL_THREAD_MODE;
|
|
473
504
|
const raw = String(envValue || "").trim().toLowerCase();
|
|
505
|
+
if (raw === "legacy" || raw === "off" || raw === "disabled" || raw === "0") return "legacy";
|
|
474
506
|
if (raw === "api") return "api";
|
|
475
|
-
return "
|
|
507
|
+
return "api";
|
|
476
508
|
}
|
|
477
509
|
|
|
478
510
|
function buildClaudeAuthProvider(projectRoot) {
|
|
@@ -493,14 +525,22 @@ function persistProviderSessionId(projectRoot, subscriber, providerSessionId) {
|
|
|
493
525
|
try {
|
|
494
526
|
const agentsFile = getUfooPaths(projectRoot).agentsFile;
|
|
495
527
|
const parsed = fs.existsSync(agentsFile)
|
|
496
|
-
?
|
|
528
|
+
? readJSON(agentsFile, null)
|
|
497
529
|
: {};
|
|
530
|
+
if (!parsed) return false;
|
|
498
531
|
if (!parsed.agents || typeof parsed.agents !== "object") return false;
|
|
499
|
-
if (!parsed.agents[subscriber] || typeof parsed.agents[subscriber] !== "object")
|
|
532
|
+
if (!parsed.agents[subscriber] || typeof parsed.agents[subscriber] !== "object") {
|
|
533
|
+
appendAgentRegistryDiagnostic(agentsFile, "provider_session_subscriber_missing", {
|
|
534
|
+
source: "agent.internalRunner.persistProviderSessionId",
|
|
535
|
+
subscriber,
|
|
536
|
+
known_ids: Object.keys(parsed.agents || {}).sort(),
|
|
537
|
+
});
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
500
540
|
if (parsed.agents[subscriber].provider_session_id === id) return false;
|
|
501
541
|
parsed.agents[subscriber].provider_session_id = id;
|
|
502
542
|
parsed.agents[subscriber].provider_session_updated_at = new Date().toISOString();
|
|
503
|
-
|
|
543
|
+
writeJSON(agentsFile, parsed);
|
|
504
544
|
return true;
|
|
505
545
|
} catch {
|
|
506
546
|
return false;
|
|
@@ -648,35 +688,23 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
648
688
|
}
|
|
649
689
|
const provider = normalizedAgentType === "codex" ? "codex-cli" : "claude-cli";
|
|
650
690
|
const model = process.env.UFOO_AGENT_MODEL || "";
|
|
691
|
+
const bootstrap = resolveInternalBootstrap({
|
|
692
|
+
projectRoot,
|
|
693
|
+
agentType: normalizedAgentType,
|
|
694
|
+
extraArgs,
|
|
695
|
+
env: process.env,
|
|
696
|
+
});
|
|
651
697
|
const busSender = createBusSender(projectRoot, subscriber);
|
|
652
698
|
const interactiveSessions = new Map();
|
|
653
699
|
const threadRuntime = createThreadRuntime({
|
|
654
700
|
projectRoot,
|
|
655
701
|
provider,
|
|
656
702
|
model,
|
|
657
|
-
extraArgs,
|
|
703
|
+
extraArgs: bootstrap.extraArgs,
|
|
658
704
|
subscriber,
|
|
659
705
|
providerSessionId: process.env.UFOO_PROVIDER_SESSION_ID || "",
|
|
660
706
|
});
|
|
661
707
|
|
|
662
|
-
// Session state management for CLI continuity
|
|
663
|
-
// Use stable path based on nickname (if exists) or agent type, NOT subscriber ID
|
|
664
|
-
const stableKey = nickname || `${agentType}-default`;
|
|
665
|
-
const sessionDir = path.join(getUfooPaths(projectRoot).agentDir, "sessions");
|
|
666
|
-
fs.mkdirSync(sessionDir, { recursive: true });
|
|
667
|
-
const stateFile = path.join(sessionDir, `${stableKey}.json`);
|
|
668
|
-
|
|
669
|
-
let cliSessionId = null;
|
|
670
|
-
// Only load session for claude (codex doesn't support sessions)
|
|
671
|
-
if (provider === "claude-cli") {
|
|
672
|
-
try {
|
|
673
|
-
const state = JSON.parse(fs.readFileSync(stateFile, "utf8"));
|
|
674
|
-
cliSessionId = state.cliSessionId;
|
|
675
|
-
} catch {
|
|
676
|
-
// No previous session
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
708
|
let running = true;
|
|
681
709
|
let processing = false;
|
|
682
710
|
let lastHeartbeat = 0;
|
|
@@ -689,7 +717,6 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
689
717
|
process.on("SIGTERM", stop);
|
|
690
718
|
process.on("SIGINT", stop);
|
|
691
719
|
|
|
692
|
-
const cliSessionState = { cliSessionId, needsSave: false };
|
|
693
720
|
const agentsFile = getUfooPaths(projectRoot).agentsFile;
|
|
694
721
|
const activityPublisher = createActivityStatePublisher({
|
|
695
722
|
agentsFile, subscriber, projectRoot,
|
|
@@ -785,30 +812,15 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
785
812
|
subscriber,
|
|
786
813
|
nickname,
|
|
787
814
|
evt,
|
|
788
|
-
cliSessionState,
|
|
789
815
|
busSender,
|
|
790
|
-
extraArgs,
|
|
791
|
-
threadRuntime
|
|
816
|
+
bootstrap.extraArgs,
|
|
817
|
+
threadRuntime,
|
|
818
|
+
bootstrap.promptText
|
|
792
819
|
);
|
|
793
820
|
if (evt.__agentViewRaw) {
|
|
794
821
|
getInteractiveSession(evt.publisher || "unknown").writeResponsePrompt();
|
|
795
822
|
}
|
|
796
823
|
}
|
|
797
|
-
|
|
798
|
-
// Persist CLI session state after processing (only if changed and for claude)
|
|
799
|
-
if (cliSessionState.needsSave && provider === "claude-cli") {
|
|
800
|
-
try {
|
|
801
|
-
fs.writeFileSync(stateFile, JSON.stringify({
|
|
802
|
-
cliSessionId: cliSessionState.cliSessionId,
|
|
803
|
-
nickname: nickname || "",
|
|
804
|
-
updated_at: new Date().toISOString(),
|
|
805
|
-
}));
|
|
806
|
-
cliSessionState.needsSave = false;
|
|
807
|
-
} catch {
|
|
808
|
-
// ignore save errors
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
824
|
// 处理消息后更新心跳
|
|
813
825
|
updateHeartbeat();
|
|
814
826
|
lastHeartbeat = now;
|
|
@@ -839,8 +851,8 @@ module.exports = {
|
|
|
839
851
|
normalizeWorkerThreadToolMode,
|
|
840
852
|
getClaudeThreadMode,
|
|
841
853
|
buildClaudeAuthProvider,
|
|
842
|
-
shouldFallbackToLegacyThreadProvider,
|
|
843
854
|
parseAgentViewRawInput,
|
|
844
855
|
createInteractiveInputSession,
|
|
856
|
+
resolveInternalBootstrap,
|
|
845
857
|
persistProviderSessionId,
|
|
846
858
|
};
|
package/src/agent/notifier.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const EventBus = require("../bus");
|
|
4
|
+
const { readJSON, writeJSON } = require("../bus/utils");
|
|
4
5
|
const Injector = require("../bus/inject");
|
|
5
6
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
7
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
6
8
|
const { shakeTerminalByTty } = require("../bus/shake");
|
|
7
9
|
const { isITerm2 } = require("../terminal/detect");
|
|
8
10
|
const iterm2 = require("../terminal/iterm2");
|
|
@@ -62,7 +64,8 @@ class AgentNotifier {
|
|
|
62
64
|
getNickname() {
|
|
63
65
|
try {
|
|
64
66
|
if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return "";
|
|
65
|
-
const data =
|
|
67
|
+
const data = readJSON(this.agentsFile, null);
|
|
68
|
+
if (!data) return "";
|
|
66
69
|
const meta = data.agents && data.agents[this.subscriber];
|
|
67
70
|
return (meta && meta.nickname) ? String(meta.nickname) : "";
|
|
68
71
|
} catch {
|
|
@@ -109,11 +112,18 @@ class AgentNotifier {
|
|
|
109
112
|
updateHeartbeat() {
|
|
110
113
|
try {
|
|
111
114
|
if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return;
|
|
112
|
-
const data =
|
|
115
|
+
const data = readJSON(this.agentsFile, null);
|
|
116
|
+
if (!data) return;
|
|
113
117
|
if (data.agents && data.agents[this.subscriber]) {
|
|
114
118
|
data.agents[this.subscriber].last_seen = new Date().toISOString();
|
|
115
|
-
|
|
119
|
+
writeJSON(this.agentsFile, data);
|
|
120
|
+
return;
|
|
116
121
|
}
|
|
122
|
+
appendAgentRegistryDiagnostic(this.agentsFile, "heartbeat_subscriber_missing", {
|
|
123
|
+
source: "agent.notifier.updateHeartbeat",
|
|
124
|
+
subscriber: this.subscriber,
|
|
125
|
+
known_ids: Object.keys(data.agents || {}).sort(),
|
|
126
|
+
});
|
|
117
127
|
} catch {
|
|
118
128
|
// 心跳更新失败时静默忽略
|
|
119
129
|
}
|
|
@@ -132,7 +142,8 @@ class AgentNotifier {
|
|
|
132
142
|
getCurrentActivityState() {
|
|
133
143
|
try {
|
|
134
144
|
if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return "";
|
|
135
|
-
const data =
|
|
145
|
+
const data = readJSON(this.agentsFile, null);
|
|
146
|
+
if (!data) return "";
|
|
136
147
|
const meta = data.agents && data.agents[this.subscriber];
|
|
137
148
|
return meta && typeof meta.activity_state === "string"
|
|
138
149
|
? String(meta.activity_state).trim().toLowerCase()
|
package/src/agent/ptyRunner.js
CHANGED
|
@@ -12,6 +12,10 @@ const {
|
|
|
12
12
|
shouldAutoReplyFromPtyToPublisher,
|
|
13
13
|
shouldForwardStreamToPublisher,
|
|
14
14
|
} = require("./publisherRouting");
|
|
15
|
+
const {
|
|
16
|
+
isValueForCodexOption,
|
|
17
|
+
resolveDefaultManualBootstrap,
|
|
18
|
+
} = require("./defaultBootstrap");
|
|
15
19
|
|
|
16
20
|
function sleep(ms) {
|
|
17
21
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -116,22 +120,70 @@ function computeInjectedSubmitDelayMs(agentType, text) {
|
|
|
116
120
|
return delayMs;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
function
|
|
123
|
+
function hasPromptArg(args = []) {
|
|
124
|
+
if (!Array.isArray(args) || args.length === 0) return false;
|
|
125
|
+
const lastIndex = args.length - 1;
|
|
126
|
+
const lastItem = String(args[lastIndex] || "").trim();
|
|
127
|
+
if (!lastItem || lastItem.startsWith("-")) return false;
|
|
128
|
+
return !isValueForCodexOption(args, lastIndex);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function appendStartupBootstrapArg(agentType, extraArgs = [], env = process.env) {
|
|
132
|
+
const normalizedAgent = String(agentType || "").trim().toLowerCase();
|
|
133
|
+
const args = Array.isArray(extraArgs) ? extraArgs.slice() : [];
|
|
134
|
+
if (normalizedAgent !== "codex") return args;
|
|
135
|
+
if (hasPromptArg(args)) return args;
|
|
136
|
+
const startupBootstrapText = String(env.UFOO_STARTUP_BOOTSTRAP_TEXT || "").trim();
|
|
137
|
+
if (!startupBootstrapText) return args;
|
|
138
|
+
return [...args, startupBootstrapText];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function resolvePtyBootstrapArgs(agentType, extraArgs = [], {
|
|
142
|
+
projectRoot = process.cwd(),
|
|
143
|
+
env = process.env,
|
|
144
|
+
} = {}) {
|
|
145
|
+
const normalizedAgent = String(agentType || "").trim().toLowerCase();
|
|
146
|
+
const args = Array.isArray(extraArgs) ? extraArgs.slice() : [];
|
|
147
|
+
if (normalizedAgent !== "codex" && normalizedAgent !== "claude" && normalizedAgent !== "claude-code") {
|
|
148
|
+
return { args, env: {} };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const bootstrapAgentType = normalizedAgent === "codex" ? "codex" : "claude-code";
|
|
152
|
+
const resolved = resolveDefaultManualBootstrap({
|
|
153
|
+
projectRoot,
|
|
154
|
+
agentType: bootstrapAgentType,
|
|
155
|
+
args,
|
|
156
|
+
env,
|
|
157
|
+
});
|
|
158
|
+
const resolvedArgs = resolved && resolved.mode !== "skip" && Array.isArray(resolved.args)
|
|
159
|
+
? resolved.args
|
|
160
|
+
: args;
|
|
161
|
+
const resolvedEnv = resolved && resolved.mode !== "skip" && resolved.env && typeof resolved.env === "object"
|
|
162
|
+
? resolved.env
|
|
163
|
+
: {};
|
|
164
|
+
return {
|
|
165
|
+
args: appendStartupBootstrapArg(normalizedAgent, resolvedArgs, { ...env, ...resolvedEnv }),
|
|
166
|
+
env: resolvedEnv,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function resolveCommand(agentType, extraArgs = [], options = {}) {
|
|
120
171
|
const normalizedAgent = String(agentType || "").trim().toLowerCase();
|
|
121
|
-
const
|
|
172
|
+
const bootstrap = resolvePtyBootstrapArgs(normalizedAgent, extraArgs, options);
|
|
173
|
+
const extra = bootstrap.args;
|
|
122
174
|
const rawCmd = String(process.env.UFOO_PTY_CMD || "").trim();
|
|
123
175
|
if (rawCmd) {
|
|
124
176
|
const rawArgs = String(process.env.UFOO_PTY_ARGS || "").trim();
|
|
125
177
|
const args = rawArgs ? rawArgs.split(/\s+/).filter(Boolean) : [];
|
|
126
|
-
return { command: rawCmd, args: [...args, ...extra] };
|
|
178
|
+
return { command: rawCmd, args: [...args, ...extra], env: bootstrap.env };
|
|
127
179
|
}
|
|
128
180
|
if (normalizedAgent === "claude" || normalizedAgent === "claude-code") {
|
|
129
|
-
return { command: "claude", args: [...extra] };
|
|
181
|
+
return { command: "claude", args: [...extra], env: bootstrap.env };
|
|
130
182
|
}
|
|
131
183
|
if (normalizedAgent === "ufoo" || normalizedAgent === "ucode" || normalizedAgent === "ufoo-code") {
|
|
132
|
-
return { command: "ucode", args: [...extra] };
|
|
184
|
+
return { command: "ucode", args: [...extra], env: bootstrap.env };
|
|
133
185
|
}
|
|
134
|
-
return { command: "codex", args: ["--no-alt-screen", "--sandbox", "workspace-write", ...extra] };
|
|
186
|
+
return { command: "codex", args: ["--no-alt-screen", "--sandbox", "workspace-write", ...extra], env: bootstrap.env };
|
|
135
187
|
}
|
|
136
188
|
|
|
137
189
|
async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }) {
|
|
@@ -160,9 +212,13 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
160
212
|
const logFile = path.join(runDir, "pty-runner.log");
|
|
161
213
|
const injectSockPath = path.join(queueDir, "inject.sock");
|
|
162
214
|
|
|
163
|
-
const { command, args } = resolveCommand(agentType, extraArgs
|
|
215
|
+
const { command, args, env: commandEnv } = resolveCommand(agentType, extraArgs, {
|
|
216
|
+
projectRoot,
|
|
217
|
+
env: process.env,
|
|
218
|
+
});
|
|
164
219
|
const env = {
|
|
165
220
|
...process.env,
|
|
221
|
+
...(commandEnv && typeof commandEnv === "object" ? commandEnv : {}),
|
|
166
222
|
UFOO_LAUNCH_MODE: "internal-pty",
|
|
167
223
|
UFOO_INTERNAL_PTY: "1",
|
|
168
224
|
};
|
|
@@ -987,4 +1043,10 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
987
1043
|
}
|
|
988
1044
|
}
|
|
989
1045
|
|
|
990
|
-
module.exports = {
|
|
1046
|
+
module.exports = {
|
|
1047
|
+
appendStartupBootstrapArg,
|
|
1048
|
+
parseInputMessage,
|
|
1049
|
+
resolvePtyBootstrapArgs,
|
|
1050
|
+
resolveCommand,
|
|
1051
|
+
runPtyRunner,
|
|
1052
|
+
};
|