u-foo 2.3.12 → 2.3.14
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/defaultBootstrap.js +28 -3
- package/src/agent/internalRunner.js +126 -124
- package/src/agent/ptyRunner.js +70 -8
- package/src/agent/ucodeBootstrap.js +23 -1
- package/src/agent/ufooAgent.js +7 -36
- 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/chat/inputListenerController.js +196 -54
- package/src/chat/layout.js +18 -2
- package/src/code/tui.js +405 -50
- package/src/config.js +1 -0
- package/src/daemon/ops.js +57 -12
- package/src/agent/cliRunner.js +0 -706
- package/src/agent/normalizeOutput.js +0 -41
package/package.json
CHANGED
|
@@ -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,
|
|
@@ -4,8 +4,6 @@ const { getUfooPaths } = require("../ufoo/paths");
|
|
|
4
4
|
const { spawnSync } = require("child_process");
|
|
5
5
|
const EventBus = require("../bus");
|
|
6
6
|
const { readJSON, writeJSON } = require("../bus/utils");
|
|
7
|
-
const { runCliAgent } = require("./cliRunner");
|
|
8
|
-
const { normalizeCliOutput } = require("./normalizeOutput");
|
|
9
7
|
const { createActivityStatePublisher } = require("./activityStatePublisher");
|
|
10
8
|
const { loadConfig, normalizeCodexInternalThreadMode } = require("../config");
|
|
11
9
|
const { createCodexThreadProvider } = require("./codexThreadProvider");
|
|
@@ -17,6 +15,10 @@ const { redactToolCallPayload, redactSecrets } = require("../providerapi/redacto
|
|
|
17
15
|
const { buildCachedMemoryPrefix } = require("../memory");
|
|
18
16
|
const { shouldForwardStreamToPublisher } = require("./publisherRouting");
|
|
19
17
|
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
18
|
+
const {
|
|
19
|
+
buildDefaultStartupBootstrapPrompt,
|
|
20
|
+
isValueForCodexOption,
|
|
21
|
+
} = require("./defaultBootstrap");
|
|
20
22
|
|
|
21
23
|
function sleep(ms) {
|
|
22
24
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -58,6 +60,98 @@ function safeSubscriber(subscriber) {
|
|
|
58
60
|
return subscriber.replace(/:/g, "_");
|
|
59
61
|
}
|
|
60
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
|
+
|
|
61
155
|
function buildMemoryPrefix(projectRoot, limit = 50) {
|
|
62
156
|
try {
|
|
63
157
|
return buildCachedMemoryPrefix(projectRoot, { limit }).prefix.trim();
|
|
@@ -90,10 +184,6 @@ function createBusSender(projectRoot, subscriber) {
|
|
|
90
184
|
return { enqueue, flush };
|
|
91
185
|
}
|
|
92
186
|
|
|
93
|
-
function shouldFallbackToLegacyThreadProvider() {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
187
|
function drainQueue(queueFile) {
|
|
98
188
|
if (!fs.existsSync(queueFile)) return [];
|
|
99
189
|
const processingFile = `${queueFile}.processing.${process.pid}.${Date.now()}`;
|
|
@@ -202,32 +292,29 @@ async function handleEvent(
|
|
|
202
292
|
subscriber,
|
|
203
293
|
nickname,
|
|
204
294
|
evt,
|
|
205
|
-
cliSessionState,
|
|
206
295
|
busSender,
|
|
207
296
|
extraArgs = [],
|
|
208
|
-
threadRuntime = null
|
|
297
|
+
threadRuntime = null,
|
|
298
|
+
bootstrapText = ""
|
|
209
299
|
) {
|
|
210
300
|
if (!evt || !evt.data || !evt.data.message) return;
|
|
211
301
|
const memoryPrefix = buildMemoryPrefix(projectRoot);
|
|
212
|
-
const prompt = memoryPrefix
|
|
213
|
-
|
|
214
|
-
|
|
302
|
+
const prompt = [bootstrapText, memoryPrefix, evt.data.message]
|
|
303
|
+
.map((item) => String(item || "").trim())
|
|
304
|
+
.filter(Boolean)
|
|
305
|
+
.join("\n\n");
|
|
215
306
|
const publisher = evt.publisher || "unknown";
|
|
216
|
-
const sandbox = "workspace-write";
|
|
217
|
-
const streamState = { emitted: false, lastChar: "" };
|
|
218
307
|
const streamToPublisher = shouldForwardStreamToPublisher(projectRoot, publisher);
|
|
219
308
|
|
|
220
309
|
const emitStreamDelta = (delta) => {
|
|
221
310
|
const text = String(delta || "");
|
|
222
311
|
if (!text) return;
|
|
223
312
|
if (!streamToPublisher) return;
|
|
224
|
-
streamState.emitted = true;
|
|
225
|
-
streamState.lastChar = text.slice(-1);
|
|
226
313
|
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: text }));
|
|
227
314
|
};
|
|
228
315
|
|
|
229
316
|
if (threadRuntime && threadRuntime.enabled && threadRuntime.thread) {
|
|
230
|
-
|
|
317
|
+
await handleThreadedEvent({
|
|
231
318
|
agentType,
|
|
232
319
|
provider,
|
|
233
320
|
publisher,
|
|
@@ -237,74 +324,18 @@ async function handleEvent(
|
|
|
237
324
|
streamToPublisher,
|
|
238
325
|
threadRuntime,
|
|
239
326
|
});
|
|
240
|
-
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
let res = await runCliAgent({
|
|
246
|
-
provider,
|
|
247
|
-
model,
|
|
248
|
-
prompt,
|
|
249
|
-
sessionId: cliSessionState.cliSessionId,
|
|
250
|
-
sandbox,
|
|
251
|
-
cwd: projectRoot,
|
|
252
|
-
extraArgs,
|
|
253
|
-
onStreamDelta: emitStreamDelta,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Handle session errors with immediate retry (only for claude)
|
|
257
|
-
if (!res.ok && provider === "claude-cli") {
|
|
258
|
-
const errMsg = (res.error || "").toLowerCase();
|
|
259
|
-
if (errMsg.includes("session") || errMsg.includes("already in use")) {
|
|
260
|
-
// Clear session and retry immediately with new session
|
|
261
|
-
cliSessionState.cliSessionId = null;
|
|
262
|
-
cliSessionState.needsSave = true;
|
|
263
|
-
|
|
264
|
-
res = await runCliAgent({
|
|
265
|
-
provider,
|
|
266
|
-
model,
|
|
267
|
-
prompt,
|
|
268
|
-
sessionId: null, // Let runCliAgent generate new session
|
|
269
|
-
sandbox,
|
|
270
|
-
cwd: projectRoot,
|
|
271
|
-
extraArgs,
|
|
272
|
-
onStreamDelta: emitStreamDelta,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Update CLI session ID for continuity (only for claude)
|
|
278
|
-
if (res.ok && res.sessionId && provider === "claude-cli") {
|
|
279
|
-
cliSessionState.cliSessionId = res.sessionId;
|
|
280
|
-
cliSessionState.needsSave = true;
|
|
327
|
+
return;
|
|
281
328
|
}
|
|
282
329
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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" }));
|
|
286
336
|
} else {
|
|
287
|
-
|
|
337
|
+
busSender.enqueue(publisher, errorText);
|
|
288
338
|
}
|
|
289
|
-
|
|
290
|
-
if (streamState.emitted) {
|
|
291
|
-
if (!res.ok) {
|
|
292
|
-
if (streamState.lastChar !== "\n") {
|
|
293
|
-
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: "\n" }));
|
|
294
|
-
}
|
|
295
|
-
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: reply }));
|
|
296
|
-
}
|
|
297
|
-
busSender.enqueue(
|
|
298
|
-
publisher,
|
|
299
|
-
JSON.stringify({ stream: true, done: true, reason: res.ok ? "complete" : "error" })
|
|
300
|
-
);
|
|
301
|
-
await busSender.flush();
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (!reply) return;
|
|
306
|
-
|
|
307
|
-
busSender.enqueue(publisher, reply);
|
|
308
339
|
await busSender.flush();
|
|
309
340
|
}
|
|
310
341
|
|
|
@@ -350,13 +381,12 @@ async function handleThreadedEvent({
|
|
|
350
381
|
}
|
|
351
382
|
await busSender.flush();
|
|
352
383
|
} catch (err) {
|
|
353
|
-
if (shouldFallbackToLegacyThreadProvider(err, provider)) {
|
|
354
|
-
return { fallbackToLegacy: true };
|
|
355
|
-
}
|
|
356
384
|
if (threadRuntime && typeof threadRuntime.rebuildThread === "function") {
|
|
357
385
|
await threadRuntime.rebuildThread();
|
|
358
386
|
}
|
|
359
387
|
const errorText = `[internal:${agentType}] error: ${err && err.message ? err.message : "unknown error"}`;
|
|
388
|
+
// eslint-disable-next-line no-console
|
|
389
|
+
console.error(errorText);
|
|
360
390
|
if (streamToPublisher) {
|
|
361
391
|
busSender.enqueue(
|
|
362
392
|
publisher,
|
|
@@ -370,7 +400,6 @@ async function handleThreadedEvent({
|
|
|
370
400
|
busSender.enqueue(publisher, errorText);
|
|
371
401
|
}
|
|
372
402
|
await busSender.flush();
|
|
373
|
-
return { fallbackToLegacy: false };
|
|
374
403
|
}
|
|
375
404
|
}
|
|
376
405
|
|
|
@@ -473,8 +502,9 @@ function buildWorkerThreadToolRuntime({ projectRoot, subscriber, observer }) {
|
|
|
473
502
|
function getClaudeThreadMode() {
|
|
474
503
|
const envValue = process.env.UFOO_CLAUDE_INTERNAL_THREAD_MODE;
|
|
475
504
|
const raw = String(envValue || "").trim().toLowerCase();
|
|
505
|
+
if (raw === "legacy" || raw === "off" || raw === "disabled" || raw === "0") return "legacy";
|
|
476
506
|
if (raw === "api") return "api";
|
|
477
|
-
return "
|
|
507
|
+
return "api";
|
|
478
508
|
}
|
|
479
509
|
|
|
480
510
|
function buildClaudeAuthProvider(projectRoot) {
|
|
@@ -658,35 +688,23 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
658
688
|
}
|
|
659
689
|
const provider = normalizedAgentType === "codex" ? "codex-cli" : "claude-cli";
|
|
660
690
|
const model = process.env.UFOO_AGENT_MODEL || "";
|
|
691
|
+
const bootstrap = resolveInternalBootstrap({
|
|
692
|
+
projectRoot,
|
|
693
|
+
agentType: normalizedAgentType,
|
|
694
|
+
extraArgs,
|
|
695
|
+
env: process.env,
|
|
696
|
+
});
|
|
661
697
|
const busSender = createBusSender(projectRoot, subscriber);
|
|
662
698
|
const interactiveSessions = new Map();
|
|
663
699
|
const threadRuntime = createThreadRuntime({
|
|
664
700
|
projectRoot,
|
|
665
701
|
provider,
|
|
666
702
|
model,
|
|
667
|
-
extraArgs,
|
|
703
|
+
extraArgs: bootstrap.extraArgs,
|
|
668
704
|
subscriber,
|
|
669
705
|
providerSessionId: process.env.UFOO_PROVIDER_SESSION_ID || "",
|
|
670
706
|
});
|
|
671
707
|
|
|
672
|
-
// Session state management for CLI continuity
|
|
673
|
-
// Use stable path based on nickname (if exists) or agent type, NOT subscriber ID
|
|
674
|
-
const stableKey = nickname || `${agentType}-default`;
|
|
675
|
-
const sessionDir = path.join(getUfooPaths(projectRoot).agentDir, "sessions");
|
|
676
|
-
fs.mkdirSync(sessionDir, { recursive: true });
|
|
677
|
-
const stateFile = path.join(sessionDir, `${stableKey}.json`);
|
|
678
|
-
|
|
679
|
-
let cliSessionId = null;
|
|
680
|
-
// Only load session for claude (codex doesn't support sessions)
|
|
681
|
-
if (provider === "claude-cli") {
|
|
682
|
-
try {
|
|
683
|
-
const state = JSON.parse(fs.readFileSync(stateFile, "utf8"));
|
|
684
|
-
cliSessionId = state.cliSessionId;
|
|
685
|
-
} catch {
|
|
686
|
-
// No previous session
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
708
|
let running = true;
|
|
691
709
|
let processing = false;
|
|
692
710
|
let lastHeartbeat = 0;
|
|
@@ -699,7 +717,6 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
699
717
|
process.on("SIGTERM", stop);
|
|
700
718
|
process.on("SIGINT", stop);
|
|
701
719
|
|
|
702
|
-
const cliSessionState = { cliSessionId, needsSave: false };
|
|
703
720
|
const agentsFile = getUfooPaths(projectRoot).agentsFile;
|
|
704
721
|
const activityPublisher = createActivityStatePublisher({
|
|
705
722
|
agentsFile, subscriber, projectRoot,
|
|
@@ -795,30 +812,15 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
795
812
|
subscriber,
|
|
796
813
|
nickname,
|
|
797
814
|
evt,
|
|
798
|
-
cliSessionState,
|
|
799
815
|
busSender,
|
|
800
|
-
extraArgs,
|
|
801
|
-
threadRuntime
|
|
816
|
+
bootstrap.extraArgs,
|
|
817
|
+
threadRuntime,
|
|
818
|
+
bootstrap.promptText
|
|
802
819
|
);
|
|
803
820
|
if (evt.__agentViewRaw) {
|
|
804
821
|
getInteractiveSession(evt.publisher || "unknown").writeResponsePrompt();
|
|
805
822
|
}
|
|
806
823
|
}
|
|
807
|
-
|
|
808
|
-
// Persist CLI session state after processing (only if changed and for claude)
|
|
809
|
-
if (cliSessionState.needsSave && provider === "claude-cli") {
|
|
810
|
-
try {
|
|
811
|
-
fs.writeFileSync(stateFile, JSON.stringify({
|
|
812
|
-
cliSessionId: cliSessionState.cliSessionId,
|
|
813
|
-
nickname: nickname || "",
|
|
814
|
-
updated_at: new Date().toISOString(),
|
|
815
|
-
}));
|
|
816
|
-
cliSessionState.needsSave = false;
|
|
817
|
-
} catch {
|
|
818
|
-
// ignore save errors
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
824
|
// 处理消息后更新心跳
|
|
823
825
|
updateHeartbeat();
|
|
824
826
|
lastHeartbeat = now;
|
|
@@ -849,8 +851,8 @@ module.exports = {
|
|
|
849
851
|
normalizeWorkerThreadToolMode,
|
|
850
852
|
getClaudeThreadMode,
|
|
851
853
|
buildClaudeAuthProvider,
|
|
852
|
-
shouldFallbackToLegacyThreadProvider,
|
|
853
854
|
parseAgentViewRawInput,
|
|
854
855
|
createInteractiveInputSession,
|
|
856
|
+
resolveInternalBootstrap,
|
|
855
857
|
persistProviderSessionId,
|
|
856
858
|
};
|
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
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
4
|
+
const { buildDefaultStartupBootstrapPrompt } = require("./defaultBootstrap");
|
|
4
5
|
|
|
5
6
|
function readFileSafe(filePath = "") {
|
|
6
7
|
if (!filePath) return "";
|
|
@@ -74,11 +75,27 @@ function buildBootstrapContent({
|
|
|
74
75
|
return `${lines.join("\n")}\n`;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
function hasUfooProtocolPrompt(promptText = "") {
|
|
79
|
+
const text = String(promptText || "");
|
|
80
|
+
return text.includes("ufoo protocol:") && text.includes("ufoo ctx decisions -l");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function mergeDefaultUfooProtocolPrompt(projectRoot = "", promptText = "") {
|
|
84
|
+
const currentPrompt = String(promptText || "").trim();
|
|
85
|
+
if (hasUfooProtocolPrompt(currentPrompt)) return currentPrompt;
|
|
86
|
+
const defaultPrompt = buildDefaultStartupBootstrapPrompt({
|
|
87
|
+
agentType: "ufoo-code",
|
|
88
|
+
projectRoot,
|
|
89
|
+
}).trim();
|
|
90
|
+
return [defaultPrompt, currentPrompt].filter(Boolean).join("\n\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
function prepareUcodeBootstrap({
|
|
78
94
|
projectRoot = process.cwd(),
|
|
79
95
|
promptFile = "",
|
|
80
96
|
promptText = "",
|
|
81
97
|
targetFile = "",
|
|
98
|
+
includeDefaultProtocol = true,
|
|
82
99
|
} = {}) {
|
|
83
100
|
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
84
101
|
const resolvedPrompt = String(promptFile || "").trim();
|
|
@@ -86,11 +103,14 @@ function prepareUcodeBootstrap({
|
|
|
86
103
|
|
|
87
104
|
const inlinePromptText = String(promptText || "").trim();
|
|
88
105
|
const resolvedPromptText = inlinePromptText || readFileSafe(resolvedPrompt);
|
|
106
|
+
const finalPromptText = includeDefaultProtocol
|
|
107
|
+
? mergeDefaultUfooProtocolPrompt(resolvedProjectRoot, resolvedPromptText)
|
|
108
|
+
: resolvedPromptText;
|
|
89
109
|
const rules = resolveProjectRules(resolvedProjectRoot);
|
|
90
110
|
const content = buildBootstrapContent({
|
|
91
111
|
projectRoot: resolvedProjectRoot,
|
|
92
112
|
promptFile: resolvedPrompt,
|
|
93
|
-
promptText:
|
|
113
|
+
promptText: finalPromptText,
|
|
94
114
|
rules,
|
|
95
115
|
});
|
|
96
116
|
|
|
@@ -107,6 +127,8 @@ function prepareUcodeBootstrap({
|
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
module.exports = {
|
|
130
|
+
hasUfooProtocolPrompt,
|
|
131
|
+
mergeDefaultUfooProtocolPrompt,
|
|
110
132
|
readFileSafe,
|
|
111
133
|
resolveProjectRules,
|
|
112
134
|
defaultBootstrapPath,
|